From 0a5f9db693d54278e8198fb00be018089d892db4 Mon Sep 17 00:00:00 2001 From: Oleg Naydenov Date: Fri, 11 Dec 2020 20:59:20 +0200 Subject: [PATCH 001/325] Add Kubient bid adapter, Remove alias from Fidelity bid adapter. (#6084) * Add New Kubient Bid Adapter * Add New Kubient Bid Adapter * Fidelity Bid Adapter Update. Less 'Kubient' Alias * New Kubient Bid Adapter. Errors fix. --- modules/fidelityBidAdapter.js | 1 - modules/kubientBidAdapter.js | 111 +++++++++ modules/kubientBidAdapter.md | 26 ++ test/spec/modules/kubientBidAdapter_spec.js | 259 ++++++++++++++++++++ 4 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 modules/kubientBidAdapter.js create mode 100644 modules/kubientBidAdapter.md create mode 100644 test/spec/modules/kubientBidAdapter_spec.js diff --git a/modules/fidelityBidAdapter.js b/modules/fidelityBidAdapter.js index baf5384fbfe..fac273721ff 100644 --- a/modules/fidelityBidAdapter.js +++ b/modules/fidelityBidAdapter.js @@ -6,7 +6,6 @@ const BIDDER_SERVER = 'x.fidelity-media.com'; const FIDELITY_VENDOR_ID = 408; export const spec = { code: BIDDER_CODE, - aliases: ['kubient'], gvlid: 408, isBidRequestValid: function isBidRequestValid(bid) { return !!(bid && bid.params && bid.params.zoneid); diff --git a/modules/kubientBidAdapter.js b/modules/kubientBidAdapter.js new file mode 100644 index 00000000000..8f6ea53ecce --- /dev/null +++ b/modules/kubientBidAdapter.js @@ -0,0 +1,111 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'kubient'; +const END_POINT = 'https://kssp.kbntx.ch/pbjs'; +const VERSION = '1.0'; +const VENDOR_ID = 794; +export const spec = { + code: BIDDER_CODE, + gvlid: VENDOR_ID, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid && bid.params); + }, + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + const result = validBidRequests.map(function (bid) { + let data = { + v: VERSION, + requestId: bid.bidderRequestId, + adSlots: [{ + bidId: bid.bidId, + zoneId: bid.params.zoneid || '', + floor: bid.params.floor || 0.0, + sizes: bid.sizes || [], + schain: bid.schain || {}, + mediaTypes: bid.mediaTypes + }], + referer: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : null, + tmax: bidderRequest.timeout, + gdpr: (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0, + consent: (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) ? bidderRequest.gdprConsent.consentString : null, + consentGiven: kubientGetConsentGiven(bidderRequest.gdprConsent), + uspConsent: bidderRequest.uspConsent + }; + return { + method: 'POST', + url: END_POINT, + data: JSON.stringify(data) + }; + }); + return result; + }, + interpretResponse: function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body || !serverResponse.body.seatbid) { + return []; + } + let bidResponses = []; + serverResponse.body.seatbid.forEach(seatbid => { + let bids = seatbid.bid || []; + bids.forEach(bid => { + bidResponses.push({ + requestId: bid.bidId, + cpm: bid.price, + currency: bid.cur, + width: bid.w, + height: bid.h, + creativeId: bid.creativeId, + netRevenue: bid.netRevenue, + ttl: bid.ttl, + ad: bid.adm + }); + }); + }); + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + let gdprParams = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + gdprParams = `?consent_str=${gdprConsent.consentString}`; + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = gdprParams + `&gdpr=${Number(gdprConsent.gdprApplies)}`; + } + gdprParams = gdprParams + `&consent_given=` + kubientGetConsentGiven(gdprConsent); + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://kdmp.kbntx.ch/init.html' + gdprParams + }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: 'https://kdmp.kbntx.ch/init.png' + gdprParams + }); + } + return syncs; + } +}; + +function kubientGetConsentGiven(gdprConsent) { + let consentGiven = 0; + if (typeof gdprConsent !== 'undefined') { + let apiVersion = utils.deepAccess(gdprConsent, `apiVersion`); + switch (apiVersion) { + case 1: + consentGiven = utils.deepAccess(gdprConsent, `vendorData.vendorConsents.${VENDOR_ID}`) ? 1 : 0; + break; + case 2: + consentGiven = utils.deepAccess(gdprConsent, `vendorData.vendor.consents.${VENDOR_ID}`) ? 1 : 0; + break; + } + } + return consentGiven; +} +registerBidder(spec); diff --git a/modules/kubientBidAdapter.md b/modules/kubientBidAdapter.md new file mode 100644 index 00000000000..9f3e1d5f52e --- /dev/null +++ b/modules/kubientBidAdapter.md @@ -0,0 +1,26 @@ +# Overview +​ +**Module Name**: Kubient Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: artem.aleksashkin@kubient.com +​ +# Description +​ +Connects to Kubient KSSP demand source to fetch bids. +​ +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250],[728, 90]], + } + }, + bids: [{ + "bidder": "kubient", + "params": { + "zoneid": "5fbb948f1e22b", + } + }] + }]; diff --git a/test/spec/modules/kubientBidAdapter_spec.js b/test/spec/modules/kubientBidAdapter_spec.js new file mode 100644 index 00000000000..1df4370b2ba --- /dev/null +++ b/test/spec/modules/kubientBidAdapter_spec.js @@ -0,0 +1,259 @@ +import { expect, assert } from 'chai'; +import { spec } from 'modules/kubientBidAdapter.js'; + +describe('KubientAdapter', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'kubient', + bidderRequestId: '145e1d6a7837c9', + params: { + zoneid: '5678', + floor: 0.05, + }, + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + domain: 'example.com' + } + ] + } + }; + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentData = '1YCC'; + let bidderRequest = { + bidderCode: 'kubient', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + uspConsent: uspConsentData, + bids: [bid] + }; + describe('buildRequests', function () { + let serverRequests = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequests).to.be.an('array'); + }); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest.method).to.be.a('string'); + expect(serverRequest.url).to.be.a('string'); + expect(serverRequest.data).to.be.a('string'); + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/pbjs'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = JSON.parse(serverRequest.data); + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); + expect(data.v).to.exist.and.to.be.a('string'); + expect(data.requestId).to.exist.and.to.be.a('string'); + expect(data.referer).to.be.a('string'); + expect(data.tmax).to.exist.and.to.be.a('number'); + expect(data.gdpr).to.exist.and.to.be.within(0, 1); + expect(data.consent).to.equal(consentString); + expect(data.uspConsent).to.exist.and.to.equal(uspConsentData); + for (let j = 0; j < data['adSlots'].length; j++) { + let adSlot = data['adSlots'][i]; + expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'floor', 'sizes', 'schain', 'mediaTypes'); + expect(adSlot.bidId).to.be.a('string'); + expect(adSlot.zoneId).to.be.a('string'); + expect(adSlot.floor).to.be.a('number'); + expect(adSlot.sizes).to.be.an('array'); + expect(adSlot.schain).to.be.an('object'); + expect(adSlot.mediaTypes).to.be.an('object'); + } + }); + } + }); + + describe('isBidRequestValid', function () { + it('Should return true when required params are found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when required params are not found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when params are not found', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret response', function () { + const serverResponse = { + body: + { + seatbid: [ + { + bid: [ + { + bidId: '000', + price: 1.5, + adm: '
test
', + creativeId: 'creativeId', + w: 300, + h: 250, + cur: 'USD', + netRevenue: false, + ttl: 360 + } + ] + } + ] + } + }; + let bannerResponses = spec.interpretResponse(serverResponse); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'ad', 'creativeId', 'width', 'height', 'currency', 'netRevenue', 'ttl'); + expect(dataItem.requestId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].bidId); + expect(dataItem.cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(dataItem.ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(dataItem.creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].creativeId); + expect(dataItem.width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(dataItem.height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(dataItem.currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].cur); + expect(dataItem.netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(serverResponse.body.seatbid[0].bid[0].netRevenue); + expect(dataItem.ttl).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].ttl); + }); + + it('Should return no ad when not given a server response', function () { + const ads = spec.interpretResponse(null); + expect(ads).to.be.an('array').and.to.have.length(0); + }); + }); + + describe('getUserSyncs', function () { + it('should register the sync iframe without gdpr', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&consent_given=0'); + }); + it('should register the sync iframe with gdpr', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + }); + it('should register the sync iframe with gdpr vendor', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString, + apiVersion: 1, + vendorData: { + vendorConsents: { + 794: 1 + } + } + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + }); + it('should register the sync image without gdpr', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&consent_given=0'); + }); + it('should register the sync image with gdpr', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + }); + it('should register the sync image with gdpr vendor', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString, + apiVersion: 2, + vendorData: { + vendor: { + consents: { + 794: 1 + } + } + } + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + }); + }) +}); From 9f38423d98a9572163541957a50c70f1dedae037 Mon Sep 17 00:00:00 2001 From: Mathieu Pheulpin Date: Fri, 11 Dec 2020 13:30:24 -0800 Subject: [PATCH 002/325] [Sharethrough] Add Support for badv/bcat and Identity Link User ID (#6100) * Add support for badv and bcat [#175358412] Co-authored-by: Michael Duran * Add support for Identity Link [#175688307] Co-authored-by: Michael Duran Co-authored-by: Michael Duran --- modules/sharethroughBidAdapter.js | 16 +++++- modules/sharethroughBidAdapter.md | 8 ++- .../modules/sharethroughBidAdapter_spec.js | 54 ++++++++++++++++--- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 7df161db713..89484b1c68b 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; -const VERSION = '3.2.1'; +const VERSION = '3.3.0'; const BIDDER_CODE = 'sharethrough'; const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; const DEFAULT_SIZE = [1, 1]; @@ -56,6 +56,10 @@ export const sharethroughAdapterSpec = { query.pubcid = bidRequest.crumbs.pubcid; } + if (bidRequest.userId && bidRequest.userId.idl_env) { + query.idluid = bidRequest.userId.idl_env; + } + if (bidRequest.schain) { query.schain = JSON.stringify(bidRequest.schain); } @@ -64,6 +68,14 @@ export const sharethroughAdapterSpec = { query.bidfloor = parseFloat(bidRequest.bidfloor); } + if (bidRequest.params.badv) { + query.badv = bidRequest.params.badv; + } + + if (bidRequest.params.bcat) { + query.bcat = bidRequest.params.bcat; + } + // Data that does not need to go to the server, // but we need as part of interpretResponse() const strData = { @@ -73,7 +85,7 @@ export const sharethroughAdapterSpec = { }; return { - method: 'GET', + method: 'POST', url: STR_ENDPOINT, data: query, strData: strData diff --git a/modules/sharethroughBidAdapter.md b/modules/sharethroughBidAdapter.md index 2290e370cae..396b8164577 100644 --- a/modules/sharethroughBidAdapter.md +++ b/modules/sharethroughBidAdapter.md @@ -29,7 +29,13 @@ Module that connects to Sharethrough's demand sources // OPTIONAL - If iframeSize is provided, we'll use this size for the iframe // otherwise we'll grab the largest size from the sizes array // This is ignored if iframe: false - iframeSize: [250, 250] + iframeSize: [250, 250], + + // OPTIONAL - Blocked Advertiser Domains + badv: ['domain1.com', 'domain2.com'], + + // OPTIONAL - Blocked Categories (IAB codes) + bcat: ['IAB1-1', 'IAB1-2'], } } ] diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index d45d1e977e6..cd9071a6098 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -14,7 +14,8 @@ const bidRequests = [ }, userId: { tdid: 'fake-tdid', - pubcid: 'fake-pubcid' + pubcid: 'fake-pubcid', + idl_env: 'fake-identity-link' }, crumbs: { pubcid: 'fake-pubcid-in-crumbs-obj' @@ -40,12 +41,32 @@ const bidRequests = [ iframe: true, iframeSize: [500, 500] } - } + }, + { + bidder: 'sharethrough', + bidId: 'bidId4', + sizes: [[700, 400]], + placementCode: 'bar', + params: { + pkey: 'dddd4444', + badv: ['domain1.com', 'domain2.com'] + } + }, + { + bidder: 'sharethrough', + bidId: 'bidId5', + sizes: [[700, 400]], + placementCode: 'bar', + params: { + pkey: 'eeee5555', + bcat: ['IAB1-1', 'IAB1-2'] + } + }, ]; const prebidRequests = [ { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -57,7 +78,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -69,7 +90,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -82,7 +103,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -94,7 +115,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -225,7 +246,7 @@ describe('sharethrough adapter spec', function() { expect(builtBidRequests[0].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); expect(builtBidRequests[1].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); - expect(builtBidRequests[0].method).to.eq('GET'); + expect(builtBidRequests[0].method).to.eq('POST'); }); it('should set the instant_play_capable parameter correctly based on browser userAgent string', function() { @@ -315,6 +336,11 @@ describe('sharethrough adapter spec', function() { expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); }); + it('should add the idluid parameter if a bid request contains a value for Identity Link from Live Ramp', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.idluid).to.eq('fake-identity-link'); + }); + it('should add Sharethrough specific parameters', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0]).to.deep.include({ @@ -346,6 +372,18 @@ describe('sharethrough adapter spec', function() { expect(builtBidRequest.data.schain).to.eq(JSON.stringify(bidRequest.schain)); }); + it('should add badv if provided', () => { + const builtBidRequest = spec.buildRequests([bidRequests[3]])[0]; + + expect(builtBidRequest.data.badv).to.have.members(['domain1.com', 'domain2.com']) + }); + + it('should add bcat if provided', () => { + const builtBidRequest = spec.buildRequests([bidRequests[4]])[0]; + + expect(builtBidRequest.data.bcat).to.have.members(['IAB1-1', 'IAB1-2']) + }); + it('should not add a supply chain parameter if schain is missing', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data).to.not.include.any.keys('schain'); From 25270510561bf2b8d3f303d25b0a6c80277fd03c Mon Sep 17 00:00:00 2001 From: Egor Gordeev <48566506+egsgordeev@users.noreply.github.com> Date: Mon, 14 Dec 2020 10:51:48 +0400 Subject: [PATCH 003/325] Sovrn: Pass the imp.ext.deals field (#6098) * EX-2549 Pass segments parameter as imp.ext.dealids array * EX-2549 Address Jon's feedback * EX-2549 Reworked the solution * EX-2549 Blackbird compatibility * EX-2549 Address Jon's comments * EX-2549 Addressed upstream PR comments --- modules/sovrnBidAdapter.js | 14 ++++++++++-- test/spec/modules/sovrnBidAdapter_spec.js | 26 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 62f5e85779e..8f8158fd0c9 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { 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)})) - sovrnImps.push({ + const imp = { adunitcode: bid.adUnitCode, id: bid.bidId, banner: { @@ -63,7 +63,17 @@ export const spec = { }, tagid: String(utils.getBidIdParameter('tagid', bid.params)), bidfloor: utils.getBidIdParameter('bidfloor', bid.params) - }); + } + + const segmentsString = utils.getBidIdParameter('segments', bid.params) + + if (segmentsString) { + imp.ext = { + deals: segmentsString.split(',').map(deal => deal.trim()) + } + } + + sovrnImps.push(imp); }); const page = bidderRequest.refererInfo.referer diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 983ade4dd14..769be73a272 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -276,6 +276,32 @@ describe('sovrnBidAdapter', function() { expect(data.user.ext.tpid[0].uid).to.equal('A_CRITEO_ID') expect(data.user.ext.prebid_criteoid).to.equal('A_CRITEO_ID') }); + + it('should ignore empty segments', function() { + const payload = JSON.parse(request.data) + expect(payload.imp[0].ext).to.be.undefined + }) + + it('should pass the segments param value as trimmed deal ids array', function() { + const segmentsRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'segments': ' test1,test2 ' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const request = spec.buildRequests(segmentsRequests, bidderRequest) + const payload = JSON.parse(request.data) + expect(payload.imp[0].ext.deals[0]).to.equal('test1') + expect(payload.imp[0].ext.deals[1]).to.equal('test2') + }) }); describe('interpretResponse', function () { From 42be508d35e10ab60d9421873d2b60ae97b66cb0 Mon Sep 17 00:00:00 2001 From: Adam Browning <19834421+adam-browning@users.noreply.github.com> Date: Mon, 14 Dec 2020 12:07:32 +0200 Subject: [PATCH 004/325] oneVideo Adapter - Dynamic TTL support (SAPR-15473) (#6108) * ttl incoming value or default 300 * ttl validation 0-3600 * Fix to oneVideo tests * added ttl unit tests * updated md file * update minor version 3.0.5 * Updated unit test for minor version check * remove x from liveIntentIdSystem_spec.js * md conflict fix * fix missing comma in md file * update minor version 3.0.5 * remove x from liveIntentIdSystem_spec.js * update md file * added ttl to md file * ttl incoming value or default 300 * ttl validation 0-3600 * Fix to oneVideo tests * added ttl unit tests * updated md file * update minor version 3.0.5 * Updated unit test for minor version check * remove x from liveIntentIdSystem_spec.js * md conflict fix * update minor version 3.0.5 * remove x from liveIntentIdSystem_spec.js * update md file * added ttl to md file * cleanup --- modules/oneVideoBidAdapter.js | 5 ++-- modules/oneVideoBidAdapter.md | 2 ++ test/spec/modules/oneVideoBidAdapter_spec.js | 28 ++++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index c287bc2f3b7..8a71910e8fc 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.4', + VERSION: '3.0.5', 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', @@ -99,7 +99,7 @@ export const spec = { width: size.width, height: size.height, currency: response.cur, - ttl: 100, + ttl: (bidRequest.params.video.ttl > 0 && bidRequest.params.video.ttl <= 3600) ? bidRequest.params.video.ttl : 300, netRevenue: true, adUnitCode: bidRequest.adUnitCode }; @@ -113,7 +113,6 @@ export const spec = { } else if (bid.adm) { bidResponse.vastXml = bid.adm; } - if (bidRequest.mediaTypes.video) { bidResponse.renderer = (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined; } diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md index 92958af9e83..d413c9d64e5 100644 --- a/modules/oneVideoBidAdapter.md +++ b/modules/oneVideoBidAdapter.md @@ -40,6 +40,7 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to inventoryid: 123, minduration: 10, maxduration: 30, + ttl: 300, custom: { key1: "value1", key2: 123345 @@ -89,6 +90,7 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to inventoryid: 123, minduration: 10, maxduration: 30, + ttl: 250 }, site: { id: 1, diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index 331ac8976e6..83adf8a4b11 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -217,7 +217,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.4'; + const VERSION = '3.0.5'; 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); @@ -407,7 +407,7 @@ describe('OneVideoBidAdapter', function () { height: 480, mediaType: 'video', currency: 'USD', - ttl: 100, + ttl: 300, netRevenue: true, adUnitCode: bidRequest.adUnitCode, renderer: (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined, @@ -434,6 +434,30 @@ describe('OneVideoBidAdapter', function () { expect(bidResponse.mediaType).to.equal('banner'); expect(bidResponse.renderer).to.be.undefined; }); + + it('should default ttl to 300', function () { + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ttl).to.equal(300); + }); + it('should not allow ttl above 3601, default to 300', function () { + bidRequest.params.video.ttl = 3601; + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ttl).to.equal(300); + }); + it('should not allow ttl below 1, default to 300', function () { + bidRequest.params.video.ttl = 0; + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ttl).to.equal(300); + }); + it('should use custom ttl if under 3600', function () { + bidRequest.params.video.ttl = 1000; + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ttl).to.equal(1000); + }); }); describe('when GDPR and uspConsent applies', function () { From a2454a5e9f18ca72e34c4eedbd144f88f5e7872e Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Mon, 14 Dec 2020 07:04:32 -0500 Subject: [PATCH 005/325] Add Gulp Review-Start Task (#6067) * add review-start gulp command * remove watch, unecessary for reviews * add instructions to reviewer file for new command * Updates to add back watch * updates to had reviewer hub page * Update PR_REVIEW.md --- PR_REVIEW.md | 11 ++++++ gulpfile.js | 20 +++++++++- integrationExamples/reviewerTools/index.html | 40 ++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100755 integrationExamples/reviewerTools/index.html diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 662a1a871c8..0519cbb7b6e 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -5,6 +5,17 @@ If the PR is for a standard bid adapter or a standard analytics adapter, just th For modules and core platform updates, the initial reviewer should request an additional team member to review as a sanity check. Merge should only happen when the PR has 2 `LGTM` from the core team and a documentation PR if required. +### Running Tests and Verifying Integrations + +General gulp commands include separate commands for serving the codebase on a built in webserver, creating code coverage reports and allowing serving integration examples. The `review-start` gulp command combinese those into one command. + +- Run `gulp review-start`, adding the host parameter `gulp review-start --host=0.0.0.0` will bind to all IPs on the machine + - A page will open which provides a hub for common reviewer tools. + - If you need to manually acceess the tools: + - Navigate to build/coverage/lcov-report/index.html to view coverage + - Navigate to integrationExamples/gpt/hellow_world.html for basic integration testing + - The hello_world.html and other exampls can be edited and used as needed to verify functionality + ### General PR review Process - All required global and bidder-adapter rules defined in the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html) must be followed. Please review these rules often - we depend on reviewers to enforce them. - Checkout the branch (these instructions are available on the github PR page as well). diff --git a/gulpfile.js b/gulpfile.js index 879e34ae588..b7a9e442a8c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -86,7 +86,8 @@ function viewCoverage(done) { connect.server({ port: coveragePort, root: 'build/coverage/lcov-report', - livereload: false + livereload: false, + debug: true }); opens('http://' + mylocalhost + ':' + coveragePort); done(); @@ -94,6 +95,19 @@ function viewCoverage(done) { viewCoverage.displayName = 'view-coverage'; +// View the reviewer tools page +function viewReview(done) { + var mylocalhost = (argv.host) ? argv.host : 'localhost'; + var reviewUrl = 'http://' + mylocalhost + ':' + port + '/integrationExamples/reviewerTools/index.html'; // reuse the main port from 9999 + + // console.log(`stdout: opening` + reviewUrl); + + opens(reviewUrl); + done(); +}; + +viewReview.displayName = 'view-review'; + // Watch Task with Live Reload function watch(done) { var mainWatcher = gulp.watch([ @@ -383,4 +397,8 @@ gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-p gulp.task(bundleToStdout); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step +// build task for reviewers, runs test-coverage, serves, without watching +gulp.task(viewReview); +gulp.task('review-start', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, testCoverage), viewReview)); + module.exports = nodeBundle; diff --git a/integrationExamples/reviewerTools/index.html b/integrationExamples/reviewerTools/index.html new file mode 100755 index 00000000000..2732cb4fd88 --- /dev/null +++ b/integrationExamples/reviewerTools/index.html @@ -0,0 +1,40 @@ + + + + + + + Prebid Reviewer Tools + + + + +
+
+
+

Reviewer Tools

+

Below are links to the most common tool used by Prebid reviewers. For more info on PR review processes check out the General PR Review Process page on Github.

+

Common

+ +

Other Tools

+ +

Documentation & Training Material

+ +
+
+
+ + \ No newline at end of file From 94c9dcc5d64b3ec2ec10636bdc68182b9d5be462 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Mon, 14 Dec 2020 09:20:34 -0500 Subject: [PATCH 006/325] appnexusBidAdapter - add support for test flag (#6119) --- modules/appnexusBidAdapter.js | 6 ++++++ test/spec/modules/appnexusBidAdapter_spec.js | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index c102bee4e58..3a9f37f4ca8 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -498,6 +498,12 @@ function formatRequest(payload, bidderRequest) { } } + if (utils.getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { + options.customHeaders = { + 'X-Is-Test': 1 + } + } + if (payload.tags.length > MAX_IMPS_PER_REQUEST) { const clonedPayload = utils.deepClone(payload); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 9b12d892440..a36eab03fe1 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -784,6 +784,18 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); + it('should set the X-Is-Test customHeader if test flag is enabled', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('apn_test') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + + config.getConfig.restore(); + }); + it('should set withCredentials to false if purpose 1 consent is not given', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { From 2102f4a1c1548bacb775cf6c011c6eba621c81d1 Mon Sep 17 00:00:00 2001 From: fgcloutier Date: Mon, 14 Dec 2020 15:55:29 +0100 Subject: [PATCH 007/325] feat(sublimeBidAdapter): updating sublimeBidAdapter module (#6113) - using a simple-request for the POST bid request - renaming our internal ver pixel param to pbav --- modules/sublimeBidAdapter.js | 11 ++--- test/spec/modules/sublimeBidAdapter_spec.js | 47 ++++++++++++++++++--- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js index e9f7cf19033..7a573ca4c4b 100644 --- a/modules/sublimeBidAdapter.js +++ b/modules/sublimeBidAdapter.js @@ -9,7 +9,7 @@ const DEFAULT_CURRENCY = 'EUR'; const DEFAULT_PROTOCOL = 'https'; const DEFAULT_TTL = 600; const SUBLIME_ANTENNA = 'antenna.ayads.co'; -const SUBLIME_VERSION = '0.6.0'; +const SUBLIME_VERSION = '0.7.0'; /** * Debug log message @@ -50,7 +50,7 @@ export function sendEvent(eventName) { src: 'pa', puid: state.transactionId || state.notifyId, trId: state.transactionId || state.notifyId, - ver: SUBLIME_VERSION, + pbav: SUBLIME_VERSION, }; log('Sending pixel for event: ' + eventName, eventObject); @@ -128,10 +128,10 @@ function buildRequests(validBidRequests, bidderRequest) { return { method: 'POST', url: protocol + '://' + bidHost + '/bid', - data: payload, + data: JSON.stringify(payload), options: { - contentType: 'application/json', - withCredentials: true + contentType: 'text/plain', + withCredentials: false }, } }); @@ -210,6 +210,7 @@ export const spec = { code: BIDDER_CODE, gvlid: BIDDER_GVLID, aliases: [], + sendEvent: sendEvent, isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, interpretResponse: interpretResponse, diff --git a/test/spec/modules/sublimeBidAdapter_spec.js b/test/spec/modules/sublimeBidAdapter_spec.js index 008f24730bc..a0765a0d396 100644 --- a/test/spec/modules/sublimeBidAdapter_spec.js +++ b/test/spec/modules/sublimeBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, sendEvent, log, setState, state } from 'modules/sublimeBidAdapter.js'; +import { spec } from 'modules/sublimeBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; let utils = require('src/utils'); @@ -9,6 +9,16 @@ describe('Sublime Adapter', function() { describe('sendEvent', function() { let sandbox; + const triggeredPixelProperties = [ + 't', + 'tse', + 'z', + 'e', + 'src', + 'puid', + 'trId', + 'pbav', + ]; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -16,8 +26,10 @@ describe('Sublime Adapter', function() { it('should trigger pixel', function () { sandbox.spy(utils, 'triggerPixel'); - sendEvent('test', true); + spec.sendEvent('test'); expect(utils.triggerPixel.called).to.equal(true); + const params = utils.parseUrl(utils.triggerPixel.args[0][0]).search; + expect(Object.keys(params)).to.have.members(triggeredPixelProperties); }); afterEach(function () { @@ -94,8 +106,9 @@ describe('Sublime Adapter', function() { }); it('should contains a request id equals to the bid id', function() { - expect(request[0].data.requestId).to.equal(bidRequests[0].bidId); - expect(request[1].data.requestId).to.equal(bidRequests[1].bidId); + for (let i = 0; i < request.length; i = i + 1) { + expect(JSON.parse(request[i].data).requestId).to.equal(bidRequests[i].bidId); + } }); it('should have an url that contains bid keyword', function() { @@ -149,7 +162,7 @@ describe('Sublime Adapter', function() { currency: 'USD', netRevenue: true, ttl: 600, - pbav: '0.6.0', + pbav: '0.7.0', ad: '', }, ]; @@ -191,7 +204,7 @@ describe('Sublime Adapter', function() { netRevenue: true, ttl: 600, ad: '', - pbav: '0.6.0', + pbav: '0.7.0', }; expect(result[0]).to.deep.equal(expectedResponse); @@ -241,7 +254,7 @@ describe('Sublime Adapter', function() { netRevenue: true, ttl: 600, ad: '', - pbav: '0.6.0', + pbav: '0.7.0', }; expect(result[0]).to.deep.equal(expectedResponse); @@ -279,4 +292,24 @@ describe('Sublime Adapter', function() { }); }); }); + + describe('onBidWon', function() { + let sandbox; + let bid = { foo: 'bar' }; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + it('should trigger "bidwon" pixel', function () { + sandbox.spy(utils, 'triggerPixel'); + spec.onBidWon(bid); + const params = utils.parseUrl(utils.triggerPixel.args[0][0]).search; + expect(params.e).to.equal('bidwon'); + }); + + afterEach(function () { + sandbox.restore(); + }); + }) }); From 0fec20d2914a291abdfa052a59b988f3b300b656 Mon Sep 17 00:00:00 2001 From: OneTagDevOps <38786435+OneTagDevOps@users.noreply.github.com> Date: Mon, 14 Dec 2020 16:36:39 +0100 Subject: [PATCH 008/325] oneTag Bid Adapter: bidRequest object adjustments (#6105) * Propagates server data directly without members picking, extracts userIds from the the validBidRequest object * Picks netRevenue value from received bid, defaulting to false Co-authored-by: francesco --- modules/onetagBidAdapter.js | 87 ++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 50 deletions(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 1a2df023b81..16b8096646f 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -6,6 +6,7 @@ import { Renderer } from '../src/Renderer.js'; import find from 'core-js-pure/features/array/find.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { createEidsArray } from './userId/eids.js'; const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; @@ -63,8 +64,8 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidderRequest && bidderRequest.uspConsent) { payload.usPrivacy = bidderRequest.uspConsent; } - if (bidderRequest && bidderRequest.userId) { - payload.userId = bidderRequest.userId; + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userId) { + payload.userId = createEidsArray(validBidRequests[0].userId); } try { if (storage.hasLocalStorage()) { @@ -88,47 +89,34 @@ function interpretResponse(serverResponse, bidderRequest) { if (!body.bids || !Array.isArray(body.bids) || body.bids.length === 0) { return bids; } - body.bids.forEach(({ - requestId, - cpm, - width, - height, - creativeId, - dealId, - currency, - mediaType, - ttl, - rendererUrl, - ad, - vastUrl, - videoCacheKey - }) => { + body.bids.forEach(bid => { const responseBid = { - requestId, - cpm, - width, - height, - creativeId, - dealId: dealId == null ? dealId : '', - currency, - netRevenue: false, + requestId: bid.requestId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.creativeId, + dealId: bid.dealId == null ? bid.dealId : '', + currency: bid.currency, + netRevenue: bid.netRevenue || false, + mediaType: bid.mediaType, meta: { - mediaType + mediaType: bid.mediaType }, - ttl: ttl || 300 + ttl: bid.ttl || 300 }; - if (mediaType === BANNER) { - responseBid.ad = ad; - } else if (mediaType === VIDEO) { - const {context, adUnitCode} = find(requestData.bids, (item) => item.bidId === requestId); + if (bid.mediaType === BANNER) { + responseBid.ad = bid.ad; + } else if (bid.mediaType === VIDEO) { + const {context, adUnitCode} = find(requestData.bids, (item) => item.bidId === bid.requestId); if (context === INSTREAM) { - responseBid.vastUrl = vastUrl; - responseBid.videoCacheKey = videoCacheKey; + responseBid.vastUrl = bid.vastUrl; + responseBid.videoCacheKey = bid.videoCacheKey; } else if (context === OUTSTREAM) { - responseBid.vastXml = ad; - responseBid.vastUrl = vastUrl; - if (rendererUrl) { - responseBid.renderer = createRenderer({requestId, rendererUrl, adUnitCode}); + responseBid.vastXml = bid.ad; + responseBid.vastUrl = bid.vastUrl; + if (bid.rendererUrl) { + responseBid.renderer = createRenderer({ ...bid, adUnitCode }); } } } @@ -146,25 +134,24 @@ function createRenderer(bid, rendererOptions = {}) { loaded: false }); try { - renderer.setRender(onetagRenderer); + renderer.setRender(({renderer, width, height, vastXml, adUnitCode}) => { + renderer.push(() => { + window.onetag.Player.init({ + ...bid, + width, + height, + vastXml, + nodeId: adUnitCode, + config: renderer.getConfig() + }); + }); + }); } catch (e) { } return renderer; } -function onetagRenderer({renderer, width, height, vastXml, adUnitCode}) { - renderer.push(() => { - window.onetag.Player.init({ - width, - height, - vastXml, - nodeId: adUnitCode, - config: renderer.getConfig() - }); - }); -} - function getFrameNesting() { let topmostFrame = window; let parent = window.parent; From 30711be00243e653a25785b2d7b46b9df8f5e120 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Mon, 14 Dec 2020 21:41:42 +0100 Subject: [PATCH 009/325] a/b testing framework baked in to the ID5 user id module (#6076) --- modules/id5IdSystem.js | 47 ++++++-- modules/id5IdSystem.md | 21 +++- modules/userId/eids.md | 3 +- test/spec/modules/id5IdSystem_spec.js | 157 +++++++++++++++++++++++++- 4 files changed, 209 insertions(+), 19 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 7033a71d015..17a808badaa 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -42,27 +42,58 @@ export const id5IdSubmodule = { * decode the stored id value for passing to bid requests * @function decode * @param {(Object|string)} value + * @param {SubmoduleConfig|undefined} config * @returns {(Object|undefined)} */ - decode(value) { - let uid; + decode(value, config) { + let universalUid; let linkType = 0; if (value && typeof value.universal_uid === 'string') { - uid = value.universal_uid; + universalUid = value.universal_uid; linkType = value.link_type || linkType; } else { return undefined; } - return { - 'id5id': { - 'uid': uid, - 'ext': { - 'linkType': linkType + // check for A/B testing configuration and hide ID if in Control Group + let abConfig = (config && config.params && config.params.abTesting) || { enabled: false }; + let controlGroup = false; + if ( + abConfig.enabled === true && + (!utils.isNumber(abConfig.controlGroupPct) || + abConfig.controlGroupPct < 0 || + abConfig.controlGroupPct > 1) + ) { + // A/B Testing is enabled, but configured improperly, so skip A/B testing + utils.logError('User ID - ID5 submodule: A/B Testing controlGroupPct must be a number >= 0 and <= 1! Skipping A/B Testing'); + } else if ( + abConfig.enabled === true && + Math.random() < abConfig.controlGroupPct + ) { + // A/B Testing is enabled and user is in the Control Group, so do not share the ID5 ID + utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - request is in the Control Group, so the ID5 ID is NOT exposed'); + universalUid = linkType = 0; + controlGroup = true; + } else if (abConfig.enabled === true) { + // A/B Testing is enabled but user is not in the Control Group, so ID5 ID is shared + utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - request is NOT in the Control Group, so the ID5 ID is exposed'); + } + + let responseObj = { + id5id: { + uid: universalUid, + ext: { + linkType: linkType } } }; + + if (abConfig.enabled === true) { + utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', controlGroup); + } + + return responseObj; }, /** diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index e5e3969c19c..6b2192834fa 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -22,14 +22,18 @@ The following configuration parameters are available: pbjs.setConfig({ userSync: { userIds: [{ - name: "id5Id", + name: 'id5Id', params: { partner: 173, // change to the Partner Number you received from ID5 - pd: "MT1iNTBjY..." // optional, see table below for a link to how to generate this + pd: 'MT1iNTBjY...', // optional, see table below for a link to how to generate this + abTesting: { // optional + enabled: true, // false by default + controlGroupPct: 0.1 // valid values are 0.0 - 1.0 (inclusive) + } }, storage: { - type: "html5", // "html5" is the required storage type - name: "id5id", // "id5id" is the required storage name + type: 'html5', // "html5" is the required storage type + name: 'id5id', // "id5id" is the required storage name expires: 90, // storage lasts for 90 days refreshInSeconds: 8*3600 // refresh ID every 8 hours to ensure it's fresh } @@ -46,6 +50,9 @@ pbjs.setConfig({ | params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | | params.pd | Optional | String | Publisher-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/x/BIAZ) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | | params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | +| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | +| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | +| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | | storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | | storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | | storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | @@ -53,3 +60,9 @@ pbjs.setConfig({ | storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | **ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). + +### A Note on A/B Testing + +Publishers may want to test the value of the ID5 ID with their downstream partners. While there are various ways to do this, A/B testing is a standard approach. Instead of publishers manually enabling or disabling the ID5 User ID Module based on their control group settings (which leads to fewer calls to ID5, reducing our ability to recognize the user), we have baked this in to our module directly. + +To turn on A/B Testing, simply edit the configuration (see above table) to enable it and set what percentage of requests you would like to set for the control group. The control group is the set of requests where an ID5 ID will not be exposed in to bid adapters or in the various user id functions available on the `pbjs` global. An additional value of `ext.abTestingControlGroup` will be set to `true` or `false` that can be used to inform reporting systems that the request was in the control group or not. It's important to note that the control group is request based, and not user based. In other words, from one page view to another, a user may be in or out of the control group. diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 0cf9b6d2d22..fecf7e888bf 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -36,7 +36,8 @@ userIdAsEids = [ atype: 1 }, ext: { - linkType: 2 + linkType: 2, + abTestingControlGroup: false }] }, diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 845cf7fa010..afde1696766 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -26,16 +26,19 @@ describe('ID5 ID System', function() { const ID5_NB_STORAGE_NAME = nbCacheName(ID5_TEST_PARTNER_ID); const ID5_STORED_ID = 'storedid5id'; const ID5_STORED_SIGNATURE = '123456'; + const ID5_STORED_LINK_TYPE = 1; const ID5_STORED_OBJ = { 'universal_uid': ID5_STORED_ID, - 'signature': ID5_STORED_SIGNATURE + 'signature': ID5_STORED_SIGNATURE, + 'link_type': ID5_STORED_LINK_TYPE }; const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; + const ID5_RESPONSE_LINK_TYPE = 2; const ID5_JSON_RESPONSE = { 'universal_uid': ID5_RESPONSE_ID, 'signature': ID5_RESPONSE_SIGNATURE, - 'link_type': 0 + 'link_type': ID5_RESPONSE_LINK_TYPE }; function getId5FetchConfig(storageName = ID5_STORAGE_NAME, storageType = 'html5') { @@ -268,7 +271,7 @@ describe('ID5 ID System', function() { source: ID5_SOURCE, uids: [{ id: ID5_STORED_ID, atype: 1 }], ext: { - linkType: 0 + linkType: ID5_STORED_LINK_TYPE } }); }); @@ -360,13 +363,155 @@ describe('ID5 ID System', function() { }); describe('Decode stored object', function() { - const expectedDecodedObject = { id5id: { uid: ID5_STORED_ID, ext: { linkType: 0 } } }; + const expectedDecodedObject = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; it('should properly decode from a stored object', function() { - expect(id5IdSubmodule.decode(ID5_STORED_OBJ)).to.deep.equal(expectedDecodedObject); + expect(id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).to.deep.equal(expectedDecodedObject); }); it('should return undefined if passed a string', function() { - expect(id5IdSubmodule.decode('somestring')).to.eq(undefined); + expect(id5IdSubmodule.decode('somestring', getId5FetchConfig())).to.eq(undefined); + }); + }); + + describe('A/B Testing', function() { + const expectedDecodedObjectWithIdAbOff = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; + const expectedDecodedObjectWithIdAbOn = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE, abTestingControlGroup: false } } }; + const expectedDecodedObjectWithoutIdAbOn = { id5id: { uid: 0, ext: { linkType: 0, abTestingControlGroup: true } } }; + let testConfig; + + beforeEach(function() { + testConfig = getId5FetchConfig(); + }); + + describe('Configuration Validation', function() { + let logErrorSpy; + let logInfoSpy; + + beforeEach(function() { + logErrorSpy = sinon.spy(utils, 'logError'); + logInfoSpy = sinon.spy(utils, 'logInfo'); + }); + afterEach(function() { + logErrorSpy.restore(); + logInfoSpy.restore(); + }); + + // A/B Testing ON, but invalid config + let testInvalidAbTestingConfigsWithError = [ + { enabled: true }, + { enabled: true, controlGroupPct: 2 }, + { enabled: true, controlGroupPct: -1 }, + { enabled: true, controlGroupPct: 'a' }, + { enabled: true, controlGroupPct: true } + ]; + testInvalidAbTestingConfigsWithError.forEach((testAbTestingConfig) => { + it('should error if config is invalid, and always return an ID', function () { + testConfig.params.abTesting = testAbTestingConfig; + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOn); + sinon.assert.calledOnce(logErrorSpy); + }); + }); + + // A/B Testing OFF, with invalid config (ignore) + let testInvalidAbTestingConfigsWithoutError = [ + { enabled: false, controlGroupPct: -1 }, + { enabled: false, controlGroupPct: 2 }, + { enabled: false, controlGroupPct: 'a' }, + { enabled: false, controlGroupPct: true } + ]; + testInvalidAbTestingConfigsWithoutError.forEach((testAbTestingConfig) => { + it('should not error if config is invalid but A/B testing is off, and always return an ID', function () { + testConfig.params.abTesting = testAbTestingConfig; + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + sinon.assert.notCalled(logErrorSpy); + sinon.assert.notCalled(logInfoSpy); + }); + }); + + // A/B Testing ON, with valid config + let testValidConfigs = [ + { enabled: true, controlGroupPct: 0 }, + { enabled: true, controlGroupPct: 0.5 }, + { enabled: true, controlGroupPct: 1 } + ]; + testValidConfigs.forEach((testAbTestingConfig) => { + it('should not error if config is valid', function () { + testConfig.params.abTesting = testAbTestingConfig; + id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + sinon.assert.notCalled(logErrorSpy); + sinon.assert.calledOnce(logInfoSpy); + }); + }); + }); + + describe('A/B Testing Config is not Set', function() { + let randStub; + + beforeEach(function() { + randStub = sinon.stub(Math, 'random').callsFake(function() { + return 0; + }); + }); + afterEach(function () { + randStub.restore(); + }); + + it('should expose ID when A/B config is not set', function () { + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + }); + + it('should expose ID when A/B config is empty', function () { + testConfig.params.abTesting = { }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + }); + }); + + describe('A/B Testing Config is Set', function() { + let randStub; + + beforeEach(function() { + randStub = sinon.stub(Math, 'random').callsFake(function() { + return 0.25; + }); + }); + afterEach(function () { + randStub.restore(); + }); + + it('should expose ID when A/B testing is off', function () { + testConfig.params.abTesting = { + enabled: false, + controlGroupPct: 0.5 + }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + }); + + it('should expose ID when not in control group', function () { + testConfig.params.abTesting = { + enabled: true, + controlGroupPct: 0.1 + }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOn); + }); + + it('should not expose ID when in control group', function () { + testConfig.params.abTesting = { + enabled: true, + controlGroupPct: 0.5 + }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithoutIdAbOn); + }); }); }); }); From f6f27dd06a8fcbc06cf47dfacf61347a7d892da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Mon, 14 Dec 2020 22:47:11 +0100 Subject: [PATCH 010/325] Add Zemanta adapter (#6039) --- modules/zemantaBidAdapter.js | 256 ++++++++++ modules/zemantaBidAdapter.md | 107 +++++ test/spec/modules/zemantaBidAdapter_spec.js | 487 ++++++++++++++++++++ 3 files changed, 850 insertions(+) create mode 100644 modules/zemantaBidAdapter.js create mode 100644 modules/zemantaBidAdapter.md create mode 100644 test/spec/modules/zemantaBidAdapter_spec.js diff --git a/modules/zemantaBidAdapter.js b/modules/zemantaBidAdapter.js new file mode 100644 index 00000000000..aa7a24985e0 --- /dev/null +++ b/modules/zemantaBidAdapter.js @@ -0,0 +1,256 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'zemanta'; +const CURRENCY = 'USD'; +const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; +const NATIVE_PARAMS = { + title: { id: 0, name: 'title' }, + icon: { id: 2, type: 1, name: 'img' }, + image: { id: 3, type: 3, name: 'img' }, + sponsoredBy: { id: 5, name: 'data', type: 1 }, + body: { id: 4, name: 'data', type: 2 }, + cta: { id: 1, type: 12, name: 'data' } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [ NATIVE, BANNER ], + isBidRequestValid: (bid) => { + return ( + !!config.getConfig('zemanta.bidderUrl') && + !!utils.deepAccess(bid, 'params.publisher.id') && + !!(bid.nativeParams || bid.sizes) + ); + }, + buildRequests: (validBidRequests, bidderRequest) => { + const page = bidderRequest.refererInfo.referer; + const ua = navigator.userAgent; + const test = setOnAny(validBidRequests, 'params.test'); + const publisher = setOnAny(validBidRequests, 'params.publisher'); + const cur = CURRENCY; + const endpointUrl = config.getConfig('zemanta.bidderUrl'); + const timeout = bidderRequest.timeout; + + const imps = validBidRequests.map((bid, id) => { + bid.netRevenue = 'net'; + const imp = { + id: id + 1 + '' + } + + if (bid.params.tagid) { + imp.tagid = bid.params.tagid + } + + if (bid.nativeParams) { + imp.native = { + request: JSON.stringify({ + assets: getNativeAssets(bid) + }) + } + } else { + imp.banner = { + format: transformSizes(bid.sizes) + } + } + + return imp; + }); + + const request = { + id: bidderRequest.auctionId, + site: { page, publisher }, + device: { ua }, + source: { fd: 1 }, + cur: [cur], + tmax: timeout, + imp: imps + }; + + if (test) { + request.is_debug = !!test; + request.test = 1; + } + + if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString) + utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1) + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent) + } + if (config.getConfig('coppa') === true) { + utils.deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) + } + + return { + method: 'POST', + url: endpointUrl, + data: JSON.stringify(request), + bids: validBidRequests + }; + }, + interpretResponse: (serverResponse, { bids }) => { + if (!serverResponse.body) { + return []; + } + const { seatbid, cur } = serverResponse.body; + + const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + result[bid.impid - 1] = bid; + return result; + }, []); + + return bids.map((bid, id) => { + const bidResponse = bidResponses[id]; + if (bidResponse) { + const type = bid.nativeParams ? NATIVE : BANNER; + const bidObject = { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: cur, + mediaType: type, + nurl: bidResponse.nurl, + }; + if (type === NATIVE) { + bidObject.native = parseNative(bidResponse); + } else { + bidObject.ad = bidResponse.adm; + bidObject.width = bidResponse.w; + bidObject.height = bidResponse.h; + } + return bidObject; + } + }).filter(Boolean); + }, + getUserSyncs: (syncOptions) => { + const syncs = []; + const syncUrl = config.getConfig('zemanta.usersyncUrl'); + if (syncOptions.pixelEnabled && syncUrl) { + syncs.push({ + type: 'image', + url: syncUrl + }); + } + return syncs; + }, + onBidWon: (bid) => { + ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + } +}; + +registerBidder(spec); + +function parseNative(bid) { + const { assets, link, eventtrackers } = JSON.parse(bid.adm); + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined + }; + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + if (eventtrackers) { + result.impressionTrackers = []; + eventtrackers.forEach(tracker => { + if (tracker.event !== 1) return; + switch (tracker.method) { + case 1: // img + result.impressionTrackers.push(tracker.url); + break; + case 2: // js + result.javascriptTrackers = ``; + break; + } + }); + } + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = utils.deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} + +function getNativeAssets(bid) { + return utils._map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } + }).filter(Boolean); +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + if (!utils.isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (utils.isArray(requestSizes[0])) { + return requestSizes.map(item => + ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + }) + ); + } + + return []; +} diff --git a/modules/zemantaBidAdapter.md b/modules/zemantaBidAdapter.md new file mode 100644 index 00000000000..d991b67d429 --- /dev/null +++ b/modules/zemantaBidAdapter.md @@ -0,0 +1,107 @@ +# Overview + +``` +Module Name: Zemanta Adapter +Module Type: Bidder Adapter +Maintainer: prog-ops-team@outbrain.com +``` + +# Description + +Module that connects to zemanta bidder to fetch bids. +Both native and display formats are supported but not at the same time. Using OpenRTB standard. + +# Configuration + +## Bidder and usersync URLs + +The Zemanta adapter does not work without setting the correct bidder and usersync URLs. +You will receive the URLs when contacting us. + +``` +pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + usersyncUrl: 'https://usersync-url.com' + } +}); +``` + + +# Test Native Parameters +``` + var adUnits = [ + code: '/19968336/prebid_native_example_1', + mediaTypes: { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'zemanta', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id' + } + }] + ]; + + pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` + +# Test Display Parameters +``` + var adUnits = [ + code: '/19968336/prebid_display_example_1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'zemanta', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id' + }, + }] + ]; + + pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` diff --git a/test/spec/modules/zemantaBidAdapter_spec.js b/test/spec/modules/zemantaBidAdapter_spec.js new file mode 100644 index 00000000000..ab685890620 --- /dev/null +++ b/test/spec/modules/zemantaBidAdapter_spec.js @@ -0,0 +1,487 @@ +import {expect} from 'chai'; +import {spec} from 'modules/zemantaBidAdapter.js'; +import {config} from 'src/config.js'; +import {server} from 'test/mocks/xhr'; + +describe('Zemanta Adapter', function () { + describe('Bid request and response', function () { + const commonBidRequest = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id' + }, + }, + bidId: '2d6815a92ba1ba', + auctionId: '12043683-3254-4f74-8934-f941b085579e', + } + const nativeBidRequestParams = { + nativeParams: { + image: { + required: true, + sizes: [ + 120, + 100 + ], + sendId: true + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false + } + }, + } + + const displayBidRequestParams = { + sizes: [ + [ + 300, + 250 + ] + ] + } + + describe('isBidRequestValid', function () { + before(() => { + config.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should fail when bid is invalid', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed when bid contains native params', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should succeed when bid contains sizes', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if publisher id is not set', function () { + const bid = { + bidder: 'zemanta', + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should fail if bidder url is not set', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + before(() => { + config.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + } + } + + it('should build native request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const expectedNativeAssets = { + assets: [ + { + required: 1, + id: 3, + img: { + type: 3, + w: 120, + h: 100 + } + }, + { + required: 1, + id: 0, + title: {} + }, + { + required: 0, + id: 5, + data: { + type: 1 + } + } + ] + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + native: { + request: JSON.stringify(expectedNativeAssets) + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }); + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + + it('should pass optional tagid in request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + bidRequest.params.tagid = 'test-tag' + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.imp[0].tagid).to.equal('test-tag') + }); + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.tmax).to.equal(500) + }); + + it('should pass GDPR consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + uspConsent: 'consentString' + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.us_privacy).to.equal('consentString') + }); + + it('should pass coppa consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + config.setConfig({coppa: true}) + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) + + describe('interpretResponse', function () { + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse({}, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should interpret native response', function () { + const serverResponse = { + body: { + id: '0a73e68c-9967-4391-b01b-dda2d9fc54e4', + seatbid: [ + { + bid: [ + { + id: '82822cf5-259c-11eb-8a52-f29e5275aa57', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + adomain: [ + 'example.co' + ], + cid: '3487171', + crid: '28023739', + cat: [ + 'IAB10-2' + ] + } + ], + seat: 'acc-5537' + } + ], + bidid: '82822cf5-259c-11eb-8a52-b48e7518c657', + cur: 'USD' + }, + } + const request = { + bids: [ + { + ...commonBidRequest, + ...nativeBidRequestParams, + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '28023739', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'native', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + native: { + clickTrackers: undefined, + clickUrl: 'http://example.com/click/url', + image: { + url: 'http://example.com/img/url', + width: 120, + height: 100 + }, + title: 'Test title', + sponsoredBy: 'Test sponsor', + impressionTrackers: [ + 'http://example.com/impression', + ] + } + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + + it('should interpret display response', function () { + const serverResponse = { + body: { + id: '6b2eedc8-8ff5-46ef-adcf-e701b508943e', + seatbid: [ + { + bid: [ + { + id: 'd90fe7fa-28d7-11eb-8ce4-462a842a7cf9', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '
ad
', + adomain: [ + 'example.com' + ], + cid: '3865084', + crid: '29998660', + cat: [ + 'IAB10-2' + ], + w: 300, + h: 250 + } + ], + seat: 'acc-6536' + } + ], + bidid: 'd90fe7fa-28d7-11eb-8ce4-13d94bfa26f9', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'banner', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + ad: '
ad
', + width: 300, + height: 250 + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + }) + + describe('getUserSyncs', function () { + before(() => { + config.setConfig({ + zemanta: { + usersyncUrl: 'https://usersync-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should return user sync if pixel enabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.deep.equal([{type: 'image', url: 'https://usersync-url.com'}]) + }) + + it('should not return user sync if pixel disabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: false}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should not return user sync if url is not set', function () { + config.resetConfig() + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.be.an('array').that.is.empty + }) + }) + + describe('onBidWon', function () { + it('should make an ajax call with the original cpm', function () { + const bid = { + nurl: 'http://example.com/win/${AUCTION_PRICE}', + cpm: 2.1, + originalCpm: 1.1, + } + spec.onBidWon(bid) + expect(server.requests[0].url).to.equals('http://example.com/win/1.1') + }); + }) +}) From 6b4494ccae3e928f8aa1bbd1cdbb17c49c079424 Mon Sep 17 00:00:00 2001 From: cpuBird <54024689+cpuBird@users.noreply.github.com> Date: Tue, 15 Dec 2020 14:57:25 +0530 Subject: [PATCH 011/325] vdoai Bid Adapter: added multisize array in bid requests (#6101) * added multisize array in vdoai bid requests * fixing a bug dimentions --- modules/vdoaiBidAdapter.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index 8cfcd67bd00..d4fda36b1fc 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -30,14 +30,12 @@ export const spec = { if (validBidRequests.length === 0) { return []; } + return validBidRequests.map(bidRequest => { - const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes)[0]; - const width = sizes.split('x')[0]; - const height = sizes.split('x')[1]; + const sizes = utils.getAdUnitSizes(bidRequest); const payload = { placementId: bidRequest.params.placementId, - width: width, - height: height, + sizes: sizes, bidId: bidRequest.bidId, referer: bidderRequest.refererInfo.referer, id: bidRequest.auctionId, @@ -64,9 +62,9 @@ export const spec = { const response = serverResponse.body; const creativeId = response.adid || 0; // const width = response.w || 0; - const width = bidRequest.data.width; + const width = response.width; // const height = response.h || 0; - const height = bidRequest.data.height; + const height = response.height; const cpm = response.price || 0; response.rWidth = width; From 53e629145010fbc97925e5dd2207324e3f2891d7 Mon Sep 17 00:00:00 2001 From: Vadim Gush Date: Wed, 16 Dec 2020 06:49:25 +0400 Subject: [PATCH 012/325] Sovrn Bid Adapter: Change TTL field (#6083) * Change TTL field for SovrnBidAdapter * Fix unit tests * Fix codestyle in unit tests * Fix tests * Fix tests * Removed ext field from some tests * Trying to make ext field optional * Codestyle changes --- modules/sovrnBidAdapter.js | 2 +- test/spec/modules/sovrnBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 8f8158fd0c9..176b090fbe5 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -153,7 +153,7 @@ export const spec = { netRevenue: true, mediaType: BANNER, ad: decodeURIComponent(`${sovrnBid.adm}`), - ttl: sovrnBid.ttl || 90 + ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90 }); }); } diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 769be73a272..2bb5cdbdf3c 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -387,7 +387,7 @@ describe('sovrnBidAdapter', function() { }); it('should get correct bid response when ttl is set', function () { - response.body.seatbid[0].bid[0].ttl = 480; + response.body.seatbid[0].bid[0].ext = { 'ttl': 480 }; let expectedResponse = [{ 'requestId': '263c448586f5a1', From a9f1795f6de579d84f03a542458b6c4228a7257f Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Wed, 16 Dec 2020 16:32:39 +0700 Subject: [PATCH 013/325] Change bidder url for Qwarry adapter (#6128) * 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 Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index 7c2ec0f085b..7cb83520979 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -4,7 +4,7 @@ import { ajax } from '../src/ajax.js'; import { VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'qwarry'; -export const ENDPOINT = 'https://ui-bidder.kantics.co/bid/adtag?prebid=true' +export const ENDPOINT = 'https://bidder.qwarry.co/bid/adtag?prebid=true' export const spec = { code: BIDDER_CODE, From 1410a738cf7802227e3dc353b62440e9545f9947 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Wed, 16 Dec 2020 05:33:36 -0500 Subject: [PATCH 014/325] Add Automatic Release Drafter Functionality to Prebid Repository (#5954) * Create release-drafter.yml * Create release-drafter.yml * Update release-drafter.yml * Update release-drafter.yml --- .github/release-drafter.yml | 28 +++++++++++++++++++++++++++ .github/workflows/release-drafter.yml | 18 +++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/release-drafter.yml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000000..8984252f4c3 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,28 @@ + +name-template: 'Prebid $RESOLVED_VERSION Release' +tag-template: '$RESOLVED_VERSION' +categories: + - title: '🚀 New Features' + label: 'feature' + - title: '🛠 Maintenance' + label: 'maintenance' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' +change-template: '- $TITLE (#$NUMBER)' +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +template: | + ## In This Release + $CHANGES diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000000..8152b61275d --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,18 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 54df547a22551cba288348768ed2d61cd2181667 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 16 Dec 2020 12:43:41 -0500 Subject: [PATCH 015/325] Price Floors update to include modelWeight in the bid request to give additional context for Analytics adapters that the Floors Module is schema mode 2, and the floors module picks a model prior to an auction based on the modelWeight supplied in the floors module definition (#6126) Rubicon Analytics Update to pass modelWeight if exists --- modules/priceFloors.js | 1 + modules/rubiconAnalyticsAdapter.js | 1 + test/spec/modules/priceFloors_spec.js | 25 +++++++++++++++++++ .../modules/rubiconAnalyticsAdapter_spec.js | 3 +++ 4 files changed, 30 insertions(+) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index c0797f710de..7c8834c2ae2 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -291,6 +291,7 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { skipRate: floorData.skipRate, floorMin: floorData.floorMin, modelVersion: utils.deepAccess(floorData, 'data.modelVersion'), + modelWeight: utils.deepAccess(floorData, 'data.modelWeight'), modelTimestamp: utils.deepAccess(floorData, 'data.modelTimestamp'), location: utils.deepAccess(floorData, 'data.location', 'noData'), floorProvider: floorData.floorProvider, diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index ad78c601ab6..38d83a40bc7 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -237,6 +237,7 @@ function sendMessage(auctionId, bidWonId) { auction.floors = utils.pick(auctionCache.floorData, [ 'location', 'modelVersion as modelName', + 'modelWeight', 'modelTimestamp', 'skipped', 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 1b3ce021068..984d6da1cb9 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -23,6 +23,7 @@ describe('the price floors module', function () { let clock; const basicFloorData = { modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, currency: 'USD', schema: { @@ -38,6 +39,7 @@ describe('the price floors module', function () { const basicFloorDataHigh = { floorMin: 7.0, modelVersion: 'basic model', + modelWeight: 10, currency: 'USD', schema: { delimiter: '|', @@ -52,6 +54,7 @@ describe('the price floors module', function () { const basicFloorDataLow = { floorMin: 2.3, modelVersion: 'basic model', + modelWeight: 10, currency: 'USD', schema: { delimiter: '|', @@ -185,6 +188,7 @@ describe('the price floors module', function () { let resultingData = getFloorsDataForAuction(inputFloorData, 'test_div_1'); expect(resultingData).to.deep.equal({ modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, currency: 'USD', schema: { @@ -203,6 +207,7 @@ describe('the price floors module', function () { resultingData = getFloorsDataForAuction(inputFloorData, 'this_is_a_div'); expect(resultingData).to.deep.equal({ modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, currency: 'USD', schema: { @@ -432,6 +437,7 @@ describe('the price floors module', function () { skipped: true, floorMin: undefined, modelVersion: undefined, + modelWeight: undefined, modelTimestamp: undefined, location: 'noData', skipRate: 0, @@ -467,6 +473,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'adUnit Model Version', + modelWeight: 10, modelTimestamp: 1606772895, location: 'adUnit', skipRate: 0, @@ -501,6 +508,7 @@ describe('the price floors module', function () { validateBidRequests(true, { skipped: false, modelVersion: 'adUnit Model Version', + modelWeight: 10, modelTimestamp: 1606772895, location: 'adUnit', skipRate: 0, @@ -516,6 +524,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -538,6 +547,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -553,6 +563,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -568,6 +579,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -592,6 +604,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 50, @@ -607,6 +620,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 10, @@ -622,6 +636,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -687,6 +702,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-1', + modelWeight: 10, modelTimestamp: undefined, location: 'setConfig', skipRate: 0, @@ -701,6 +717,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-2', + modelWeight: 40, modelTimestamp: undefined, location: 'setConfig', skipRate: 0, @@ -715,6 +732,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-3', + modelWeight: 50, modelTimestamp: undefined, location: 'setConfig', skipRate: 0, @@ -745,6 +763,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -825,6 +844,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -864,6 +884,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelWeight: 10, modelTimestamp: 1606772895, location: 'fetch', skipRate: 0, @@ -902,6 +923,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelWeight: 10, modelTimestamp: 1606772895, location: 'fetch', skipRate: 0, @@ -943,6 +965,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelWeight: 10, modelTimestamp: 1606772895, location: 'fetch', skipRate: 95, @@ -966,6 +989,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -991,6 +1015,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 0d6cf331e52..1dd524e1838 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -852,6 +852,7 @@ describe('rubicon analytics adapter', function () { auctionInit.bidderRequests[0].bids[0].floorData = { skipped: false, modelVersion: 'someModelName', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 15, @@ -954,6 +955,7 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].floors).to.deep.equal({ location: 'setConfig', modelName: 'someModelName', + modelWeight: 10, modelTimestamp: 1606772895, skipped: false, enforcement: true, @@ -1000,6 +1002,7 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].floors).to.deep.equal({ location: 'setConfig', modelName: 'someModelName', + modelWeight: 10, modelTimestamp: 1606772895, skipped: false, enforcement: true, From eebdee25e946792e21cf64eff5f97f32d4e299a0 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 16 Dec 2020 19:39:22 +0100 Subject: [PATCH 016/325] Prebid 4.20.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 04ec495c93d..30ba2494182 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.20.0-pre", + "version": "4.20.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 282866ae79e92873d02620beac56d15b03dc7635 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 16 Dec 2020 20:12:28 +0100 Subject: [PATCH 017/325] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30ba2494182..f65b996a393 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.20.0", + "version": "4.21.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 47e96ad7ae62040ede8c9b84a192e0b718d9947a Mon Sep 17 00:00:00 2001 From: susyt Date: Wed, 16 Dec 2020 12:00:48 -0800 Subject: [PATCH 018/325] GumGum: makes slot and invideo products avail with pubId (#6107) --- modules/gumgumBidAdapter.js | 10 ++-- modules/gumgumBidAdapter.md | 69 +++++++++++++++++++++- test/spec/modules/gumgumBidAdapter_spec.js | 58 ++++++++---------- 3 files changed, 97 insertions(+), 40 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 2cb5ce61064..c9bf77494cf 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -273,8 +273,9 @@ function buildRequests (validBidRequests, bidderRequest) { data.iriscat = params.iriscat; } - if (params.zone) { - data.t = params.zone; + if (params.zone || params.pubId) { + params.zone ? (data.t = params.zone) : (data.pubId = params.pubId); + data.pi = 2; // inscreen // override pi if the following is found if (params.slot) { @@ -285,11 +286,8 @@ function buildRequests (validBidRequests, bidderRequest) { data.ni = parseInt(params.native, 10); data.pi = 5; } else if (mediaTypes.video) { - data.pi = mediaTypes.video.linearity === 1 ? 7 : 6; // video : invideo + data.pi = mediaTypes.video.linearity === 2 ? 6 : 7; // invideo : video } - } else if (params.pubId) { - data.pubId = params.pubId - data.pi = mediaTypes.video ? 7 : 2; // video : inscreen } else { // legacy params data = { ...data, ...handleLegacyParams(params, sizes) } } diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md index 7b4f0c98ea7..57d56235d1c 100644 --- a/modules/gumgumBidAdapter.md +++ b/modules/gumgumBidAdapter.md @@ -10,11 +10,76 @@ Maintainer: engineering@gumgum.com GumGum adapter for Prebid.js Please note that both video and in-video products require a mediaType of video. -All other products (in-screen, slot, native) should have a mediaType of banner. - +In-screen and slot products should have a mediaType of banner. # Test Parameters ``` +var adUnits = [ + { + code: 'slot-placement', + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'dc9d6be1', // GumGum Zone ID given to the client + slot: '15901', // GumGum Slot ID given to the client, + bidfloor: 0.03 // CPM bid floor + } + } + ] + },{ + code: 'inscreen-placement', + sizes: [[300, 50]], + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'dc9d6be1', // GumGum Zone ID given to the client + bidfloor: 0.03 // CPM bid floor + } + } + ] + },{ + code: 'video-placement', + sizes: [[300, 50]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + minduration: 1, + maxduration: 2, + linearity: 1, + startdelay: 1, + placement: 1, + protocols: [1, 2] + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'ggumtest', // GumGum Zone ID given to the client + bidfloor: 0.03 // CPM bid floor + } + } + ] + } +]; +``` + +# Legacy Test Parameters +``` var adUnits = [ { code: 'test-div', diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 52a3a21db4e..a2a1b733d3c 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -35,7 +35,7 @@ describe('gumgumAdapter', function () { it('should return true when required params found', function () { const zoneBid = { ...bid, params: { 'zone': '123' } }; - const pubIdBid = { ...bid, params: { 'pubId': '123' } }; + const pubIdBid = { ...bid, params: { 'pubId': 123 } }; expect(spec.isBidRequestValid(bid)).to.equal(true); expect(spec.isBidRequestValid(zoneBid)).to.equal(true); expect(spec.isBidRequestValid(pubIdBid)).to.equal(true); @@ -143,23 +143,24 @@ describe('gumgumAdapter', function () { protocols: [1, 2] } }; + const zoneParam = { 'zone': '123a' }; + const pubIdParam = { 'pubId': 123 }; - describe('zone param', function () { - const zoneParam = { 'zone': '123a' }; + it('should set pubId param if found', function () { + const request = { ...bidRequests[0], params: pubIdParam }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pubId).to.equal(pubIdParam.pubId); + }); - it('should set t and pi param', function () { - const request = { ...bidRequests[0], params: zoneParam }; - const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.t).to.equal(zoneParam.zone); - expect(bidRequest.data.pi).to.equal(2); - }); - it('should set the correct pi param if slot param is found', function () { - const request = { ...bidRequests[0], params: { ...zoneParam, 'slot': 1 } }; - const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.pi).to.equal(3); - }); + it('should set t param when zone param is found', function () { + const request = { ...bidRequests[0], params: zoneParam }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.t).to.equal(zoneParam.zone); + }); + + describe('product id', function () { it('should set the correct pi param if native param is found', function () { - const request = { ...bidRequests[0], params: { ...zoneParam, 'native': 2 } }; + const request = { ...bidRequests[0], params: { ...zoneParam, native: 2 } }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.pi).to.equal(5); }); @@ -174,25 +175,18 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.pi).to.equal(6); }); - }); - - describe('pubId zone', function () { - const pubIdParam = { 'pubId': 'abc' }; - - it('should set t param', function () { - const request = { ...bidRequests[0], params: pubIdParam }; + it('should set the correct pi param if slot param is found', function () { + const request = { ...bidRequests[0], params: { ...zoneParam, slot: '123s' } }; const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.pubId).to.equal(pubIdParam.pubId); + expect(bidRequest.data.pi).to.equal(3); }); - - it('should set the correct pi depending on what is found in mediaTypes', function () { - const request = { ...bidRequests[0], params: pubIdParam }; - const bidRequest = spec.buildRequests([request])[0]; - const vidRequest = { ...bidRequests[0], mediaTypes: vidMediaTypes, params: { 'videoPubID': 123 } }; - const vidBidRequest = spec.buildRequests([vidRequest])[0]; - - expect(bidRequest.data.pi).to.equal(2); - expect(vidBidRequest.data.pi).to.equal(7); + it('should default the pi param to 2 if only zone or pubId param is found', function () { + const zoneRequest = { ...bidRequests[0], params: zoneParam }; + const pubIdRequest = { ...bidRequests[0], params: pubIdParam }; + const zoneBidRequest = spec.buildRequests([zoneRequest])[0]; + const pubIdBidRequest = spec.buildRequests([pubIdRequest])[0]; + expect(zoneBidRequest.data.pi).to.equal(2); + expect(pubIdBidRequest.data.pi).to.equal(2); }); }); From 453e080e678570a394d8c5a9fbed8650b2a04be5 Mon Sep 17 00:00:00 2001 From: readpeaktuomo <66239046+readpeaktuomo@users.noreply.github.com> Date: Fri, 18 Dec 2020 18:37:43 +0200 Subject: [PATCH 019/325] Add support for tagId parameter (#6133) * Add support for tagId parameter * Update maintainer email to an alias --- modules/readpeakBidAdapter.js | 3 ++- modules/readpeakBidAdapter.md | 5 +++-- test/spec/modules/readpeakBidAdapter_spec.js | 7 +++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index c72bbdd658f..2f4173f240b 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -98,7 +98,8 @@ function impression(slot) { id: slot.bidId, native: nativeImpression(slot), bidfloor: slot.params.bidfloor || 0, - bidfloorcur: slot.params.bidfloorcur || 'USD' + bidfloorcur: slot.params.bidfloorcur || 'USD', + tagId: slot.params.tagId || '0' }; } diff --git a/modules/readpeakBidAdapter.md b/modules/readpeakBidAdapter.md index a15767f29a7..da250e7f77a 100644 --- a/modules/readpeakBidAdapter.md +++ b/modules/readpeakBidAdapter.md @@ -4,7 +4,7 @@ Module Name: ReadPeak Bid Adapter Module Type: Bidder Adapter -Maintainer: kurre.stahlberg@readpeak.com +Maintainer: devteam@readpeak.com # Description @@ -23,7 +23,8 @@ Please reach out to your account team or hello@readpeak.com for more information params: { bidfloor: 5.00, publisherId: 'test', - siteId: 'test' + siteId: 'test', + tagId: 'test-tag-1' }, }] }]; diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index 0c6f942e724..d5a877f6221 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -28,7 +28,8 @@ describe('ReadPeakAdapter', function() { params: { bidfloor: 5.0, publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - siteId: '11bc5dd5-7421-4dd8-c926-40fa653bec77' + siteId: '11bc5dd5-7421-4dd8-c926-40fa653bec77', + tagId: 'test-tag-1' }, bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', @@ -104,7 +105,8 @@ describe('ReadPeakAdapter', function() { ver: '1.1' }, bidfloor: 5, - bidfloorcur: 'USD' + bidfloorcur: 'USD', + tagId: 'test-tag-1' } ], site: { @@ -177,6 +179,7 @@ describe('ReadPeakAdapter', function() { expect(data.id).to.equal(bidRequest.bidderRequestId); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); expect(data.imp[0].bidfloorcur).to.equal('USD'); + expect(data.imp[0].tagId).to.equal('test-tag-1'); expect(data.site.publisher.id).to.equal(bidRequest.params.publisherId); expect(data.site.id).to.equal(bidRequest.params.siteId); expect(data.site.page).to.equal(bidderRequest.refererInfo.referer); From 78917f5a5128cde43e063facfaa61c81c9b14e6a Mon Sep 17 00:00:00 2001 From: Zak Andree Date: Fri, 18 Dec 2020 11:47:21 -0800 Subject: [PATCH 020/325] Inmar bidder adapter: Make adNetId an optional paramater (#6136) * Update Inmar bidder adapter * Set withCredentials to true * Remove inmarId from Inmar bidder adapter * Remove withCredentials because it defaults to true --- modules/inmarBidAdapter.js | 5 +---- modules/inmarBidAdapter.md | 2 -- test/spec/modules/inmarBidAdapter_spec.js | 11 ----------- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js index b5ab72266fc..7583985b23c 100755 --- a/modules/inmarBidAdapter.js +++ b/modules/inmarBidAdapter.js @@ -17,7 +17,7 @@ export const spec = { * @returns {boolean} True if this is a valid bid, and false otherwise */ isBidRequestValid: function(bid) { - return !!(bid.params && bid.params.partnerId && bid.params.adnetId); + return !!(bid.params && bid.params.partnerId); }, /** @@ -49,9 +49,6 @@ export const spec = { return { method: 'POST', url: 'https://prebid.owneriq.net:8443/bidder/pb/bid', - options: { - withCredentials: false - }, data: payloadString, }; }, diff --git a/modules/inmarBidAdapter.md b/modules/inmarBidAdapter.md index 1bacb30f2dd..8ed6b998602 100644 --- a/modules/inmarBidAdapter.md +++ b/modules/inmarBidAdapter.md @@ -25,7 +25,6 @@ Please reach out to your account manager for more information. bidder: 'inmar', params: { partnerId: 12345, - adnetId: 'ADb1f40rmi', position: 1 } }] @@ -37,7 +36,6 @@ Please reach out to your account manager for more information. bidder: 'inmar', params: { partnerId: 12345, - adnetId: 'ADb1f40rmo', position: 0 } }] diff --git a/test/spec/modules/inmarBidAdapter_spec.js b/test/spec/modules/inmarBidAdapter_spec.js index 86b7ab3a8af..998fe20d369 100644 --- a/test/spec/modules/inmarBidAdapter_spec.js +++ b/test/spec/modules/inmarBidAdapter_spec.js @@ -17,7 +17,6 @@ describe('Inmar adapter tests', function () { }, bidder: 'inmar', params: { - adnetId: 'ADb1f40rmi', partnerId: 12345 }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', @@ -39,7 +38,6 @@ describe('Inmar adapter tests', function () { }, bidder: 'inmar', params: { - adnetId: 'ADb1f40rmi', partnerId: 12345 }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', @@ -60,7 +58,6 @@ describe('Inmar adapter tests', function () { ], bidder: 'inmar', params: { - adnetId: 'ADb1f40rmi', partnerId: 12345, }, auctionId: '851adee7-d843-48f9-a7e9-9ff00573fcbf', @@ -118,7 +115,6 @@ describe('Inmar adapter tests', function () { expect(request).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request.data); - expect(requestContent.bidRequests[0].params).to.have.property('adnetId').and.to.equal('ADb1f40rmi'); expect(requestContent.bidRequests[0].params).to.have.property('partnerId').and.to.equal(12345); expect(requestContent.bidRequests[0]).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); expect(requestContent.bidRequests[0]).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); @@ -198,19 +194,12 @@ describe('Inmar adapter tests', function () { })).to.equal(false); expect(spec.isBidRequestValid({ params: { - adnetId: 'ADb1f40rmi' } })).to.equal(false); expect(spec.isBidRequestValid({ params: { partnerId: 12345 } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - adnetId: 'ADb1f40rmi', - partnerId: 12345 - } })).to.equal(true); }); From 2c1e5352efafc8fb5eae02eff4ac59bee82fdc74 Mon Sep 17 00:00:00 2001 From: hybrid-ai <58724131+hybrid-ai@users.noreply.github.com> Date: Sat, 19 Dec 2020 18:46:55 +0300 Subject: [PATCH 021/325] Added VOX Bidder Adapter (#6030) * Added voxBidAdapter.js to get a bid from partners.hybrid.ai * Added placements ids for testing Co-authored-by: s-shevtsov --- modules/voxBidAdapter.js | 247 ++++++++++++++++++ modules/voxBidAdapter.md | 237 ++++++++++++++++++ test/spec/modules/voxBidAdapter_spec.js | 320 ++++++++++++++++++++++++ 3 files changed, 804 insertions(+) create mode 100644 modules/voxBidAdapter.js create mode 100644 modules/voxBidAdapter.md create mode 100644 test/spec/modules/voxBidAdapter_spec.js diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js new file mode 100644 index 00000000000..450f270db31 --- /dev/null +++ b/modules/voxBidAdapter.js @@ -0,0 +1,247 @@ +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import {BANNER, VIDEO} from '../src/mediaTypes.js' +import find from 'core-js-pure/features/array/find.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {Renderer} from '../src/Renderer.js'; + +const BIDDER_CODE = 'vox'; +const SSP_ENDPOINT = 'https://ssp.hybrid.ai/auction/prebid'; +const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const TTL = 60; + +function buildBidRequests(validBidRequests) { + return utils._map(validBidRequests, function(validBidRequest) { + const params = validBidRequest.params; + const bidRequest = { + bidId: validBidRequest.bidId, + transactionId: validBidRequest.transactionId, + sizes: validBidRequest.sizes, + placement: params.placement, + placeId: params.placementId, + imageUrl: params.imageUrl + }; + + return bidRequest; + }) +} + +const outstreamRender = bid => { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: bid.vastXml + } + }); + }); +} + +const createRenderer = (bid) => { + const renderer = Renderer.install({ + targetId: bid.adUnitCode, + url: VIDEO_RENDERER_URL, + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + +function buildBid(bidData) { + const bid = { + requestId: bidData.bidId, + cpm: bidData.price, + width: bidData.content.width, + height: bidData.content.height, + creativeId: bidData.content.seanceId || bidData.bidId, + currency: bidData.currency, + netRevenue: true, + mediaType: BANNER, + ttl: TTL, + content: bidData.content + }; + + if (bidData.placement === 'video') { + bid.vastXml = bidData.content; + bid.mediaType = VIDEO; + + let adUnit = find(auctionManager.getAdUnits(), function (unit) { + return unit.transactionId === bidData.transactionId; + }); + + if (adUnit) { + bid.width = adUnit.mediaTypes.video.playerSize[0][0]; + bid.height = adUnit.mediaTypes.video.playerSize[0][1]; + + if (adUnit.mediaTypes.video.context === 'outstream') { + bid.renderer = createRenderer(bid); + } + } + } else if (bidData.placement === 'inImage') { + bid.mediaType = BANNER; + bid.ad = wrapInImageBanner(bid, bidData); + } else { + bid.mediaType = BANNER; + bid.ad = wrapBanner(bid, bidData); + } + + return bid; +} + +function getMediaTypeFromBid(bid) { + return bid.mediaTypes && Object.keys(bid.mediaTypes)[0]; +} + +function hasVideoMandatoryParams(mediaTypes) { + const isHasVideoContext = !!mediaTypes.video && (mediaTypes.video.context === 'instream' || mediaTypes.video.context === 'outstream'); + + const isPlayerSize = + !!utils.deepAccess(mediaTypes, 'video.playerSize') && + utils.isArray(utils.deepAccess(mediaTypes, 'video.playerSize')); + + return isHasVideoContext && isPlayerSize; +} + +function wrapInImageBanner(bid, bidData) { + return ` + + + + + + + + +
+ + + `; +} + +function wrapBanner(bid, bidData) { + return ` + + + + + + + + +
+ + + `; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid(bid) { + return ( + !!bid.params.placementId && + !!bid.params.placement && + ( + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'banner') || + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'inImage' && !!bid.params.imageUrl) || + (getMediaTypeFromBid(bid) === VIDEO && bid.params.placement === 'video' && hasVideoMandatoryParams(bid.mediaTypes)) + ) + ); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests(validBidRequests, bidderRequest) { + const payload = { + url: bidderRequest.refererInfo.referer, + cmp: !!bidderRequest.gdprConsent, + bidRequests: buildBidRequests(validBidRequests) + }; + + if (payload.cmp) { + const gdprApplies = bidderRequest.gdprConsent.gdprApplies; + if (gdprApplies !== undefined) payload['ga'] = gdprApplies; + payload['cs'] = bidderRequest.gdprConsent.consentString; + } + + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: SSP_ENDPOINT, + data: payloadString, + options: { + contentType: 'application/json' + } + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + let bidRequests = JSON.parse(bidRequest.data).bidRequests; + const serverBody = serverResponse.body; + + if (serverBody && serverBody.bids && utils.isArray(serverBody.bids)) { + return utils._map(serverBody.bids, function(bid) { + let rawBid = find(bidRequests, function (item) { + return item.bidId === bid.bidId; + }); + bid.placement = rawBid.placement; + bid.transactionId = rawBid.transactionId; + bid.placeId = rawBid.placeId; + return buildBid(bid); + }); + } else { + return []; + } + } + +} +registerBidder(spec); diff --git a/modules/voxBidAdapter.md b/modules/voxBidAdapter.md new file mode 100644 index 00000000000..3fc0383e6f8 --- /dev/null +++ b/modules/voxBidAdapter.md @@ -0,0 +1,237 @@ +# Overview + + +**Module Name**: VOX Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@hybrid.ai + +# Description + +You can use this adapter to get a bid from partners.hybrid.ai + + +## Sample Banner Ad Unit + +```js +var adUnits = [{ + code: 'banner_ad_unit', + mediaTypes: { + banner: { + sizes: [[160, 600]] + } + }, + bids: [{ + bidder: "vox", + params: { + placement: "banner", // required + placementId: "5fc77bc5a757531e24c89a4c" // required + } + }] +}]; +``` + +## Sample Video Ad Unit + +```js +var adUnits = [{ + code: 'video_ad_unit', + mediaTypes: { + video: { + context: 'outstream', // required + playerSize: [[640, 480]] // required + } + }, + bids: [{ + bidder: 'vox', + params: { + placement: "video", // required + placementId: "5fc77a94a757531e24c89a3d" // required + } + }] +}]; +``` + +# Sample In-Image Ad Unit + +```js +var adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [0, 0] + } + }, + bids: [{ + bidder: "vox", + params: { + placement: "inImage", + placementId: "5fc77b40a757531e24c89a42", + imageUrl: "https://gallery.voxexchange.io/vox-main.png" + } + }] +}]; +``` + +# Example page with In-Image + +```html + + + + + Prebid.js Banner Example + + + + + +

Prebid.js InImage Banner Test

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

Prebid.js Banner Ad Unit Test

+
+ + +
+ + +``` diff --git a/test/spec/modules/voxBidAdapter_spec.js b/test/spec/modules/voxBidAdapter_spec.js new file mode 100644 index 00000000000..c6221cba9e5 --- /dev/null +++ b/test/spec/modules/voxBidAdapter_spec.js @@ -0,0 +1,320 @@ +import { expect } from 'chai' +import { spec } from 'modules/voxBidAdapter.js' + +function getSlotConfigs(mediaTypes, params) { + return { + params: params, + sizes: [], + bidId: '2df8c0733f284e', + bidder: 'vox', + mediaTypes: mediaTypes, + transactionId: '31a58515-3634-4e90-9c96-f86196db1459' + } +} + +describe('VOX Adapter', function() { + const PLACE_ID = '5af45ad34d506ee7acad0c26'; + const bidderRequest = { + refererInfo: { referer: 'referer' } + } + const bannerMandatoryParams = { + placementId: PLACE_ID, + placement: 'banner' + } + const videoMandatoryParams = { + placementId: PLACE_ID, + placement: 'video' + } + const inImageMandatoryParams = { + placementId: PLACE_ID, + placement: 'inImage', + imageUrl: 'https://hybrid.ai/images/image.jpg' + } + const validBidRequests = [ + getSlotConfigs({ banner: {} }, bannerMandatoryParams), + getSlotConfigs({ video: {playerSize: [[640, 480]], context: 'outstream'} }, videoMandatoryParams), + getSlotConfigs({ banner: {sizes: [0, 0]} }, inImageMandatoryParams) + ] + describe('isBidRequestValid method', function() { + describe('returns true', function() { + describe('when banner slot config has all mandatory params', () => { + describe('and banner placement has the correct value', function() { + const slotConfig = getSlotConfigs( + {banner: {}}, + { + placementId: PLACE_ID, + placement: 'banner' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) + describe('and In-Image placement has the correct value', function() { + const slotConfig = getSlotConfigs( + { + banner: { + sizes: [[0, 0]] + } + }, + { + placementId: PLACE_ID, + placement: 'inImage', + imageUrl: 'imageUrl' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) + describe('when video slot has all mandatory params.', function() { + it('should return true, when video mediatype object are correct.', function() { + const slotConfig = getSlotConfigs( + { + video: { + context: 'instream', + playerSize: [[640, 480]] + } + }, + { + placementId: PLACE_ID, + placement: 'video' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) + }) + }) + }) + describe('returns false', function() { + describe('when params are not correct', function() { + function createSlotconfig(params) { + return getSlotConfigs({ banner: {} }, params) + } + it('does not have the placementId.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placement: 'banner' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placementId: PLACE_ID + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the imageUrl.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placementId: PLACE_ID, + placement: 'inImage' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have a the correct placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placementId: PLACE_ID, + placement: 'something' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + describe('when video mediaType object is not correct.', function() { + function createVideoSlotconfig(mediaType) { + return getSlotConfigs(mediaType, { + placementId: PLACE_ID, + placement: 'video' + }) + } + it('is a void object', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ video: {} }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have playerSize.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ video: { context: 'instream' } }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have context', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ + video: { + playerSize: [[640, 480]] + } + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + }) + }) + it('Url params should be correct ', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + expect(request.method).to.equal('POST') + expect(request.url).to.equal('https://ssp.hybrid.ai/auction/prebid') + }) + + describe('buildRequests method', function() { + it('Common data request should be correct', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(Array.isArray(data.bidRequests)).to.equal(true) + expect(data.url).to.equal('referer') + data.bidRequests.forEach(bid => { + expect(bid.bidId).to.equal('2df8c0733f284e') + expect(bid.placeId).to.equal(PLACE_ID) + expect(bid.transactionId).to.equal('31a58515-3634-4e90-9c96-f86196db1459') + }) + }) + + describe('GDPR params', function() { + describe('when there are not consent management platform', function() { + it('cmp should be false', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(false) + }) + }) + describe('when there are consent management platform', function() { + it('cmps should be true and ga should not sended, when gdprApplies is undefined', function() { + bidderRequest['gdprConsent'] = { + gdprApplies: undefined, + consentString: 'consentString' + } + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(true) + expect(Object.keys(data).indexOf('data')).to.equal(-1) + expect(data.cs).to.equal('consentString') + }) + it('cmps should be true and all gdpr parameters should be sended, when there are gdprApplies', function() { + bidderRequest['gdprConsent'] = { + gdprApplies: true, + consentString: 'consentString' + } + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(true) + expect(data.ga).to.equal(true) + expect(data.cs).to.equal('consentString') + }) + }) + }) + }) + + describe('interpret response method', function() { + it('should return a void array, when the server response are not correct.', function() { + const request = { data: JSON.stringify({}) } + const serverResponse = { + body: {} + } + const bids = spec.interpretResponse(serverResponse, request) + 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() { + const request = { data: JSON.stringify({}) } + const serverResponse = { body: { bids: [] } } + const bids = spec.interpretResponse(serverResponse, request) + expect(typeof bids).to.equal('object') + expect(bids.length).to.equal(0) + }) + describe('when the server response return a bid', function() { + describe('the bid is a banner', function() { + it('should return a banner bid', function() { + const request = spec.buildRequests([validBidRequests[0]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: { + content: 'html', + width: 100, + height: 100 + } + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].mediaType).to.equal(spec.supportedMediaTypes[0]) + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].ad).to.equal('string') + }) + it('should return a In-Image bid', function() { + const request = spec.buildRequests([validBidRequests[2]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: { + content: 'html', + width: 100, + height: 100 + }, + ttl: 360 + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].ad).to.equal('string') + }) + }) + describe('the bid is a video', function() { + it('should return a video bid', function() { + const request = spec.buildRequests([validBidRequests[1]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: 'html', + transactionId: '31a58515-3634-4e90-9c96-f86196db1459' + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].mediaType).to.equal(spec.supportedMediaTypes[1]) + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].vastXml).to.equal('string') + }) + }) + }) + }) +}) From 5dd60a1078ebe39602ce5722e32fb3fd3a3f9d13 Mon Sep 17 00:00:00 2001 From: reemeng <29702905+reemeng@users.noreply.github.com> Date: Mon, 21 Dec 2020 10:12:30 +0200 Subject: [PATCH 022/325] added Engageya bid adapter (#6109) * added Engageya bid adapter * moved test function from adapter to spec * remove function import * PAGE_URL should be String --- modules/engageyaBidAdapter.js | 133 +++++++++++++++ modules/engageyaBidAdapter.md | 68 ++++++++ test/spec/modules/engageyaBidAdapter_spec.js | 161 +++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 modules/engageyaBidAdapter.js create mode 100644 modules/engageyaBidAdapter.md create mode 100644 test/spec/modules/engageyaBidAdapter_spec.js diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js new file mode 100644 index 00000000000..321b3287c2b --- /dev/null +++ b/modules/engageyaBidAdapter.js @@ -0,0 +1,133 @@ +import { + BANNER, + NATIVE +} from '../src/mediaTypes.js'; + +const { + registerBidder +} = require('../src/adapters/bidderFactory.js'); +const BIDDER_CODE = 'engageya'; +const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; +const ENDPOINT_METHOD = 'GET'; + +function getPageUrl() { + var pUrl = window.location.href; + if (isInIframe()) { + pUrl = document.referrer ? document.referrer : pUrl; + } + pUrl = encodeURIComponent(pUrl); + return pUrl; +} + +function isInIframe() { + try { + var isInIframe = (window.self !== window.top); + } catch (e) { + isInIframe = true; + } + return isInIframe; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE], + isBidRequestValid: function(bid) { + return bid && bid.params && bid.params.hasOwnProperty('widgetId') && bid.params.hasOwnProperty('websiteId') && !isNaN(bid.params.widgetId) && !isNaN(bid.params.websiteId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + var bidRequests = []; + if (validBidRequests && validBidRequests.length > 0) { + validBidRequests.forEach(function(bidRequest) { + if (bidRequest.params) { + var mediaType = bidRequest.hasOwnProperty('nativeParams') ? 1 : 2; + var imageWidth = -1; + var imageHeight = -1; + if (bidRequest.sizes && bidRequest.sizes.length > 0) { + imageWidth = bidRequest.sizes[0][0]; + imageHeight = bidRequest.sizes[0][1]; + } else if (bidRequest.nativeParams && bidRequest.nativeParams.image && bidRequest.nativeParams.image.sizes) { + imageWidth = bidRequest.nativeParams.image.sizes[0]; + imageHeight = bidRequest.nativeParams.image.sizes[1]; + } + + var widgetId = bidRequest.params.widgetId; + var websiteId = bidRequest.params.websiteId; + var pageUrl = (bidRequest.params.pageUrl && bidRequest.params.pageUrl != '[PAGE_URL]') ? bidRequest.params.pageUrl : ''; + if (!pageUrl) { + pageUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : getPageUrl(); + } + var bidId = bidRequest.bidId; + var finalUrl = ENDPOINT_URL + '?pubid=0&webid=' + websiteId + '&wid=' + widgetId + '&url=' + pageUrl + '&ireqid=' + bidId + '&pbtpid=' + mediaType + '&imw=' + imageWidth + '&imh=' + imageHeight; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprApplies && bidderRequest.consentString) { + finalUrl += '&is_gdpr=1&gdpr_consent=' + bidderRequest.consentString; + } + bidRequests.push({ + url: finalUrl, + method: ENDPOINT_METHOD, + data: '' + }); + } + }); + } + + return bidRequests; + }, + + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + if (serverResponse.body && serverResponse.body.recs && serverResponse.body.recs.length > 0) { + var response = serverResponse.body; + var isNative = response.pbtypeId == 1; + response.recs.forEach(function(rec) { + var imageSrc = rec.thumbnail_path.indexOf('http') == -1 ? 'https:' + rec.thumbnail_path : rec.thumbnail_path; + if (isNative) { + bidResponses.push({ + requestId: response.ireqId, + cpm: rec.ecpm, + width: response.imageWidth, + height: response.imageHeight, + creativeId: rec.postId, + currency: 'USD', + netRevenue: false, + ttl: 360, + native: { + title: rec.title, + body: '', + image: { + url: imageSrc, + width: response.imageWidth, + height: response.imageHeight + }, + privacyLink: '', + clickUrl: rec.clickUrl, + displayUrl: rec.url, + cta: '', + sponsoredBy: rec.displayName, + impressionTrackers: [], + }, + }); + } else { + // var htmlTag = ""; + var htmlTag = '
'; + var tag = rec.tag ? rec.tag : htmlTag; + bidResponses.push({ + requestId: response.ireqId, + cpm: rec.ecpm, + width: response.imageWidth, + height: response.imageHeight, + creativeId: rec.postId, + currency: 'USD', + netRevenue: false, + ttl: 360, + ad: tag, + }); + } + }); + } + + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/engageyaBidAdapter.md b/modules/engageyaBidAdapter.md new file mode 100644 index 00000000000..541ba548eeb --- /dev/null +++ b/modules/engageyaBidAdapter.md @@ -0,0 +1,68 @@ +# Overview + +``` +Module Name: Engageya's Bidder Adapter +Module Type: Bidder Adapter +Maintainer: reem@engageya.com +``` + +# Description + +Module that connects to Engageya's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "engageya", + params: { + widgetId: '', + websiteId: '', + pageUrl:'[PAGE_URL]' + } + } + ] + },{ + code: 'test-div', + mediaTypes: { + native: { + image: { + required: true, + sizes: [236, 202] + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + } + } + }, + bids: [ + { + bidder: "engageya", + params: { + widgetId: '', + websiteId: '', + pageUrl:'[PAGE_URL]' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/engageyaBidAdapter_spec.js b/test/spec/modules/engageyaBidAdapter_spec.js new file mode 100644 index 00000000000..ad411fc9350 --- /dev/null +++ b/test/spec/modules/engageyaBidAdapter_spec.js @@ -0,0 +1,161 @@ +import {expect} from 'chai'; +import {spec} from 'modules/engageyaBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; + +export const _getUrlVars = function(url) { + var hash; + var myJson = {}; + var hashes = url.slice(url.indexOf('?') + 1).split('&'); + for (var i = 0; i < hashes.length; i++) { + hash = hashes[i].split('='); + myJson[hash[0]] = hash[1]; + } + return myJson; +} + +describe('engageya adapter', function() { + let bidRequests; + let nativeBidRequests; + + beforeEach(function() { + bidRequests = [ + { + bidder: 'engageya', + params: { + widgetId: 85610, + websiteId: 91140, + pageUrl: '[PAGE_URL]' + } + } + ] + + nativeBidRequests = [ + { + bidder: 'engageya', + params: { + widgetId: 85610, + websiteId: 91140, + pageUrl: '[PAGE_URL]' + }, + nativeParams: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + sponsoredBy: { + required: true + } + } + } + ] + }) + describe('isBidRequestValid', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'engageya', + params: { + widgetId: 85610, + websiteId: 91140, + pageUrl: '[PAGE_URL]' + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('invalid bid case: widgetId and websiteId is not passed', function() { + let validBid = { + bidder: 'engageya', + params: { + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }) + + it('invalid bid case: widget id must be number', function() { + let invalidBid = { + bidder: 'engageya', + params: { + widgetId: '157746a', + websiteId: 91140, + pageUrl: '[PAGE_URL]' + } + } + let isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }) + }) + + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.include(ENDPOINT_URL); + expect(request.method).to.equal('GET'); + }); + + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + + it('buildRequests function should not modify original nativeBidRequests object', function () { + let originalBidRequests = utils.deepClone(nativeBidRequests); + let request = spec.buildRequests(nativeBidRequests); + expect(nativeBidRequests).to.deep.equal(originalBidRequests); + }); + + it('Request params check', function() { + let request = spec.buildRequests(bidRequests)[0]; + const data = _getUrlVars(request.url) + expect(parseInt(data.wid)).to.exist.and.to.equal(bidRequests[0].params.widgetId); + expect(parseInt(data.webid)).to.exist.and.to.equal(bidRequests[0].params.websiteId); + }) + }) + + describe('interpretResponse', function () { + let response = {recs: [ + { + 'ecpm': 0.0920, + 'postId': '', + 'ad': '', + 'thumbnail_path': 'https://engageya.live/wp-content/uploads/2019/05/images.png' + } + ], + imageWidth: 300, + imageHeight: 250, + ireqId: '1d236f7890b', + pbtypeId: 2}; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '1d236f7890b', + 'cpm': 0.0920, + 'width': 300, + 'height': 250, + 'netRevenue': false, + 'currency': 'USD', + 'creativeId': '', + 'ttl': 700, + 'ad': '' + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({body: response}, request); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].cpm).to.not.equal(null); + expect(result[0].creativeId).to.not.equal(null); + expect(result[0].ad).to.not.equal(null); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(false); + }); + }) +}) From d68f2e0c43df4429ea81fc0d8675b0ddf8fddec7 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Mon, 21 Dec 2020 12:54:18 +0300 Subject: [PATCH 023/325] Fix userIds format for TheMediaGrid Bid Adapter (#6142) * 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 --- modules/gridBidAdapter.js | 67 +++----------------- test/spec/modules/gridBidAdapter_spec.js | 78 +++++++----------------- 2 files changed, 30 insertions(+), 115 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 3f9d4fba31d..6e610b67e0e 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -51,6 +51,7 @@ export const spec = { let content = null; let schain = null; let userId = null; + let userIdAsEids = null; let user = null; let userExt = null; let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; @@ -72,6 +73,9 @@ export const spec = { if (!userId) { userId = bid.userId; } + if (!userIdAsEids) { + userIdAsEids = bid.userIdAsEids; + } const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; bidsMap[bidId] = bid; if (!pageKeywords && !utils.isEmpty(keywords)) { @@ -161,66 +165,9 @@ export const spec = { userExt = {consent: gdprConsent.consentString}; } - if (userId) { - if (userId.tdid) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'adserver.org', // Unified ID - uids: [{ - id: userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); - } - if (userId.id5id && userId.id5id.uid) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'id5-sync.com', - uids: [{ - id: userId.id5id.uid - }], - ext: userId.id5id.ext - }); - } - if (userId.lipb && userId.lipb.lipbid) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'liveintent.com', - uids: [{ - id: userId.lipb.lipbid - }] - }); - } - if (userId.idl_env) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'identityLink', - uids: [{ - id: userId.idl_env - }] - }); - } - if (userId.criteoId) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'criteo.com', - uids: [{ - id: userId.criteoId - }] - }); - } - - if (userId.digitrustid && userId.digitrustid.data && userId.digitrustid.data.id) { - userExt = userExt || {}; - userExt.digitrust = Object.assign({}, userId.digitrustid.data); - } + if (userIdAsEids && userIdAsEids.length) { + userExt = userExt || {}; + userExt.eids = [...userIdAsEids]; } if (userExt && Object.keys(userExt).length) { diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index e884df40c5e..084c67562e6 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -311,68 +311,36 @@ describe('TheMediaGrid Adapter', function () { }); it('if userId is present payload must have user.ext param with right keys', function () { - const bidRequestsWithUserIds = bidRequests.map((bid) => { - return Object.assign({ - userId: { - id5id: { uid: 'id5id_1', ext: { linkType: 2 } }, - tdid: 'tdid_1', - digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - lipb: {lipbid: 'lipb_1'}, - idl_env: 'idl_env_1', - criteoId: 'criteoId_1' - } - }, bid); - }); - const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('user'); - expect(payload.user).to.have.property('ext'); - expect(payload.user.ext.digitrust).to.deep.equal({ - id: 'DTID', - keyv: 4, - privacy: { - optout: false + const eids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] }, - producer: 'ABC', - version: 2 - }); - expect(payload.user.ext.eids).to.deep.equal([ { source: 'adserver.org', uids: [{ - id: 'tdid_1', + id: 'some-random-id-value', + atype: 1, ext: { rtiPartner: 'TDID' } }] - }, - { - source: 'id5-sync.com', - uids: [{ - id: 'id5id_1' - }], - ext: { linkType: 2 } - }, - { - source: 'liveintent.com', - uids: [{ - id: 'lipb_1' - }] - }, - { - source: 'identityLink', - uids: [{ - id: 'idl_env_1' - }] - }, - { - source: 'criteo.com', - uids: [{ - id: 'criteoId_1' - }] } - ]); + ]; + const bidRequestsWithUserIds = bidRequests.map((bid) => { + return Object.assign({ + userIdAsEids: eids + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext.eids).to.deep.equal(eids); }); it('if schain is present payload must have source.ext.schain param', function () { @@ -403,7 +371,7 @@ describe('TheMediaGrid Adapter', function () { it('if content and segment is present in jwTargeting, payload must have right params', function () { const jsContent = {id: 'test_jw_content_id'}; const jsSegments = ['test_seg_1', 'test_seg_2']; - const bidRequestsWithUserIds = bidRequests.map((bid) => { + const bidRequestsWithJwTargeting = bidRequests.map((bid) => { return Object.assign({ rtd: { jwplayer: { @@ -415,7 +383,7 @@ describe('TheMediaGrid Adapter', function () { } }, bid); }); - const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + const request = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); From 3e73c11e1285aa40588c21e616602f853e4a4dab Mon Sep 17 00:00:00 2001 From: Newton <5769156+iamnewton@users.noreply.github.com> Date: Mon, 21 Dec 2020 02:00:15 -0800 Subject: [PATCH 024/325] ID Library feat: turn off fullscan by default (#6140) * chore: lint HTML example page * chore: add space to logging statement for clarity * chore: update package lock * feat: turn off fullscan by default * chore: format the HTML for readibility * chore: fix test * Revert "chore: update package lock" This reverts commit 4faf19489677de3c278b625535f4ba01877ccacb. --- integrationExamples/gpt/userId_example.html | 589 ++++++++++---------- modules/idLibrary.js | 4 +- modules/idLibrary.md | 38 +- test/spec/modules/idLibrary_spec.js | 6 +- 4 files changed, 318 insertions(+), 319 deletions(-) diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 7375293fdf0..e6f9255326a 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -1,334 +1,335 @@ + - - + + + ] + } + ]; - + - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300,250],[300,600],[728,90]] - } - }, - bids: [ - { - bidder: 'rubicon', - params: { - accountId: '1001', - siteId: '113932', - zoneId: '535510' - } - } - ] - } - ]; + - + pbjs.que.push(function() { + pbjs.setConfig({ + debug: true, + consentManagement: { + cmpApi: 'iab', + timeout: 1000, + defaultGdprScope: true + }, + // consentManagement: { + // cmpApi: 'static', + // consentData: { + // consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' + // vendorData: { + // purposeConsents: { + // '1': true + // } + // } + // } + // }, + userSync: { + userIds: [{ + name: "pubProvidedId", + params: { + eids: [{ + source: "domain.com", + uids:[{ + id: "value read from cookie or local storage", + atype: 1, + ext: { + stype: "ppuid" // allowable options are sha256email, DMP, ppuid for now + } + }] + },{ + source: "3rdpartyprovided.com", + uids:[{ + id: "value read from cookie or local storage", + atype: 3, + ext: { + stype: "sha256email" + } + }] + }], + eidsFunction: getHashedEmail // any user defined function that exists in the page + } + },{ + name: "unifiedId", + params: { + partner: "prebid", + url: "http://match.adsrvr.org/track/rid?ttd_pid=prebid&fmt=json" + }, + storage: { + type: "html5", + name: "unifiedid", + expires: 30 + }, + },{ + name: "intentIqId", + params: { + partner: 0, //Set your real IntentIQ partner ID here for production. + }, + storage: { + type: "cookie", + name: "intentIqId", + expires: 30 + }, + }, + { + name: "id5Id", + params: { + partner: 173 //Set your real ID5 partner ID here for production, please ask for one at http://id5.io/prebid + }, + storage: { + type: "cookie", + name: "id5id", + expires: 90, + refreshInSeconds: 8*3600 // Refresh frequency of cookies, defaulting to 'expires' + }, - + function sendAdserverRequest() { + if (pbjs.adserverRequestSent) return; + pbjs.adserverRequestSent = true; + googletag.cmd.push(function() { + pbjs.que.push(function() { + pbjs.setTargetingForGPTAsync(); + googletag.pubads().refresh(); + }); + }); + } - + setTimeout(function() { + sendAdserverRequest(); + }, FAILSAFE_TIMEOUT); + - - + - -

Rubicon Project Prebid

+ + -
- -
- + +

Rubicon Project Prebid

+
+ +
+ diff --git a/modules/idLibrary.js b/modules/idLibrary.js index ba3cc0b5efb..988f3417b30 100644 --- a/modules/idLibrary.js +++ b/modules/idLibrary.js @@ -8,7 +8,7 @@ let email; let conf; const LOG_PRE_FIX = 'ID-Library: '; const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250; -const CONF_DEFAULT_FULL_BODY_SCAN = true; +const CONF_DEFAULT_FULL_BODY_SCAN = false; const OBSERVER_CONFIG = { subtree: true, attributes: true, @@ -38,7 +38,7 @@ function getEmail(value) { if (!matched) { return null; } - logInfo('Email found' + matched[0]); + logInfo('Email found: ' + matched[0]); return matched[0]; } diff --git a/modules/idLibrary.md b/modules/idLibrary.md index 69b63dc466b..28d40c389f3 100644 --- a/modules/idLibrary.md +++ b/modules/idLibrary.md @@ -1,24 +1,22 @@ -## ID Library Configuration Example +# ID Library +## Configuration Options -|Param |Required |Description | -|----------------|-------------------------------|-----------------------------| -|url |Yes | The url endpoint is used to post the hashed email and user ids. | -|target |No |It should contain the element id from which the email can be read. | -|debounce |No | Time in milliseconds before the email and ids are fetched | -|fullscan |No | Option to enable/disable full body scan to get email. By default the full body scan is enabled. | +| Parameter | Required | Type | Default | Description | +| :--------- | :------- | :------ | :------ | :---------- | +| `target` | Yes | String | N/A | ID attribute of the element from which the email can be read. | +| `url` | Yes | String | N/A | URL endpoint used to post the hashed email and user IDs. | +| `debounce` | No | Number | 250 | Time in milliseconds before the email and IDs are fetched. | +| `fullscan` | No | Boolean | false | Enable/disable a full page body scan to get email. | -### Example -``` - pbjs.setConfig({ - idLibrary:{ - url: , - debounce: 250, - target: 'username', - fullscan: false - }, - }); -``` +## Example - -``` +```javascript +pbjs.setConfig({ + idLibrary: { + target: 'username', + url: 'https://example.com', + debounce: 250, + fullscan: false, + }, +}); diff --git a/test/spec/modules/idLibrary_spec.js b/test/spec/modules/idLibrary_spec.js index da61850f29b..682c2df1e44 100644 --- a/test/spec/modules/idLibrary_spec.js +++ b/test/spec/modules/idLibrary_spec.js @@ -50,12 +50,12 @@ describe('currency', function () { it('results with config default fullscan ', function () { let config = { 'url': 'URL' } idlibrary.setConfig(config); - expect(config.fullscan).to.be.equal(true); + expect(config.fullscan).to.be.equal(false); }); it('results with config fullscan ', function () { - let config = { 'url': 'URL', 'fullscan': false } + let config = { 'url': 'URL', 'fullscan': true } idlibrary.setConfig(config); - expect(config.fullscan).to.be.equal(false); + expect(config.fullscan).to.be.equal(true); }); }); }); From 2f055063ccca4d08c2cb5e8fe13abadd8346ba4c Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Mon, 21 Dec 2020 11:01:50 +0100 Subject: [PATCH 025/325] update release process for notes on release drafter checks (#6137) * update release process for notes on release drafter checks * update section titles --- RELEASE_SCHEDULE.md | 83 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md index 7b2c6244bd7..bfbd0772c3e 100644 --- a/RELEASE_SCHEDULE.md +++ b/RELEASE_SCHEDULE.md @@ -1,6 +1,14 @@ **Table of Contents** - [Release Schedule](#release-schedule) - [Release Process](#release-process) + - [1. Make sure that all PRs have been named and labeled properly per the PR Process](#1-make-sure-that-all-prs-have-been-named-and-labeled-properly-per-the-pr-process) + - [2. Make sure all browserstack tests are passing](#2-make-sure-all-browserstack-tests-are-passing) + - [3. Prepare Prebid Code](#3-prepare-prebid-code) + - [4. Verify the Release](#4-verify-the-release) + - [5. Create a GitHub release](#5-create-a-github-release) + - [6. Update coveralls _(skip for legacy)_](#6-update-coveralls-skip-for-legacy) + - [7. Distribute the code](#7-distribute-the-code) + - [8. Increment Version for Next Release](#8-increment-version-for-next-release) - [Beta Releases](#beta-releases) - [FAQs](#faqs) @@ -9,7 +17,7 @@ We aim to push a new release of Prebid.js every week on Tuesday. While the releases will be available immediately for those using direct Git access, -it will be about a week before the Prebid Org [Download Page](http://prebid.org/download.html) will be updated. +it will be about a week before the Prebid Org [Download Page](http://prebid.org/download.html) will be updated. You can determine what is in a given build using the [releases page](https://github.com/prebid/Prebid.js/releases) @@ -19,14 +27,20 @@ Announcements regarding releases will be made to the #headerbidding-dev channel _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for your repo, all of the following git commands will have to be modified to reference the proper remote (e.g. `upstream`)_ -1. Make Sure all browserstack tests are passing. On PR merge to master CircleCI will run unit tests on browserstack. Checking the last CircleCI build [here](https://circleci.com/gh/prebid/Prebid.js) for master branch will show you detailed results. - - In case of failure do following, +### 1. Make sure that all PRs have been named and labeled properly per the [PR Process](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md#general-pr-review-process) + * Do this by checking the latest draft release from the [releases page](https://github.com/prebid/Prebid.js/releases) and make sure nothing appears in the first section called "In This Release". If they do, please open the PRs and add the appropriate labels. + * Do a quick check that all the titles/descriptions look ok, and if not, adjust the PR title. + +### 2. Make sure all browserstack tests are passing + + On PR merge to master, CircleCI will run unit tests on browserstack. Checking the last CircleCI build [here](https://circleci.com/gh/prebid/Prebid.js) for master branch will show you detailed results.** + + In case of failure do following, - Try to fix the failing tests. - If you are not able to fix tests in time. Skip the test, create issue and tag contributor. - #### How to run tests in browserstack - + **How to run tests in browserstack** + _Note: the following browserstack information is only relevant for debugging purposes, if you will not be debugging then it can be skipped._ Set the environment variables. You may want to add these to your `~/.bashrc` for convenience. @@ -35,40 +49,40 @@ _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for export BROWSERSTACK_USERNAME="my browserstack username" export BROWSERSTACK_ACCESS_KEY="my browserstack access key" ``` - + ``` gulp test --browserstack >> prebid_test.log - + vim prebid_test.log // Will show the test results ``` -2. Prepare Prebid Code +### 3. Prepare Prebid Code Update the package.json version to become the current release. Then commit your changes. ``` - git commit -m "Prebid 1.x.x Release" + git commit -m "Prebid 4.x.x Release" git push ``` -3. Verify Release +### 4. Verify the Release Make sure your there are no more merges to master branch. Prebid code is clean and up to date. -4. Create a GitHub release +### 5. Create a GitHub release + + Edit the most recent [release notes](https://github.com/prebid/Prebid.js/releases) draft and make sure the correct version is set and the master branch is selected in the dropdown. Click `Publish release`. GitHub will create release tag. - Edit the most recent [release notes](https://github.com/prebid/Prebid.js/releases) draft and make sure the correct tag is in the dropdown. Click `Publish`. GitHub will create release tag. - - Pull these changes locally by running command + Pull these changes locally by running command ``` git pull git fetch --tags - ``` - + ``` + and verify the tag. -5. Update coveralls _(skip for legacy)_ +### 6. Update coveralls _(skip for legacy)_ We use https://coveralls.io/ to show parts of code covered by unit tests. @@ -80,35 +94,23 @@ _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for Run `gulp coveralls` to update code coverage history. -6. Distribute the code +### 7. Distribute the code - _Note: do not go to step 7 until step 6 has been verified completed._ + _Note: do not go to step 8 until step 7 has been verified completed._ Reach out to any of the Appnexus folks to trigger the jenkins job. - // TODO + // TODO: Jenkins job is moving files to appnexus cdn, pushing prebid.js to npm, purging cache and sending notification to slack. Move all the files from Appnexus CDN to jsDelivr and create bash script to do above tasks. -7. Post Release Version - - Update the version - Manually edit Prebid's package.json to become "1.x.x-pre" (using the values for the next release). Then commit your changes. +### 8. Increment Version for Next Release + + Update the version by manually editing Prebid's `package.json` to become "4.x.x-pre" (using the values for the next release). Then commit your changes. ``` git commit -m "Increment pre version" git push ``` - -8. Create new release draft - - Go to [github releases](https://github.com/prebid/Prebid.js/releases) and add a new draft for the next version of Prebid.js with the following template: -``` -## 🚀New Features - -## 🛠Maintenance - -## 🐛Bug Fixes -``` ## Beta Releases @@ -129,11 +131,8 @@ Characteristics of a `GA` release: ## FAQs **1. Is there flexibility in the schedule?** - -If a major bug is found in the current release, a maintenance patch will be done as soon as possible. - -It is unlikely that we will put out a maintenance patch at the request of a given bid adapter or module owner. +* If a major bug is found in the current release, a maintenance patch will be done as soon as possible. +* It is unlikely that we will put out a maintenance patch at the request of a given bid adapter or module owner. **2. What Pull Requests make it into a release?** - -Every PR that's merged into master will be part of a release. Here are the [PR review guidelines](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md). +* Every PR that's merged into master will be part of a release. Here are the [PR review guidelines](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md). From 5d2ccb2fdd52c7bffa1b75b01afcd2af94036da6 Mon Sep 17 00:00:00 2001 From: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Date: Mon, 21 Dec 2020 22:56:52 +0100 Subject: [PATCH 026/325] Sspbc Bid Adapter: multiple updates (#6118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add sspbc adapter * tests for sspbc adapter * sspBC adapter v4.5: set correct creativeId, add adomain to bid.meta, set test mode in adexchange, read site SN from bid response * sspBC adapter v4.5: set meta.advertiserDomains, update test to expect bid.meta * sspBC Adapter: add ajax tests (test ad with & without gdpr) * sspBC Adapter: remove ajax tests * Update adapter to v4.6 Update adapter to v4.6 - add notification endpoint - send bidWon and onTimeout notifications - send CMP version to user sync endpoint * Remove console logs for notification events * Change payload data in onTimeout event * Update tests for sspBC adapter Update tests for sspBC adapter: - add onBidWon test - add onTimeout test - alter getUserSyncs test * Update sspBC adapter to v4.7; enable oneCodeId mode; change module name to ensure combatibility with prebid.org downloader * sspBc adapter: Bug fixes in v4.7 - change notification format, fix oneCode detection data, convert slot number to int Co-authored-by: Wojciech Biały --- .../{sspBCAdapter.js => sspBCBidAdapter.js} | 140 +++++++++++------- .../{sspBCAdapter.md => sspBCBidAdapter.md} | 16 +- ...dapter_spec.js => sspBCBidAdapter_spec.js} | 109 ++++++++++++-- 3 files changed, 188 insertions(+), 77 deletions(-) rename modules/{sspBCAdapter.js => sspBCBidAdapter.js} (72%) rename modules/{sspBCAdapter.md => sspBCBidAdapter.md} (68%) rename test/spec/modules/{sspBCAdapter_spec.js => sspBCBidAdapter_spec.js} (78%) diff --git a/modules/sspBCAdapter.js b/modules/sspBCBidAdapter.js similarity index 72% rename from modules/sspBCAdapter.js rename to modules/sspBCBidAdapter.js index 4069c722e9d..1f250992fee 100644 --- a/modules/sspBCAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -8,11 +8,58 @@ const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; const TMAX = 450; -const BIDDER_VERSION = '4.6'; +const BIDDER_VERSION = '4.7'; const W = window; const { navigator } = W; +const oneCodeDetection = {}; var consentApiVersion; +/** + * Get bid parameters for notification + * @param {*} bidData - bid (bidWon), or array of bids (timeout) + */ +const getNotificationPayload = bidData => { + if (bidData) { + const bids = utils.isArray(bidData) ? bidData : [bidData]; + if (bids.length > 0) { + const result = { + requestId: undefined, + siteId: [], + adUnit: [], + slotId: [], + } + bids.forEach(bid => { + let params = utils.isArray(bid.params) ? bid.params[0] : bid.params; + params = params || {}; + + // check for stored detection + if (oneCodeDetection[bid.requestId]) { + params.siteId = oneCodeDetection[bid.requestId][0]; + params.id = oneCodeDetection[bid.requestId][1]; + } + + if (params.siteId) { + result.siteId.push(params.siteId); + } + if (params.id) { + result.slotId.push(params.id); + } + if (bid.cpm) { + const meta = bid.meta || {}; + result.cpm = bid.cpm; + result.creativeId = bid.creativeId; + result.adomain = meta.advertiserDomains && meta.advertiserDomains[0]; + result.networkName = meta.networkName; + } + result.adUnit.push(bid.adUnitCode) + result.requestId = bid.auctionId || result.requestId; + result.timeout = bid.timeout || result.timeout; + }) + return result; + } + } +} + const cookieSupport = () => { const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); const useCookies = navigator.cookieEnabled || !!document.cookie.length; @@ -104,13 +151,13 @@ function mapBanner(slot) { function mapImpression(slot) { const imp = { - id: slot.params.id, + id: (slot.params && slot.params.id) ? slot.params.id : 'bidid-' + slot.bidId, banner: mapBanner(slot), /* native: mapNative(slot), */ - tagid: slot.params.id, + tagid: slot.adUnitCode, }; - const bidfloor = parseFloat(slot.params.bidfloor); + const bidfloor = (slot.params && slot.params.bidFloor) ? parseFloat(slot.params.bidFloor) : undefined; if (bidfloor) { imp.bidfloor = bidfloor; @@ -170,6 +217,7 @@ function renderCreative(site, auctionId, bid, seat, request) { ` + ad: `` } if (isVideo(bidRequest, adType)) { @@ -124,8 +124,7 @@ export const spec = { bidResponse.height = playersize[1] } bidResponse.mediaType = VIDEO - bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/${customsize[0]}x${customsize[1]}?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}` - + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}` if (isOutstream(bidRequest)) { const renderer = Renderer.install({ id: bidRequest.bidId, diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md index 37897b83f12..a7a3f2715dc 100644 --- a/modules/yieldlabBidAdapter.md +++ b/modules/yieldlabBidAdapter.md @@ -21,7 +21,6 @@ Module that connects to Yieldlab's demand sources params: { adslotId: "5220336", supplyId: "1381604", - adSize: "728x90", targeting: { key1: "value1", key2: "value2" @@ -41,8 +40,7 @@ Module that connects to Yieldlab's demand sources bidder: "yieldlab", params: { adslotId: "5220339", - supplyId: "1381604", - adSize: "640x480" + supplyId: "1381604" } }] } diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index cd2c46a5664..72f70ca0208 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -7,7 +7,6 @@ const REQUEST = { 'params': { 'adslotId': '1111', 'supplyId': '2222', - 'adSize': '728x90', 'targeting': { 'key1': 'value1', 'key2': 'value2', @@ -57,6 +56,7 @@ const RESPONSE = { id: 1111, price: 1, pid: 2222, + adsize: '728x90', adtype: 'BANNER' } @@ -88,8 +88,7 @@ describe('yieldlabBidAdapter', function () { const request = { 'params': { 'adslotId': '1111', - 'supplyId': '2222', - 'adSize': '728x90' + 'supplyId': '2222' } } expect(spec.isBidRequestValid(request)).to.equal(true) @@ -180,7 +179,7 @@ describe('yieldlabBidAdapter', function () { expect(result[0].netRevenue).to.equal(false) expect(result[0].ttl).to.equal(300) expect(result[0].referrer).to.equal('') - expect(result[0].ad).to.include(' @@ -126,14 +128,9 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse) { - const serverBody = serverResponse.body; - if (serverBody && utils.isArray(serverBody)) { - return utils._map(serverBody, function(bid) { - return buildBid(bid); - }); - } else { - return []; - } + const bids = serverResponse.body && serverResponse.body.bids; + + return Array.isArray(bids) ? bids.map(bid => buildBid(bid)) : [] } } diff --git a/modules/astraoneBidAdapter.md b/modules/astraoneBidAdapter.md index a7eaeeef5a4..e090cfe1e54 100644 --- a/modules/astraoneBidAdapter.md +++ b/modules/astraoneBidAdapter.md @@ -18,17 +18,17 @@ About us: https://astraone.io var adUnits = [{ code: 'test-div', mediaTypes: { - banner: { - sizes: [1, 1] - } + banner: { + sizes: [1, 1], + } }, bids: [{ - bidder: "astraone", - params: { - placement: "inImage", - placeId: "5af45ad34d506ee7acad0c26", - imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" - } + bidder: "astraone", + params: { + placement: "inImage", + placeId: "5f477bf94d506ebe2c4240f3", + imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" + } }] }]; ``` @@ -39,66 +39,69 @@ var adUnits = [{ - - Prebid.js Banner Example - - + + }, + bids: [{ + bidder: "astraone", + params: { + placement: "inImage", + placeId: "5f477bf94d506ebe2c4240f3", + imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" + } + }] + }]; + + var pbjs = pbjs || {}; + pbjs.que = pbjs.que || []; + + pbjs.que.push(function() { + pbjs.addAdUnits(adUnits); + pbjs.requestBids({ + bidsBackHandler: function (e) { + if (pbjs.adserverRequestSent) return; + pbjs.adserverRequestSent = true; + var params = pbjs.getAdserverTargetingForAdUnitCode("test-div"); + var iframe = document.getElementById('test-div'); + + if (params && params['hb_adid']) { + iframe.parentElement.style.position = "relative"; + iframe.style.display = "block"; + pbjs.renderAd(iframe.contentDocument, params['hb_adid']); + } + } + }); + }); + -

Prebid.js InImage Banner Test

+

Prebid.js InImage Banner Test

-
- - -
+
+ + +
@@ -109,90 +112,91 @@ var adUnits = [{ - - Prebid.js Banner Example - - - - + + Prebid.js Banner gpt Example + + + + -

Prebid.js Banner Ad Unit Test

+

Prebid.js InImage Banner gpt Test

-
- +
+ - -
+ +
``` diff --git a/test/spec/modules/astraoneBidAdapter_spec.js b/test/spec/modules/astraoneBidAdapter_spec.js index e422f64b570..0e545081869 100644 --- a/test/spec/modules/astraoneBidAdapter_spec.js +++ b/test/spec/modules/astraoneBidAdapter_spec.js @@ -14,7 +14,7 @@ function getSlotConfigs(mediaTypes, params) { describe('AstraOne Adapter', function() { describe('isBidRequestValid method', function() { - const PLACE_ID = '5af45ad34d506ee7acad0c26'; + const PLACE_ID = '5f477bf94d506ebe2c4240f3'; const IMAGE_URL = 'https://creative.astraone.io/files/default_image-1-600x400.jpg'; describe('returns true', function() { @@ -176,21 +176,23 @@ describe('AstraOne Adapter', function() { describe('the bid is a banner', function() { it('should return a banner bid', function() { const serverResponse = { - body: [ - { - bidId: '2df8c0733f284e', - price: 0.5, - currency: 'USD', - content: { - content: 'html', - actionUrls: {}, - seanceId: '123123' - }, - width: 100, - height: 100, - ttl: 360 - } - ] + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: { + content: 'html', + actionUrls: {}, + seanceId: '123123' + }, + width: 100, + height: 100, + ttl: 360 + } + ] + } } const bids = spec.interpretResponse(serverResponse) expect(bids.length).to.equal(1) From b7ec359ca242ceab4cd0c7e37307247974126e51 Mon Sep 17 00:00:00 2001 From: Steve Alliance Date: Thu, 28 Jan 2021 03:14:49 -0500 Subject: [PATCH 098/325] update banner ttl (#6228) --- modules/districtmDMXBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index b5969d4ed1e..f01c3c2ce9f 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -39,7 +39,7 @@ export const spec = { nBid.requestId = nBid.impid; nBid.width = nBid.w || width; nBid.height = nBid.h || height; - nBid.ttl = 360; + nBid.ttl = 300; nBid.mediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner'; if (nBid.mediaType) { nBid.vastXml = cleanVast(nBid.adm, nBid.nurl); From 4f2af660066308f166b10da2c11220205f45ab3b Mon Sep 17 00:00:00 2001 From: bretg Date: Thu, 28 Jan 2021 09:31:36 -0500 Subject: [PATCH 099/325] browsi: updating test parameters (#6048) --- modules/browsiRtdProvider.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/browsiRtdProvider.md b/modules/browsiRtdProvider.md index 0dd8c1d7609..9eed4b2b2d4 100644 --- a/modules/browsiRtdProvider.md +++ b/modules/browsiRtdProvider.md @@ -19,9 +19,9 @@ Configuration example for using RTD module with `browsi` provider "name": "browsi", "waitForIt": "true" "params": { - "url": "testUrl.com", - "siteKey": "testKey", - "pubKey": "testPub", + "url": "yield-manager.browsiprod.com", + "siteKey": "browsidemo", + "pubKey": "browsidemo" "keyName":"bv" } }] From 5f56b1875bb3072fee3645f7c582bfbb3d4b980a Mon Sep 17 00:00:00 2001 From: Amanda Dillon <41923726+agdillon@users.noreply.github.com> Date: Thu, 28 Jan 2021 08:23:51 -0700 Subject: [PATCH 100/325] SpotX Bid Adapter: default to 4/3 aspect ratio when response doesn't contain w or h (#6159) * Default to 4/3 aspect ratio when response doesn't contain w or h * SpotX bid adapter: reorder tests and remove extra assertions --- modules/spotxBidAdapter.js | 46 +++++++++++------------ test/spec/modules/spotxBidAdapter_spec.js | 43 +++++++++++++++++++-- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 5ed3884e977..c85a836435e 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -51,7 +51,7 @@ export const spec = { return false; } if (!utils.getBidIdParameter('slot', bid.params.outstream_options)) { - utils.logError(BIDDER_CODE + ': please define parameters slot outstream_options object in the configuration.'); + utils.logError(BIDDER_CODE + ': please define parameter slot in outstream_options object in the configuration.'); return false; } } @@ -382,7 +382,7 @@ export const spec = { } }); } catch (err) { - utils.logWarn('Prebid Error calling setRender or setEve,tHandlers on renderer', err); + utils.logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err); } bid.renderer = renderer; } @@ -408,7 +408,7 @@ function createOutstreamScript(bid) { dataSpotXParams['data-spotx_content_page_url'] = bid.renderer.config.content_page_url; dataSpotXParams['data-spotx_ad_unit'] = 'incontent'; - utils.logMessage('[SPOTX][renderer] Default beahavior'); + utils.logMessage('[SPOTX][renderer] Default behavior'); if (utils.getBidIdParameter('ad_mute', bid.renderer.config.outstream_options)) { dataSpotXParams['data-spotx_ad_mute'] = '1'; } @@ -419,30 +419,26 @@ function createOutstreamScript(bid) { const playersizeAutoAdapt = utils.getBidIdParameter('playersize_auto_adapt', bid.renderer.config.outstream_options); if (playersizeAutoAdapt && utils.isBoolean(playersizeAutoAdapt) && playersizeAutoAdapt === true) { - if (bid.width && utils.isNumber(bid.width) && bid.height && utils.isNumber(bid.height)) { - const ratio = bid.width / bid.height; - const slotClientWidth = window.document.getElementById(slot).clientWidth; - let playerWidth = bid.renderer.config.player_width; - let playerHeight = bid.renderer.config.player_height; - let contentWidth = 0; - let contentHeight = 0; - if (slotClientWidth < playerWidth) { - playerWidth = slotClientWidth; - playerHeight = playerWidth / ratio; - } - if (ratio <= 1) { - contentWidth = Math.round(playerHeight * ratio); - contentHeight = playerHeight; - } else { - contentWidth = playerWidth; - contentHeight = Math.round(playerWidth / ratio); - } - - dataSpotXParams['data-spotx_content_width'] = '' + contentWidth; - dataSpotXParams['data-spotx_content_height'] = '' + contentHeight; + const ratio = bid.width && utils.isNumber(bid.width) && bid.height && utils.isNumber(bid.height) ? bid.width / bid.height : 4 / 3; + const slotClientWidth = window.document.getElementById(slot).clientWidth; + let playerWidth = bid.renderer.config.player_width; + let playerHeight = bid.renderer.config.player_height; + let contentWidth = 0; + let contentHeight = 0; + if (slotClientWidth < playerWidth) { + playerWidth = slotClientWidth; + playerHeight = playerWidth / ratio; + } + if (ratio <= 1) { + contentWidth = Math.round(playerHeight * ratio); + contentHeight = playerHeight; } else { - utils.logWarn('[SPOTX][renderer] PlayerSize auto adapt: bid.width and bid.height are incorrect'); + contentWidth = playerWidth; + contentHeight = Math.round(playerWidth / ratio); } + + dataSpotXParams['data-spotx_content_width'] = '' + contentWidth; + dataSpotXParams['data-spotx_content_height'] = '' + contentHeight; } const customOverride = utils.getBidIdParameter('custom_override', bid.renderer.config.outstream_options); diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index fb5ce63f543..927599ac986 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -488,7 +488,7 @@ describe('the spotx adapter', function () { }); }); - describe('oustreamRender', function() { + describe('outstreamRender', function() { var serverResponse, bidderRequestObj; beforeEach(function() { @@ -545,7 +545,7 @@ describe('the spotx adapter', function () { it('should attempt to insert the EASI script', function() { var scriptTag; sinon.stub(window.document, 'getElementById').returns({ - appendChild: sinon.stub().callsFake(function(script) { scriptTag = script }) + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script; }) }); var responses = spec.interpretResponse(serverResponse, bidderRequestObj); @@ -573,7 +573,7 @@ describe('the spotx adapter', function () { nodeName: 'IFRAME', contentDocument: { body: { - appendChild: sinon.stub().callsFake(function(script) { scriptTag = script }) + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script; }) } } }); @@ -598,5 +598,42 @@ describe('the spotx adapter', function () { expect(scriptTag.getAttribute('data-spotx_content_height')).to.equal('300'); window.document.getElementById.restore(); }); + + it('should adjust width and height to match slot clientWidth if playersize_auto_adapt is used', function() { + var scriptTag; + sinon.stub(window.document, 'getElementById').returns({ + clientWidth: 200, + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script; }) + }); + var responses = spec.interpretResponse(serverResponse, bidderRequestObj); + + responses[0].renderer.render(responses[0]); + + expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); + expect(scriptTag.getAttribute('src')).to.equal('https://js.spotx.tv/easi/v1/12345.js'); + expect(scriptTag.getAttribute('data-spotx_content_width')).to.equal('200'); + expect(scriptTag.getAttribute('data-spotx_content_height')).to.equal('150'); + window.document.getElementById.restore(); + }); + + it('should use a default 4/3 ratio if playersize_auto_adapt is used and response does not contain width or height', function() { + delete serverResponse.body.seatbid[0].bid[0].w; + delete serverResponse.body.seatbid[0].bid[0].h; + + var scriptTag; + sinon.stub(window.document, 'getElementById').returns({ + clientWidth: 200, + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script; }) + }); + var responses = spec.interpretResponse(serverResponse, bidderRequestObj); + + responses[0].renderer.render(responses[0]); + + expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); + expect(scriptTag.getAttribute('src')).to.equal('https://js.spotx.tv/easi/v1/12345.js'); + expect(scriptTag.getAttribute('data-spotx_content_width')).to.equal('200'); + expect(scriptTag.getAttribute('data-spotx_content_height')).to.equal('150'); + window.document.getElementById.restore(); + }); }); }); From a926dee9e108ca4b8792ba8992a9bca7c2f42781 Mon Sep 17 00:00:00 2001 From: YerkovichM <48519843+YerkovichM@users.noreply.github.com> Date: Thu, 28 Jan 2021 18:22:54 +0200 Subject: [PATCH 101/325] Extended ID permissions supported by bidder (#6112) * User id bidder permission scheme * styling * prebidServer support * - * fix * prebidServerBidAdapter take eidPermissions directly from userId module * - * unit tests * - * - * update * - * - * changed pbjs_bidders to pbjsBidders * changed pbjsBidders to bidders ext.prebid.data.eidPermissions to ext.prebid.data.eidpermissions * rerun circleci * rerun circleci * omitting eidPermission entry if 'bidders' is not configured Co-authored-by: myerkovich Co-authored-by: Marko Yerkovich --- integrationExamples/gpt/userId_example.html | 1 + modules/prebidServerBidAdapter/index.js | 17 ++- modules/userId/eids.js | 22 ++++ modules/userId/index.js | 47 +++++-- plugins/eslint/validateImports.js | 3 +- .../modules/prebidServerBidAdapter_spec.js | 46 ++++--- test/spec/modules/userId_spec.js | 117 +++++++++++++++++- 7 files changed, 212 insertions(+), 41 deletions(-) diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index fa9b1e3b5fd..71299a4a6e1 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -236,6 +236,7 @@ }, { name: "sharedId", + // bidders: ["rubicon", "sampleBidders"], // to allow this ID for specific bidders params: { syncTime: 60 // in seconds, default is 24 hours }, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 8fb512ff7e2..7274912efb5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -12,6 +12,7 @@ import includes from 'core-js-pure/features/array/includes.js'; import { S2S_VENDORS } from './config.js'; import { ajax } from '../../src/ajax.js'; import find from 'core-js-pure/features/array/find.js'; +import { getEidPermissions } from '../userId/index.js'; const getConfig = config.getConfig; @@ -455,7 +456,7 @@ export function resetWurlMap() { } const OPEN_RTB_PROTOCOL = { - buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig) { + buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig, requestedBidders) { let imps = []; let aliases = {}; const firstBidRequest = bidRequests[0]; @@ -700,6 +701,18 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'user.ext.eids', bidUserIdAsEids); } + const eidPermissions = getEidPermissions(); + if (utils.isArray(eidPermissions) && eidPermissions.length > 0) { + if (requestedBidders && utils.isArray(requestedBidders)) { + eidPermissions.forEach(i => { + if (i.bidders) { + i.bidders = i.bidders.filter(bidder => requestedBidders.includes(bidder)) + } + }); + } + utils.deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); + } + if (bidRequests) { if (firstBidRequest.gdprConsent) { // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module @@ -958,7 +971,7 @@ export function PrebidServer() { queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); } - const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig); + const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig, requestedBidders); const requestJson = request && JSON.stringify(request); if (request && requestJson) { ajax( diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 27665de4136..80750ccaae8 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -224,3 +224,25 @@ export function createEidsArray(bidRequestUserId) { } return eids; } + +/** + * @param {SubmoduleContainer[]} submodules + */ +export function buildEidPermissions(submodules) { + let eidPermissions = []; + submodules.filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length) + .forEach(i => { + Object.keys(i.idObj).forEach(key => { + if (utils.deepAccess(i, 'config.bidders') && Array.isArray(i.config.bidders) && + utils.deepAccess(USER_IDS_CONFIG, key + '.source')) { + eidPermissions.push( + { + source: USER_IDS_CONFIG[key].source, + bidders: i.config.bidders + } + ); + } + }); + }); + return eidPermissions; +} diff --git a/modules/userId/index.js b/modules/userId/index.js index 9294311de69..8f7d2a36699 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -136,7 +136,7 @@ import { getGlobal } from '../../src/prebidGlobal.js'; import { gdprDataHandler } from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; import { module, hook } from '../../src/hook.js'; -import { createEidsArray } from './eids.js'; +import { createEidsArray, buildEidPermissions } from './eids.js'; import { getCoreStorageManager } from '../../src/storageManager.js'; const MODULE_NAME = 'User ID'; @@ -214,6 +214,10 @@ export function setStoredValue(submodule, value) { } } +export function getEidPermissions() { + return buildEidPermissions(initializedSubmodules); +} + /** * @param {SubmoduleStorage} storage * @param {String|undefined} key optional key of the value @@ -433,6 +437,26 @@ function getCombinedSubmoduleIds(submodules) { return combinedSubmoduleIds; } +/** + * This function will create a combined object for bidder with allowed subModule Ids + * @param {SubmoduleContainer[]} submodules + * @param {string} bidder + */ +function getCombinedSubmoduleIdsForBidder(submodules, bidder) { + if (!Array.isArray(submodules) || !submodules.length || !bidder) { + return {}; + } + return submodules + .filter(i => !i.config.bidders || !utils.isArray(i.config.bidders) || i.config.bidders.includes(bidder)) + .filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length) + .reduce((carry, i) => { + Object.keys(i.idObj).forEach(key => { + carry[key] = i.idObj[key]; + }); + return carry; + }, {}); +} + /** * @param {AdUnit[]} adUnits * @param {SubmoduleContainer[]} submodules @@ -441,19 +465,18 @@ function addIdDataToAdUnitBids(adUnits, submodules) { if ([adUnits].some(i => !Array.isArray(i) || !i.length)) { return; } - const combinedSubmoduleIds = getCombinedSubmoduleIds(submodules); - const combinedSubmoduleIdsAsEids = createEidsArray(combinedSubmoduleIds); - if (Object.keys(combinedSubmoduleIds).length) { - adUnits.forEach(adUnit => { - if (adUnit.bids && utils.isArray(adUnit.bids)) { - adUnit.bids.forEach(bid => { + adUnits.forEach(adUnit => { + if (adUnit.bids && utils.isArray(adUnit.bids)) { + adUnit.bids.forEach(bid => { + const combinedSubmoduleIds = getCombinedSubmoduleIdsForBidder(submodules, bid.bidder); + if (Object.keys(combinedSubmoduleIds).length) { // create a User ID object on the bid, bid.userId = combinedSubmoduleIds; - bid.userIdAsEids = combinedSubmoduleIdsAsEids; - }); - } - }); - } + bid.userIdAsEids = createEidsArray(combinedSubmoduleIds); + } + }); + } + }); } /** diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index a39bf9b26d5..53f4ace8381 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -26,7 +26,8 @@ function flagErrors(context, node, importPath) { if ( path.dirname(absImportPath) === absModulePath || ( absImportPath.startsWith(absModulePath) && - path.basename(absImportPath) === 'index.js' + path.basename(absImportPath) === 'index.js' && + path.basename(absFileDir) !== 'prebidServerBidAdapter' ) ) { context.report(node, `import "${importPath}" cannot require module entry point`); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index f17cd3ab14f..4652bbb63f7 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -953,17 +953,15 @@ describe('S2S Adapter', function () { adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - - expect(requestBid.ext).to.deep.equal({ - prebid: { - aliases: { - brealtime: 'appnexus' - }, - auctiontimestamp: 1510852447530, - targeting: { - includebidderkeys: false, - includewinners: true - } + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.include({ + aliases: { + brealtime: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true } }); }); @@ -985,17 +983,15 @@ describe('S2S Adapter', function () { adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - - expect(requestBid.ext).to.deep.equal({ - prebid: { - aliases: { - [alias]: 'appnexus' - }, - auctiontimestamp: 1510852447530, - targeting: { - includebidderkeys: false, - includewinners: true - } + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.include({ + aliases: { + [alias]: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true } }); }); @@ -1376,7 +1372,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, foo: 'bar', targeting: { @@ -1410,7 +1406,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, targeting: { includewinners: false, @@ -1446,7 +1442,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index ed592c0cba5..1ae023ae947 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -2,6 +2,7 @@ import { attachIdSystem, auctionDelay, coreStorage, + getEidPermissions, init, requestBidsHook, setStoredConsentData, @@ -70,7 +71,7 @@ describe('User ID', function () { code, mediaTypes: {banner: {}, native: {}}, sizes: [[300, 200], [300, 600]], - bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}, {bidder: 'anotherSampleBidder', params: {placementId: 'banner-only-bidder'}}] }; } @@ -1196,6 +1197,120 @@ describe('User ID', function () { }, {adUnits}); }); + it('eidPermissions fun with bidders', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test222', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'sharedId', + bidders: [ + 'sampleBidder' + ], + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } + } + ] + } + }); + + requestBidsHook(function () { + const eidPermissions = getEidPermissions(); + expect(eidPermissions).to.deep.equal( + [ + {source: 'sharedid.org', bidders: ['sampleBidder']} + ] + ); + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + if (bid.bidder === 'sampleBidder') { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid.id).to.equal('test222'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [ + { + id: 'test222', + atype: 1, + ext: { + third: 'test222' + } + } + ] + }); + } + if (bid.bidder === 'anotherSampleBidder') { + expect(bid).to.not.have.deep.nested.property('userId.sharedid'); + expect(bid).to.not.have.property('userIdAsEids'); + } + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('eidPermissions fun without bidders', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test222', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'sharedId', + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } + } + ] + } + }); + + requestBidsHook(function () { + const eidPermissions = getEidPermissions(); + expect(eidPermissions).to.deep.equal( + [] + ); + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid.id).to.equal('test222'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [ + { + id: 'test222', + atype: 1, + ext: { + third: 'test222' + } + }] + }); + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + it('test hook from pubProvidedId config params', function (done) { setSubmoduleRegistry([pubProvidedIdSubmodule]); init(config); From 524efda0ec579d82ee29848b77bd829aead76107 Mon Sep 17 00:00:00 2001 From: samuel-palmer-relevant-digital <77437973+samuel-palmer-relevant-digital@users.noreply.github.com> Date: Thu, 28 Jan 2021 17:27:43 +0100 Subject: [PATCH 102/325] Relevant Yield analytics adapter (#6195) --- modules/relevantAnalyticsAdapter.js | 33 ++++++++++++++ modules/relevantAnalyticsAdapter.md | 13 ++++++ .../modules/relevantAnalyticsAdapter_spec.js | 43 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 modules/relevantAnalyticsAdapter.js create mode 100644 modules/relevantAnalyticsAdapter.md create mode 100644 test/spec/modules/relevantAnalyticsAdapter_spec.js diff --git a/modules/relevantAnalyticsAdapter.js b/modules/relevantAnalyticsAdapter.js new file mode 100644 index 00000000000..5917262c810 --- /dev/null +++ b/modules/relevantAnalyticsAdapter.js @@ -0,0 +1,33 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; + +const relevantAnalytics = adapter({ analyticsType: 'bundle', handler: 'on' }); + +const { enableAnalytics: orgEnableAnalytics } = relevantAnalytics; + +Object.assign(relevantAnalytics, { + /** + * Save event in the global array that will be consumed later by the Relevant Yield library + */ + track: ({ eventType: ev, args }) => { + window.relevantDigital.pbEventLog.push({ ev, args, ts: new Date() }); + }, + + /** + * Before forwarding the call to the original enableAnalytics function - + * create (if needed) the global array that is used to pass events to the Relevant Yield library + * by the 'track' function above. + */ + enableAnalytics: function(...args) { + window.relevantDigital = window.relevantDigital || {}; + window.relevantDigital.pbEventLog = window.relevantDigital.pbEventLog || []; + return orgEnableAnalytics.call(this, ...args); + }, +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: relevantAnalytics, + code: 'relevant', +}); + +export default relevantAnalytics; diff --git a/modules/relevantAnalyticsAdapter.md b/modules/relevantAnalyticsAdapter.md new file mode 100644 index 00000000000..e6383fa77e1 --- /dev/null +++ b/modules/relevantAnalyticsAdapter.md @@ -0,0 +1,13 @@ +# Overview + +Module Name: Relevant Yield Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: [support@relevant-digital.com](mailto:support@relevant-digital.com) + +# Description + +Analytics adapter to be used with [Relevant Yield](https://www.relevant-digital.com/relevantyield) + +Contact [sales@relevant-digital.com](mailto:sales@relevant-digital.com) for information. diff --git a/test/spec/modules/relevantAnalyticsAdapter_spec.js b/test/spec/modules/relevantAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..3e31db2d7dc --- /dev/null +++ b/test/spec/modules/relevantAnalyticsAdapter_spec.js @@ -0,0 +1,43 @@ +import relevantAnalytics from '../../../modules/relevantAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager'; +import events from 'src/events'; +import constants from 'src/constants.json' +import { expect } from 'chai'; + +describe('Relevant Analytics Adapter', () => { + beforeEach(() => { + adapterManager.enableAnalytics({ + provider: 'relevant' + }); + }); + + afterEach(() => { + relevantAnalytics.disableAnalytics(); + }); + + it('should pass all events to the global array', () => { + // Given + const testEvents = [ + { ev: constants.EVENTS.AUCTION_INIT, args: { test: 1 } }, + { ev: constants.EVENTS.BID_REQUESTED, args: { test: 2 } }, + ]; + + // When + testEvents.forEach(({ ev, args }) => ( + events.emit(ev, args) + )); + + // Then + const eventQueue = (window.relevantDigital || {}).pbEventLog; + expect(eventQueue).to.be.an('array'); + expect(eventQueue.length).to.be.at.least(testEvents.length); + + // The last events should be our test events + const myEvents = eventQueue.slice(-testEvents.length); + testEvents.forEach(({ ev, args }, idx) => { + const actualEvent = myEvents[idx]; + expect(actualEvent.ev).to.eql(ev); + expect(actualEvent.args).to.eql(args); + }); + }); +}); From 136ad4cee32bda212ceee67d92afe494ecbd16d8 Mon Sep 17 00:00:00 2001 From: pm-shashank-jain <40654031+pm-shashank-jain@users.noreply.github.com> Date: Thu, 28 Jan 2021 23:34:31 +0530 Subject: [PATCH 103/325] Pubmatic: fix issue where using an adUnit outstream renderer throws an error (#6152) --- modules/pubmaticBidAdapter.js | 4 ++-- modules/pubmaticBidAdapter.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index b5514ab0344..c70ecb4af27 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -861,8 +861,8 @@ export const spec = { utils.logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid); return false; } - if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU)) { - utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids outstreamAU is required. Rejecting bid: `, bid); + if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU) && !bid.hasOwnProperty('renderer') && !bid.mediaTypes[VIDEO].hasOwnProperty('renderer')) { + utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting bid: `, bid); return false; } } else { diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index 0ef89a22cbd..a34df148630 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -25,7 +25,7 @@ var adUnits = [ bidder: 'pubmatic', params: { publisherId: '156209', // required, must be wrapped in quotes - oustreamAU: 'renderer_test_pubmatic', // required if mediaTypes-> video-> context is 'outstream'. This value can be get by BlueBillyWig Team. + oustreamAU: 'renderer_test_pubmatic', // required if mediaTypes-> video-> context is 'outstream' and optional if renderer is defined in adUnits or in mediaType video. This value can be get by BlueBillyWig Team. adSlot: 'pubmatic_test2', // optional pmzoneid: 'zone1, zone11', // optional lat: '40.712775', // optional From f61311769823d0431cd90a05c125d88905c0555e Mon Sep 17 00:00:00 2001 From: ardit-baloku <77985953+ardit-baloku@users.noreply.github.com> Date: Fri, 29 Jan 2021 11:51:51 +0100 Subject: [PATCH 104/325] Malltv Bid Adapter : added data object as a param (#6232) * Updated malltv adapter * Updated markdown * Added test for malltvBidAdapter --- modules/malltvBidAdapter.js | 7 ++- modules/malltvBidAdapter.md | 63 +++++++++++++++------- test/spec/modules/malltvBidAdapter_spec.js | 30 +++++++++-- 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index 776e3f6458b..7deffe6c07a 100644 --- a/modules/malltvBidAdapter.js +++ b/modules/malltvBidAdapter.js @@ -31,6 +31,7 @@ export const spec = { let bidderRequestId = ''; let url = ''; let contents = []; + let data = {}; let placements = validBidRequests.map(bidRequest => { if (!propertyId) { propertyId = bidRequest.params.propertyId; } @@ -38,7 +39,8 @@ export const spec = { if (!storageId && bidRequest.params) { storageId = bidRequest.params.storageId || ''; } if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } - if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } + if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } let adUnitId = bidRequest.adUnitCode; let placementId = bidRequest.params.placementId; @@ -61,7 +63,8 @@ export const spec = { url: url, requestid: bidderRequestId, placements: placements, - contents: contents + contents: contents, + data: data } return [{ diff --git a/modules/malltvBidAdapter.md b/modules/malltvBidAdapter.md index 3d419fa0916..e32eb54f90f 100644 --- a/modules/malltvBidAdapter.md +++ b/modules/malltvBidAdapter.md @@ -1,45 +1,68 @@ # Overview -Module Name: MallTV Bidder Adapter Module -Type: Bidder Adapter -Maintainer: drilon@gjirafa.com +Module Name: MallTV Bidder Adapter Module + +Type: Bidder Adapter + +Maintainer: arditb@gjirafa.com # Description MallTV Bidder Adapter for Prebid.js. # Test Parameters +```js var adUnits = [ { code: 'test-div', mediaTypes: { banner: { - sizes: [[300, 250], [300, 300]] + sizes: [ + [300, 250], + [300, 300] + ] } }, - bids: [ - { - bidder: 'malltv', - params: { - propertyId: '105134', - placementId: '846832' + bids: [{ + bidder: 'malltv', + params: { + propertyId: '105134', //Required + placementId: '846832', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } } } - ] + }] }, { code: 'test-div', mediaTypes: { - video: { + video: { context: 'instream' - } + } }, - bids: [ - { - bidder: 'malltv', - params: { - propertyId: '105134', - placementId: '846841' + bids: [{ + bidder: 'malltv', + params: { + propertyId: '105134', //Required + placementId: '846832', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } } } - ] + }] } ]; +``` diff --git a/test/spec/modules/malltvBidAdapter_spec.js b/test/spec/modules/malltvBidAdapter_spec.js index e1e9ad867e7..ffe08ad1a5e 100644 --- a/test/spec/modules/malltvBidAdapter_spec.js +++ b/test/spec/modules/malltvBidAdapter_spec.js @@ -34,9 +34,7 @@ describe('malltvAdapterTest', () => { it('bidRequest without propertyId or placementId', () => { expect(spec.isBidRequestValid({ bidder: 'malltv', - params: { - propertyId: '{propertyId}', - } + params: {} })).to.equal(false); }); }); @@ -46,7 +44,17 @@ describe('malltvAdapterTest', () => { 'bidder': 'malltv', 'params': { 'propertyId': '{propertyId}', - 'placementId': '{placementId}' + 'placementId': '{placementId}', + 'data': { + 'catalogs': [{ + 'catalogId': 1, + 'items': ['1', '2', '3'] + }], + 'inventory': { + 'category': ['category1', 'category2'], + 'query': ['query'] + } + } }, 'adUnitCode': 'hb-leaderboard', 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', @@ -86,6 +94,20 @@ describe('malltvAdapterTest', () => { expect(requestItem.data.placements[0].sizes).to.equal('300x250'); }); }); + + it('bidRequest data param', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach((requestItem) => { + expect(requestItem.data.data).to.exist; + expect(requestItem.data.data.catalogs).to.exist; + expect(requestItem.data.data.inventory).to.exist; + expect(requestItem.data.data.catalogs.length).to.equal(1); + expect(requestItem.data.data.catalogs[0].items.length).to.equal(3); + expect(Object.keys(requestItem.data.data.inventory).length).to.equal(2); + expect(requestItem.data.data.inventory.category.length).to.equal(2); + expect(requestItem.data.data.inventory.query.length).to.equal(1); + }); + }); }); describe('interpretResponse', () => { From 25dd35c99f404390242133b84245bb5cafeb7d7b Mon Sep 17 00:00:00 2001 From: msm0504 <51493331+msm0504@users.noreply.github.com> Date: Mon, 1 Feb 2021 06:27:04 -0500 Subject: [PATCH 105/325] support setting coopSync in s2sConfig (#6213) Co-authored-by: Mark Monday --- 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 7274912efb5..cb059b809b7 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -201,6 +201,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 4652bbb63f7..7ebb570823a 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2434,5 +2434,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 cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.coopSync = false; + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + let consentConfig = { s2sConfig: cookieSyncConfig }; + config.setConfig(consentConfig); + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(REQUEST, 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 cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + let consentConfig = { s2sConfig: cookieSyncConfig }; + config.setConfig(consentConfig); + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.coopSync).to.be.undefined; + }); }); }); From e60d10b89e2380c949c3e1d6b388d8f460107f79 Mon Sep 17 00:00:00 2001 From: bretg Date: Mon, 1 Feb 2021 14:00:12 -0500 Subject: [PATCH 106/325] Revert "support setting coopSync in s2sConfig (#6213)" (#6249) This reverts commit 25dd35c99f404390242133b84245bb5cafeb7d7b. --- modules/prebidServerBidAdapter/index.js | 4 --- .../modules/prebidServerBidAdapter_spec.js | 31 ------------------- 2 files changed, 35 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index cb059b809b7..7274912efb5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -201,10 +201,6 @@ 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 7ebb570823a..4652bbb63f7 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2434,36 +2434,5 @@ 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 cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.coopSync = false; - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; - - let consentConfig = { s2sConfig: cookieSyncConfig }; - config.setConfig(consentConfig); - - let bidRequest = utils.deepClone(BID_REQUESTS); - - adapter.callBids(REQUEST, 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 cookieSyncConfig = utils.deepClone(CONFIG); - cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; - - let consentConfig = { s2sConfig: cookieSyncConfig }; - config.setConfig(consentConfig); - - let bidRequest = utils.deepClone(BID_REQUESTS); - - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(server.requests[0].requestBody); - - expect(requestBid.coopSync).to.be.undefined; - }); }); }); From 802cfd0b7f221508ebb99682acea04e424dc4460 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 2 Feb 2021 14:08:40 -0500 Subject: [PATCH 107/325] pbsBidAdapter: change order of client syncs (#6248) * pbsBidAdapter: change order cookie_syncs Prebid Server places cookie-sync URLs in a specific order. PBJS was pulling them off in reverse order. * moving comment * reverting coopSync --- modules/prebidServerBidAdapter/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 7274912efb5..04aedb63ae6 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -223,10 +223,14 @@ function doAllSyncs(bidders, s2sConfig) { return; } - const thisSync = bidders.pop(); + // pull the syncs off the list in the order that prebid server sends them + const thisSync = bidders.shift(); + + // if PBS reports this bidder doesn't have an ID, then call the sync and recurse to the next sync entry if (thisSync.no_cookie) { doPreBidderSync(thisSync.usersync.type, thisSync.usersync.url, thisSync.bidder, utils.bind.call(doAllSyncs, null, bidders, s2sConfig), s2sConfig); } else { + // bidder already has an ID, so just recurse to the next sync entry doAllSyncs(bidders, s2sConfig); } } From 99c1256f53cf95ed75f2d85ac2a249d801836ae9 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Tue, 2 Feb 2021 22:05:38 +0100 Subject: [PATCH 108/325] pass a flag back to ID5 servers if abTesting was enabled by the publisher for monitoring usage of the feature (#6170) --- modules/id5IdSystem.js | 16 ++++++++++- test/spec/modules/id5IdSystem_spec.js | 41 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 45ddc85da5f..baa2ff954dc 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -58,7 +58,7 @@ export const id5IdSubmodule = { } // check for A/B testing configuration and hide ID if in Control Group - let abConfig = (config && config.params && config.params.abTesting) || { enabled: false }; + let abConfig = getAbTestingConfig(config); let controlGroup = false; if ( abConfig.enabled === true && @@ -130,6 +130,10 @@ export const id5IdSubmodule = { 'v': '$prebid.version$' }; + if (getAbTestingConfig(config).enabled === true) { + utils.deepSetValue(data, 'features.ab', 1); + } + const resp = function (callback) { const callbacks = { success: response => { @@ -282,4 +286,14 @@ export function storeInLocalStorage(key, value, expDays) { storage.setDataInLocalStorage(`${key}`, value); } +/** + * gets the existing abTesting config or generates a default config with abTesting off + * + * @param {SubmoduleConfig|undefined} config + * @returns {(Object|undefined)} + */ +function getAbTestingConfig(config) { + return (config && config.params && config.params.abTesting) || { enabled: false }; +} + submodule('userId', id5IdSubmodule); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 24f50db9980..59fa8977fc8 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -238,6 +238,47 @@ describe('ID5 ID System', function() { expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); }); + it('should call the ID5 server with ab feature = 1 when abTesting is turned on', function () { + let id5Config = getId5FetchConfig(); + id5Config.params.abTesting = { enabled: true, controlGroupPct: 10 } + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.features.ab).to.eq(1); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should call the ID5 server without ab feature when abTesting is turned off', function () { + let id5Config = getId5FetchConfig(); + id5Config.params.abTesting = { enabled: false, controlGroupPct: 10 } + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.features).to.be.undefined; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should call the ID5 server without ab feature when when abTesting is not set', function () { + let id5Config = getId5FetchConfig(); + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.features).to.be.undefined; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + it('should store the privacy object from the ID5 server response', function () { let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); From 62e2169ec72c17ee3e7eb70e8a1263c961bf5d1a Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Wed, 3 Feb 2021 00:18:09 -0500 Subject: [PATCH 109/325] appneuxs Bid Adapter - add support for identitylink userId (#6245) --- modules/appnexusBidAdapter.js | 46 +++++++++----------- test/spec/modules/appnexusBidAdapter_spec.js | 8 +++- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index a3cabe62d39..6feec56e919 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -240,34 +240,17 @@ export const spec = { }); } - const criteoId = utils.deepAccess(bidRequests[0], `userId.criteoId`); - let eids = []; - if (criteoId) { - eids.push({ - source: 'criteo.com', - id: criteoId - }); - } + if (bidRequests[0].userId) { + let eids = []; - const netidId = utils.deepAccess(bidRequests[0], `userId.netId`); - if (netidId) { - eids.push({ - source: 'netid.de', - id: netidId - }); - } - - const tdid = utils.deepAccess(bidRequests[0], `userId.tdid`); - if (tdid) { - eids.push({ - source: 'adserver.org', - id: tdid, - rti_partner: 'TDID' - }); - } + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); - if (eids.length) { - payload.eids = eids; + if (eids.length) { + payload.eids = eids; + } } if (tags[0].publisher_id) { @@ -1042,4 +1025,15 @@ function parseMediaType(rtbBid) { } } +function addUserId(eids, id, source, rti) { + if (id) { + if (rti) { + eids.push({ source, id, rti_partner: rti }); + } else { + eids.push({ source, id }); + } + } + return eids; +} + registerBidder(spec); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 81ff5daeaeb..3a3f4effcb8 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -827,7 +827,8 @@ describe('AppNexusAdapter', function () { userId: { tdid: 'sample-userid', criteoId: 'sample-criteo-userid', - netId: 'sample-netId-userid' + netId: 'sample-netId-userid', + idl_env: 'sample-idl-userid' } }); @@ -848,6 +849,11 @@ describe('AppNexusAdapter', function () { source: 'netid.de', id: 'sample-netId-userid', }); + + expect(payload.eids).to.deep.include({ + source: 'liveramp.com', + id: 'sample-idl-userid' + }) }); it('should populate iab_support object at the root level if omid support is detected', function () { From 6a8f95341ec9a3acb29bb3cf0d7433f5006cfa93 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 3 Feb 2021 00:37:38 -0500 Subject: [PATCH 110/325] Update britepoolIdSystem.md (#6254) Eliot from Britepool says you can set just the api key without any params (eg ssid or hash) --- modules/britepoolIdSystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md index a15f601aee3..72edbe2324b 100644 --- a/modules/britepoolIdSystem.md +++ b/modules/britepoolIdSystem.md @@ -4,7 +4,7 @@ BritePool User ID Module. For assistance setting up your module please contact u ### Prebid Params -Individual params may be set for the BritePool User ID Submodule. At least one identifier must be set in the params. +Individual params may be set for the BritePool User ID Submodule. ``` pbjs.setConfig({ userSync: { From c95427605699cdd262280de4697229f7d2a1f43e Mon Sep 17 00:00:00 2001 From: Kotaro Shikata Date: Wed, 3 Feb 2021 14:41:28 +0900 Subject: [PATCH 111/325] UNICORN Adapter - accept multiple formats (#6255) * enable multiple formats add version * add banner w/h * fix w/h & spec --- modules/unicornBidAdapter.js | 18 ++++++-- test/spec/modules/unicornBidAdapter_spec.js | 51 +++++++++++++++------ 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js index 33e60ad2789..2d4b5b53966 100644 --- a/modules/unicornBidAdapter.js +++ b/modules/unicornBidAdapter.js @@ -8,6 +8,7 @@ const BIDDER_CODE = 'unicorn'; const UNICORN_ENDPOINT = 'https://ds.uncn.jp/pb/0/bid.json'; const UNICORN_DEFAULT_CURRENCY = 'JPY'; const UNICORN_PB_COOKIE_KEY = '__pb_unicorn_aud'; +const UNICORN_PB_VERSION = '1.0'; /** * Placement ID and Account ID are required. @@ -47,12 +48,12 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { bidderRequest ); const imp = validBidRequests.map(br => { - const sizes = utils.parseSizesInput(br.sizes)[0]; return { id: br.bidId, banner: { - w: sizes.split('x')[0], - h: sizes.split('x')[1] + format: makeFormat(br.sizes), + w: br.sizes[0][0], + h: br.sizes[0][1], }, tagid: utils.deepAccess(br, 'params.placementId') || br.adUnitCode, secure: 1, @@ -84,7 +85,8 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { source: { ext: { stype: 'prebid_uncn', - bidder: BIDDER_CODE + bidder: BIDDER_CODE, + prebid_version: UNICORN_PB_VERSION } }, ext: { @@ -138,6 +140,14 @@ const getUid = () => { } }; +/** + * Make imp.banner.format + * @param {Array} arr + */ +const makeFormat = arr => arr.map((s) => { + return { w: s[0], h: s[1] }; +}); + export const spec = { code: BIDDER_CODE, aliases: ['uncn'], diff --git a/test/spec/modules/unicornBidAdapter_spec.js b/test/spec/modules/unicornBidAdapter_spec.js index 4c56c37700b..dcd446b2bb0 100644 --- a/test/spec/modules/unicornBidAdapter_spec.js +++ b/test/spec/modules/unicornBidAdapter_spec.js @@ -11,12 +11,12 @@ const bidRequests = [ }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [[300, 250], [336, 280]] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'ea0aa332-a6e1-4474-8180-83720e6b87bc', - sizes: [[300, 250]], + sizes: [[300, 250], [336, 280]], bidId: '226416e6e6bf41', bidderRequestId: '1f41cbdcbe58d5', auctionId: '77987c3a-9be9-4e43-985a-26fc91d84724', @@ -81,12 +81,12 @@ const validBidRequests = [ }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [[300, 250], [336, 280]] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'fbf94ccf-f377-4201-a662-32c2feb8ab6d', - sizes: [[300, 250]], + sizes: [[300, 250], [336, 280]], bidId: '2fb90842443e24', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -156,12 +156,12 @@ const bidderRequest = { }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [[300, 250], [336, 280]] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'fbf94ccf-f377-4201-a662-32c2feb8ab6d', - sizes: [[300, 250]], + sizes: [[300, 250], [336, 280]], bidId: '2fb90842443e24', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -234,8 +234,18 @@ const openRTBRequest = { { id: '216255f234b602', banner: { - w: '300', - h: '250' + w: 300, + h: 250, + format: [ + { + w: 300, + h: 250 + }, + { + w: 336, + h: 280 + } + ] }, secure: 1, bidfloor: 0, @@ -244,8 +254,14 @@ const openRTBRequest = { { id: '31e2b28ced2475', banner: { - w: '300', - h: '250' + w: 300, + h: 250, + format: [ + { + w: 300, + h: 250 + } + ] }, secure: 1, bidfloor: 0, @@ -254,8 +270,14 @@ const openRTBRequest = { { id: '40a333e047a9bd', banner: { - w: '300', - h: '250' + w: 300, + h: 250, + format: [ + { + w: 300, + h: 250 + } + ] }, secure: 1, bidfloor: 0, @@ -287,7 +309,8 @@ const openRTBRequest = { source: { ext: { stype: 'prebid_uncn', - bidder: 'unicorn' + bidder: 'unicorn', + prebid_version: '1.0' } } }; @@ -378,7 +401,7 @@ const request = { method: 'POST', url: 'https://ds.uncn.jp/pb/0/bid.json', data: - '{"id":"5ebea288-f13a-4754-be6d-4ade66c68877","at":1,"imp":[{"id":"216255f234b602","banner":{"w":"300","h":"250"},"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-0"},{"id":"31e2b28ced2475","banner":{"w":"300","h":"250"},"secure":1,"bidfloor":0"tagid":"/19968336/header-bid-tag-1"},{"id":"40a333e047a9bd","banner":{"w":"300","h":"250"},"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-2"}],"cur":"JPY","site":{"id":"uni-corn.net","publisher":{"id":12345},"domain":"uni-corn.net","page":"https://uni-corn.net/","ref":"https://uni-corn.net/"},"device":{"language":"ja","ua":"Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A5000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36"},"user":{"id":"69d9e1c2-801e-4901-a665-fad467550fec"},"bcat":[],"source":{"ext":{"stype":"prebid_uncn","bidder":"unicorn"}}}' + '{"id":"5ebea288-f13a-4754-be6d-4ade66c68877","at":1,"imp":[{"id":"216255f234b602","banner":{"w":300,"h":250},"format":[{"w":300,"h":250},{"w":336,"h":280}],"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-0"},{"id":"31e2b28ced2475","banner":{"w":"300","h":"250"},"format":[{"w":"300","h":"250"}],"secure":1,"bidfloor":0"tagid":"/19968336/header-bid-tag-1"},{"id":"40a333e047a9bd","banner":{"w":300,"h":250},"format":[{"w":300,"h":250}],"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-2"}],"cur":"JPY","site":{"id":"uni-corn.net","publisher":{"id":12345},"domain":"uni-corn.net","page":"https://uni-corn.net/","ref":"https://uni-corn.net/"},"device":{"language":"ja","ua":"Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A5000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36"},"user":{"id":"69d9e1c2-801e-4901-a665-fad467550fec"},"bcat":[],"source":{"ext":{"stype":"prebid_uncn","bidder":"unicorn","prebid_version":"1.0"}}}' }; const interpretedBids = [ From 8c0c7ab844c3ffff41fcc8350f9a98cb71d99c92 Mon Sep 17 00:00:00 2001 From: mamatic <52153441+mamatic@users.noreply.github.com> Date: Wed, 3 Feb 2021 06:46:59 +0100 Subject: [PATCH 112/325] ATS-analytics - add comment clarifying ownership of atsAnalytics (#6257) --- modules/atsAnalyticsAdapter.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 3e618692725..31e46dead89 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -7,6 +7,11 @@ import {getStorageManager} from '../src/storageManager.js'; export const storage = getStorageManager(); +/** + * Analytics adapter for - https://liveramp.com + * Maintainer - prebid@liveramp.com + */ + const analyticsType = 'endpoint'; // dev endpoints // const preflightUrl = 'https://analytics-check.publishersite.xyz/check/'; From 951f1e459cd5c619ab077dfc7981bb32e611ae6d Mon Sep 17 00:00:00 2001 From: Ian Flournoy Date: Wed, 3 Feb 2021 00:52:59 -0500 Subject: [PATCH 113/325] [ParrableIdSystem] Ensure base64 payload is url-safe (#6258) * Added url safe base64 encoding * Added url safe base64 encoding test Co-authored-by: Victor --- modules/parrableIdSystem.js | 11 ++++++++++- test/spec/modules/parrableIdSystem_spec.js | 13 +++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 7587962c62b..9aa2b251f2c 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -70,6 +70,15 @@ function isValidConfig(configParams) { return true; } +function encodeBase64UrlSafe(base64) { + const ENC = { + '+': '-', + '/': '_', + '=': '.' + }; + return base64.replace(/[+/=]/g, (m) => ENC[m]); +} + function readCookie() { const parrableIdStr = storage.getCookie(PARRABLE_COOKIE_NAME); if (parrableIdStr) { @@ -182,7 +191,7 @@ function fetchId(configParams) { }; const searchParams = { - data: btoa(JSON.stringify(data)), + data: encodeBase64UrlSafe(btoa(JSON.stringify(data))), _rand: Math.random() }; diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 5e62af9b2fa..6d0748b592d 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -136,6 +136,19 @@ describe('Parrable ID System', function() { expect(server.requests[0].url).to.contain('us_privacy=' + uspString); }); + it('xhr base64 safely encodes url data object', function() { + const urlSafeBase64EncodedData = '-_.'; + const btoaStub = sinon.stub(window, 'btoa').returns('+/='); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); + + getIdResult.callback(callbackSpy); + + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + expect(queryParams.data).to.equal(urlSafeBase64EncodedData); + btoaStub.restore(); + }); + it('should log an error and continue to callback if ajax request errors', function () { let callBackSpy = sinon.spy(); let submoduleCallback = parrableIdSubmodule.getId({ params: {partner: 'prebid'} }).callback; From 17ce37606611012726576150bd1f185295f0032e Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Wed, 3 Feb 2021 06:57:21 +0100 Subject: [PATCH 114/325] Keywords + Screen resolution + CPU Core (#6259) Co-authored-by: sgimenez --- modules/richaudienceBidAdapter.js | 13 ++++++++++++- modules/richaudienceBidAdapter.md | 1 + test/spec/modules/richaudienceBidAdapter_spec.js | 7 +++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index a6b4202fc91..07de3e40594 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -49,7 +49,10 @@ export const spec = { timeout: config.getConfig('bidderTimeout'), user: raiSetEids(bid), demand: raiGetDemandType(bid), - videoData: raiGetVideoInfo(bid) + videoData: raiGetVideoInfo(bid), + scr_rsl: raiGetResolution(), + cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), + kws: (!utils.isEmpty(bid.params.keywords) ? bid.params.keywords : null) }; REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null) @@ -242,3 +245,11 @@ function renderAd(bid) { window.raParams(raPlayerHB, raOutstreamHBPassback, true); } + +function raiGetResolution() { + let resolution = ''; + if (typeof window.screen != 'undefined') { + resolution = window.screen.width + 'x' + window.screen.height; + } + return resolution; +} diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index 932cdb8f8de..fbf59a0208a 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -39,6 +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;" } }] } diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 90723fb863f..3b9f07b6efc 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -4,7 +4,6 @@ import { spec } from 'modules/richaudienceBidAdapter.js'; import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; describe('Richaudience adapter tests', function () { var DEFAULT_PARAMS_NEW_SIZES = [{ @@ -20,7 +19,8 @@ describe('Richaudience adapter tests', function () { params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site' + supplyType: 'site', + keywords: 'coche=mercedes;coche=audi' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -240,6 +240,9 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); expect(requestContent).to.have.property('timeout').and.to.equal(3000); 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'); }) it('Verify build request to prebid video inestream', function() { From 3e99b0dc100d23d50eba7f10b0c62ad334b1284b Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 3 Feb 2021 04:17:03 -0800 Subject: [PATCH 115/325] Rubicon Analytics: Fire event once gptSlots render (#6241) * Once all gam slots are back fire event * push to cmd queue --- modules/rubiconAnalyticsAdapter.js | 71 +++++++++---------- .../modules/rubiconAnalyticsAdapter_spec.js | 27 ------- 2 files changed, 33 insertions(+), 65 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index af5228b8b92..644a7d24e16 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -458,47 +458,35 @@ function updateRpaCookie() { return decodedRpaCookie; } -const gamEventFunctions = { - 'slotOnload': (auctionId, bid) => { - cache.auctions[auctionId].gamHasRendered[bid.adUnit.adUnitCode] = true; - }, - 'slotRenderEnded': (auctionId, bid, event) => { - if (event.isEmpty) { - cache.auctions[auctionId].gamHasRendered[bid.adUnit.adUnitCode] = true; - } - bid.adUnit.gam = utils.pick(event, [ - // 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, - 'adSlot', () => event.slot.getAdUnitPath(), - 'isSlotEmpty', () => event.isEmpty || undefined - ]); - } -} - function subscribeToGamSlots() { - ['slotOnload', 'slotRenderEnded'].forEach(eventName => { - window.googletag.pubads().addEventListener(eventName, event => { - const isMatchingAdSlot = utils.isAdUnitCodeMatchingSlot(event.slot); - // loop through auctions and adUnits and mark the info - Object.keys(cache.auctions).forEach(auctionId => { - (Object.keys(cache.auctions[auctionId].bids) || []).forEach(bidId => { - let bid = cache.auctions[auctionId].bids[bidId]; - // if this slot matches this bids adUnit, add the adUnit info - if (isMatchingAdSlot(bid.adUnit.adUnitCode)) { - // mark this adUnit as having been rendered by gam - gamEventFunctions[eventName](auctionId, bid, event); - } - }); - // Now if all adUnits have gam rendered, send the payload - 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); + window.googletag.pubads().addEventListener('slotRenderEnded', event => { + const isMatchingAdSlot = utils.isAdUnitCodeMatchingSlot(event.slot); + // loop through auctions and adUnits and mark the info + Object.keys(cache.auctions).forEach(auctionId => { + (Object.keys(cache.auctions[auctionId].bids) || []).forEach(bidId => { + let bid = cache.auctions[auctionId].bids[bidId]; + // if this slot matches this bids adUnit, add the adUnit info + if (isMatchingAdSlot(bid.adUnit.adUnitCode)) { + // mark this adUnit as having been rendered by gam + cache.auctions[auctionId].gamHasRendered[bid.adUnit.adUnitCode] = true; + + bid.adUnit.gam = utils.pick(event, [ + // 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, + 'adSlot', () => event.slot.getAdUnitPath(), + 'isSlotEmpty', () => event.isEmpty || undefined + ]); } }); + // Now if all adUnits have gam rendered, send the payload + 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); + } }); }); } @@ -576,6 +564,13 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { if (!cache.gpt.registered && utils.isGptPubadsDefined()) { subscribeToGamSlots(); 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() { + subscribeToGamSlots(); + }); } break; case BID_REQUESTED: diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 4bb2f6e217c..d8eb4fb0bdc 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1361,7 +1361,6 @@ describe('rubicon analytics adapter', function () { describe('with googletag enabled', function () { let gptSlot0, gptSlot1; let gptSlotRenderEnded0, gptSlotRenderEnded1; - let gptslotOnload0, gptslotOnload1; beforeEach(function () { mockGpt.enable(); gptSlot0 = mockGpt.makeSlot({code: '/19968336/header-bid-tag-0'}); @@ -1375,16 +1374,6 @@ describe('rubicon analytics adapter', function () { lineItemId: 3333 } }; - gptslotOnload0 = { - eventName: 'slotOnload', - params: { - slot: gptSlot0, - isEmpty: false, - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333 - } - }; gptSlot1 = mockGpt.makeSlot({code: '/19968336/header-bid-tag1'}); gptSlotRenderEnded1 = { @@ -1397,16 +1386,6 @@ describe('rubicon analytics adapter', function () { lineItemId: 6666 } }; - gptslotOnload1 = { - eventName: 'slotOnload', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333 - } - }; }); afterEach(function () { @@ -1536,12 +1515,6 @@ describe('rubicon analytics adapter', function () { mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); - expect(server.requests.length).to.equal(0); - - // now emit slotOnload and it should send - mockGpt.emitEvent(gptslotOnload0.eventName, gptslotOnload0.params); - mockGpt.emitEvent(gptslotOnload1.eventName, gptslotOnload1.params); - expect(server.requests.length).to.equal(1); let request = server.requests[0]; let message = JSON.parse(request.requestBody); From d1900a9bb0070880890e0ab60311a8ad29ce60ab Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 3 Feb 2021 04:19:04 -0800 Subject: [PATCH 116/325] New dimension for tracking name of the matching adUnit pattern (#6252) --- modules/rubiconAnalyticsAdapter.js | 6 ++-- .../modules/rubiconAnalyticsAdapter_spec.js | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 644a7d24e16..679e86aa0a0 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -185,7 +185,8 @@ function sendMessage(auctionId, bidWonId) { 'dimensions', 'adserverTargeting', () => stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), 'gam', - 'pbAdSlot' + 'pbAdSlot', + 'pattern' ]); adUnit.bids = []; adUnit.status = 'no-bid'; // default it to be no bid @@ -651,7 +652,8 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { return {adSlot: bid.fpd.context.adServer.adSlot} } }, - 'pbAdSlot', () => utils.deepAccess(bid, 'fpd.context.pbAdSlot') + 'pbAdSlot', () => utils.deepAccess(bid, 'fpd.context.pbAdSlot'), + 'pattern', () => utils.deepAccess(bid, 'fpd.context.aupName') ]) ]); return memo; diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index d8eb4fb0bdc..16d25ec400c 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1669,6 +1669,35 @@ describe('rubicon analytics adapter', function () { expect(timedOutBid).to.not.have.property('bidResponse'); }); + it('should pass aupName as pattern', function () { + let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); + bidRequest.bids[0].fpd = { + context: { + aupName: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' + } + }; + bidRequest.bids[1].fpd = { + context: { + aupName: '1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile' + } + }; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, bidRequest); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + + clock.tick(SEND_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].pattern).to.equal('1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile'); + expect(message.auctions[0].adUnits[1].pattern).to.equal('1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile'); + }); + it('should successfully convert bid price to USD in parseBidResponse', function () { // Set the rates setConfig({ From f704369adf1aece9ad6fa887bd7135e944107f74 Mon Sep 17 00:00:00 2001 From: mefjush Date: Wed, 3 Feb 2021 14:26:47 +0100 Subject: [PATCH 117/325] Adhese Bid Adapter: Per adunit targets (#6256) * adpod category support test * Revert "adpod category support test" --- modules/adheseBidAdapter.js | 13 +++++++++---- test/spec/modules/adheseBidAdapter_spec.js | 14 ++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 80758668a95..5510db62950 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -22,15 +22,19 @@ export const spec = { } const { gdprConsent, refererInfo } = bidderRequest; - const targets = validBidRequests.map(bid => bid.params.data).reduce(mergeTargets, {}); 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 slots = validBidRequests.map(bid => ({ slotname: bidToSlotName(bid) })); + const commonParams = { ...gdprParams, ...refererParams, ...id5Params }; + + const slots = validBidRequests.map(bid => ({ + slotname: bidToSlotName(bid), + parameters: cleanTargets(bid.params.data) + })); const payload = { slots: slots, - parameters: { ...targets, ...gdprParams, ...refererParams, ...id5Params } + parameters: commonParams } const account = getAccount(validBidRequests); @@ -110,7 +114,8 @@ function adResponse(bid, ad) { return bidResponse; } -function mergeTargets(targets, target) { +function cleanTargets(target) { + const targets = {}; if (target) { Object.keys(target).forEach(function (key) { const val = target[key]; diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 4d888db269d..0089a403749 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -74,31 +74,33 @@ describe('AdheseAdapter', function () { it('should include requested slots', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(JSON.parse(req.data).slots).to.deep.include({ 'slotname': '_main_page_-leaderboard' }); + expect(JSON.parse(req.data).slots[0].slotname).to.equal('_main_page_-leaderboard'); }); it('should include all extra bid params', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }) ], bidderRequest); - expect(JSON.parse(req.data).parameters).to.deep.include({ 'ag': [ '25' ] }); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ag': [ '25' ] }); }); - it('should include duplicate bid params once', function () { + it('should assign bid params per slot', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }), bidWithParams({ 'ag': '25', 'ci': 'gent' }) ], bidderRequest); - expect(JSON.parse(req.data).parameters).to.deep.include({'ag': ['25']}).and.to.deep.include({ 'ci': [ 'gent' ] }); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ag': [ '25' ] }).and.not.to.deep.include({ 'ci': [ 'gent' ] }); + expect(JSON.parse(req.data).slots[1].parameters).to.deep.include({ 'ag': [ '25' ] }).and.to.deep.include({ 'ci': [ 'gent' ] }); }); it('should split multiple target values', function () { let req = spec.buildRequests([ bidWithParams({ 'ci': 'london' }), bidWithParams({ 'ci': 'gent' }) ], bidderRequest); - expect(JSON.parse(req.data).parameters).to.deep.include({ 'ci': [ 'london', 'gent' ] }); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ci': [ 'london' ] }); + expect(JSON.parse(req.data).slots[1].parameters).to.deep.include({ 'ci': [ 'gent' ] }); }); it('should filter out empty params', function () { let req = spec.buildRequests([ bidWithParams({ 'aa': [], 'bb': null, 'cc': '', 'dd': [ '', '' ], 'ee': [ 0, 1, null ], 'ff': 0, 'gg': [ 'x', 'y', '' ] }) ], bidderRequest); - let params = JSON.parse(req.data).parameters; + let params = JSON.parse(req.data).slots[0].parameters; expect(params).to.not.have.any.keys('aa', 'bb', 'cc', 'dd'); expect(params).to.deep.include({ 'ee': [ 0, 1 ], 'ff': [ 0 ], 'gg': [ 'x', 'y' ] }); }); From 97e5351c2ed0900557bc09277cce33a4db1a4678 Mon Sep 17 00:00:00 2001 From: ardit-baloku <77985953+ardit-baloku@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:03:46 +0100 Subject: [PATCH 118/325] Gjirafa Bid Adapter: added data object as a param (#6231) * Added data parameter to gjirafaBidAdapter * Updated gjirafaBidAdapter markdown * Added test for gjirafaBidAdapter --- modules/gjirafaBidAdapter.js | 7 ++- modules/gjirafaBidAdapter.md | 62 ++++++++++++++------- test/spec/modules/gjirafaBidAdapter_spec.js | 30 ++++++++-- 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js index fc1232436b4..77589cd9071 100644 --- a/modules/gjirafaBidAdapter.js +++ b/modules/gjirafaBidAdapter.js @@ -31,6 +31,7 @@ export const spec = { let bidderRequestId = ''; let url = ''; let contents = []; + let data = {}; let placements = validBidRequests.map(bidRequest => { if (!propertyId) { propertyId = bidRequest.params.propertyId; } @@ -38,7 +39,8 @@ export const spec = { if (!storageId && bidRequest.params) { storageId = bidRequest.params.storageId || ''; } if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } - if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } + if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } let adUnitId = bidRequest.adUnitCode; let placementId = bidRequest.params.placementId; @@ -61,7 +63,8 @@ export const spec = { url: url, requestid: bidderRequestId, placements: placements, - contents: contents + contents: contents, + data: data } return [{ diff --git a/modules/gjirafaBidAdapter.md b/modules/gjirafaBidAdapter.md index deb06e74a27..fb4960d61f6 100644 --- a/modules/gjirafaBidAdapter.md +++ b/modules/gjirafaBidAdapter.md @@ -1,45 +1,67 @@ # Overview -Module Name: Gjirafa Bidder Adapter Module -Type: Bidder Adapter -Maintainer: drilon@gjirafa.com +Module Name: Gjirafa Bidder Adapter Module + +Type: Bidder Adapter + +Maintainer: arditb@gjirafa.com # Description Gjirafa Bidder Adapter for Prebid.js. # Test Parameters +```js var adUnits = [ { code: 'test-div', mediaTypes: { banner: { - sizes: [[728, 90]] + sizes: [ + [728, 90] + ] } }, - bids: [ - { - bidder: 'gjirafa', - params: { - propertyId: '105227', - placementId: '846841' + bids: [{ + bidder: 'gjirafa', + params: { + propertyId: '105227', //Required + placementId: '846841', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } } } - ] + }] }, { code: 'test-div', mediaTypes: { - video: { + video: { context: 'instream' - } + } }, - bids: [ - { - bidder: 'gjirafa', - params: { - propertyId: '105227', - placementId: '846836' + bids: [{ + bidder: 'gjirafa', + params: { + propertyId: '105227', //Required + placementId: '846836', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } } } - ] + }] } ]; +``` diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js index db9b82e0a10..f0fb01f4398 100644 --- a/test/spec/modules/gjirafaBidAdapter_spec.js +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -34,9 +34,7 @@ describe('gjirafaAdapterTest', () => { it('bidRequest without propertyId or placementId', () => { expect(spec.isBidRequestValid({ bidder: 'gjirafa', - params: { - propertyId: '{propertyId}', - } + params: {} })).to.equal(false); }); }); @@ -46,7 +44,17 @@ describe('gjirafaAdapterTest', () => { 'bidder': 'gjirafa', 'params': { 'propertyId': '{propertyId}', - 'placementId': '{placementId}' + 'placementId': '{placementId}', + 'data': { + 'catalogs': [{ + 'catalogId': 1, + 'items': ['1', '2', '3'] + }], + 'inventory': { + 'category': ['category1', 'category2'], + 'query': ['query'] + } + } }, 'adUnitCode': 'hb-leaderboard', 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', @@ -86,6 +94,20 @@ describe('gjirafaAdapterTest', () => { expect(requestItem.data.placements[0].sizes).to.equal('728x90'); }); }); + + it('bidRequest data param', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach((requestItem) => { + expect(requestItem.data.data).to.exist; + expect(requestItem.data.data.catalogs).to.exist; + expect(requestItem.data.data.inventory).to.exist; + expect(requestItem.data.data.catalogs.length).to.equal(1); + expect(requestItem.data.data.catalogs[0].items.length).to.equal(3); + expect(Object.keys(requestItem.data.data.inventory).length).to.equal(2); + expect(requestItem.data.data.inventory.category.length).to.equal(2); + expect(requestItem.data.data.inventory.query.length).to.equal(1); + }); + }); }); describe('interpretResponse', () => { From f8d42badebf950b7749f46de7ade1ce99a295fbe Mon Sep 17 00:00:00 2001 From: Catalin Ciocov Date: Wed, 3 Feb 2021 17:28:25 +0200 Subject: [PATCH 119/325] Fix a TypeError when message event source is not available (#6224) --- modules/inskinBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index 2a55b5280db..3951e27c870 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -188,7 +188,7 @@ export const spec = { const id = 'ism_tag_' + Math.floor((Math.random() * 10e16)); window[id] = { - plr_AdSlot: e.source.frameElement, + plr_AdSlot: e.source && e.source.frameElement, bidId: e.data.bidId, bidPrice: bidsMap[e.data.bidId].price, serverResponse From 5504f12a3a29dc8f44a147443dca72fc9c214edc Mon Sep 17 00:00:00 2001 From: harpere Date: Wed, 3 Feb 2021 13:10:41 -0500 Subject: [PATCH 120/325] fix broken tests due to "encoded" base64 logic (#6268) --- test/spec/modules/parrableIdSystem_spec.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 6d0748b592d..046ab3a4005 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -80,6 +80,15 @@ describe('Parrable ID System', function() { let logErrorStub; let callbackSpy = sinon.spy(); + let decodeBase64UrlSafe = function (encBase64) { + const DEC = { + '-': '+', + '_': '/', + '.': '=' + }; + return encBase64.replace(/[-_.]/g, (m) => DEC[m]); + } + beforeEach(function() { logErrorStub = sinon.stub(utils, 'logError'); callbackSpy.resetHistory(); @@ -98,7 +107,7 @@ describe('Parrable ID System', function() { let request = server.requests[0]; let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(queryParams.data)); + let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); expect(getIdResult.callback).to.be.a('function'); expect(request.url).to.contain('h.parrable.com'); From eed6db7cfa79e92ff030ca1154c04ce0b818fa96 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Wed, 3 Feb 2021 20:19:45 +0200 Subject: [PATCH 121/325] Adkernel Bid Adapter: stringads alias added (#6262) * Adkernel: stringads alias --- modules/adkernelBidAdapter.js | 2 +- test/spec/modules/adkernelBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 1b238f8d1c4..20ed65fe2e2 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -52,7 +52,7 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { export const spec = { code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad'], + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 0bc14c877ab..4454aa00a71 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -556,7 +556,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(10); + expect(spec.aliases).to.have.lengthOf(11); }); }); From 4d1e906c6a627fc7f249f8e8b9d2f2dd390963ec Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Wed, 3 Feb 2021 14:33:17 -0500 Subject: [PATCH 122/325] Revert "Extended ID permissions supported by bidder (#6112)" (#6269) This reverts commit a926dee9e108ca4b8792ba8992a9bca7c2f42781. --- integrationExamples/gpt/userId_example.html | 1 - modules/prebidServerBidAdapter/index.js | 17 +-- modules/userId/eids.js | 22 ---- modules/userId/index.js | 47 ++----- plugins/eslint/validateImports.js | 3 +- .../modules/prebidServerBidAdapter_spec.js | 46 +++---- test/spec/modules/userId_spec.js | 117 +----------------- 7 files changed, 41 insertions(+), 212 deletions(-) diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 71299a4a6e1..fa9b1e3b5fd 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -236,7 +236,6 @@ }, { name: "sharedId", - // bidders: ["rubicon", "sampleBidders"], // to allow this ID for specific bidders params: { syncTime: 60 // in seconds, default is 24 hours }, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 04aedb63ae6..d878b520c78 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -12,7 +12,6 @@ import includes from 'core-js-pure/features/array/includes.js'; import { S2S_VENDORS } from './config.js'; import { ajax } from '../../src/ajax.js'; import find from 'core-js-pure/features/array/find.js'; -import { getEidPermissions } from '../userId/index.js'; const getConfig = config.getConfig; @@ -460,7 +459,7 @@ export function resetWurlMap() { } const OPEN_RTB_PROTOCOL = { - buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig, requestedBidders) { + buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig) { let imps = []; let aliases = {}; const firstBidRequest = bidRequests[0]; @@ -705,18 +704,6 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'user.ext.eids', bidUserIdAsEids); } - const eidPermissions = getEidPermissions(); - if (utils.isArray(eidPermissions) && eidPermissions.length > 0) { - if (requestedBidders && utils.isArray(requestedBidders)) { - eidPermissions.forEach(i => { - if (i.bidders) { - i.bidders = i.bidders.filter(bidder => requestedBidders.includes(bidder)) - } - }); - } - utils.deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); - } - if (bidRequests) { if (firstBidRequest.gdprConsent) { // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module @@ -975,7 +962,7 @@ export function PrebidServer() { queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); } - const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig, requestedBidders); + const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig); const requestJson = request && JSON.stringify(request); if (request && requestJson) { ajax( diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 80750ccaae8..27665de4136 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -224,25 +224,3 @@ export function createEidsArray(bidRequestUserId) { } return eids; } - -/** - * @param {SubmoduleContainer[]} submodules - */ -export function buildEidPermissions(submodules) { - let eidPermissions = []; - submodules.filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length) - .forEach(i => { - Object.keys(i.idObj).forEach(key => { - if (utils.deepAccess(i, 'config.bidders') && Array.isArray(i.config.bidders) && - utils.deepAccess(USER_IDS_CONFIG, key + '.source')) { - eidPermissions.push( - { - source: USER_IDS_CONFIG[key].source, - bidders: i.config.bidders - } - ); - } - }); - }); - return eidPermissions; -} diff --git a/modules/userId/index.js b/modules/userId/index.js index 8f7d2a36699..9294311de69 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -136,7 +136,7 @@ import { getGlobal } from '../../src/prebidGlobal.js'; import { gdprDataHandler } from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; import { module, hook } from '../../src/hook.js'; -import { createEidsArray, buildEidPermissions } from './eids.js'; +import { createEidsArray } from './eids.js'; import { getCoreStorageManager } from '../../src/storageManager.js'; const MODULE_NAME = 'User ID'; @@ -214,10 +214,6 @@ export function setStoredValue(submodule, value) { } } -export function getEidPermissions() { - return buildEidPermissions(initializedSubmodules); -} - /** * @param {SubmoduleStorage} storage * @param {String|undefined} key optional key of the value @@ -437,26 +433,6 @@ function getCombinedSubmoduleIds(submodules) { return combinedSubmoduleIds; } -/** - * This function will create a combined object for bidder with allowed subModule Ids - * @param {SubmoduleContainer[]} submodules - * @param {string} bidder - */ -function getCombinedSubmoduleIdsForBidder(submodules, bidder) { - if (!Array.isArray(submodules) || !submodules.length || !bidder) { - return {}; - } - return submodules - .filter(i => !i.config.bidders || !utils.isArray(i.config.bidders) || i.config.bidders.includes(bidder)) - .filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length) - .reduce((carry, i) => { - Object.keys(i.idObj).forEach(key => { - carry[key] = i.idObj[key]; - }); - return carry; - }, {}); -} - /** * @param {AdUnit[]} adUnits * @param {SubmoduleContainer[]} submodules @@ -465,18 +441,19 @@ function addIdDataToAdUnitBids(adUnits, submodules) { if ([adUnits].some(i => !Array.isArray(i) || !i.length)) { return; } - adUnits.forEach(adUnit => { - if (adUnit.bids && utils.isArray(adUnit.bids)) { - adUnit.bids.forEach(bid => { - const combinedSubmoduleIds = getCombinedSubmoduleIdsForBidder(submodules, bid.bidder); - if (Object.keys(combinedSubmoduleIds).length) { + const combinedSubmoduleIds = getCombinedSubmoduleIds(submodules); + const combinedSubmoduleIdsAsEids = createEidsArray(combinedSubmoduleIds); + if (Object.keys(combinedSubmoduleIds).length) { + adUnits.forEach(adUnit => { + if (adUnit.bids && utils.isArray(adUnit.bids)) { + adUnit.bids.forEach(bid => { // create a User ID object on the bid, bid.userId = combinedSubmoduleIds; - bid.userIdAsEids = createEidsArray(combinedSubmoduleIds); - } - }); - } - }); + bid.userIdAsEids = combinedSubmoduleIdsAsEids; + }); + } + }); + } } /** diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index 53f4ace8381..a39bf9b26d5 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -26,8 +26,7 @@ function flagErrors(context, node, importPath) { if ( path.dirname(absImportPath) === absModulePath || ( absImportPath.startsWith(absModulePath) && - path.basename(absImportPath) === 'index.js' && - path.basename(absFileDir) !== 'prebidServerBidAdapter' + path.basename(absImportPath) === 'index.js' ) ) { context.report(node, `import "${importPath}" cannot require module entry point`); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 4652bbb63f7..f17cd3ab14f 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -953,15 +953,17 @@ describe('S2S Adapter', function () { adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.include({ - aliases: { - brealtime: 'appnexus' - }, - auctiontimestamp: 1510852447530, - targeting: { - includebidderkeys: false, - includewinners: true + + expect(requestBid.ext).to.deep.equal({ + prebid: { + aliases: { + brealtime: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true + } } }); }); @@ -983,15 +985,17 @@ describe('S2S Adapter', function () { adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.include({ - aliases: { - [alias]: 'appnexus' - }, - auctiontimestamp: 1510852447530, - targeting: { - includebidderkeys: false, - includewinners: true + + expect(requestBid.ext).to.deep.equal({ + prebid: { + aliases: { + [alias]: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true + } } }); }); @@ -1372,7 +1376,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.include({ + expect(requestBid.ext.prebid).to.deep.equal({ auctiontimestamp: 1510852447530, foo: 'bar', targeting: { @@ -1406,7 +1410,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.include({ + expect(requestBid.ext.prebid).to.deep.equal({ auctiontimestamp: 1510852447530, targeting: { includewinners: false, @@ -1442,7 +1446,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.include({ + expect(requestBid.ext.prebid).to.deep.equal({ auctiontimestamp: 1510852447530, cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 1ae023ae947..ed592c0cba5 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -2,7 +2,6 @@ import { attachIdSystem, auctionDelay, coreStorage, - getEidPermissions, init, requestBidsHook, setStoredConsentData, @@ -71,7 +70,7 @@ describe('User ID', function () { code, mediaTypes: {banner: {}, native: {}}, sizes: [[300, 200], [300, 600]], - bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}, {bidder: 'anotherSampleBidder', params: {placementId: 'banner-only-bidder'}}] + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] }; } @@ -1197,120 +1196,6 @@ describe('User ID', function () { }, {adUnits}); }); - it('eidPermissions fun with bidders', function (done) { - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test222', - 'ts': 1590525289611 - }), (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [ - { - name: 'sharedId', - bidders: [ - 'sampleBidder' - ], - storage: { - type: 'cookie', - name: 'sharedid', - expires: 28 - } - } - ] - } - }); - - requestBidsHook(function () { - const eidPermissions = getEidPermissions(); - expect(eidPermissions).to.deep.equal( - [ - {source: 'sharedid.org', bidders: ['sampleBidder']} - ] - ); - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - if (bid.bidder === 'sampleBidder') { - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid.id).to.equal('test222'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', - uids: [ - { - id: 'test222', - atype: 1, - ext: { - third: 'test222' - } - } - ] - }); - } - if (bid.bidder === 'anotherSampleBidder') { - expect(bid).to.not.have.deep.nested.property('userId.sharedid'); - expect(bid).to.not.have.property('userIdAsEids'); - } - }); - }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('eidPermissions fun without bidders', function (done) { - coreStorage.setCookie('sharedid', JSON.stringify({ - 'id': 'test222', - 'ts': 1590525289611 - }), (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [ - { - name: 'sharedId', - storage: { - type: 'cookie', - name: 'sharedid', - expires: 28 - } - } - ] - } - }); - - requestBidsHook(function () { - const eidPermissions = getEidPermissions(); - expect(eidPermissions).to.deep.equal( - [] - ); - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.sharedid'); - expect(bid.userId.sharedid.id).to.equal('test222'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'sharedid.org', - uids: [ - { - id: 'test222', - atype: 1, - ext: { - third: 'test222' - } - }] - }); - }); - }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - it('test hook from pubProvidedId config params', function (done) { setSubmoduleRegistry([pubProvidedIdSubmodule]); init(config); From a4c6efae61ca4966d0f8eacfd700c83c45cde0f1 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 3 Feb 2021 15:14:49 -0500 Subject: [PATCH 123/325] Prebid 4.25.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d23415dca0..ceea005dfe6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.25.0-pre", + "version": "4.25.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 6cbb15e0248ed1e887ab2131b03e670443632cff Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 3 Feb 2021 15:51:29 -0500 Subject: [PATCH 124/325] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ceea005dfe6..d8b6af04379 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.25.0", + "version": "4.26.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 28cbd2940a8cebb75987527168220e0a0d6b5a80 Mon Sep 17 00:00:00 2001 From: Skylinar <53079123+Skylinar@users.noreply.github.com> Date: Thu, 4 Feb 2021 07:34:34 +0100 Subject: [PATCH 125/325] smartxBidAdapter: new Feature - Made Out-Stream Player configurable (#6239) * 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 Co-authored-by: smartclip AdTechnology Co-authored-by: Gino Cirlini --- modules/smartxBidAdapter.js | 50 ++++++++++++++++++---- modules/smartxBidAdapter.md | 34 ++++++++++----- test/spec/modules/smartxBidAdapter_spec.js | 2 +- 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 4409e4e9dfb..804b25d1afc 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -328,16 +328,49 @@ export const spec = { } function createOutstreamScript(bid) { - // const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); + // 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 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'); - const elementId = bid.adUnitCode; + let smartPlayObj = { - minAdWidth: 290, - maxAdWidth: 900, - elementLocator: { - allowInViewport: false, - minimumElementWidth: 290, - scanPixelsBelowViewport: 800 + minAdWidth: confMinAdWidth, + maxAdWidth: confMaxAdWidth, + headerText: confHeaderText, + skipOffset: confSkipOffset, + behaviourMatrix: { + init: { + 'collapsed': initCollapsed + }, + complete: { + 'collapsed': completeCollapsed + } + }, + environmentVars: { + desiredBitrate: confDesiredBitrate, }, onStartCallback: function (m, n) { try { @@ -351,6 +384,7 @@ function createOutstreamScript(bid) { }, onEndCallback: function (m, n) { try { + scPrebidClose(n, completeCollapsed); // for SmartPlay 4.12 window.sc_smartIntxtEnd(n); } catch (f) {} }, diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index a53af839e2b..082a36f3dde 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -29,11 +29,18 @@ This adapter requires setup and approval from the smartclip team. publisherId: '11986', siteId: '22860', bidfloor: 0.3, - bidfloorcur: "EUR", + bidfloorcur: 'EUR', at: 2, - cur: ["EUR"], + cur: ['EUR'], outstream_options: { - slot: 'video1' + slot: 'video1', + minAdWidth: 290, + maxAdWidth: 900, + headerText: '', + skipOffset: 0, + startOpen: 'true', + endingScreen: 'true', + desiredBitrate: 1600, }, } }], @@ -57,11 +64,18 @@ This adapter requires setup and approval from the smartclip team. publisherId: '11986', siteId: '22860', bidfloor: 0.3, - bidfloorcur: "EUR", + bidfloorcur: 'EUR', at: 2, - cur: ["EUR"], + cur: ['EUR'], outstream_options: { - slot: 'video1' + slot: 'video1', + minAdWidth: 290, + maxAdWidth: 900, + headerText: '', + skipOffset: 0, + startOpen: 'true', + endingScreen: 'true', + desiredBitrate: 1600, }, user: { data: [{ @@ -104,9 +118,9 @@ This adapter requires setup and approval from the smartclip team. publisherId: '11986', siteId: '22860', bidfloor: 0.3, - bidfloorcur: "EUR", + bidfloorcur: 'EUR', at: 2, - cur: ["EUR"] + cur: ['EUR'] } }], }]; @@ -129,9 +143,9 @@ This adapter requires setup and approval from the smartclip team. publisherId: '11986', siteId: '22860', bidfloor: 0.3, - bidfloorcur: "EUR", + bidfloorcur: 'EUR', at: 2, - cur: ["EUR"], + cur: ['EUR'], user: { data: [{ id: 'emq', diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index efc6abcc5fa..82c6642bd74 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -493,7 +493,7 @@ describe('The smartx adapter', function () { }; }); - it('should attempt to insert the EASI script', function () { + it('should attempt to insert the script', function () { var scriptTag; sinon.stub(window.document, 'getElementById').returns({ appendChild: sinon.stub().callsFake(function (script) { From 11a925d5fb13c48b369155ff4a33ed397b3c5c66 Mon Sep 17 00:00:00 2001 From: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Date: Wed, 3 Feb 2021 22:47:02 -0800 Subject: [PATCH 126/325] Idl1 (#6242) * Renaming idLibrary to idImportLibrary * Renaming idLibrary to idImportLibrary Co-authored-by: skocheri --- modules/{idLibrary.js => idImportLibrary.js} | 2 +- modules/{idLibrary.md => idImportLibrary.md} | 4 ++-- .../{idLibrary_spec.js => idImportLibrary_spec.js} | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) rename modules/{idLibrary.js => idImportLibrary.js} (98%) rename modules/{idLibrary.md => idImportLibrary.md} (94%) rename test/spec/modules/{idLibrary_spec.js => idImportLibrary_spec.js} (82%) diff --git a/modules/idLibrary.js b/modules/idImportLibrary.js similarity index 98% rename from modules/idLibrary.js rename to modules/idImportLibrary.js index 0d8616c3f88..2a3a86cf270 100644 --- a/modules/idLibrary.js +++ b/modules/idImportLibrary.js @@ -240,4 +240,4 @@ export function setConfig(config) { associateIds(); } -config.getConfig('idLibrary', config => setConfig(config.idLibrary)); +config.getConfig('idImportLibrary', config => setConfig(config.idImportLibrary)); diff --git a/modules/idLibrary.md b/modules/idImportLibrary.md similarity index 94% rename from modules/idLibrary.md rename to modules/idImportLibrary.md index 28d40c389f3..3dd78ee25d8 100644 --- a/modules/idLibrary.md +++ b/modules/idImportLibrary.md @@ -1,4 +1,4 @@ -# ID Library +# ID Import Library ## Configuration Options @@ -13,7 +13,7 @@ ```javascript pbjs.setConfig({ - idLibrary: { + idImportLibrary: { target: 'username', url: 'https://example.com', debounce: 250, diff --git a/test/spec/modules/idLibrary_spec.js b/test/spec/modules/idImportLibrary_spec.js similarity index 82% rename from test/spec/modules/idLibrary_spec.js rename to test/spec/modules/idImportLibrary_spec.js index 682c2df1e44..699c2c43a94 100644 --- a/test/spec/modules/idLibrary_spec.js +++ b/test/spec/modules/idImportLibrary_spec.js @@ -1,5 +1,5 @@ import * as utils from 'src/utils.js'; -import * as idlibrary from 'modules/idLibrary.js'; +import * as idImportlibrary from 'modules/idImportLibrary.js'; var expect = require('chai').expect; @@ -20,7 +20,7 @@ describe('currency', function () { utils.logInfo.restore(); utils.logError.restore(); fakeCurrencyFileServer.restore(); - idlibrary.setConfig({}); + idImportlibrary.setConfig({}); }); describe('setConfig', function () { @@ -35,26 +35,26 @@ describe('currency', function () { }); it('results when no config available', function () { - idlibrary.setConfig({}); + idImportlibrary.setConfig({}); sinon.assert.called(utils.logError); }); it('results with config available', function () { - idlibrary.setConfig({ 'url': 'URL' }); + idImportlibrary.setConfig({ 'url': 'URL' }); sinon.assert.called(utils.logInfo); }); it('results with config default debounce ', function () { let config = { 'url': 'URL' } - idlibrary.setConfig(config); + idImportlibrary.setConfig(config); expect(config.debounce).to.be.equal(250); }); it('results with config default fullscan ', function () { let config = { 'url': 'URL' } - idlibrary.setConfig(config); + idImportlibrary.setConfig(config); expect(config.fullscan).to.be.equal(false); }); it('results with config fullscan ', function () { let config = { 'url': 'URL', 'fullscan': true } - idlibrary.setConfig(config); + idImportlibrary.setConfig(config); expect(config.fullscan).to.be.equal(true); }); }); From 4e1be70d08f2b8e9c038abc1f74c486289f7182f Mon Sep 17 00:00:00 2001 From: Nepomuk Seiler Date: Thu, 4 Feb 2021 08:02:27 +0100 Subject: [PATCH 127/325] Add the trade desk gvlid (#6263) UnifiedId only works with a vendor exception in the gdpr enforcement module. However this is not okay as the unifiedId matching endpoint doesn't take the consent into account and cookies are being dropped even without consent. --- modules/unifiedIdSystem.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/unifiedIdSystem.js b/modules/unifiedIdSystem.js index 3db4003c424..bc033f37992 100644 --- a/modules/unifiedIdSystem.js +++ b/modules/unifiedIdSystem.js @@ -18,6 +18,10 @@ export const unifiedIdSubmodule = { * @type {string} */ name: MODULE_NAME, + /** + * required for the gdpr enforcement module + */ + gvlid: 21, /** * decode the stored id value for passing to bid requests * @function From cca20295cd6d217422e0af2c5ce311523a0a9cf6 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 4 Feb 2021 03:47:15 -0800 Subject: [PATCH 128/325] pick up wrapper family detail (#6272) --- modules/rubiconAnalyticsAdapter.js | 3 ++- test/spec/modules/rubiconAnalyticsAdapter_spec.js | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 679e86aa0a0..f216cbd6235 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -167,9 +167,10 @@ function sendMessage(auctionId, bidWonId) { referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), channel: 'web', }; - if (rubiConf.wrapperName || rubiConf.rule_name) { + if (rubiConf.wrapperName) { message.wrapper = { name: rubiConf.wrapperName, + family: rubiConf.wrapperFamily, rule: rubiConf.rule_name } } diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 16d25ec400c..71e5446ed06 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1754,7 +1754,8 @@ describe('rubicon analytics adapter', function () { describe('wrapper details passed in', () => { it('should correctly pass in the wrapper details if provided', () => { config.setConfig({rubicon: { - wrapperName: '1001_wrapperName', + wrapperName: '1001_wrapperName_exp.4', + wrapperFamily: '1001_wrapperName', rule_name: 'na-mobile' }}); @@ -1771,7 +1772,8 @@ describe('rubicon analytics adapter', function () { const request = server.requests[0]; const message = JSON.parse(request.requestBody); expect(message.wrapper).to.deep.equal({ - name: '1001_wrapperName', + name: '1001_wrapperName_exp.4', + family: '1001_wrapperName', rule: 'na-mobile' }); From da13bdaa09b1725519e38cecaf55d75270c6fb2c Mon Sep 17 00:00:00 2001 From: Adprime <64427228+Adprime@users.noreply.github.com> Date: Thu, 4 Feb 2021 16:24:51 -0500 Subject: [PATCH 129/325] Add user sync (#6244) * initial * fix * remove redundant language mod, use player sizes in video traff * test modify * fix * Adding Tests * add keywords param * log * log * log * fix * add idl * add idl * fix test * lint * lint * fix * lint * lint * lint * lint * add sync * fix Co-authored-by: Aigolkin1991 Co-authored-by: Aiholkin --- modules/adprimeBidAdapter.js | 20 ++++++++++++++++++++ test/spec/modules/adprimeBidAdapter_spec.js | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js index 50303b82979..12d0410a821 100644 --- a/modules/adprimeBidAdapter.js +++ b/modules/adprimeBidAdapter.js @@ -4,6 +4,7 @@ import * as utils from '../src/utils.js'; const BIDDER_CODE = 'adprime'; const AD_URL = 'https://delta.adprime.com/?c=o&m=multi'; +const SYNC_URL = 'https://delta.adprime.com/?c=rtb&m=sync'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -104,6 +105,25 @@ export const spec = { } return response; }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncUrl = SYNC_URL + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + return [{ + type: 'image', + url: syncUrl + }]; + } + }; registerBidder(spec); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index fe05634baae..8627941dc80 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -284,4 +284,14 @@ describe('AdprimebBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://delta.adprime.com/?c=rtb&m=sync'); + }); + }); }); From 1cd714f53b48a327c5b134915c965ca982638357 Mon Sep 17 00:00:00 2001 From: shikharsharma-zeotap Date: Fri, 5 Feb 2021 03:01:29 +0530 Subject: [PATCH 130/325] Zeotap id plus gvlid (#6260) * Add gvlid for ZeotapIdPlus module * Pass gvlid and module name to storage manager * add testcases to zeotapIdPlusIdSystem * remove unwanted code --- modules/zeotapIdPlusIdSystem.js | 20 ++++-- .../spec/modules/zeotapIdPlusIdSystem_spec.js | 64 ++++++++++++++++++- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js index d800286b00e..8f26cc827d6 100644 --- a/modules/zeotapIdPlusIdSystem.js +++ b/modules/zeotapIdPlusIdSystem.js @@ -9,23 +9,35 @@ import {submodule} from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; -export const storage = getStorageManager(); +const ZEOTAP_VENDOR_ID = 301; +const ZEOTAP_MODULE_NAME = 'zeotapIdPlus'; function readCookie() { - return storage.cookiesAreEnabled ? storage.getCookie(ZEOTAP_COOKIE_NAME) : null; + return storage.cookiesAreEnabled() ? storage.getCookie(ZEOTAP_COOKIE_NAME) : null; } function readFromLocalStorage() { - return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(ZEOTAP_COOKIE_NAME) : null; + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(ZEOTAP_COOKIE_NAME) : null; } +export function getStorage() { + return getStorageManager(ZEOTAP_VENDOR_ID, ZEOTAP_MODULE_NAME); +} + +export const storage = getStorage(); + /** @type {Submodule} */ export const zeotapIdPlusSubmodule = { /** * used to link submodule with config * @type {string} */ - name: 'zeotapIdPlus', + name: ZEOTAP_MODULE_NAME, + /** + * Vendor ID of Zeotap + * @type {Number} + */ + gvlid: ZEOTAP_VENDOR_ID, /** * decode the stored id value for passing to bid requests * @function diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 4f9e691f12e..9de6fa843bc 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -2,7 +2,8 @@ import { expect } from 'chai'; import find from 'core-js-pure/features/array/find.js'; import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; -import { storage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; +import { storage, getStorage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; +import * as storageManager from 'src/storageManager.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_COOKIE = 'THIS-IS-A-DUMMY-COOKIE'; @@ -43,6 +44,67 @@ function unsetLocalStorage() { } describe('Zeotap ID System', function() { + describe('Zeotap Module invokes StorageManager with appropriate arguments', function() { + let getStorageManagerSpy; + + beforeEach(function() { + getStorageManagerSpy = sinon.spy(storageManager, 'getStorageManager'); + }); + + it('when a stored Zeotap ID exists it is added to bids', function() { + let store = getStorage(); + expect(getStorageManagerSpy.calledOnce).to.be.true; + sinon.assert.calledWith(getStorageManagerSpy, 301, 'zeotapIdPlus'); + }); + }); + + describe('test method: getId calls storage methods to fetch ID', function() { + let cookiesAreEnabledStub; + let getCookieStub; + let localStorageIsEnabledStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(() => { + storage.cookiesAreEnabled.restore(); + storage.getCookie.restore(); + storage.localStorageIsEnabled.restore(); + storage.getDataFromLocalStorage.restore(); + unsetCookie(); + unsetLocalStorage(); + }); + + it('should check if cookies are enabled', function() { + let id = zeotapIdPlusSubmodule.getId(); + expect(cookiesAreEnabledStub.calledOnce).to.be.true; + }); + + it('should call getCookie if cookies are enabled', function() { + cookiesAreEnabledStub.returns(true); + let id = zeotapIdPlusSubmodule.getId(); + expect(cookiesAreEnabledStub.calledOnce).to.be.true; + expect(getCookieStub.calledOnce).to.be.true; + sinon.assert.calledWith(getCookieStub, 'IDP'); + }); + + it('should check for localStorage if cookies are disabled', function() { + cookiesAreEnabledStub.returns(false); + localStorageIsEnabledStub.returns(true) + let id = zeotapIdPlusSubmodule.getId(); + expect(cookiesAreEnabledStub.calledOnce).to.be.true; + expect(getCookieStub.called).to.be.false; + expect(localStorageIsEnabledStub.calledOnce).to.be.true; + expect(getDataFromLocalStorageStub.calledOnce).to.be.true; + sinon.assert.calledWith(getDataFromLocalStorageStub, 'IDP'); + }); + }); + describe('test method: getId', function() { afterEach(() => { unsetCookie(); From da1a6e9b284a088439b0b5442fcb252dad42d585 Mon Sep 17 00:00:00 2001 From: nyakove <43004249+nyakove@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:39:00 +0200 Subject: [PATCH 131/325] adWMG adapter: add new parameter, fix minor bugs (#6265) * Support floorCPM parameter, fix some minor bugs * fix space-in-parens circleci error * example fix * clean usersync URL * spaces * spaces * add new unit tests, compatibility with IE11 * remove logInfo Co-authored-by: Mikhail Dykun --- modules/adWMGBidAdapter.js | 8 +++-- modules/adWMGBidAdapter.md | 9 ++--- test/spec/modules/adWMGBidAdapter_spec.js | 40 +++++++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js index 3a0a8a22274..87c40db51e6 100644 --- a/modules/adWMGBidAdapter.js +++ b/modules/adWMGBidAdapter.js @@ -113,14 +113,18 @@ export const spec = { return bidResponses; }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - if (gdprConsent) { + if (gdprConsent && SYNC_ENDPOINT.indexOf('gdpr') === -1) { SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); } - if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (gdprConsent && typeof gdprConsent.consentString === 'string' && SYNC_ENDPOINT.indexOf('gdpr_consent') === -1) { SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'gdpr_consent', gdprConsent.consentString); } + if (SYNC_ENDPOINT.slice(-1) === '&') { + SYNC_ENDPOINT = SYNC_ENDPOINT.slice(0, -1); + } + /* if (uspConsent) { SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'us_privacy', uspConsent); } */ diff --git a/modules/adWMGBidAdapter.md b/modules/adWMGBidAdapter.md index 8c277b803db..ec5541e6168 100644 --- a/modules/adWMGBidAdapter.md +++ b/modules/adWMGBidAdapter.md @@ -12,10 +12,11 @@ Module that connects to adWMG demand sources to fetch bids. Supports 'banner' ad # Bid Parameters -| Key | Required | Example | Description | -| --- | -------- | ------- | ----------- | -| `publisherId` | yes | `'5cebea3c9eea646c7b623d5e'` | publisher ID from WMG Dashboard | -| `IABCategories` | no | `['IAB1', 'IAB5']` | IAB ad categories for adUnit | +| Key | Required | Example | Description | +| --------------- | -------- | -----------------------------| ------------------------------- | +| `publisherId` | yes | `'5cebea3c9eea646c7b623d5e'` | publisher ID from WMG Dashboard | +| `IABCategories` | no | `['IAB1', 'IAB5']` | IAB ad categories for adUnit | +| `floorCPM` | no | `0.5` | Floor price for adUnit | # Test Parameters diff --git a/test/spec/modules/adWMGBidAdapter_spec.js b/test/spec/modules/adWMGBidAdapter_spec.js index 5c2364d454c..1f881897fd8 100644 --- a/test/spec/modules/adWMGBidAdapter_spec.js +++ b/test/spec/modules/adWMGBidAdapter_spec.js @@ -288,5 +288,45 @@ describe('adWMGBidAdapter', function () { expect(syncs[0].url).includes('gdpr=1'); expect(syncs[0].url).includes(`gdpr_consent=${gdprConsent.consentString}`); }); + + it('should not add GDPR consent params twice', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const gdprConsent2 = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_7_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const serverResponse = {}; + let syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent); + syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent2); + expect(syncs[0].url.match(/gdpr/g).length).to.equal(2); // gdpr + gdpr_consent + expect(syncs[0].url.match(/gdpr_consent/g).length).to.equal(1); + }); + + it('should delete \'&\' symbol at the end of usersync URL', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const serverResponse = {}; + let syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent); + expect(syncs[0].url.slice(-1)).to.not.equal('&'); + }); }); }); From 0a333497fe3d2cff6be93cb6cc465eaef3eb23a3 Mon Sep 17 00:00:00 2001 From: Ian Flournoy Date: Thu, 4 Feb 2021 17:02:02 -0500 Subject: [PATCH 132/325] [ParrableIdSystem] Supply iframe state to backend (#6278) * Add iframe detection * Remove forgotten .only Co-authored-by: Victor --- modules/parrableIdSystem.js | 3 ++- test/spec/modules/parrableIdSystem_spec.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 9aa2b251f2c..32543e3863b 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -187,7 +187,8 @@ function fetchId(configParams) { const data = { eid, trackers: configParams.partner.split(','), - url: refererInfo.referer + url: refererInfo.referer, + isIframe: utils.inIframe(), }; const searchParams = { diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 046ab3a4005..f4f485affe9 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -116,7 +116,8 @@ describe('Parrable ID System', function() { expect(data).to.deep.equal({ eid: P_COOKIE_EID, trackers: P_CONFIG_MOCK.params.partner.split(','), - url: getRefererInfo().referer + url: getRefererInfo().referer, + isIframe: true }); server.requests[0].respond(200, From 8f776608ac1adcd2f909db78cdd67c73f8fa0515 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Fri, 5 Feb 2021 11:48:58 -0800 Subject: [PATCH 133/325] Bid Viewability Module (#6206) * introducing a new event, bidViewable * new module: bidViewability * details in bidViewability.md --- modules/bidViewability.js | 97 +++++++ modules/bidViewability.md | 49 ++++ src/adapterManager.js | 4 + src/constants.json | 3 +- test/spec/modules/bidViewability_spec.js | 297 +++++++++++++++++++++ test/spec/unit/core/adapterManager_spec.js | 32 +++ 6 files changed, 481 insertions(+), 1 deletion(-) create mode 100644 modules/bidViewability.js create mode 100644 modules/bidViewability.md create mode 100644 test/spec/modules/bidViewability_spec.js diff --git a/modules/bidViewability.js b/modules/bidViewability.js new file mode 100644 index 00000000000..c3b72cda8d4 --- /dev/null +++ b/modules/bidViewability.js @@ -0,0 +1,97 @@ +// This module, when included, will trigger a BID_VIEWABLE event which can be consumed by Bidders and Analytics adapters +// GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent +// Does not work with other than GPT integration + +import { config } from '../src/config.js'; +import * as events from '../src/events.js'; +import { EVENTS } from '../src/constants.json'; +import { logWarn, isFn, triggerPixel } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import adapterManager, { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import find from 'core-js-pure/features/array/find.js'; + +const MODULE_NAME = 'bidViewability'; +const CONFIG_ENABLED = 'enabled'; +const CONFIG_FIRE_PIXELS = 'firePixels'; +const CONFIG_CUSTOM_MATCH = 'customMatchFunction'; +const BID_VURL_ARRAY = 'vurls'; +const GPT_IMPRESSION_VIEWABLE_EVENT = 'impressionViewable'; + +export let isBidAdUnitCodeMatchingSlot = (bid, slot) => { + return (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode); +} + +export let getMatchingWinningBidForGPTSlot = (globalModuleConfig, slot) => { + return find(getGlobal().getAllWinningBids(), + // supports custom match function from config + bid => isFn(globalModuleConfig[CONFIG_CUSTOM_MATCH]) + ? globalModuleConfig[CONFIG_CUSTOM_MATCH](bid, slot) + : isBidAdUnitCodeMatchingSlot(bid, slot) + ) || null; +}; + +export let fireViewabilityPixels = (globalModuleConfig, bid) => { + if (globalModuleConfig[CONFIG_FIRE_PIXELS] === true && bid.hasOwnProperty(BID_VURL_ARRAY)) { + let queryParams = {}; + + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } + } + + const uspConsent = uspDataHandler.getConsentData(); + if (uspConsent) { queryParams.us_privacy = uspConsent; } + + bid[BID_VURL_ARRAY].forEach(url => { + // add '?' if not present in URL + if (Object.keys(queryParams).length > 0 && url.indexOf('?') === -1) { + url += '?'; + } + // append all query params, `&key=urlEncoded(value)` + url += Object.keys(queryParams).reduce((prev, key) => prev += `&${key}=${encodeURIComponent(queryParams[key])}`, ''); + triggerPixel(url) + }); + } +}; + +export let logWinningBidNotFound = (slot) => { + logWarn(`bid details could not be found for ${slot.getSlotElementId()}, probable reasons: a non-prebid bid is served OR check the prebid.AdUnit.code to GPT.AdSlot relation.`); +}; + +export let impressionViewableHandler = (globalModuleConfig, slot, event) => { + let respectiveBid = getMatchingWinningBidForGPTSlot(globalModuleConfig, slot); + if (respectiveBid === null) { + logWinningBidNotFound(slot); + } else { + // if config is enabled AND VURL array is present then execute each pixel + fireViewabilityPixels(globalModuleConfig, respectiveBid); + // trigger respective bidder's onBidViewable handler + adapterManager.callBidViewableBidder(respectiveBid.bidder, respectiveBid); + // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels + events.emit(EVENTS.BID_VIEWABLE, respectiveBid); + } +}; + +export let init = () => { + events.on(EVENTS.AUCTION_INIT, () => { + // read the config for the module + const globalModuleConfig = config.getConfig(MODULE_NAME) || {}; + // do nothing if module-config.enabled is not set to true + // this way we are adding a way for bidders to know (using pbjs.getConfig('bidViewability').enabled === true) whether this module is added in build and is enabled + if (globalModuleConfig[CONFIG_ENABLED] !== true) { + return; + } + // add the GPT event listener + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().addEventListener(GPT_IMPRESSION_VIEWABLE_EVENT, function(event) { + impressionViewableHandler(globalModuleConfig, event.slot, event); + }); + }); + }); +} + +init() diff --git a/modules/bidViewability.md b/modules/bidViewability.md new file mode 100644 index 00000000000..78a1539fb1a --- /dev/null +++ b/modules/bidViewability.md @@ -0,0 +1,49 @@ +# Overview + +Module Name: bidViewability + +Purpose: Track when a bid is viewable + +Maintainer: harshad.mane@pubmatic.com + +# Description +- This module, when included, will trigger a BID_VIEWABLE event which can be consumed by Analytics adapters, bidders will need to implement `onBidViewable` method to capture this event +- Bidderes can check if this module is part of the final build and whether it is enabled or not by accessing ```pbjs.getConfig('bidViewability')``` +- GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent . This event is fired when an impression becomes viewable, according to the Active View criteria. +Refer: https://support.google.com/admanager/answer/4524488 +- The module does not work with adserver other than GAM with GPT integration +- Logic used to find a matching pbjs-bid for a GPT slot is ``` (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ``` this logic can be changed by using param ```customMatchFunction``` +- When a rendered PBJS bid is viewable the module will trigger BID_VIEWABLE event, which can be consumed by bidders and analytics adapters +- For the viewable bid if ```bid.vurls type array``` param is and module config ``` firePixels: true ``` is set then the URLs mentioned in bid.vurls will be executed. Please note that GDPR and USP related parameters will be added to the given URLs + +# Params +- enabled [required] [type: boolean, default: false], when set to true, the module will emit BID_VIEWABLE when applicable +- firePixels [optional] [type: boolean], when set to true, will fire the urls mentioned in bid.vurls which should be array of urls +- customMatchFunction [optional] [type: function(bid, slot)], when passed this function will be used to `find` the matching winning bid for the GPT slot. Default value is ` (bid, slot) => (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ` + +# Example of consuming BID_VIEWABLE event +``` + pbjs.onEvent('bidViewable', function(bid){ + console.log('got bid details in bidViewable event', bid); + }); + +``` + +# Example of using config +``` + pbjs.setConfig({ + bidViewability: { + enabled: true, + firePixels: true, + customMatchFunction: function(bid, slot){ + console.log('using custom match function....'); + return bid.adUnitCode === slot.getAdUnitPath(); + } + } + }); +``` + +# Please Note: +- Doesn't seems to work with Instream Video, https://docs.prebid.org/dev-docs/examples/instream-banner-mix.html as GPT's impressionViewable event is not triggered for instream-video-creative +- Works with Banner, Outsteam, Native creatives + diff --git a/src/adapterManager.js b/src/adapterManager.js index 9c3ef7ac1d1..cb84607e130 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -606,4 +606,8 @@ adapterManager.callSetTargetingBidder = function(bidder, bid) { tryCallBidderMethod(bidder, 'onSetTargeting', bid); }; +adapterManager.callBidViewableBidder = function(bidder, bid) { + tryCallBidderMethod(bidder, 'onBidViewable', bid); +}; + export default adapterManager; diff --git a/src/constants.json b/src/constants.json index 4025e084682..e6b9687f911 100644 --- a/src/constants.json +++ b/src/constants.json @@ -38,7 +38,8 @@ "ADD_AD_UNITS": "addAdUnits", "AD_RENDER_FAILED": "adRenderFailed", "TCF2_ENFORCEMENT": "tcf2Enforcement", - "AUCTION_DEBUG": "auctionDebug" + "AUCTION_DEBUG": "auctionDebug", + "BID_VIEWABLE": "bidViewable" }, "AD_RENDER_FAILED_REASON" : { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", diff --git a/test/spec/modules/bidViewability_spec.js b/test/spec/modules/bidViewability_spec.js new file mode 100644 index 00000000000..211dec090a5 --- /dev/null +++ b/test/spec/modules/bidViewability_spec.js @@ -0,0 +1,297 @@ +import * as bidViewability from 'modules/bidViewability.js'; +import { config } from 'src/config.js'; +import * as events from 'src/events.js'; +import * as utils from 'src/utils.js'; +import * as sinon from 'sinon'; +import {expect, spy} from 'chai'; +import * as prebidGlobal from 'src/prebidGlobal.js'; +import { EVENTS } from 'src/constants.json'; +import adapterManager, { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; +import parse from 'url-parse'; + +const GPT_SLOT = { + getAdUnitPath() { + return '/harshad/Jan/2021/'; + }, + + getSlotElementId() { + return 'DIV-1'; + } +}; + +const PBJS_WINNING_BID = { + 'adUnitCode': '/harshad/Jan/2021/', + 'bidderCode': 'pubmatic', + 'bidder': 'pubmatic', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': 'id', + 'requestId': 1024, + 'source': 'client', + 'no_bid': false, + 'cpm': '1.1495', + 'ttl': 180, + 'creativeId': 'id', + 'netRevenue': true, + 'currency': 'USD', + 'vurls': [ + 'https://domain-1.com/end-point?a=1', + 'https://domain-2.com/end-point/', + 'https://domain-3.com/end-point?a=1' + ] +}; + +describe('#bidViewability', function() { + let gptSlot; + let pbjsWinningBid; + + beforeEach(function() { + gptSlot = Object.assign({}, GPT_SLOT); + pbjsWinningBid = Object.assign({}, PBJS_WINNING_BID); + }); + + describe('isBidAdUnitCodeMatchingSlot', function() { + it('match found by GPT Slot getAdUnitPath', function() { + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(true); + }); + + it('match found by GPT Slot getSlotElementId', function() { + pbjsWinningBid.adUnitCode = 'DIV-1'; + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(true); + }); + + it('match not found', function() { + pbjsWinningBid.adUnitCode = 'DIV-10'; + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(false); + }); + }); + + describe('getMatchingWinningBidForGPTSlot', function() { + let winningBidsArray; + let sandbox + beforeEach(function() { + sandbox = sinon.sandbox.create(); + // mocking winningBidsArray + winningBidsArray = []; + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + getAllWinningBids: function (number) { + return winningBidsArray; + } + }); + }); + + afterEach(function() { + sandbox.restore(); + }) + + it('should find a match by using customMatchFunction provided in config', function() { + // Needs config to be passed with customMatchFunction + let bidViewabilityConfig = { + customMatchFunction(bid, slot) { + return ('AD-' + slot.getAdUnitPath()) === bid.adUnitCode; + } + }; + let newWinningBid = Object.assign({}, PBJS_WINNING_BID, {adUnitCode: 'AD-' + PBJS_WINNING_BID.adUnitCode}); + // Needs pbjs.getWinningBids to be implemented with match + winningBidsArray.push(newWinningBid); + let wb = bidViewability.getMatchingWinningBidForGPTSlot(bidViewabilityConfig, gptSlot); + expect(wb).to.deep.equal(newWinningBid); + }); + + it('should NOT find a match by using customMatchFunction provided in config', function() { + // Needs config to be passed with customMatchFunction + let bidViewabilityConfig = { + customMatchFunction(bid, slot) { + return ('AD-' + slot.getAdUnitPath()) === bid.adUnitCode; + } + }; + // Needs pbjs.getWinningBids to be implemented without match; winningBidsArray is set to empty in beforeEach + let wb = bidViewability.getMatchingWinningBidForGPTSlot(bidViewabilityConfig, gptSlot); + expect(wb).to.equal(null); + }); + + it('should find a match by using default matching function', function() { + // Needs config to be passed without customMatchFunction + // Needs pbjs.getWinningBids to be implemented with match + winningBidsArray.push(PBJS_WINNING_BID); + let wb = bidViewability.getMatchingWinningBidForGPTSlot({}, gptSlot); + expect(wb).to.deep.equal(PBJS_WINNING_BID); + }); + + it('should NOT find a match by using default matching function', function() { + // Needs config to be passed without customMatchFunction + // Needs pbjs.getWinningBids to be implemented without match; winningBidsArray is set to empty in beforeEach + let wb = bidViewability.getMatchingWinningBidForGPTSlot({}, gptSlot); + expect(wb).to.equal(null); + }); + }); + + describe('fireViewabilityPixels', function() { + let sandbox; + let triggerPixelSpy; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + }); + + afterEach(function() { + sandbox.restore(); + }); + + it('DO NOT fire pixels if NOT mentioned in module config', function() { + let moduleConfig = {}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + expect(triggerPixelSpy.callCount).to.equal(0); + }); + + it('fire pixels if mentioned in module config', function() { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0]).to.equal(url); + }); + }); + + it('USP: should include the us_privacy key when USP Consent is available', function () { + let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspDataHandlerStub.returns('1YYY'); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.us_privacy).to.equal('1YYY'); + }); + uspDataHandlerStub.restore(); + }); + + it('USP: should not include the us_privacy key when USP Consent is not available', function () { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.us_privacy).to.equal(undefined); + }); + }); + + it('GDPR: should include the GDPR keys when GDPR Consent is available', function() { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + addtlConsent: 'moreConsent' + }); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal('moreConsent'); + }); + gdprDataHandlerStub.restore(); + }); + + it('GDPR: should not include the GDPR keys when GDPR Consent is not available', function () { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal(undefined); + expect(queryObject.gdpr_consent).to.equal(undefined); + expect(queryObject.addtl_consent).to.equal(undefined); + }); + }); + + it('GDPR: should only include the GDPR keys for GDPR Consent fields with values', function () { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent' + }); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal(undefined); + }); + gdprDataHandlerStub.restore(); + }) + }); + + describe('impressionViewableHandler', function() { + let sandbox; + let triggerPixelSpy; + let eventsEmitSpy; + let logWinningBidNotFoundSpy; + let callBidViewableBidderSpy; + let winningBidsArray; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + eventsEmitSpy = sandbox.spy(events, ['emit']); + callBidViewableBidderSpy = sandbox.spy(adapterManager, ['callBidViewableBidder']); + // mocking winningBidsArray + winningBidsArray = []; + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + getAllWinningBids: function (number) { + return winningBidsArray; + } + }); + }); + + afterEach(function() { + sandbox.restore(); + }) + + it('matching winning bid is found', function() { + let moduleConfig = { + firePixels: true + }; + winningBidsArray.push(PBJS_WINNING_BID); + bidViewability.impressionViewableHandler(moduleConfig, GPT_SLOT, null); + // fire pixels should be called + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0]).to.equal(url); + }); + // adapterManager.callBidViewableBidder is called with required args + let call = callBidViewableBidderSpy.getCall(0); + expect(call.args[0]).to.equal(PBJS_WINNING_BID.bidder); + expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); + // EVENTS.BID_VIEWABLE is triggered + call = eventsEmitSpy.getCall(0); + expect(call.args[0]).to.equal(EVENTS.BID_VIEWABLE); + expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); + }); + + it('matching winning bid is NOT found', function() { + // fire pixels should NOT be called + expect(triggerPixelSpy.callCount).to.equal(0); + // adapterManager.callBidViewableBidder is NOT called + expect(callBidViewableBidderSpy.callCount).to.equal(0); + // EVENTS.BID_VIEWABLE is NOT triggered + expect(eventsEmitSpy.callCount).to.equal(0); + }); + }); +}); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 00a0579e676..25b2307b943 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -404,6 +404,38 @@ describe('adapterManager tests', function () { }); }); // end onSetTargeting + describe('onBidViewable', function () { + var criteoSpec = { onBidViewable: sinon.stub() } + var criteoAdapter = { + bidder: 'criteo', + getSpec: function() { return criteoSpec; } + } + before(function () { + config.setConfig({s2sConfig: { enabled: false }}); + }); + + beforeEach(function () { + adapterManager.bidderRegistry['criteo'] = criteoAdapter; + }); + + afterEach(function () { + delete adapterManager.bidderRegistry['criteo']; + }); + + it('should call spec\'s onBidViewable callback when callBidViewableBidder is called', function () { + const bids = [ + {bidder: 'criteo', params: {placementId: 'id'}}, + ]; + const adUnits = [{ + code: 'adUnit-code', + sizes: [[728, 90]], + bids + }]; + adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); + sinon.assert.called(criteoSpec.onBidViewable); + }); + }); // end onBidViewable + describe('S2S tests', function () { beforeEach(function () { config.setConfig({s2sConfig: CONFIG}); From 8d464b6715bf5995bb9ea455bf370cee61f743e7 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Fri, 5 Feb 2021 15:05:10 -0500 Subject: [PATCH 134/325] disable webdriver tests in trionBidAdapter spec (#6280) --- test/spec/modules/trionBidAdapter_spec.js | 82 +++++++++++------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/test/spec/modules/trionBidAdapter_spec.js b/test/spec/modules/trionBidAdapter_spec.js index 596e8a3e2d9..ae329b4a028 100644 --- a/test/spec/modules/trionBidAdapter_spec.js +++ b/test/spec/modules/trionBidAdapter_spec.js @@ -146,47 +146,47 @@ describe('Trion adapter tests', function () { expect(bidUrlParams).to.include(getPublisherUrl()); }); - describe('webdriver', function () { - let originalWD; - - beforeEach(function () { - originalWD = window.navigator.webdriver; - }); - - afterEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return originalWD; - }); - }); - - describe('is present', function () { - beforeEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return 1; - }); - }); - - it('when there is non human traffic', function () { - let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - let bidUrlParams = bidRequests[0].data; - expect(bidUrlParams).to.include('tr_wd=1'); - }); - }); - - describe('is not present', function () { - beforeEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return 0; - }); - }); - - it('when there is not non human traffic', function () { - let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - let bidUrlParams = bidRequests[0].data; - expect(bidUrlParams).to.include('tr_wd=0'); - }); - }); - }); + // describe('webdriver', function () { + // let originalWD; + + // beforeEach(function () { + // originalWD = window.navigator.webdriver; + // }); + + // afterEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return originalWD; + // }); + // }); + + // describe('is present', function () { + // beforeEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return 1; + // }); + // }); + + // it('when there is non human traffic', function () { + // let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + // let bidUrlParams = bidRequests[0].data; + // expect(bidUrlParams).to.include('tr_wd=1'); + // }); + // }); + + // describe('is not present', function () { + // beforeEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return 0; + // }); + // }); + + // it('when there is not non human traffic', function () { + // let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + // let bidUrlParams = bidRequests[0].data; + // expect(bidUrlParams).to.include('tr_wd=0'); + // }); + // }); + // }); describe('document', function () { let originalHD; From ccd570bf76e13cc753f39ffe20465bfcdbb5f820 Mon Sep 17 00:00:00 2001 From: Ian Flournoy Date: Fri, 5 Feb 2021 15:08:30 -0500 Subject: [PATCH 135/325] [ParrableIdSystem] Supply Prebid library version to backend (#6279) * Add prebid version to data object * Renamed prebid to prebidVersion * Fix missing coma Co-authored-by: Victor --- modules/parrableIdSystem.js | 3 ++- test/spec/modules/parrableIdSystem_spec.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 32543e3863b..237708aec57 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -188,7 +188,8 @@ function fetchId(configParams) { eid, trackers: configParams.partner.split(','), url: refererInfo.referer, - isIframe: utils.inIframe(), + prebidVersion: '$prebid.version$', + isIframe: utils.inIframe() }; const searchParams = { diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index f4f485affe9..9db5df2a518 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -117,6 +117,7 @@ describe('Parrable ID System', function() { eid: P_COOKIE_EID, trackers: P_CONFIG_MOCK.params.partner.split(','), url: getRefererInfo().referer, + prebidVersion: '$prebid.version$', isIframe: true }); From 86516aba8a6341cd6a82393c15438d0d73446d38 Mon Sep 17 00:00:00 2001 From: Ian Flournoy Date: Fri, 5 Feb 2021 15:11:36 -0500 Subject: [PATCH 136/325] [ParrableIdSystem] Accept list of partners as an array or string (#6277) * Accept partners as an array and fallthrough partner if no partners * Ensure that Parrable data object decodes with urlsafe base64 in tests * Fixed tests caused by typo in config property * Fix failing test due to accessing unexisting property 'partner' Co-authored-by: Victor --- modules/parrableIdSystem.js | 8 +- test/spec/modules/parrableIdSystem_spec.js | 89 ++++++++++++++++------ 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 237708aec57..afaf794513e 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -60,7 +60,7 @@ function isValidConfig(configParams) { utils.logError('User ID - parrableId submodule requires configParams'); return false; } - if (!configParams.partner) { + if (!configParams.partners && !configParams.partner) { utils.logError('User ID - parrableId submodule requires partner list'); return false; } @@ -183,10 +183,14 @@ function fetchId(configParams) { const eid = (parrableId) ? parrableId.eid : null; const refererInfo = getRefererInfo(); const uspString = uspDataHandler.getConsentData(); + const partners = configParams.partners || configParams.partner + const trackers = typeof partners === 'string' + ? partners.split(',') + : partners; const data = { eid, - trackers: configParams.partner.split(','), + trackers, url: refererInfo.referer, prebidVersion: '$prebid.version$', isIframe: utils.inIframe() diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 9db5df2a518..5a10529778c 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -18,7 +18,7 @@ const P_XHR_EID = '01.1588030911.test-new-eid' const P_CONFIG_MOCK = { name: 'parrableId', params: { - partner: 'parrable_test_partner_123,parrable_test_partner_456' + partners: 'parrable_test_partner_123,parrable_test_partner_456' } }; @@ -74,21 +74,21 @@ function removeParrableCookie() { storage.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); } +function decodeBase64UrlSafe(encBase64) { + const DEC = { + '-': '+', + '_': '/', + '.': '=' + }; + return encBase64.replace(/[-_.]/g, (m) => DEC[m]); +} + describe('Parrable ID System', function() { describe('parrableIdSystem.getId()', function() { describe('response callback function', function() { let logErrorStub; let callbackSpy = sinon.spy(); - let decodeBase64UrlSafe = function (encBase64) { - const DEC = { - '-': '+', - '_': '/', - '.': '=' - }; - return encBase64.replace(/[-_.]/g, (m) => DEC[m]); - } - beforeEach(function() { logErrorStub = sinon.stub(utils, 'logError'); callbackSpy.resetHistory(); @@ -115,7 +115,7 @@ describe('Parrable ID System', function() { expect(queryParams).to.not.have.property('us_privacy'); expect(data).to.deep.equal({ eid: P_COOKIE_EID, - trackers: P_CONFIG_MOCK.params.partner.split(','), + trackers: P_CONFIG_MOCK.params.partners.split(','), url: getRefererInfo().referer, prebidVersion: '$prebid.version$', isIframe: true @@ -162,7 +162,7 @@ describe('Parrable ID System', function() { it('should log an error and continue to callback if ajax request errors', function () { let callBackSpy = sinon.spy(); - let submoduleCallback = parrableIdSubmodule.getId({ params: {partner: 'prebid'} }).callback; + let submoduleCallback = parrableIdSubmodule.getId({ params: {partners: 'prebid'} }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.contain('h.parrable.com'); @@ -237,7 +237,7 @@ describe('Parrable ID System', function() { it('permits an impression when no timezoneFilter is configured', function() { expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', } })).to.have.property('callback'); }); @@ -249,7 +249,7 @@ describe('Parrable ID System', function() { writeParrableCookie({ eid: P_COOKIE_EID }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } @@ -265,7 +265,7 @@ describe('Parrable ID System', function() { Intl.DateTimeFormat.returns({ resolvedOptions }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { allowedZones: [ allowedZone ] } @@ -279,7 +279,7 @@ describe('Parrable ID System', function() { Intl.DateTimeFormat.returns({ resolvedOptions }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } @@ -293,7 +293,7 @@ describe('Parrable ID System', function() { Intl.DateTimeFormat.returns({ resolvedOptions }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } @@ -307,7 +307,7 @@ describe('Parrable ID System', function() { Intl.DateTimeFormat.returns({ resolvedOptions }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { allowedZones: [ timezone ], blockedZones: [ timezone ] @@ -337,7 +337,7 @@ describe('Parrable ID System', function() { writeParrableCookie({ eid: P_COOKIE_EID }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } @@ -351,7 +351,7 @@ describe('Parrable ID System', function() { Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { allowedOffsets: [ allowedOffset ] } @@ -365,7 +365,7 @@ describe('Parrable ID System', function() { Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } @@ -378,7 +378,7 @@ describe('Parrable ID System', function() { Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } @@ -391,7 +391,7 @@ describe('Parrable ID System', function() { Date.prototype.getTimezoneOffset.returns(offset * 60); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { allowedOffset: [ offset ], blockedOffsets: [ offset ] @@ -468,4 +468,47 @@ describe('Parrable ID System', function() { }, { adUnits }); }); }); + + describe('partners parsing', () => { + let callbackSpy = sinon.spy(); + + const partnersTestCase = [ + { + name: '"partners" as an array', + config: { params: { partners: ['parrable_test_partner_123', 'parrable_test_partner_456'] } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partners" as a string list', + config: { params: { partners: 'parrable_test_partner_123,parrable_test_partner_456' } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partners" as a string', + config: { params: { partners: 'parrable_test_partner_123' } }, + expected: ['parrable_test_partner_123'] + }, + { + name: '"partner" as a string list', + config: { params: { partner: 'parrable_test_partner_123,parrable_test_partner_456' } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partner" as string', + config: { params: { partner: 'parrable_test_partner_123' } }, + expected: ['parrable_test_partner_123'] + }, + ]; + partnersTestCase.forEach(testCase => { + it(`accepts config property ${testCase.name}`, () => { + parrableIdSubmodule.getId(testCase.config).callback(callbackSpy); + + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); + + expect(data.trackers).to.deep.equal(testCase.expected); + }); + }); + }); }); From b7dcdf9e412bff45b374fb4ce21d3ebd25652ece Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Sun, 7 Feb 2021 10:56:12 -0500 Subject: [PATCH 137/325] Rubicon Bid Adapter FPD Update (#6122) * 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 * Update to consolidate several lines of duplicate code into one location --- modules/rubiconBidAdapter.js | 160 ++++++++++---------- test/spec/modules/rubiconBidAdapter_spec.js | 63 +++++--- 2 files changed, 117 insertions(+), 106 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 2981bd4f3fa..395b7a693b2 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -254,46 +254,7 @@ export const spec = { 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')); - if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { - const bidderData = { - bidders: [ bidderRequest.bidderCode ], - config: { - fpd: {} - } - }; - - if (!utils.isEmpty(siteData)) { - bidderData.config.fpd.site = siteData; - } - - if (!utils.isEmpty(userData)) { - bidderData.config.fpd.user = userData; - } - - utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); - } - - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(data.imp[0].ext, 'context.data.pbadslot', pbAdSlot); - } - - /** - * Copy GAM AdUnit and Name to imp - */ - ['name', 'adSlot'].forEach(name => { - /** @type {(string|undefined)} */ - const value = utils.deepAccess(bidRequest, `fpd.context.adserver.${name}`); - if (typeof value === 'string' && value) { - utils.deepSetValue(data.imp[0].ext, `context.data.adserver.${name.toLowerCase()}`, value); - } - }); + applyFPD(bidRequest, VIDEO, data); // if storedAuctionResponse has been set, pass SRID if (bidRequest.storedAuctionResponse) { @@ -547,49 +508,7 @@ export const spec = { data['us_privacy'] = encodeURIComponent(bidderRequest.uspConsent); } - // visitor properties - const visitorData = Object.assign({}, params.visitor, config.getConfig('fpd.user')); - Object.keys(visitorData).forEach((key) => { - if (visitorData[key] != null && key !== 'keywords') { - data[`tg_v.${key}`] = typeof visitorData[key] === 'object' && !Array.isArray(visitorData[key]) - ? JSON.stringify(visitorData[key]) - : visitorData[key].toString(); // initialize array; - } - }); - - // inventory properties - const inventoryData = Object.assign({}, params.inventory, config.getConfig('fpd.context')); - Object.keys(inventoryData).forEach((key) => { - if (inventoryData[key] != null && key !== 'keywords') { - data[`tg_i.${key}`] = typeof inventoryData[key] === 'object' && !Array.isArray(inventoryData[key]) - ? JSON.stringify(inventoryData[key]) - : inventoryData[key].toString(); - } - }); - - // keywords - const keywords = (params.keywords || []).concat( - utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || [], - utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || []); - data.kw = Array.isArray(keywords) && keywords.length ? keywords.join(',') : ''; - - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - data['tg_i.pbadslot'] = pbAdSlot.replace(/^\/+/, ''); - } - - /** - * GAM Ad Unit - * @type {(string|undefined)} - */ - const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot'); - if (typeof gamAdUnit === 'string' && gamAdUnit) { - data['tg_i.dfp_ad_unit_code'] = gamAdUnit.replace(/^\/+/, ''); - } + applyFPD(bidRequest, BANNER, data); if (config.getConfig('coppa') === true) { data['coppa'] = 1; @@ -949,6 +868,81 @@ function addVideoParameters(data, bidRequest) { data.imp[0].video.h = size[1] } +function applyFPD(bidRequest, mediaType, data) { + const bidFpd = { + user: {...bidRequest.params.visitor}, + context: {...bidRequest.params.inventory} + }; + + if (bidRequest.params.keywords) bidFpd.context.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 = []; + const validate = function(prop, key) { + 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 => { + if (typeof value !== 'object' && typeof value !== 'undefined') return value.toString(); + + utils.logWarn('Rubicon: Filtered value: ', value, 'for key', key, ': Expected value to be string, integer, or an array of strings/ints'); + }).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; + }); + } + }); + + 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 (mediaType === BANNER) { + let kw = validate(keywords, 'keywords'); + if (kw) data.kw = kw; + } else { + utils.mergeDeep(data, obj); + } +} + /** * @param sizes * @returns {*} diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 6944034a787..8c25d97dada 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -826,16 +826,22 @@ describe('the rubicon adapter', function () { }); }); - it('should use first party data from getConfig over the bid params, if present', () => { + it('should merge first party data from getConfig with the bid params, if present', () => { const context = { - keywords: ['e', 'f'], - rating: '4-star' + keywords: 'e,f', + rating: '4-star', + data: { + page: 'home' + } }; const user = { - keywords: ['d'], gender: 'M', yob: '1984', - geo: {country: 'ca'} + geo: {country: 'ca'}, + keywords: 'd', + data: { + age: 40 + } }; sandbox.stub(config, 'getConfig').callsFake(key => { @@ -849,14 +855,15 @@ describe('the rubicon adapter', function () { }); const expectedQuery = { - 'kw': 'a,b,c,d,e,f', + 'kw': 'a,b,c,d', 'tg_v.ucat': 'new', 'tg_v.lastsearch': 'iphone', 'tg_v.likes': 'sports,video games', 'tg_v.gender': 'M', + 'tg_v.age': '40', 'tg_v.yob': '1984', - 'tg_v.geo': '{"country":"ca"}', - 'tg_i.rating': '4-star', + 'tg_i.rating': '5-star', + 'tg_i.page': 'home', 'tg_i.prodtype': 'tech,mobile', }; @@ -1865,11 +1872,17 @@ describe('the rubicon adapter', function () { createVideoBidderRequest(); const context = { - keywords: ['e', 'f'], + data: { + page: 'home' + }, + keywords: 'e,f', rating: '4-star' }; const user = { - keywords: ['d'], + data: { + age: 31 + }, + keywords: 'd', gender: 'M', yob: '1984', geo: {country: 'ca'} @@ -1885,18 +1898,22 @@ describe('the rubicon adapter', function () { return utils.deepAccess(config, key); }); - const expected = [{ - bidders: ['rubicon'], - config: { - fpd: { - site: Object.assign({}, bidderRequest.bids[0].params.inventory, context), - user: Object.assign({}, bidderRequest.bids[0].params.visitor, user) - } - } - }]; - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.bidderconfig).to.deep.equal(expected); + + const expected = { + site: Object.assign({}, context, context.data, bidderRequest.bids[0].params.inventory), + user: Object.assign({}, user, user.data, bidderRequest.bids[0].params.visitor) + }; + + delete expected.site.data; + delete expected.user.data; + delete expected.site.keywords; + delete expected.user.keywords; + + 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); }); it('should include storedAuctionResponse in video bid request', function () { @@ -1935,7 +1952,7 @@ describe('the rubicon adapter', function () { createVideoBidderRequest(); bidderRequest.bids[0].fpd = { context: { - adserver: { + adServer: { adSlot: '1234567890', name: 'adServerName1' } @@ -2079,7 +2096,7 @@ describe('the rubicon adapter', function () { it('should not fail if keywords param is not an array', function () { bidderRequest.bids[0].params.keywords = 'a,b,c'; const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); - expect(slotParams.kw).to.equal(''); + expect(slotParams.kw).to.equal('a,b,c'); }); }); From 2640d0806a9b28a7e21e44dc46aeccbf83fdd0fd Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Mon, 8 Feb 2021 12:08:19 +0100 Subject: [PATCH 138/325] ID5 User Id Module: update a/b testing to be user based not request based (#6281) * convert A/B testing to be user-based, rather than request-based * update docs to say a/b testing is user based, not request based --- modules/id5IdSystem.js | 51 ++++++++++----- modules/id5IdSystem.md | 4 +- test/spec/modules/id5IdSystem_spec.js | 91 ++++++++++++++++++++------- 3 files changed, 105 insertions(+), 41 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index baa2ff954dc..bf2365b938c 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -18,6 +18,7 @@ const NB_EXP_DAYS = 30; export const ID5_STORAGE_NAME = 'id5id'; export const ID5_PRIVACY_STORAGE_NAME = `${ID5_STORAGE_NAME}_privacy`; const LOCAL_STORAGE = 'html5'; +const ABTEST_RESOLUTION = 10000; // order the legacy cookie names in reverse priority order so the last // cookie in the array is the most preferred to use @@ -58,27 +59,18 @@ export const id5IdSubmodule = { } // check for A/B testing configuration and hide ID if in Control Group - let abConfig = getAbTestingConfig(config); - let controlGroup = false; - if ( - abConfig.enabled === true && - (!utils.isNumber(abConfig.controlGroupPct) || - abConfig.controlGroupPct < 0 || - abConfig.controlGroupPct > 1) - ) { + const abConfig = getAbTestingConfig(config); + const controlGroup = isInControlGroup(universalUid, abConfig.controlGroupPct); + if (abConfig.enabled === true && typeof controlGroup === 'undefined') { // A/B Testing is enabled, but configured improperly, so skip A/B testing utils.logError('User ID - ID5 submodule: A/B Testing controlGroupPct must be a number >= 0 and <= 1! Skipping A/B Testing'); - } else if ( - abConfig.enabled === true && - Math.random() < abConfig.controlGroupPct - ) { + } else if (abConfig.enabled === true && controlGroup === true) { // A/B Testing is enabled and user is in the Control Group, so do not share the ID5 ID - utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - request is in the Control Group, so the ID5 ID is NOT exposed'); + utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - user is in the Control Group, so the ID5 ID is NOT exposed'); universalUid = linkType = 0; - controlGroup = true; } else if (abConfig.enabled === true) { // A/B Testing is enabled but user is not in the Control Group, so ID5 ID is shared - utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - request is NOT in the Control Group, so the ID5 ID is exposed'); + utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - user is NOT in the Control Group, so the ID5 ID is exposed'); } let responseObj = { @@ -91,7 +83,7 @@ export const id5IdSubmodule = { }; if (abConfig.enabled === true) { - utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', controlGroup); + utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', (typeof controlGroup === 'undefined' ? false : controlGroup)); } return responseObj; @@ -296,4 +288,31 @@ function getAbTestingConfig(config) { return (config && config.params && config.params.abTesting) || { enabled: false }; } +/** + * Return a consistant random number between 0 and ABTEST_RESOLUTION-1 for this user + * Falls back to plain random if no user provided + * @param {string} userId + * @returns {number} + */ +function abTestBucket(userId) { + if (userId) { + return ((utils.cyrb53Hash(userId) % ABTEST_RESOLUTION) + ABTEST_RESOLUTION) % ABTEST_RESOLUTION; + } else { + return Math.floor(Math.random() * ABTEST_RESOLUTION); + } +} + +/** + * Return a consistant boolean if this user is within the control group ratio provided + * @param {string} userId + * @param {number} controlGroupPct - Ratio [0,1] of users expected to be in the control group + * @returns {boolean} + */ +export function isInControlGroup(userId, controlGroupPct) { + if (!utils.isNumber(controlGroupPct) || controlGroupPct < 0 || controlGroupPct > 1) { + return undefined; + } + return abTestBucket(userId) < controlGroupPct * ABTEST_RESOLUTION; +} + submodule('userId', id5IdSubmodule); diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 6b2192834fa..6a662361492 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -1,6 +1,6 @@ # ID5 Universal ID -The ID5 Universal ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 Universal ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 Universal ID and detailed integration docs, please visit [our documentation](https://console.id5.io/docs/public/prebid). We also recommend that you sign up for our [release notes](https://id5.io/universal-id/release-notes) to stay up-to-date with any changes to the implementation of the ID5 Universal ID in Prebid. +The ID5 Universal ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 Universal ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 Universal ID and detailed integration docs, please visit [our documentation](https://wiki.id5.io/x/BIAZ). We also recommend that you sign up for our [release notes](https://id5.io/universal-id/release-notes) to stay up-to-date with any changes to the implementation of the ID5 Universal ID in Prebid. ## ID5 Universal ID Registration @@ -65,4 +65,4 @@ pbjs.setConfig({ Publishers may want to test the value of the ID5 ID with their downstream partners. While there are various ways to do this, A/B testing is a standard approach. Instead of publishers manually enabling or disabling the ID5 User ID Module based on their control group settings (which leads to fewer calls to ID5, reducing our ability to recognize the user), we have baked this in to our module directly. -To turn on A/B Testing, simply edit the configuration (see above table) to enable it and set what percentage of requests you would like to set for the control group. The control group is the set of requests where an ID5 ID will not be exposed in to bid adapters or in the various user id functions available on the `pbjs` global. An additional value of `ext.abTestingControlGroup` will be set to `true` or `false` that can be used to inform reporting systems that the request was in the control group or not. It's important to note that the control group is request based, and not user based. In other words, from one page view to another, a user may be in or out of the control group. +To turn on A/B Testing, simply edit the configuration (see above table) to enable it and set what percentage of users you would like to set for the control group. The control group is the set of user where an ID5 ID will not be exposed in to bid adapters or in the various user id functions available on the `pbjs` global. An additional value of `ext.abTestingControlGroup` will be set to `true` or `false` that can be used to inform reporting systems that the user was in the control group or not. It's important to note that the control group is user based, and not request based. In other words, from one page view to another, a user will always be in or out of the control group. diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 59fa8977fc8..7f104ff403d 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -7,7 +7,8 @@ import { expDaysStr, nbCacheName, getNbFromCache, - storeNbInCache + storeNbInCache, + isInControlGroup } from 'modules/id5IdSystem.js'; import { init, requestBidsHook, setSubmoduleRegistry, coreStorage } from 'modules/userId/index.js'; import { config } from 'src/config.js'; @@ -477,6 +478,9 @@ describe('ID5 ID System', function() { { enabled: true, controlGroupPct: true } ]; testInvalidAbTestingConfigsWithError.forEach((testAbTestingConfig) => { + it('should be undefined if ratio is invalid', () => { + expect(isInControlGroup('userId', testAbTestingConfig.controlGroupPct)).to.be.undefined; + }); it('should error if config is invalid, and always return an ID', function () { testConfig.params.abTesting = testAbTestingConfig; let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); @@ -493,6 +497,9 @@ describe('ID5 ID System', function() { { enabled: false, controlGroupPct: true } ]; testInvalidAbTestingConfigsWithoutError.forEach((testAbTestingConfig) => { + it('should be undefined if ratio is invalid', () => { + expect(isInControlGroup('userId', testAbTestingConfig.controlGroupPct)).to.be.undefined; + }); it('should not error if config is invalid but A/B testing is off, and always return an ID', function () { testConfig.params.abTesting = testAbTestingConfig; let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); @@ -509,6 +516,9 @@ describe('ID5 ID System', function() { { enabled: true, controlGroupPct: 1 } ]; testValidConfigs.forEach((testAbTestingConfig) => { + it('should not be undefined if ratio is valid', () => { + expect(isInControlGroup('userId', testAbTestingConfig.controlGroupPct)).to.not.be.undefined; + }); it('should not error if config is valid', function () { testConfig.params.abTesting = testAbTestingConfig; id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); @@ -555,34 +565,69 @@ describe('ID5 ID System', function() { randStub.restore(); }); - it('should expose ID when A/B testing is off', function () { - testConfig.params.abTesting = { - enabled: false, - controlGroupPct: 0.5 - }; + describe('IsInControlGroup', function () { + it('Nobody is in a 0% control group', function () { + expect(isInControlGroup('dsdndskhsdks', 0)).to.be.false; + expect(isInControlGroup('3erfghyuijkm', 0)).to.be.false; + expect(isInControlGroup('', 0)).to.be.false; + expect(isInControlGroup(undefined, 0)).to.be.false; + }); - let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); - expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); - }); + it('Everybody is in a 100% control group', function () { + expect(isInControlGroup('dsdndskhsdks', 1)).to.be.true; + expect(isInControlGroup('3erfghyuijkm', 1)).to.be.true; + expect(isInControlGroup('', 1)).to.be.true; + expect(isInControlGroup(undefined, 1)).to.be.true; + }); - it('should expose ID when not in control group', function () { - testConfig.params.abTesting = { - enabled: true, - controlGroupPct: 0.1 - }; + it('Being in the control group must be consistant', function () { + const inControlGroup = isInControlGroup('dsdndskhsdks', 0.5); + expect(inControlGroup === isInControlGroup('dsdndskhsdks', 0.5)).to.be.true; + expect(inControlGroup === isInControlGroup('dsdndskhsdks', 0.5)).to.be.true; + expect(inControlGroup === isInControlGroup('dsdndskhsdks', 0.5)).to.be.true; + }); - let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); - expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOn); + it('Control group ratio must be within a 10% error on a large sample', function () { + let nbInControlGroup = 0; + const sampleSize = 100; + for (let i = 0; i < sampleSize; i++) { + nbInControlGroup = nbInControlGroup + (isInControlGroup('R$*df' + i, 0.5) ? 1 : 0); + } + expect(nbInControlGroup).to.be.greaterThan(sampleSize / 2 - sampleSize / 10); + expect(nbInControlGroup).to.be.lessThan(sampleSize / 2 + sampleSize / 10); + }); }); - it('should not expose ID when in control group', function () { - testConfig.params.abTesting = { - enabled: true, - controlGroupPct: 0.5 - }; + describe('Decode', function() { + it('should expose ID when A/B testing is off', function () { + testConfig.params.abTesting = { + enabled: false, + controlGroupPct: 0.5 + }; - let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); - expect(decoded).to.deep.equal(expectedDecodedObjectWithoutIdAbOn); + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + }); + + it('should expose ID when no one is in control group', function () { + testConfig.params.abTesting = { + enabled: true, + controlGroupPct: 0 + }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOn); + }); + + it('should not expose ID when everyone is in control group', function () { + testConfig.params.abTesting = { + enabled: true, + controlGroupPct: 1 + }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithoutIdAbOn); + }); }); }); }); From 727bf206f4e7c50215a3ae6ae2920cb304336953 Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Mon, 8 Feb 2021 15:32:53 +0100 Subject: [PATCH 139/325] Shared ID gdpr support (#6275) * SharedId gdpr support * Reverted commented locally failing tests --- modules/id5IdSystem.js | 3 +- modules/pubCommonIdSystem.js | 20 +++++++-- modules/sharedIdSystem.js | 21 +++++++-- modules/userId/index.js | 3 +- test/spec/modules/sharedIdSystem_spec.js | 55 ++++++++++++++++++++++++ test/spec/modules/userId_spec.js | 53 +++++++++++++++++++++++ 6 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 test/spec/modules/sharedIdSystem_spec.js diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index bf2365b938c..6dade7d01f0 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -166,10 +166,11 @@ export const id5IdSubmodule = { * It's permissible to return neither, one, or both fields. * @function extendId * @param {SubmoduleConfig} config + * @param {ConsentData|undefined} consentData * @param {Object} cacheIdObj - existing id, if any * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ - extendId(config, cacheIdObj) { + extendId(config, consentData, cacheIdObj) { const partnerId = (config && config.params && config.params.partner) || 0; incrementNb(partnerId); return cacheIdObj; diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index cb0c07cefa8..339029120a8 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -136,6 +136,17 @@ function handleResponse(pubcid, callback, config) { } } +/** + * Builds and returns the shared Id URL with attached consent data if applicable + * @param {Object} consentData + * @return {string} + */ +function sharedIdUrl(consentData) { + if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return SHAREDID_URL; + + return `${SHAREDID_URL}?gdpr=1&gdpr_consent=${consentData.consentString}` +} + /** * Wraps pixelCallback in order to call sharedid sync * @param {string} pubcid Pubcommon id value @@ -144,12 +155,12 @@ function handleResponse(pubcid, callback, config) { * @return {function(...[*]=)} */ -function getIdCallback(pubcid, pixelCallback, config) { +function getIdCallback(pubcid, pixelCallback, config, consentData) { return function (callback) { if (typeof pixelCallback === 'function') { pixelCallback(); } - ajax(SHAREDID_URL, handleResponse(pubcid, callback, config), undefined, {method: 'GET', withCredentials: true}); + ajax(sharedIdUrl(consentData), handleResponse(pubcid, callback, config), undefined, {method: 'GET', withCredentials: true}); } } @@ -227,7 +238,7 @@ export const pubCommonIdSubmodule = { } const pixelCallback = this.makeCallback(pixelUrl, newId); - const combinedCallback = enableSharedId ? getIdCallback(newId, pixelCallback, config) : pixelCallback; + const combinedCallback = enableSharedId ? getIdCallback(newId, pixelCallback, config, consentData) : pixelCallback; return {id: newId, callback: combinedCallback}; }, @@ -247,10 +258,11 @@ export const pubCommonIdSubmodule = { * * @function * @param {SubmoduleParams} [config] + * @param {ConsentData|undefined} consentData * @param {Object} storedId existing id * @returns {IdResponse|undefined} */ - extendId: function(config = {}, storedId) { + extendId: function(config = {}, consentData, storedId) { const {params: {extend = false, pixelUrl, enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config; if (extend) { diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 49cac46f1df..762454af5fa 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -276,6 +276,17 @@ function detectPrng(root) { return () => Math.random(); } +/** + * Builds and returns the shared Id URL with attached consent data if applicable + * @param {Object} consentData + * @return {string} + */ +function sharedIdUrl(consentData) { + if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return ID_SVC; + + return `${ID_SVC}?gdpr=1&gdpr_consent=${consentData.consentString}` +} + /** @type {Submodule} */ export const sharedIdSubmodule = { /** @@ -303,12 +314,13 @@ export const sharedIdSubmodule = { * performs action to obtain id and return a value. * @function * @param {SubmoduleConfig} [config] + * @param {ConsentData|undefined} consentData * @returns {sharedId} */ - getId(config) { + getId(config, consentData) { const resp = function (callback) { utils.logInfo('SharedId: Sharedid doesnt exists, new cookie creation'); - ajax(ID_SVC, idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true}); + ajax(sharedIdUrl(consentData), idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true}); }; return {callback: resp}; }, @@ -316,10 +328,11 @@ export const sharedIdSubmodule = { /** * performs actions even if the id exists and returns a value * @param config + * @param consentData * @param storedId * @returns {{callback: *}} */ - extendId(config, storedId) { + extendId(config, consentData, storedId) { const configParams = (config && config.params) || {}; utils.logInfo('SharedId: Existing shared id ' + storedId.id); const resp = function (callback) { @@ -329,7 +342,7 @@ export const sharedIdSubmodule = { const sharedIdPayload = {}; sharedIdPayload.sharedId = storedId.id; const payloadString = JSON.stringify(sharedIdPayload); - ajax(ID_SVC, existingIdCallback(storedId, callback), payloadString, {method: 'POST', withCredentials: true}); + ajax(sharedIdUrl(consentData), existingIdCallback(storedId, callback), payloadString, {method: 'POST', withCredentials: true}); } }; return {callback: resp}; diff --git a/modules/userId/index.js b/modules/userId/index.js index 9294311de69..da2c7b225fa 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -28,6 +28,7 @@ * It's permissible to return neither, one, or both fields. * @name Submodule#extendId * @param {SubmoduleConfig} config + * @param {ConsentData|undefined} consentData * @param {Object} storedId - existing id, if any * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ @@ -621,7 +622,7 @@ function populateSubmoduleId(submodule, consentData, storedConsentData, forceRef response = submodule.submodule.getId(submodule.config, consentData, storedId); } else if (typeof submodule.submodule.extendId === 'function') { // If the id exists already, give submodule a chance to decide additional actions that need to be taken - response = submodule.submodule.extendId(submodule.config, storedId); + response = submodule.submodule.extendId(submodule.config, consentData, storedId); } if (utils.isPlainObject(response)) { diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js new file mode 100644 index 00000000000..904d6fe1c78 --- /dev/null +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -0,0 +1,55 @@ +import { + sharedIdSubmodule, +} from 'modules/sharedIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; + +let expect = require('chai').expect; + +describe('SharedId System', function() { + const SHAREDID_RESPONSE = {sharedId: 'testsharedid'}; + + describe('Xhr Requests from getId()', function() { + let callbackSpy = sinon.spy(); + + beforeEach(function() { + callbackSpy.resetHistory(); + }); + + afterEach(function () { + + }); + + it('should call shared id endpoint without consent data and handle a valid response', function () { + let submoduleCallback = sharedIdSubmodule.getId(undefined, undefined).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + expect(request.url).to.equal('https://id.sharedid.org/id'); + expect(request.withCredentials).to.be.true; + + request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE)); + + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId); + }); + + it('should call shared id endpoint with consent data and handle a valid response', function () { + let consentData = { + gdprApplies: true, + consentString: 'abc12345234', + }; + + let submoduleCallback = sharedIdSubmodule.getId(undefined, consentData).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + expect(request.url).to.equal('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234'); + expect(request.withCredentials).to.be.true; + + request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE)); + + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId); + }); + }); +}); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index ed592c0cba5..0fcf03dde67 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -2037,6 +2037,8 @@ describe('User ID', function () { coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); + resetConsentData(); + delete window.__tcfapi; }); it('pubcid callback with url', function () { @@ -2171,6 +2173,57 @@ describe('User ID', function () { expect(server.requests[0].url).to.equal('https://id.sharedid.org/id'); expect(coreStorage.getCookie('pubcid_sharedid')).to.be.null; }); + + it('verify sharedid called with consent data when gdpr applies', function () { + let adUnits = [getAdUnitMock()]; + let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); + let consentConfig = { + cmpApi: 'iab', + timeout: 7500, + allowAuctionWithoutConsent: false + }; + customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url', enableSharedId: true}); + + server.respondWith('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234', function(xhr) { + xhr.respond(200, {}, '{"sharedId":"testsharedid"}'); + }); + server.respondImmediately = true; + + let testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded', + vendor: {consents: {887: true}}, + purpose: { + consents: { + 1: true + } + } + }; + + window.__tcfapi = function () { }; + sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(customCfg); + setConsentConfig(consentConfig); + + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + }, {adUnits}); + + expect(utils.triggerPixel.called).to.be.false; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + + expect(server.requests[0].url).to.equal('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234'); + expect(coreStorage.getCookie('pubcid_sharedid')).to.equal('testsharedid'); + }); }); describe('Set cookie behavior', function () { From ab9a8d276867182ff2fd191b6cd25c6182d55f55 Mon Sep 17 00:00:00 2001 From: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Date: Mon, 8 Feb 2021 22:05:21 +0530 Subject: [PATCH 140/325] Lemma:set mediaType key value (#6006) * lemmaBidAdapter.js Added lemma bid adapter file * lemmaBidAdapter.md Added lemma bid adapter md file * lemmaBidAdapter_spec.js Added lemma bid adapter test spec file * Update lemmaBidAdapter.js Fixed automated code review alert comparison between inconvertible types * Update lemmaBidAdapter.js Fixed review changes * Update lemmaBidAdapter.md Correct parameter value. * Update lemmaBidAdapter.js Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter_spec.js Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter.md Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter.js Added user sync support into bid adapter. * updated include modules file extension. updated include modules js file extension. * Update lemmaBidAdapter_spec.js Added unit test for user sync feature. * Update lemmaBidAdapter.js Fixed format error. * Update lemmaBidAdapter_spec.js Fixed format error and typo error. * Set mediaType key value into bid object Set mediaType key value into the bid object. * Update lemmaBidAdapter.js remove duplicate function * Update lemmaBidAdapter.js Remove non supported code. * Update lemmaBidAdapter_spec.js Remove GDPR test cases. --- modules/lemmaBidAdapter.js | 27 +++++++++-------------- test/spec/modules/lemmaBidAdapter_spec.js | 18 --------------- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/modules/lemmaBidAdapter.js b/modules/lemmaBidAdapter.js index 5941802f97d..926761e5ab2 100644 --- a/modules/lemmaBidAdapter.js +++ b/modules/lemmaBidAdapter.js @@ -62,18 +62,6 @@ export var spec = { }, getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { let syncurl = USER_SYNC + 'pid=' + pubId; - - // Attaching GDPR Consent Params in UserSync url - if (gdprConsent) { - syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); - syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); - } - - // CCPA - if (uspConsent) { - syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); - } - if (syncOptions.iframeEnabled) { return [{ type: 'iframe', @@ -122,9 +110,9 @@ function parseRTBResponse(request, response) { newBid.dealId = bid.dealid; } if (req.imp && req.imp.length > 0) { - newBid.mediaType = req.mediaType; req.imp.forEach(robj => { if (bid.impid === robj.id) { + _checkMediaType(bid.adm, newBid); switch (newBid.mediaType) { case BANNER: break; @@ -277,10 +265,6 @@ function _getDeviceObject(request) { function setOtherParams(request, ortbRequest) { var params = request && request.params ? request.params : null; - if (request && request.gdprConsent) { - ortbRequest.regs = { ext: { gdpr: request.gdprConsent.gdprApplies ? 1 : 0 } }; - ortbRequest.user = { ext: { consent: request.gdprConsent.consentString } }; - } if (params) { ortbRequest.tmax = params.tmax; ortbRequest.bcat = params.bcat; @@ -424,4 +408,13 @@ function parse(rawResp) { return null; } +function _checkMediaType(adm, newBid) { + // Create a regex here to check the strings + var videoRegex = new RegExp(/VAST.*version/); + if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + newBid.mediaType = BANNER; + } +} registerBidder(spec); diff --git a/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js index a00c25d126c..a22f5650e50 100644 --- a/test/spec/modules/lemmaBidAdapter_spec.js +++ b/test/spec/modules/lemmaBidAdapter_spec.js @@ -346,24 +346,6 @@ describe('lemmaBidAdapter', function() { type: 'iframe', url: syncurl_iframe }]); }); - - it('CCPA/USP', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&us_privacy=1NYN` - }]); - }); - - it('GDPR', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=0&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=` - }]); - }); }); }); }); From 19b4885892a14a8177c3b45284351b3492b1b8ad Mon Sep 17 00:00:00 2001 From: ix-certification Date: Mon, 8 Feb 2021 11:41:28 -0500 Subject: [PATCH 141/325] added support for addtlConsent (#6005) Co-authored-by: Ix-Prebid-Support --- modules/ixBidAdapter.js | 6 ++++++ test/spec/modules/ixBidAdapter_spec.js | 27 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 97341fbfd78..48a2e741c5a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -341,6 +341,12 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.user.ext = { consent: gdprConsent.consentString || '' }; + + if (gdprConsent.hasOwnProperty('addtlConsent') && gdprConsent.addtlConsent) { + r.user.ext.consented_providers_settings = { + consented_providers: gdprConsent.addtlConsent + } + } } } diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index edbcc5725ac..fca3f8f992e 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -1772,5 +1772,32 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.regs.ext.us_privacy).to.equal('1YYN'); }); + + it('should contain `consented_providers_settings.consented_providers` & consent on user.ext when both are provided', function () { + const options = { + gdprConsent: { + consentString: '3huaa11=qu3198ae', + addtlConsent: '1~1.35.41.101', + } + }; + + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(requestWithConsent.user.ext.consented_providers_settings.consented_providers).to.equal('1~1.35.41.101'); + expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); + }); + + it('should not contain `consented_providers_settings.consented_providers` on user.ext when consent is not provided', function () { + const options = { + gdprConsent: { + addtlConsent: '1~1.35.41.101', + } + }; + + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings')).to.not.exist; + expect(utils.deepAccess(requestWithConsent, 'user.ext.consent')).to.not.exist; + }); }); }); From 1654d53b8c7676d652c3bd35c74ca7c5413bf3a7 Mon Sep 17 00:00:00 2001 From: Missena <78362128+dev-missena@users.noreply.github.com> Date: Mon, 8 Feb 2021 22:49:01 +0100 Subject: [PATCH 142/325] Add bid adapter for Missena (#6247) * adds support for getFloor of video mediaTypes * adds test for calling getFloor with correct mediaType * checks that _getFloor converts string floors to float * Add bid adapter for Missena * Use publisher demo token in tests * Add Missena global vendor ID to spec * Use apiKey in the current bidRequest * Add referer info to payload Co-authored-by: Nick Llerandi Co-authored-by: Brandon Ling <51931757+blingster7@users.noreply.github.com> Co-authored-by: Brandon Ling --- modules/missenaBidAdapter.js | 94 ++++++++++++++ modules/missenaBidAdapter.md | 70 +++++++++++ test/spec/modules/missenaBidAdapter_spec.js | 131 ++++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 modules/missenaBidAdapter.js create mode 100644 modules/missenaBidAdapter.md create mode 100644 test/spec/modules/missenaBidAdapter_spec.js diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js new file mode 100644 index 00000000000..2b1d6bdb118 --- /dev/null +++ b/modules/missenaBidAdapter.js @@ -0,0 +1,94 @@ +import * as utils from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'missena'; +const ENDPOINT_URL = 'https://bid.missena.io/'; + +export const spec = { + aliases: [BIDDER_CODE], + code: BIDDER_CODE, + gvlid: 687, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return typeof bid == 'object' && !!bid.params.apiKey; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map((bidRequest) => { + const payload = { + request_id: bidRequest.bidId, + timeout: bidderRequest.timeout, + }; + + if (bidderRequest && bidderRequest.refererInfo) { + payload.referer = bidderRequest.refererInfo.referer; + payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.consent_string = bidderRequest.gdprConsent.consentString; + payload.consent_required = bidderRequest.gdprConsent.gdprApplies; + } + + return { + method: 'POST', + url: + ENDPOINT_URL + + '?' + + utils.formatQS({ + t: bidRequest.params.apiKey, + }), + data: JSON.stringify(payload), + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + + if (response && !response.timeout && !!response.ad) { + bidResponses.push(response); + } + + return bidResponses; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function onTimeout(timeoutData) { + utils.logInfo('Missena - Timeout from adapter', timeoutData); + }, + + /** + * Register bidder specific code, which@ will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function (bid) { + utils.logInfo('Missena - Bid won', bid); + }, +}; + +registerBidder(spec); diff --git a/modules/missenaBidAdapter.md b/modules/missenaBidAdapter.md new file mode 100644 index 00000000000..d5fcacf04ab --- /dev/null +++ b/modules/missenaBidAdapter.md @@ -0,0 +1,70 @@ +# Overview + +``` +Module Name: Missena Bid Adapter +Module Type: Bidder Adapter +Maintainer: jney@missena.com +``` + +## Introduction + +Connects to Missena for bids. + +**Note:** this adapter doesn't support SafeFrame. + +Useful resources: + +- [README](../README.md#Build) +- [https://docs.prebid.org/dev-docs/bidder-adaptor.html](https://docs.prebid.org/dev-docs/bidder-adaptor.html) + +## Develop + +Setup the missena adapter in `integrationExamples/gpt/userId_example.html`. + +For example: + +```js +const AD_UNIT_CODE = "test-div"; +const PUBLISHER_MISSENA_TOKEN = "PA-34745704"; + +var adUnits = [ + { + code: AD_UNIT_CODE, + mediaTypes: { + banner: { + sizes: [1, 1], + }, + }, + bids: [ + { + bidder: "missena", + params: { + apiKey: PUBLISHER_MISSENA_TOKEN, + }, + }, + ], + }, +]; +``` + +Then start the demo app: + +```shell +gulp serve-fast --modules=missenaBidAdapter +``` + +And open [http://localhost:9999/integrationExamples/gpt/userId_example.html](http://localhost:9999/integrationExamples/gpt/userId_example.html) + +## Test + +```shell +gulp test --file test/spec/modules/missenaBidAdapter_spec.js +``` + +Add the `--watch` option to re-run unit tests whenever the source code changes. + +## Build + +```shell +gulp build --modules=missenaBidAdapter +``` diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js new file mode 100644 index 00000000000..026e79c6d5a --- /dev/null +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -0,0 +1,131 @@ +import { expect } from 'chai'; +import { spec, _getPlatform } from 'modules/missenaBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('Missena Adapter', function () { + const adapter = newBidder(spec); + + const bidId = 'abc'; + + const bid = { + bidder: 'missena', + bidId: bidId, + sizes: [[1, 1]], + params: { + apiKey: 'PA-34745704', + }, + }; + + describe('codes', function () { + it('should return a bidder code of missena', function () { + expect(spec.code).to.equal('missena'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true if the apiKey param is present', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if the apiKey is missing', function () { + expect( + spec.isBidRequestValid(Object.assign(bid, { params: {} })) + ).to.equal(false); + }); + + it('should return false if the apiKey is an empty string', function () { + expect( + spec.isBidRequestValid(Object.assign(bid, { params: { apiKey: '' } })) + ).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const consentString = 'AAAAAAAAA=='; + + const bidderRequest = { + gdprConsent: { + consentString: consentString, + gdprApplies: true, + }, + refererInfo: { + referer: 'https://referer', + canonicalUrl: 'https://canonical', + }, + }; + + const requests = spec.buildRequests([bid, bid], bidderRequest); + const request = requests[0]; + const payload = JSON.parse(request.data); + + it('should return as many server requests as bidder requests', function () { + expect(requests.length).to.equal(2); + }); + + it('should have a post method', function () { + expect(request.method).to.equal('POST'); + }); + + it('should send the bidder id', function () { + expect(payload.request_id).to.equal(bidId); + }); + + it('should send referer information to the request', function () { + expect(payload.referer).to.equal('https://referer'); + expect(payload.referer_canonical).to.equal('https://canonical'); + }); + + it('should send gdpr consent information to the request', function () { + expect(payload.consent_string).to.equal(consentString); + expect(payload.consent_required).to.equal(true); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + requestId: bidId, + cpm: 0.5, + currency: 'USD', + ad: '', + }; + + const serverTimeoutResponse = { + requestId: bidId, + timeout: true, + ad: '', + }; + + const serverEmptyAdResponse = { + requestId: bidId, + cpm: 0.5, + currency: 'USD', + ad: '', + }; + + it('should return a proper bid response', function () { + const result = spec.interpretResponse({ body: serverResponse }, bid); + + expect(result.length).to.equal(1); + + expect(Object.keys(result[0])).to.have.members( + Object.keys(serverResponse) + ); + }); + + it('should return an empty response when the server answers with a timeout', function () { + const result = spec.interpretResponse( + { body: serverTimeoutResponse }, + bid + ); + expect(result).to.deep.equal([]); + }); + + it('should return an empty response when the server answers with an empty ad', function () { + const result = spec.interpretResponse( + { body: serverEmptyAdResponse }, + bid + ); + expect(result).to.deep.equal([]); + }); + }); +}); From c27ff14b31c5a7a08695eda7dc929c768dbbb809 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Tue, 9 Feb 2021 02:12:10 -0800 Subject: [PATCH 143/325] Gulp test file example added in readme (#6287) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 40df62ccee4..3e541080bf9 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,11 @@ To run the unit tests: gulp test ``` +To run the unit tests for a perticular file (example for pubmaticBidAdapter_spec.js): +```bash +gulp test --file "test/spec/modules/pubmaticBidAdapter_spec.js" +``` + To generate and view the code coverage reports: ```bash From dde585bbd38d16157668a955690b00989dcf100b Mon Sep 17 00:00:00 2001 From: Pooja Pasawala Date: Tue, 9 Feb 2021 02:38:53 -0800 Subject: [PATCH 144/325] Sharethrough: Add support for ID5, Shared ID, and Live Intent ID (#6261) * Update prebid adapter universal ids to include ID5, SharedID, and LiveIntent ID. [#176447070](https://www.pivotaltracker.com/story/show/176447070) Co-authored-by: Mathieu Pheulpin * Addressing review [#176447070] * Quick rewrite [#176447070] * Address ID5 review, forward linkType to adserver * Reformatting SharedID to align with ID5 Co-authored-by: Mathieu Pheulpin --- modules/sharethroughBidAdapter.js | 48 +++++++++++++------ .../modules/sharethroughBidAdapter_spec.js | 42 +++++++++++++++- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 89484b1c68b..24be8673615 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; -const VERSION = '3.3.0'; +const VERSION = '3.3.1'; const BIDDER_CODE = 'sharethrough'; const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; const DEFAULT_SIZE = [1, 1]; @@ -31,6 +31,8 @@ export const sharethroughAdapterSpec = { strVersion: VERSION }; + Object.assign(query, handleUniversalIds(bidRequest)); + const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; query.secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); @@ -46,20 +48,6 @@ export const sharethroughAdapterSpec = { query.us_privacy = bidderRequest.uspConsent } - if (bidRequest.userId && bidRequest.userId.tdid) { - query.ttduid = bidRequest.userId.tdid; - } - - if (bidRequest.userId && bidRequest.userId.pubcid) { - query.pubcid = bidRequest.userId.pubcid; - } else if (bidRequest.crumbs && bidRequest.crumbs.pubcid) { - query.pubcid = bidRequest.crumbs.pubcid; - } - - if (bidRequest.userId && bidRequest.userId.idl_env) { - query.idluid = bidRequest.userId.idl_env; - } - if (bidRequest.schain) { query.schain = JSON.stringify(bidRequest.schain); } @@ -147,6 +135,36 @@ export const sharethroughAdapterSpec = { onSetTargeting: (bid) => {} }; +function handleUniversalIds(bidRequest) { + if (!bidRequest.userId) return {}; + + const universalIds = {}; + + const ttd = utils.deepAccess(bidRequest, 'userId.tdid'); + if (ttd) universalIds.ttduid = ttd; + + const pubc = utils.deepAccess(bidRequest, 'userId.pubcid') || utils.deepAccess(bidRequest, 'crumbs.pubcid'); + if (pubc) universalIds.pubcid = pubc; + + const idl = utils.deepAccess(bidRequest, 'userId.idl_env'); + if (idl) universalIds.idluid = idl; + + const id5 = utils.deepAccess(bidRequest, 'userId.id5id.uid'); + if (id5) { + universalIds.id5uid = { id: id5 }; + const id5link = utils.deepAccess(bidRequest, 'userId.id5id.ext.linkType'); + if (id5link) universalIds.id5uid.linkType = id5link; + } + + const lipb = utils.deepAccess(bidRequest, 'userId.lipb.lipbid'); + if (lipb) universalIds.liuid = lipb; + + const shd = utils.deepAccess(bidRequest, 'userId.sharedid'); + if (shd) universalIds.shduid = shd; // object with keys: id & third + + return universalIds; +} + function getLargestSize(sizes) { function area(size) { return size[0] * size[1]; diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index cd9071a6098..b3451a09dde 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from '../../../src/utils.js'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); const bidRequests = [ @@ -15,7 +16,20 @@ const bidRequests = [ userId: { tdid: 'fake-tdid', pubcid: 'fake-pubcid', - idl_env: 'fake-identity-link' + idl_env: 'fake-identity-link', + id5id: { + uid: 'fake-id5id', + ext: { + linkType: 2 + } + }, + sharedid: { + id: 'fake-sharedid', + third: 'fake-sharedthird' + }, + lipb: { + lipbid: 'fake-lipbid' + } }, crumbs: { pubcid: 'fake-pubcid-in-crumbs-obj' @@ -329,6 +343,15 @@ describe('sharethrough adapter spec', function() { expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); }); + it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + + ' crumbs object of the bidrequest', function() { + const bidData = utils.deepClone(bidRequests); + delete bidData[0].userId.pubcid; + + const bidRequest = spec.buildRequests(bidData)[0]; + expect(bidRequest.data.pubcid).to.eq('fake-pubcid-in-crumbs-obj'); + }); + it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + ' crumbs object of the bidrequest', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; @@ -341,6 +364,23 @@ describe('sharethrough adapter spec', function() { expect(bidRequest.data.idluid).to.eq('fake-identity-link'); }); + it('should add the id5uid parameter if a bid request contains a value for ID5', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.id5uid.id).to.eq('fake-id5id'); + expect(bidRequest.data.id5uid.linkType).to.eq(2); + }); + + it('should add the shduid parameter if a bid request contains a value for Shared ID', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.shduid.id).to.eq('fake-sharedid'); + expect(bidRequest.data.shduid.third).to.eq('fake-sharedthird'); + }); + + it('should add the liuid parameter if a bid request contains a value for LiveIntent ID', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.liuid).to.eq('fake-lipbid'); + }); + it('should add Sharethrough specific parameters', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0]).to.deep.include({ From 73cfeb537a9ffbca707e80c1c171d14f57b46869 Mon Sep 17 00:00:00 2001 From: lowendavid <66423906+lowendavid@users.noreply.github.com> Date: Tue, 9 Feb 2021 11:39:14 +0100 Subject: [PATCH 145/325] SmartAdServer Bid Adapter: image sync and noAd (#6236) * SIM-889 Now we have image based sync * SIM-889 Added test to check noad and image sync * SIM-889 Fixing indenting issues --- modules/smartadserverBidAdapter.js | 12 +++- .../modules/smartadserverBidAdapter_spec.js | 69 ++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index ed9003e3b4d..bb9364c72c3 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -135,7 +135,7 @@ export const spec = { const bidResponses = []; let response = serverResponse.body; try { - if (response) { + if (response && !response.isNoAd) { const bidRequest = JSON.parse(bidRequestString.data); let bidResponse = { @@ -147,7 +147,8 @@ export const spec = { dealId: response.dealId, currency: response.currency, netRevenue: response.isNetCpm, - ttl: response.ttl + ttl: response.ttl, + dspPixels: response.dspPixels }; if (bidRequest.mediaType === VIDEO) { @@ -182,6 +183,13 @@ export const spec = { type: 'iframe', url: serverResponses[0].body.cSyncUrl }); + } else if (syncOptions.pixelEnabled && serverResponses.length > 0 && serverResponses[0].body.dspPixels !== undefined) { + serverResponses[0].body.dspPixels.forEach(function(pixel) { + syncs.push({ + type: 'image', + url: pixel + }); + }); } return syncs; } diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index e3bca240a47..749de43b9af 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -115,7 +115,41 @@ describe('Smart bid adapter tests', function () { ttl: 300, adUrl: 'http://awesome.fake.url', ad: '< --- awesome script --- >', - cSyncUrl: 'http://awesome.fake.csync.url' + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: false + } + }; + + var BID_RESPONSE_IS_NO_AD = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: true + } + }; + + var BID_RESPONSE_IMAGE_SYNC = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: false, + dspPixels: ['pixelOne', 'pixelTwo', 'pixelThree'] } }; @@ -149,6 +183,18 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('ckid').and.to.equal(42); }); + it('Verify parse response with no ad', function () { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE_IS_NO_AD, request[0]); + expect(bids).to.have.lengthOf(0); + + expect(function () { + spec.interpretResponse(BID_RESPONSE_IS_NO_AD, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + it('Verify parse response', function () { const request = spec.buildRequests(DEFAULT_PARAMS); const bids = spec.interpretResponse(BID_RESPONSE, request[0]); @@ -258,6 +304,27 @@ describe('Smart bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); + it('Verifies user sync using dspPixels', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE_IMAGE_SYNC]); + expect(syncs).to.have.lengthOf(3); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE_IMAGE_SYNC]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); + }); + describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); From fae47aab59b29b54421b57cfc22ab9271b436ebf Mon Sep 17 00:00:00 2001 From: iskmerof Date: Wed, 10 Feb 2021 06:19:01 -0500 Subject: [PATCH 146/325] Add client Alias Adkernel (#6291) Adding "bcm" alias to Adkernel adapter --- modules/adkernelBidAdapter.js | 2 +- test/spec/modules/adkernelBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 20ed65fe2e2..03fa5c2b2d9 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -52,7 +52,7 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { export const spec = { code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads'], + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads', 'bcm'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 4454aa00a71..0d772423a22 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -556,7 +556,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(11); + expect(spec.aliases).to.have.lengthOf(12); }); }); From 4a7b465d6bd640046b7da3675e203082caed2cd4 Mon Sep 17 00:00:00 2001 From: Ian Flournoy Date: Wed, 10 Feb 2021 06:55:26 -0500 Subject: [PATCH 147/325] [ParrableIdSystem] Add GVLID and handle TC Consent data (#6283) * Added GDPR support * Remove forgotten .only Co-authored-by: Victor --- modules/parrableIdSystem.js | 22 ++++++++++++--- test/spec/modules/parrableIdSystem_spec.js | 32 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index afaf794513e..f072de8736f 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -14,12 +14,13 @@ import { getStorageManager } from '../src/storageManager.js'; const PARRABLE_URL = 'https://h.parrable.com/prebid'; const PARRABLE_COOKIE_NAME = '_parrable_id'; +const PARRABLE_GVLID = 928; const LEGACY_ID_COOKIE_NAME = '_parrable_eid'; const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout'; const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000; const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; -const storage = getStorageManager(); +const storage = getStorageManager(PARRABLE_GVLID); function getExpirationDate() { const oneYearFromNow = new Date(utils.timestamp() + ONE_YEAR_MS); @@ -167,7 +168,7 @@ function shouldFilterImpression(configParams, parrableId) { return !isAllowed() || isBlocked(); } -function fetchId(configParams) { +function fetchId(configParams, gdprConsentData) { if (!isValidConfig(configParams)) return; let parrableId = readCookie(); @@ -183,6 +184,8 @@ function fetchId(configParams) { const eid = (parrableId) ? parrableId.eid : null; const refererInfo = getRefererInfo(); const uspString = uspDataHandler.getConsentData(); + const gdprApplies = (gdprConsentData && typeof gdprConsentData.gdprApplies === 'boolean' && gdprConsentData.gdprApplies); + const gdprConsentString = (gdprConsentData && gdprApplies && gdprConsentData.consentString) || ''; const partners = configParams.partners || configParams.partner const trackers = typeof partners === 'string' ? partners.split(',') @@ -198,6 +201,7 @@ function fetchId(configParams) { const searchParams = { data: encodeBase64UrlSafe(btoa(JSON.stringify(data))), + gdpr: gdprApplies ? 1 : 0, _rand: Math.random() }; @@ -205,6 +209,10 @@ function fetchId(configParams) { searchParams.us_privacy = uspString; } + if (gdprApplies) { + searchParams.gdpr_consent = gdprConsentString; + } + const options = { method: 'GET', withCredentials: true @@ -251,7 +259,7 @@ function fetchId(configParams) { callback, id: parrableId }; -}; +} /** @type {Submodule} */ export const parrableIdSubmodule = { @@ -260,6 +268,12 @@ export const parrableIdSubmodule = { * @type {string} */ name: 'parrableId', + /** + * Global Vendor List ID + * @type {number} + */ + gvlid: PARRABLE_GVLID, + /** * decode the stored id value for passing to bid requests * @function @@ -282,7 +296,7 @@ export const parrableIdSubmodule = { */ getId(config, gdprConsentData, currentStoredId) { const configParams = (config && config.params) || {}; - return fetchId(configParams); + return fetchId(configParams, gdprConsentData); } }; diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 5a10529778c..f0aac7666f9 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -210,6 +210,38 @@ describe('Parrable ID System', function() { removeParrableCookie(); }); }); + + describe('GDPR consent', () => { + let callbackSpy = sinon.spy(); + + const config = { + params: { + partner: 'partner' + } + }; + + const gdprConsentTestCases = [ + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: { gdpr: 1, gdpr_consent: 'expectedConsentString' } }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, + { consentData: { gdprApplies: true, consentString: undefined }, expected: { gdpr: 1, gdpr_consent: '' } }, + { consentData: { gdprApplies: 'yes', consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, + { consentData: undefined, expected: { gdpr: 0 } } + ]; + + gdprConsentTestCases.forEach((testCase, index) => { + it(`should call user sync url with the gdprConsent - case ${index}`, () => { + parrableIdSubmodule.getId(config, testCase.consentData).callback(callbackSpy); + + if (testCase.expected.gdpr === 1) { + expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); + expect(server.requests[0].url).to.contain('gdpr_consent=' + testCase.expected.gdpr_consent); + } else { + expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); + expect(server.requests[0].url).to.not.contain('gdpr_consent'); + } + }) + }); + }); }); describe('parrableIdSystem.decode()', function() { From 8e2501e881d6f2790d474e1393b56d9af5400478 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Wed, 10 Feb 2021 16:24:04 -0500 Subject: [PATCH 148/325] 4.26.0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8b6af04379..97020126830 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.26.0-pre", + "version": "4.26.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ff0b015c96c1c707ca66a66505a350c935adac62 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Wed, 10 Feb 2021 16:38:25 -0500 Subject: [PATCH 149/325] 4.27.0-pre --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97020126830..45493de7aea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.26.0", + "version": "4.27.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a037c5455b038b25c0be07a1625aac4761ef7f3e Mon Sep 17 00:00:00 2001 From: pnh-pubx <73683023+pnh-pubx@users.noreply.github.com> Date: Thu, 11 Feb 2021 04:34:49 +0530 Subject: [PATCH 150/325] Updated data mapping of winning bid and auction logs in pubxai analytics adapter (#6285) Co-authored-by: Phaneendra Hegde --- modules/pubxaiAnalyticsAdapter.js | 13 +- .../modules/pubxaiAnalyticsAdapter_spec.js | 164 ++++++------------ 2 files changed, 64 insertions(+), 113 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 7e2f5061621..894752d607b 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -27,9 +27,13 @@ var pubxaiAnalyticsAdapter = Object.assign(adapter( if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { args.forEach(item => { mapBidResponse(item, 'timeout'); }); } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { - events.auctionInit = args; + events.floorDetail = {}; + if (typeof args.bidderRequests[0].bids[0] !== 'undefined' && typeof args.bidderRequests[0].bids[0].floorData !== 'undefined') { + Object.assign(events.floorDetail, args.bidderRequests[0].bids[0].floorData); + } auctionTimestamp = args.timestamp; } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { + events.bids = []; mapBidRequests(args).forEach(item => { events.bids.push(item) }); } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { mapBidResponse(args, 'response'); @@ -56,6 +60,8 @@ function mapBidRequests(params) { adUnitCode: bid.adUnitCode, requestId: bid.bidderRequestId, auctionId: bid.auctionId, + placementId: bid.params.placementId, + floorData: bid.floorData, transactionId: bid.transactionId, sizes: utils.parseSizesInput(bid.mediaTypes.banner.sizes).toString(), renderStatus: 1, @@ -134,8 +140,9 @@ pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { function send(data, status) { if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { let location = utils.getWindowLocation(); - if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - Object.assign(data.auctionInit, { host: location.host, path: location.pathname, search: location.search }); + if (typeof data !== 'undefined') { + data.pageDetail = {}; + Object.assign(data.pageDetail, { host: location.host, path: location.pathname, search: location.search }); } data.initOptions = initOptions; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 91c81dcae8d..95245f2c6c9 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -27,6 +27,8 @@ describe('pubxai analytics adapter', function() { pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625' }; + let location = utils.getWindowLocation(); + let prebidEvent = { 'auctionInit': { 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', @@ -219,6 +221,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -381,6 +389,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -449,6 +463,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -488,9 +508,13 @@ describe('pubxai analytics adapter', function() { 'params': [{ 'placementId': 13144370 }] - } + }, + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search + }, }; - let location = utils.getWindowLocation(); let expectedAfterBid = { 'bids': [{ @@ -509,6 +533,12 @@ describe('pubxai analytics adapter', function() { 'mediaType': 'banner', 'statusMessage': 'Bid available', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -527,118 +557,21 @@ describe('pubxai analytics adapter', function() { 'timeToRespond': 267, 'responseTimestamp': 1603865707449, 'platform': navigator.platform, + 'placementId': 13144370, 'deviceType': getDeviceType() }], - 'auctionInit': { + 'pageDetail': { 'host': location.host, 'path': location.pathname, - 'search': location.search, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'timestamp': 1603865707180, - 'auctionStatus': 'inProgress', - 'adUnits': [{ - 'code': '/19968336/header-bid-tag-1', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'new model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloor', - 'fetchStatus': 'success' - } - }], - 'sizes': [ - [ - 300, - 250 - ] - ], - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' - }], - 'adUnitCodes': [ - '/19968336/header-bid-tag-1' - ], - 'bidderRequests': [{ - 'bidderCode': 'appnexus', - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'bidderRequestId': '184cbc05bb90ba', - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'new model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloor', - 'fetchStatus': 'success' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '248f9a4489835e', - 'bidderRequestId': '184cbc05bb90ba', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }], - 'auctionStart': 1603865707180, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://local-pnh.net:8080/stream/', - 'reachedTop': true, - 'isAmp': false, - 'numIframes': 0, - 'stack': [ - 'http://local-pnh.net:8080/stream/' - ], - 'canonicalUrl': null - }, - 'start': 1603865707182 - }], - 'noBids': [], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 1000, - 'config': { - 'samplingRate': '1', - 'pubxId': '6c415fc0-8b0e-4cf5-be73-01526a4db625' - } + 'search': location.search + }, + 'floorDetail': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false }, 'initOptions': initOptions }; @@ -660,6 +593,12 @@ describe('pubxai analytics adapter', function() { 'status': 'rendered', 'statusMessage': 'Bid available', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -680,6 +619,11 @@ describe('pubxai analytics adapter', function() { 'platform': navigator.platform, 'deviceType': getDeviceType() }, + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search + }, 'initOptions': initOptions } From c9e8869ddd867a475efa4531d594ed422252aa8c Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Thu, 11 Feb 2021 14:41:38 +0300 Subject: [PATCH 151/325] Grid Bid Adapter: Added video protocols to the ad request (#6299) --- modules/gridBidAdapter.js | 6 +++++- test/spec/modules/gridBidAdapter_spec.js | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 6e610b67e0e..b296fe39ae4 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -365,7 +365,7 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { } function createVideoRequest(bid, mediaType) { - const {playerSize, mimes, durationRangeSec} = mediaType; + const {playerSize, mimes, durationRangeSec, protocols} = mediaType; const size = (playerSize || bid.sizes || [])[0]; if (!size) return; @@ -380,6 +380,10 @@ function createVideoRequest(bid, mediaType) { result.maxduration = durationRangeSec[1]; } + if (protocols && protocols.length) { + result.protocols = protocols; + } + return result; } diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 084c67562e6..b4e87119111 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -105,7 +105,8 @@ describe('TheMediaGrid Adapter', function () { 'sizes': [[728, 90]], 'mediaTypes': { 'video': { - 'playerSize': [[400, 600]] + 'playerSize': [[400, 600]], + 'protocols': [1, 2, 3] }, 'banner': { 'sizes': [[728, 90]] @@ -281,7 +282,8 @@ describe('TheMediaGrid Adapter', function () { }, 'video': { 'w': 400, - 'h': 600 + 'h': 600, + 'protocols': [1, 2, 3] } }] }); From f672209110ef30b9cdf96048d4e8285d43716b4a Mon Sep 17 00:00:00 2001 From: bretg Date: Thu, 11 Feb 2021 06:52:17 -0500 Subject: [PATCH 152/325] Rubicon Bid Adapter: updated transactionId to auctionId for OpenRTB (#6298) --- modules/rubiconBidAdapter.js | 4 ++-- test/spec/modules/rubiconBidAdapter_spec.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 395b7a693b2..e9f25c9411e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -153,7 +153,7 @@ export const spec = { test: config.getConfig('debug') ? 1 : 0, cur: ['USD'], source: { - tid: bidRequest.transactionId + tid: bidRequest.auctionId }, tmax: bidderRequest.timeout, imp: [{ @@ -424,7 +424,7 @@ export const spec = { 'rp_floor': (params.floor = parseFloat(params.floor)) >= 0.01 ? params.floor : undefined, 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, - 'x_source.tid': bidRequest.transactionId, + 'x_source.tid': bidRequest.auctionId, 'x_source.pchain': params.pchain, 'p_screen_res': _getScreenResolution(), 'tk_user_key': params.userId, diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 8c25d97dada..5b881299210 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -415,7 +415,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'x_source.pchain': 'GAM:11111-reseller1:22222', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', @@ -572,7 +572,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -895,7 +895,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2067,7 +2067,7 @@ describe('the rubicon adapter', function () { 'p_pos': 'atf', 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', From 2702e964cd0d540002450ddacd776f7c1b635794 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Fri, 12 Feb 2021 02:50:29 -0800 Subject: [PATCH 153/325] Fix for Issue 6117: Added Module Name in Build to Comments (#6297) --- gulpfile.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index b7a9e442a8c..51536992bd0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -31,7 +31,7 @@ const execa = require('execa'); var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); -var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n'; +var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '\nModules: <%= modules %> */\n'; var port = 9999; const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; const FAKE_SERVER_PORT = 4444; @@ -157,12 +157,13 @@ function makeWebpackPkg() { const analyticsSources = helpers.getAnalyticsSources(); const moduleSources = helpers.getModulePaths(externalModules); + const modulesString = (externalModules.length > 0) ? externalModules.join(', ') : 'All available modules in current version.'; return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) .pipe(uglify()) - .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) + .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid, modules: modulesString }))) .pipe(gulp.dest('build/dist')); } From a6c3986c0a306f4dc50b149d4a5f617282741a96 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 12 Feb 2021 06:39:20 -0500 Subject: [PATCH 154/325] map tripleliftBidAdapter.js tl_source to bid.meta.mediaType (#6303) --- modules/tripleliftBidAdapter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index f97165f3d1c..029cdd68331 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -322,6 +322,14 @@ function _buildResponseObject(bidderRequest, bid) { if (bid.adomain && bid.adomain.length) { bidResponse.meta.advertiserDomains = bid.adomain; } + + if (bid.tl_source && bid.tl_source == 'hdx') { + bidResponse.meta.mediaType = 'banner'; + } + + if (bid.tl_source && bid.tl_source == 'tlx') { + bidResponse.meta.mediaType = 'native'; + } }; return bidResponse; } From 62ca969575e82eb1ae5416e412982d562d74c2d5 Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Fri, 12 Feb 2021 15:54:43 +0100 Subject: [PATCH 155/325] Tappx Bid Adapter: new bidder adapter added (#6233) * ADD: tappx bid adapter * FIX: replace .includes by .indexOf * UPDATE: Expand the test coverage * FIX: format spacing tests * FIX: get auctionId from validBidRequests * UPDATE: add bannerMediaType tablet sizes * FIX: get timeout from bidderRequest.timeout * UPDATE: replace the way to get the hostname * UPDATE: adding support multiple bid requests in a single call * UPDATE: remove hardcoded test payload param Co-authored-by: marc_tappx --- modules/tappxBidAdapter.js | 289 ++++++++++++++++++++++ modules/tappxBidAdapter.md | 37 +++ test/spec/modules/tappxBidAdapter_spec.js | 86 +++++++ 3 files changed, 412 insertions(+) create mode 100644 modules/tappxBidAdapter.js create mode 100644 modules/tappxBidAdapter.md create mode 100644 test/spec/modules/tappxBidAdapter_spec.js diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js new file mode 100644 index 00000000000..7782f151802 --- /dev/null +++ b/modules/tappxBidAdapter.js @@ -0,0 +1,289 @@ +'use strict'; + +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'tappx'; +const TTL = 360; +const CUR = 'USD'; +var HOST; +var hostDomain; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if ((bid.params == null) || (bid.params.endpoint == null) || (bid.params.tappxkey == null)) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters. ${JSON.stringify(bid)}`); + return false; + } + return true; + }, + + /** + * Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. + * Make a server request from the list of BidRequests. + * + * @param {*} validBidRequests + * @param {*} bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + let requests = []; + validBidRequests.forEach(oneValidRequest => { + requests.push(buildOneRequest(oneValidRequest, bidderRequest)); + }); + return requests; + }, + + /** + * Parse the response and generate one or more bid objects. + * + * @param {*} serverResponse + * @param {*} originalRequest + */ + interpretResponse: function(serverResponse, originalRequest) { + const responseBody = serverResponse.body; + if (!serverResponse.body) { + utils.logWarn('[TAPPX]: Empty response body HTTP 204, no bids'); + return []; + } + + const bids = []; + responseBody.seatbid.forEach(serverSeatBid => { + serverSeatBid.bid.forEach(serverBid => { + bids.push(interpretBannerBid(serverBid, originalRequest)); + }); + }); + + return bids; + }, + + /** + * If the publisher allows user-sync activity, the platform will call this function and the adapter may register pixels and/or iframe user syncs. + * + * @param {*} syncOptions + * @param {*} serverResponses + * @param {*} gdprConsent + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let url = `https://${hostDomain}/cs/usersync.php?`; + + // GDPR & CCPA + if (gdprConsent) { + url += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + url += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent) { + url += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + // SyncOptions + if (syncOptions.iframeEnabled) { + url += '&type=iframe' + return [{ + type: 'iframe', + url: url + }]; + } else { + url += '&type=img' + return [{ + type: 'image', + url: url + }]; + } + } +} + +/** + * Parse the response and generate one bid object. + * + * @param {object} serverBid Bid by OpenRTB 2.5 + * @returns {object} Prebid banner bidObject + */ +function interpretBannerBid(serverBid, request) { + return { + requestId: request.bids.bidId, + cpm: serverBid.price, + currency: serverBid.cur ? serverBid.cur : CUR, + width: serverBid.w, + height: serverBid.h, + ad: serverBid.adm, + ttl: TTL, + creativeId: serverBid.crid, + netRevenue: true, + mediaType: BANNER, + } +} + +/** +* Build and makes the request +* +* @param {*} validBidRequests +* @param {*} bidderRequest +* @return response ad +*/ +function buildOneRequest(validBidRequests, bidderRequest) { + HOST = utils.deepAccess(validBidRequests, 'params.host'); + hostDomain = HOST.split('/', 1)[0]; + + const ENDPOINT = utils.deepAccess(validBidRequests, 'params.endpoint'); + const TAPPXKEY = utils.deepAccess(validBidRequests, 'params.tappxkey'); + const BIDFLOOR = utils.deepAccess(validBidRequests, 'params.bidfloor'); + const bannerMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.banner'); + const { refererInfo } = bidderRequest; + + // let requests = []; + let payload = {}; + let publisher = {}; + let tagid; + let api = {}; + + // > App/Site object + if (utils.deepAccess(validBidRequests, 'params.app')) { + let app = {}; + app.name = utils.deepAccess(validBidRequests, 'params.app.name'); + app.bundle = utils.deepAccess(validBidRequests, 'params.app.bundle'); + app.domain = utils.deepAccess(validBidRequests, 'params.app.domain'); + publisher.name = utils.deepAccess(validBidRequests, 'params.app.publisher.name'); + publisher.domain = utils.deepAccess(validBidRequests, 'params.app.publisher.domain'); + tagid = `${app.name}_typeAdBanVid_${getOs()}`; + payload.app = app; + api[0] = utils.deepAccess(validBidRequests, 'params.api') ? utils.deepAccess(validBidRequests, 'params.api') : [3, 5]; + } else { + let site = {}; + site.name = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + site.bundle = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + site.domain = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + publisher.name = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + publisher.domain = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + tagid = `${site.name}_typeAdBanVid_${getOs()}`; + payload.site = site; + } + // < App/Site object + + // > Imp object + let imp = {}; + let w; + let h; + + if (bannerMediaType) { + let banner = {}; + w = bannerMediaType.sizes[0][0]; + h = bannerMediaType.sizes[0][1]; + banner.w = w; + banner.h = h; + if ( + ((bannerMediaType.sizes[0].indexOf(480) >= 0) && (bannerMediaType.sizes[0].indexOf(320) >= 0)) || + ((bannerMediaType.sizes[0].indexOf(768) >= 0) && (bannerMediaType.sizes[0].indexOf(1024) >= 0))) { + banner.pos = 7 + } else { + banner.pos = 4 + } + + banner.api = api; + + let format = {}; + format[0] = {}; + format[0].w = w; + format[0].h = h; + banner.format = format; + + imp.banner = banner; + } + + imp.id = validBidRequests.bidId; + imp.tagid = tagid; + imp.secure = 1; + + imp.bidfloor = utils.deepAccess(validBidRequests, 'params.bidfloor'); + // < Imp object + + // > Device object + let device = {}; + // Mandatory + device.os = getOs(); + device.ip = 'peer'; + device.ua = navigator.userAgent; + device.ifa = validBidRequests.ifa; + + // Optional + device.h = screen.height; + device.w = screen.width; + device.dnt = utils.getDNT() ? 1 : 0; + device.language = getLanguage(); + device.make = navigator.vendor ? navigator.vendor : ''; + + let geo = {}; + geo.country = utils.deepAccess(validBidRequests, 'params.geo.country'); + // < Device object + + // > Params + let params = {}; + params.host = 'tappx.com'; + params.tappxkey = TAPPXKEY; + params.endpoint = ENDPOINT; + params.bidfloor = BIDFLOOR; + // < Params + + // > GDPR + let regs = {}; + regs.gdpr = 0; + if (!(bidderRequest.gdprConsent == null)) { + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { regs.gdpr = bidderRequest.gdprConsent.gdprApplies; } + if (regs.gdpr) { regs.consent = bidderRequest.gdprConsent.consentString; } + } + + // CCPA + regs.ext = {}; + if (!(bidderRequest.uspConsent == null)) { + regs.ext.us_privacy = bidderRequest.uspConsent; + } + + // COPPA compliance + if (config.getConfig('coppa') === true) { + regs.coppa = config.getConfig('coppa') === true ? 1 : 0; + } + // < GDPR + + // > Payload + payload.id = validBidRequests.auctionId; + payload.test = utils.deepAccess(validBidRequests, 'params.test') ? 1 : 0; + payload.at = 1; + payload.tmax = bidderRequest.timeout ? bidderRequest.timeout : 600; + payload.bidder = BIDDER_CODE; + payload.imp = [imp]; + + payload.device = device; + payload.params = params; + payload.regs = regs; + // < Payload + + return { + method: 'POST', + url: `https://${HOST}/${ENDPOINT}?type_cnn=prebidjs`, + data: JSON.stringify(payload), + bids: validBidRequests + }; +} + +function getLanguage() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; +} + +function getOs() { + let ua = navigator.userAgent; + if (ua == null) { return 'unknown'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'ios'; } else if (ua.match(/Android/)) { return 'android'; } else if (ua.match(/Window/)) { return 'windows'; } else { return 'unknown'; } +} + +registerBidder(spec); diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md new file mode 100644 index 00000000000..d9ffd98b6c5 --- /dev/null +++ b/modules/tappxBidAdapter.md @@ -0,0 +1,37 @@ +# Overview +``` +Module Name: Tappx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@tappx.com +``` + +# Description +Module that connects to :tappx demand sources. +Please use ```tappx``` as the bidder code. +Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] + +# Banner Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[320,50]] + } + }, + bids: [ + { + bidder: "tappx", + params: { + host: "testing.ssp.tappx.com/rtb/v2/", + tappxkey: "pub-1234-android-1234", + endpoint: "ZZ1234PBJS", + bidfloor: 0.005, + test: true // Optional for testing purposes + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js new file mode 100644 index 00000000000..c4410d8ce5e --- /dev/null +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -0,0 +1,86 @@ +import { assert } from 'chai'; +import {spec} from 'modules/tappxBidAdapter'; + +describe('Tappx adapter tests', function () { + describe('isBidRequestValid', function () { + let bid = { bidder: 'tappx', params: { host: 'testing.ssp.tappx.com', tappxkey: 'pub-1234-test-1234', endpoint: 'ZZ1234PBJS', bidfloor: 0.005 } }; + + it('should return true when required params found', function () { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params are missing', function () { + const bid = { + host: 'testing.ssp.tappx.com' + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequest', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + // Web Test + let validBidRequests = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'api': [3, 5]}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'div-1', 'transactionId': '713f2c01-61e3-45b5-9e4e-2b163033f3d6', 'sizes': [[320, 480]], 'bidId': '27818d05971607', 'bidderRequestId': '1320551a307df5', 'auctionId': '3f1281d3-9860-4657-808d-3c1d42231ef3', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}] + // App Test + let validAppBidRequests = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'api': [3, 5], 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 50]]}}, 'adUnitCode': 'div-1', 'transactionId': '713f2c01-61e3-45b5-9e4e-2b163033f3d6', 'sizes': [[320, 50]], 'bidId': '27818d05971607', 'bidderRequestId': '1320551a307df5', 'auctionId': '3f1281d3-9860-4657-808d-3c1d42231ef3', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}] + let bidderRequest = {'bidderCode': 'tappx', 'auctionId': '6cca2192-2262-468b-8a3c-d00c58a5d911', 'bidderRequestId': '1ae5c6a02684df', 'bids': [{'bidder': 'tappx', 'params': {'host': 'tests.tappx.com', 'tappxkey': 'pub-1234-test-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'banner-ad-div', 'transactionId': 'c44cdbde-ab6d-47a0-8dde-6b4ff7909a35', 'sizes': [[320, 50]], 'bidId': '2e3a5feb30cfe4', 'bidderRequestId': '1ae5c6a02684df', 'auctionId': '6cca2192-2262-468b-8a3c-d00c58a5d911', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1611308859094, 'timeout': 700, 'refererInfo': {'referer': 'http://tappx.local:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://tappx.local:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': consentString, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; + + it('should add gdpr/usp consent information to the request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.regs.gdpr).to.exist.and.to.be.true; + expect(payload.regs.consent).to.exist.and.to.equal(consentString); + expect(payload.regs.ext.us_privacy).to.exist; + }); + + it('should properly build a banner request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + + it('should properly build a banner request with app params', function () { + const request = spec.buildRequests(validAppBidRequests, bidderRequest); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + }); + + describe('interpretResponse', function () { + const bidRequest = { + data: {}, + bids: [ {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.05, 'api': [3, 5]}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'div-1', 'transactionId': '47dd44e8-e7db-417c-a8f1-621a2e1a117d', 'sizes': [[320, 480]], 'bidId': '2170932097e505', 'bidderRequestId': '140ba7a1ab7aeb', 'auctionId': '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0} ] + }; + + const serverResponse = {'body': {'id': '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', 'bidid': 'bid3811165568213389257', 'seatbid': [{'seat': '1', 'group': 0, 'bid': [{'id': '3811165568213389257', 'impid': 1, 'price': 0.05, 'adm': "\t", 'w': 320, 'h': 480, 'lurl': 'http://testing.ssp.tappx.com/rtb/RTBv2Loss?id=3811165568213389257&ep=ZZ1234PBJS&au=test&bu=localhost&sz=320x480&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', 'cid': '01744fbb521e9fb10ffea926190effea', 'crid': 'a13cf884e66e7c660afec059c89d98b6', 'adomain': []}]}], 'cur': 'USD'}, 'headers': {}}; + it('receive reponse with single placement', function () { + const bids = spec.interpretResponse(serverResponse, bidRequest); + const bid = bids[0]; + expect(bid.cpm).to.exist; + expect(bid.ad).to.match(/^ ortb2.site.ATTR - fpd.context.data.ATTR --> ortb2.site.ext.data - fpd.user.ATTR --> ortb2.user.ATTR - fpd.user.data.ATTR --> ortb2.user.ext.data 2) gptPreAuction: a) move adunit.fpd to adunit.ortb2 b) adUnit.ortb2Imp.ext.data.adserver.{name, adSlot} c) pbAdSlot moves to AdUnit.ortb2Imp.ext.data.pbAdSlot 3) pbsBidAdapter a) merge the new ortb2 and AdUnit.ortb2Imp.ext objects into the OpenRTB JSON. b) therefore imp[].ext.context.data.pbadslot is now changed to imp[].ext.data.pbadslot (no context) c) read adUnit.ortb2Imp.ext.data.adserver from the new location. Output location is moved to imp[].ext.data.adserver (no context) * FPD 2.0 Update Update to adrelevantis adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to amx adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to avocet adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to criteo adapter to look at config.ortb2 instead of config.fpd * Update to correct imp fpd structure * Update to s2s adapter to coincide with imp fpd alteration * Update to consolidate several lines of duplicate code into one location * Slight modification for ortb2Imp to use ortb2Imp.ext as opposed to ortb2Imp.ext.data * FPD 2.0 Update Update to grid adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to inmar adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to luponmedia adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to smaato adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to triplelift adapter to look at config.ortb2 instead of config.fpd * Update to gptPreAuction to move over to imp level ortb2 * FPD 2.0 Update Update to triplelift adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update * FPD 2.0 Update Update to jwplayerRtd adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to admixer adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to rubicon adapter to look at config.ortb2 instead of config.fpd * Update to fix keyword bug * Added backwards compatibility functions for FPD both global/bidder and adunit level data * Update to utilize new backward functionality for fpd 2.0 * Removed extra new line * Update to include new backward functionality for FPD 2.0 data * Update to utilize new backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Fixed typo in fpd config object location * Uodate to utilize backward functionality to pass FPD 2.0 data * Update to change all ortb2Imp.ext.data.adserver.adSlot references to ortb2Imp.ext.data.adserver.adslot - all lowercase. Corresponding adapter and unit tests to adhere to these changes * Fixed typo * Fixed typo * FPD 2.0 update to rubicon adapter to pass iab values * Updates: 1) Change function name 2) addAdUnits always pass array 3) Remove unecessary comment 4) Bug fix for ortb2.user.data to be filtered on legacy fpd conversion --- modules/admixerBidAdapter.js | 8 +- modules/adrelevantisBidAdapter.js | 6 +- modules/amxBidAdapter.js | 2 +- modules/avocetBidAdapter.js | 2 +- modules/criteoBidAdapter.js | 11 +- modules/gptPreAuction.js | 32 +-- modules/gridBidAdapter.js | 4 +- modules/inmarBidAdapter.js | 2 +- modules/jwplayerRtdProvider.js | 4 +- modules/luponmediaBidAdapter.js | 7 +- modules/prebidServerBidAdapter/index.js | 60 +++--- modules/rubiconBidAdapter.js | 97 +++++---- modules/smaatoBidAdapter.js | 6 +- modules/tripleliftBidAdapter.js | 11 +- src/adapterManager.js | 2 +- src/config.js | 135 ++++++++++++- src/prebid.js | 6 +- test/spec/adUnits_spec.js | 11 + test/spec/config_spec.js | 15 ++ .../modules/adrelevantisBidAdapter_spec.js | 10 +- test/spec/modules/amxBidAdapter_spec.js | 20 +- test/spec/modules/criteoBidAdapter_spec.js | 24 ++- test/spec/modules/gptPreAuction_spec.js | 54 ++--- test/spec/modules/gridBidAdapter_spec.js | 10 + test/spec/modules/jwplayerRtdProvider_spec.js | 48 ++--- .../modules/prebidServerBidAdapter_spec.js | 110 ++++++---- test/spec/modules/rubiconBidAdapter_spec.js | 190 +++++++++++------- test/spec/modules/smaatoBidAdapter_spec.js | 6 +- .../spec/modules/tripleliftBidAdapter_spec.js | 20 +- 29 files changed, 583 insertions(+), 330 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index d2a6f9a639e..6cf738a0086 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -21,7 +21,7 @@ export const spec = { buildRequests: function (validRequest, bidderRequest) { const payload = { imps: [], - fpd: config.getConfig('fpd') + fpd: config.getLegacyFpd(config.getConfig('ortb2')) }; let endpointUrl; if (bidderRequest) { @@ -42,7 +42,11 @@ export const spec = { } } validRequest.forEach((bid) => { - payload.imps.push(bid); + let imp = {}; + Object.keys(bid).forEach(key => { + (key === 'ortb2Imp') ? imp.fpd = config.getLegacyImpFpd(bid[key]) : imp[key] = bid[key]; + }); + payload.imps.push(imp); }); const payloadString = JSON.stringify(payload); return { diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 5da941c65ca..c6298cffde9 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -120,11 +120,11 @@ export const spec = { payload.referrer_detection = refererinfo; } - let fpdcfg = config.getConfig('fpd') + let fpdcfg = config.getLegacyFpd(config.getConfig('ortb2')); if (fpdcfg && fpdcfg.context) { let fdata = { - keywords: fpdcfg.context.keywords, - category: fpdcfg.context.data.category + keywords: fpdcfg.context.keywords || '', + category: utils.deepAccess(fpdcfg, 'context.data.category') || '' } payload.fpd = fdata; } diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index a1fa202c154..497c2142b9b 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -259,7 +259,7 @@ export const spec = { d: '', m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, - fpd: config.getConfig('fpd'), + fpd: config.getLegacyFpd(config.getConfig('ortb2')), eids: values(bidRequests.reduce((all, bid) => { // we only want unique ones in here if (bid == null || bid.userIdAsEids == null) { diff --git a/modules/avocetBidAdapter.js b/modules/avocetBidAdapter.js index 7a9e5062c0f..1283bb865d4 100644 --- a/modules/avocetBidAdapter.js +++ b/modules/avocetBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { const publisherDomain = config.getConfig('publisherDomain'); // First-party data from config - const fpd = config.getConfig('fpd'); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')); // GDPR status and TCF consent string let tcfConsentString; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index e3a6b9eaa12..41cbb0670c8 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -28,7 +28,7 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [ BANNER, VIDEO, NATIVE ], - /** + /** f * @param {object} bid * @return {boolean} */ @@ -56,10 +56,11 @@ export const spec = { buildRequests: (bidRequests, bidderRequest) => { let url; let data; + let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; Object.assign(bidderRequest, { - publisherExt: config.getConfig('fpd.context'), - userExt: config.getConfig('fpd.user'), + publisherExt: fpd.context, + userExt: fpd.user, ceh: config.getConfig('criteo.ceh') }); @@ -280,8 +281,8 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; } - if (bidRequest.fpd && bidRequest.fpd.context) { - slot.ext = bidRequest.fpd.context; + if (utils.deepAccess(bidRequest, 'ortb2Imp.ext')) { + slot.ext = bidRequest.ortb2Imp.ext; } if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 48b72671d6a..ee2b5406453 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -26,46 +26,48 @@ export const appendGptSlots = adUnits => { if (matchingAdUnitCode) { const adUnit = adUnitMap[matchingAdUnitCode]; - adUnit.fpd = adUnit.fpd || {}; - adUnit.fpd.context = adUnit.fpd.context || {}; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; - const context = adUnit.fpd.context; - context.adServer = context.adServer || {}; - context.adServer.name = 'gam'; - context.adServer.adSlot = slot.getAdUnitPath(); + const context = adUnit.ortb2Imp.ext.data; + context.adserver = context.adserver || {}; + context.adserver.name = 'gam'; + context.adserver.adslot = slot.getAdUnitPath(); } }); }; export const appendPbAdSlot = adUnit => { - adUnit.fpd = adUnit.fpd || {}; - adUnit.fpd.context = adUnit.fpd.context || {}; - const context = adUnit.fpd.context; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; + const context = adUnit.ortb2Imp.ext.data; const { customPbAdSlot } = _currentConfig; if (customPbAdSlot) { - context.pbAdSlot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adServer.adSlot')); + context.pbadslot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adserver.adslot')); return; } // use context.pbAdSlot if set - if (context.pbAdSlot) { + if (context.pbadslot) { return; } // use data attribute 'data-adslotid' if set try { const adUnitCodeDiv = document.getElementById(adUnit.code); if (adUnitCodeDiv.dataset.adslotid) { - context.pbAdSlot = adUnitCodeDiv.dataset.adslotid; + context.pbadslot = adUnitCodeDiv.dataset.adslotid; return; } } catch (e) {} // banner adUnit, use GPT adunit if defined - if (context.adServer) { - context.pbAdSlot = context.adServer.adSlot; + if (utils.deepAccess(context, 'adserver.adslot')) { + context.pbadslot = context.adserver.adslot; return; } - context.pbAdSlot = adUnit.code; + context.pbadslot = adUnit.code; }; export const makeBidRequestsHook = (fn, adUnits, ...args) => { diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 8ab2e2b8a95..964b34dcfa2 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -180,8 +180,8 @@ export const spec = { } const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + 'user': utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null }); if (configKeywords.length) { diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js index 7583985b23c..e1edd935587 100755 --- a/modules/inmarBidAdapter.js +++ b/modules/inmarBidAdapter.js @@ -40,7 +40,7 @@ export const spec = { uspConsent: bidderRequest.uspConsent, currencyCode: config.getConfig('currency.adServerCurrency'), coppa: config.getConfig('coppa'), - firstPartyData: config.getConfig('fpd'), + firstPartyData: config.getLegacyFpd(config.getConfig('ortb2')), prebidVersion: '$prebid.version$' }; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index a29c4ce5fa7..8332e720ae7 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -144,7 +144,7 @@ function enrichBidRequest(bidReqConfig, onDone) { * @param {function} onDone */ export function enrichAdUnits(adUnits) { - const fpdFallback = config.getConfig('fpd.context.data.jwTargeting'); + const fpdFallback = config.getConfig('ortb2.site.ext.data.jwTargeting'); adUnits.forEach(adUnit => { const jwTargeting = extractPublisherParams(adUnit, fpdFallback); if (!jwTargeting || !Object.keys(jwTargeting).length) { @@ -170,7 +170,7 @@ function supportsInstreamVideo(mediaTypes) { export function extractPublisherParams(adUnit, fallback) { let adUnitTargeting; try { - adUnitTargeting = adUnit.fpd.context.data.jwTargeting; + adUnitTargeting = adUnit.ortb2Imp.ext.data.jwTargeting; } catch (e) {} if (!adUnitTargeting && !supportsInstreamVideo(adUnit.mediaTypes)) { diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 4f7fd2ae1a0..29b54f77fbb 100644 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -279,8 +279,9 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + const siteData = Object.assign({}, bidRequest.params.inventory, fpd.context); + const userData = Object.assign({}, bidRequest.params.visitor, fpd.user); if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { const bidderData = { @@ -301,7 +302,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); } - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + const pbAdSlot = utils.deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 7cfef6a0784..20cf93caae5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -336,18 +336,18 @@ function addBidderFirstPartyDataToRequest(request) { const bidderConfig = config.getBidderConfig(); const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => { const currBidderConfig = bidderConfig[bidder]; - if (currBidderConfig.fpd) { - const fpd = {}; - if (currBidderConfig.fpd.context) { - fpd.site = currBidderConfig.fpd.context; + if (currBidderConfig.ortb2) { + const ortb2 = {}; + if (currBidderConfig.ortb2.site) { + ortb2.site = currBidderConfig.ortb2.site; } - if (currBidderConfig.fpd.user) { - fpd.user = currBidderConfig.fpd.user; + if (currBidderConfig.ortb2.user) { + ortb2.user = currBidderConfig.ortb2.user; } acc.push({ bidders: [ bidder ], - config: { fpd } + config: { ortb2 } }); } return acc; @@ -618,23 +618,27 @@ const OPEN_RTB_PROTOCOL = { const imp = { id: adUnit.code, ext, secure: s2sConfig.secure }; - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(adUnit, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(imp, 'ext.context.data.pbadslot', pbAdSlot); - } - - /** - * Copy GAM AdUnit and Name to imp - */ - ['name', 'adSlot'].forEach(name => { - /** @type {(string|undefined)} */ - const value = utils.deepAccess(adUnit, `fpd.context.adserver.${name}`); - if (typeof value === 'string' && value) { - utils.deepSetValue(imp, `ext.context.data.adserver.${name.toLowerCase()}`, value); + const ortb2 = {...utils.deepAccess(adUnit, 'ortb2Imp.ext.data')}; + Object.keys(ortb2).forEach(prop => { + /** + * Prebid AdSlot + * @type {(string|undefined)} + */ + if (prop === 'pbadslot') { + if (typeof ortb2[prop] === 'string' && ortb2[prop]) utils.deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); + } else if (prop === 'adserver') { + /** + * Copy GAM AdUnit and Name to imp + */ + ['name', 'adslot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(ortb2, `adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); + } + }); + } else { + utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); } }); @@ -763,12 +767,12 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'regs.coppa', 1); } - const commonFpd = getConfig('fpd') || {}; - if (commonFpd.context) { - utils.deepSetValue(request, 'site.ext.data', commonFpd.context); + const commonFpd = getConfig('ortb2') || {}; + if (commonFpd.site) { + utils.deepSetValue(request, 'site', commonFpd.site); } if (commonFpd.user) { - utils.deepSetValue(request, 'user.ext.data', commonFpd.user); + utils.deepSetValue(request, 'user', commonFpd.user); } addBidderFirstPartyDataToRequest(request); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 7297c82440f..9905498edee 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -871,21 +871,26 @@ function addVideoParameters(data, bidRequest) { } function applyFPD(bidRequest, mediaType, data) { - const bidFpd = { - user: {...bidRequest.params.visitor}, - context: {...bidRequest.params.inventory} + const BID_FPD = { + user: {ext: {data: {...bidRequest.params.visitor}}}, + site: {ext: {data: {...bidRequest.params.inventory}}} }; - if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; + if (bidRequest.params.keywords) BID_FPD.site.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; - let fpd = utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd); - - const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}, adserver: 'dfp_ad_unit_code'}; - let obj = {}; - let impData = {}; - let keywords = []; + let fpd = utils.mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); + let impData = utils.deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; + const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key) { - if (typeof prop === 'object' && !Array.isArray(prop)) { + if (key === 'data' && Array.isArray(prop)) { + return prop.filter(name => name.segment && utils.deepAccess(name, 'ext.taxonomyname').match(/iab/i)).map(value => { + let segments = value.segment.filter(obj => obj.id).reduce((result, obj) => { + result.push(obj.id); + return result; + }, []); + if (segments.length > 0) return segments.toString(); + }).toString(); + } else if (typeof prop === 'object' && !Array.isArray(prop)) { utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); } else if (typeof prop !== 'undefined') { return (Array.isArray(prop)) ? prop.filter(value => { @@ -895,53 +900,43 @@ function applyFPD(bidRequest, mediaType, data) { }).toString() : prop.toString(); } }; - - Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { - obj[map[type].code] = Object.keys(fpd[type]).filter(value => typeof fpd[type][value] !== 'undefined').reduce((result, key) => { - if (key === 'keywords') { - if (!Array.isArray(fpd[type][key]) && mediaType === BANNER) fpd[type][key] = [fpd[type][key]] - - result[key] = fpd[type][key]; - - if (mediaType === BANNER) keywords = keywords.concat(fpd[type][key]); - } else if (key === 'data') { - utils.mergeDeep(result, {ext: {data: fpd[type][key]}}); - } else if (key === 'adServer' || key === 'pbAdSlot') { - (key === 'adServer') ? ['name', 'adSlot'].forEach(name => { - const value = validate(fpd[type][key][name]); - if (value) utils.deepSetValue(impData, `adserver.${name.toLowerCase()}`, value.replace(/^\/+/, '')) - }) : impData[key.toLowerCase()] = fpd[type][key].replace(/^\/+/, '') - } else { - utils.mergeDeep(result, {ext: {data: {[key]: fpd[type][key]}}}); - } - - return result; - }, {}); - - if (mediaType === BANNER) { - let duplicate = (typeof obj[map[type].code].ext === 'object' && obj[map[type].code].ext.data) || {}; - - Object.keys(duplicate).forEach((key) => { - const val = (key === 'adserver') ? duplicate.adserver.adslot : validate(duplicate[key], key); - - if (val) data[(map[key]) ? `${map[type][BANNER]}${map[key]}` : `${map[type][BANNER]}${key}`] = val; - }); - } - }); + const addBannerData = function(obj, name, key) { + let val = validate(obj, key); + let loc = (MAP[key]) ? `${MAP[key]}` : (key === 'data') ? `${MAP[name]}iab` : `${MAP[name]}${key}`; + data[loc] = (data[loc]) ? data[loc].concat(',', val) : val; + } Object.keys(impData).forEach((key) => { - if (mediaType === BANNER) { - (map[key]) ? data[`tg_i.${map[key]}`] = impData[key].adslot : data[`tg_i.${key.toLowerCase()}`] = impData[key]; - } else { - utils.mergeDeep(data.imp[0], {ext: {context: {data: {[key]: impData[key]}}}}); + if (key === 'adserver') { + ['name', 'adslot'].forEach(prop => { + if (impData[key][prop]) impData[key][prop] = impData[key][prop].replace(/^\/+/, ''); + }); + } else if (key === 'pbadslot') { + impData[key] = impData[key].replace(/^\/+/, ''); } }); if (mediaType === BANNER) { - let kw = validate(keywords, 'keywords'); - if (kw) data.kw = kw; + ['site', 'user'].forEach(name => { + Object.keys(fpd[name]).forEach((key) => { + if (key !== 'ext') { + addBannerData(fpd[name][key], name, key); + } else if (fpd[name][key].data) { + Object.keys(fpd[name].ext.data).forEach((key) => { + addBannerData(fpd[name].ext.data[key], name, key); + }); + } + }); + }); + Object.keys(impData).forEach((key) => { + (key === 'adserver') ? addBannerData(impData[key].adslot, name, key) : addBannerData(impData[key], 'site', key); + }); } else { - utils.mergeDeep(data, obj); + if (Object.keys(impData).length) { + utils.mergeDeep(data.imp[0].ext, {data: impData}); + } + + utils.mergeDeep(data, fpd); } } diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 93915689cee..fdb4d2df984 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -98,8 +98,10 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { } }; - Object.assign(request.user, config.getConfig('fpd.user')); - Object.assign(request.site, config.getConfig('fpd.context')); + let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + + Object.assign(request.user, fpd.user); + Object.assign(request.site, fpd.context); if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index b681b0980ea..e8d248eea03 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -122,8 +122,8 @@ function _buildPostBody(bidRequests) { } else if (bidRequest.mediaTypes.banner) { imp.banner = { format: _sizes(bidRequest.sizes) }; }; - if (!utils.isEmpty(bidRequest.fpd)) { - imp.fpd = _getAdUnitFpd(bidRequest.fpd); + if (!utils.isEmpty(bidRequest.ortb2Imp)) { + imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp); } return imp; }); @@ -190,9 +190,10 @@ function _getGlobalFpd() { const fpd = {}; const context = {} const user = {}; + const ortbData = config.getLegacyFpd(config.getConfig('ortb2')) || {}; - const fpdContext = Object.assign({}, config.getConfig('fpd.context')); - const fpdUser = Object.assign({}, config.getConfig('fpd.user')); + const fpdContext = Object.assign({}, ortbData.context); + const fpdUser = Object.assign({}, ortbData.user); _addEntries(context, fpdContext); _addEntries(user, fpdUser); @@ -210,7 +211,7 @@ function _getAdUnitFpd(adUnitFpd) { const fpd = {}; const context = {}; - _addEntries(context, adUnitFpd.context); + _addEntries(context, adUnitFpd.ext); if (!utils.isEmpty(context)) { fpd.context = context; diff --git a/src/adapterManager.js b/src/adapterManager.js index cb84607e130..f7f5d821932 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -68,7 +68,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) } bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'fpd', + 'ortb2Imp', 'mediaType', 'renderer', 'storedAuctionResponse' diff --git a/src/config.js b/src/config.js index daaf739bbbd..51184a8014d 100644 --- a/src/config.js +++ b/src/config.js @@ -324,6 +324,119 @@ export function newConfig() { return bidderConfig; } + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + Object.keys(obj).forEach((type) => { + let prop = (type === 'site') ? 'context' : type; + duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(obj[type]).filter(key => key !== 'data').reduce((result, key) => { + if (key === 'ext') { + utils.mergeDeep(result, obj[type][key]); + } else { + utils.mergeDeep(result, {[key]: obj[type][key]}); + } + + return result; + }, {}) : obj[type]; + }); + + return duplicate; + } + + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyImpFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + if (utils.deepAccess(obj, 'ext.data')) { + Object.keys(obj.ext.data).forEach((key) => { + if (key === 'pbadslot') { + utils.mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); + } else if (key === 'adserver') { + utils.mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); + } else { + utils.mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); + } + }); + } + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in config + */ + function convertFpd(opt) { + let duplicate = {}; + + Object.keys(opt).forEach((type) => { + let prop = (type === 'context') ? 'site' : type; + duplicate[prop] = (prop === 'site' || prop === 'user') ? Object.keys(opt[type]).reduce((result, key) => { + if (key === 'data') { + utils.mergeDeep(result, {ext: {data: opt[type][key]}}); + } else { + utils.mergeDeep(result, {[key]: opt[type][key]}); + } + + return result; + }, {}) : opt[type]; + }); + + return duplicate; + } + + /** + * Copy Impression FPD over to OpenRTB standard format in config + * Only accepts bid level context.data values with pbAdSlot and adServer exceptions + */ + function convertImpFpd(opt) { + let duplicate = {}; + + Object.keys(opt).filter(prop => prop === 'context').forEach((type) => { + Object.keys(opt[type]).forEach((key) => { + if (key === 'data') { + utils.mergeDeep(duplicate, {ext: {data: opt[type][key]}}); + } else { + if (typeof opt[type][key] === 'object' && !Array.isArray(opt[type][key])) { + Object.keys(opt[type][key]).forEach(data => { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: {[data.toLowerCase()]: opt[type][key][data]}}}}); + }); + } else { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); + } + } + }); + }); + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in each adunit + */ + function convertAdUnitFpd(arr) { + let convert = []; + + arr.forEach((adunit) => { + if (adunit.fpd) { + (adunit['ortb2Imp']) ? utils.mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); + convert.push((({ fpd, ...duplicate }) => duplicate)(adunit)); + } else { + convert.push(adunit); + } + }); + + return convert; + } + /* * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function @@ -338,13 +451,14 @@ export function newConfig() { let topicalConfig = {}; topics.forEach(topic => { - let option = options[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(options[topic]) : options[topic]; - if (utils.isPlainObject(defaults[topic]) && utils.isPlainObject(option)) { - option = Object.assign({}, defaults[topic], option); + if (utils.isPlainObject(defaults[prop]) && utils.isPlainObject(option)) { + option = Object.assign({}, defaults[prop], option); } - topicalConfig[topic] = config[topic] = option; + topicalConfig[prop] = config[prop] = option; }); callSubscribers(topicalConfig); @@ -437,11 +551,13 @@ export function newConfig() { bidderConfig[bidder] = {}; } Object.keys(config.config).forEach(topic => { - let option = config.config[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; + if (utils.isPlainObject(option)) { - bidderConfig[bidder][topic] = Object.assign({}, bidderConfig[bidder][topic] || {}, option); + bidderConfig[bidder][prop] = Object.assign({}, bidderConfig[bidder][prop] || {}, option); } else { - bidderConfig[bidder][topic] = option; + bidderConfig[bidder][prop] = option; } }); }); @@ -499,7 +615,10 @@ export function newConfig() { runWithBidder, callbackWithBidder, setBidderConfig, - getBidderConfig + getBidderConfig, + convertAdUnitFpd, + getLegacyFpd, + getLegacyImpFpd }; } diff --git a/src/prebid.js b/src/prebid.js index 3452107effd..6565c1610d8 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -595,11 +595,7 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - if (utils.isArray(adUnitArr)) { - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, adUnitArr); - } else if (typeof adUnitArr === 'object') { - $$PREBID_GLOBAL$$.adUnits.push(adUnitArr); - } + $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(utils.isArray(adUnitArr) ? adUnitArr : [adUnitArr])); // emit event events.emit(ADD_AD_UNITS); }; diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 7dd48a13208..baa5b4ac8c4 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -23,6 +23,16 @@ describe('Publisher API _ AdUnits', function () { } ] }, { + fpd: { + context: { + pbAdSlot: 'adSlotTest', + data: { + inventory: [4], + keywords: 'foo,bar', + visitor: [1, 2, 3], + } + } + }, code: '/1996833/slot-2', sizes: [[468, 60]], bids: [ @@ -85,6 +95,7 @@ describe('Publisher API _ AdUnits', function () { it('the second adUnits value should be same with the adUnits that is added by $$PREBID_GLOBAL$$.addAdUnits();', function () { assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.deepEqual(adUnit2['ortb2Imp'], {'ext': {'data': {'pbadslot': 'adSlotTest', 'inventory': [4], 'keywords': 'foo,bar', 'visitor': [1, 2, 3]}}}, 'adUnit2 ortb2Imp'); assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 81ce966efb2..0b8dd6978cf 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -6,6 +6,8 @@ const utils = require('src/utils'); let getConfig; let setConfig; +let getBidderConfig; +let setBidderConfig; let setDefaults; describe('config API', function () { @@ -15,6 +17,8 @@ describe('config API', function () { const config = newConfig(); getConfig = config.getConfig; setConfig = config.setConfig; + getBidderConfig = config.getBidderConfig; + setBidderConfig = config.setBidderConfig; setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -57,6 +61,17 @@ describe('config API', function () { expect(getConfig('foo')).to.eql({baz: 'qux'}); }); + it('moves fpd config into ortb2 properties', function () { + setConfig({fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}); + expect(getConfig('ortb2')).to.eql({site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}); + expect(getConfig('fpd')).to.eql(undefined); + }); + + it('moves fpd bidderconfig into ortb2 properties', function () { + setBidderConfig({bidders: ['bidderA'], config: {fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}}); + expect(getBidderConfig()).to.eql({'bidderA': {ortb2: {site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}}}); + }); + it('sets debugging', function () { setConfig({ debug: true }); expect(getConfig('debug')).to.be.true; diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 11a6a14a353..596f3bade5d 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -224,12 +224,14 @@ describe('AdrelevantisAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0]); sinon .stub(config, 'getConfig') - .withArgs('fpd') + .withArgs('ortb2') .returns({ - context: { + site: { keywords: 'US Open', - data: { - category: 'sports/tennis' + ext: { + data: { + category: 'sports/tennis' + } } } }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 766045b0f3e..0658fe9f33c 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -17,10 +17,26 @@ const embeddedTrackingPixel = `https://1x1.a-mo.net/hbx/g_impression?A=sample&B= const sampleNurl = 'https://example.exchange/nurl'; const sampleFPD = { + site: { + keywords: 'sample keywords', + ext: { + data: { + pageType: 'article' + } + } + }, + user: { + gender: 'O', + yob: 1982, + } +}; + +const legacySampleFPD = { context: { keywords: 'sample keywords', data: { pageType: 'article' + } }, user: { @@ -31,7 +47,7 @@ const sampleFPD = { const stubConfig = (withStub) => { const stub = sinon.stub(config, 'getConfig').callsFake( - (arg) => arg === 'fpd' ? sampleFPD : null + (arg) => arg === 'ortb2' ? sampleFPD : null ) withStub(); @@ -253,7 +269,7 @@ describe('AmxBidAdapter', () => { it('will forward first-party data', () => { stubConfig(() => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd).to.deep.equal(sampleFPD) + expect(data.fpd).to.deep.equal(legacySampleFPD) }); }); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e91d3b10abb..cad1e3f8114 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -820,14 +820,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const contextData = { keywords: ['power tools'], - data: { - pageType: 'article' + ext: { + data: { + pageType: 'article' + } } }; const userData = { gender: 'M', - data: { - registered: true + ext: { + data: { + registered: true + } } }; const bidRequests = [ @@ -842,8 +846,8 @@ describe('The Criteo bidding adapter', function () { bidfloor: 0.75 } }, - fpd: { - context: { + ortb2Imp: { + ext: { data: { someContextAttribute: 'abc' } @@ -854,8 +858,8 @@ describe('The Criteo bidding adapter', function () { sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context: contextData, + ortb2: { + site: contextData, user: userData } }; @@ -863,8 +867,8 @@ describe('The Criteo bidding adapter', function () { }); const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.publisher.ext).to.deep.equal(contextData); - expect(request.data.user.ext).to.deep.equal(userData); + expect(request.data.publisher.ext).to.deep.equal({keywords: ['power tools'], data: {pageType: 'article'}}); + expect(request.data.user.ext).to.deep.equal({gender: 'M', data: {registered: true}}); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 16b84467af2..c4a81c21d5c 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -30,34 +30,34 @@ describe('GPT pre-auction module', () => { '
test2
'; it('should be unchanged if already defined on adUnit', () => { - const adUnit = { fpd: { context: { pbAdSlot: '12345' } } }; + const adUnit = { ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('12345'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('12345'); }); it('should use adUnit.code if matching id exists', () => { - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('bar1'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('bar1'); }); it('should use the gptSlot.adUnitPath if the adUnit.code matches a div id but does not have a data-adslotid', () => { - const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, fpd: { context: { adServer: { name: 'gam', adSlot: '/baz' } } } }; + const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: { adserver: { name: 'gam', adslot: '/baz' } } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('/baz'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('/baz'); }); it('should use the video adUnit.code (which *should* match the configured "adSlotName", but is not being tested) if there is no matching div with "data-adslotid" defined', () => { - const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, fpd: { context: {} } }; + const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: {} } } }; adUnit.code = 'foo5'; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo5'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo5'); }); it('should use the adUnit.code if all other sources failed', () => { - const adUnit = { code: 'foo4', fpd: { context: {} } }; + const adUnit = { code: 'foo4', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo4'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo4'); }); it('should use the customPbAdSlot function if one is given', () => { @@ -67,32 +67,32 @@ describe('GPT pre-auction module', () => { } }); - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('customPbAdSlotName'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('customPbAdSlotName'); }); }); describe('appendGptSlots', () => { it('should not add adServer object to context if no slots defined', () => { - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should not add adServer object to context if no slot matches', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should add adServer object to context if matching slot is found', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'slotCode2', fpd: { context: {} } }; + const adUnit = { code: 'slotCode2', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode2' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode2' }); }); it('should use the customGptSlotMatching function if one is given', () => { @@ -104,10 +104,10 @@ describe('GPT pre-auction module', () => { }); window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'SlOtCoDe1', fpd: { context: {} } }; + const adUnit = { code: 'SlOtCoDe1', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode1' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode1' }); }); }); @@ -164,23 +164,23 @@ describe('GPT pre-auction module', () => { it('should append PB Ad Slot and GPT Slot info to first-party data in each ad unit', () => { const testAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890' } } + ortb2Imp: { ext: { data: { pbadslot: '67890' } } } }, { code: 'slotCode3', }]; const expectedAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890', adServer: { name: 'gam', adSlot: 'slotCode1' } } } + ortb2Imp: { ext: { data: { pbadslot: '67890', adserver: { name: 'gam', adslot: 'slotCode1' } } } } }, { code: 'slotCode3', - fpd: { context: { pbAdSlot: 'slotCode3', adServer: { name: 'gam', adSlot: 'slotCode3' } } } + ortb2Imp: { ext: { data: { pbadslot: 'slotCode3', adserver: { name: 'gam', adslot: 'slotCode3' } } } } }]; window.googletag.pubads().setSlots(testSlots); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index df119bb689d..2e8601bddf6 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -400,6 +400,16 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content).to.deep.equal(jsContent); }); + it('should contain the keyword values if it present in ortb2.(site/user)', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user' ? {'keywords': 'foo'} : (arg === 'ortb2.site' ? {'keywords': 'bar'} : null)); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.ext.keywords).to.deep.equal([{'key': 'user', 'value': ['foo']}, {'key': 'context', 'value': ['bar']}]); + getConfigStub.restore(); + }); + it('shold be right tmax when timeout in config is less then timeout in bidderRequest', function() { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'bidderTimeout' ? 2000 : null); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index f7beb6ba486..458e45e8ae7 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -229,8 +229,8 @@ describe('jwplayerRtdProvider', function() { const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: mediaIdWithSegment, @@ -298,8 +298,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -345,8 +345,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -392,8 +392,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -435,19 +435,19 @@ describe('jwplayerRtdProvider', function() { }); it('should include banner ad units that specify jwTargeting', function() { - const adUnit = { mediaTypes: { banner: {} }, fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { mediaTypes: { banner: {} }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); it('should include outstream ad units that specify jwTargeting', function() { - const adUnit = { mediaTypes: { video: { context: 'outstream' } }, fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { mediaTypes: { video: { context: 'outstream' } }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); it('should fallback to config when empty jwTargeting is defined in ad unit', function () { - const adUnit = { fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); @@ -457,7 +457,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: 'bad_id', mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -468,7 +468,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: expectedPlayerID, mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -577,8 +577,8 @@ describe('jwplayerRtdProvider', function() { bidReqConfig = { adUnits: [ { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[0] @@ -591,8 +591,8 @@ describe('jwplayerRtdProvider', function() { ] }, { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[1] @@ -658,8 +658,8 @@ describe('jwplayerRtdProvider', function() { it('sets targeting data in proper structure', function () { const bid = {}; const adUnitWithMediaId = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -690,8 +690,8 @@ describe('jwplayerRtdProvider', function() { const adUnitCode = 'test_ad_unit'; const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -701,7 +701,7 @@ describe('jwplayerRtdProvider', function() { }, bids: [ bid ] }; - const expectedContentId = 'jw_' + adUnit.fpd.context.data.jwTargeting.mediaID; + const expectedContentId = 'jw_' + adUnit.ortb2Imp.ext.data.jwTargeting.mediaID; const expectedTargeting = { content: { id: expectedContentId @@ -732,8 +732,8 @@ describe('jwplayerRtdProvider', function() { const adUnitEmptyfpd = { code: 'test_ad_unit_empty_fpd', - fpd: { - context: { + ortb2Imp: { + ext: { id: 'sthg' } }, diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9e20629fe45..babee7e10d7 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1607,7 +1607,29 @@ describe('S2S Adapter', function () { const expected = allowedBidders.map(bidder => ({ bidders: [ bidder ], - config: { fpd: { site: context, user } } + config: { + ortb2: { + site: { + content: { userrating: 4 }, + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }, + user: { + yob: '1984', + geo: { country: 'ca' }, + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } })); config.setConfig({ fpd: { context: commonContext, user: commonUser } }); @@ -1615,12 +1637,12 @@ describe('S2S Adapter', function () { adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); - expect(parsedRequestBody.site.ext.data).to.deep.equal(commonContext); - expect(parsedRequestBody.user.ext.data).to.deep.equal(commonUser); + expect(parsedRequestBody.site).to.deep.equal(commonContext); + expect(parsedRequestBody.user).to.deep.equal(commonUser); }); describe('pbAdSlot config', function () { - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1630,30 +1652,32 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1662,16 +1686,18 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { + it('should send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a non-empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } }; @@ -1680,13 +1706,13 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.pbadslot'); - expect(parsedRequestBody.imp[0].ext.context.data.pbadslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.pbadslot'); + expect(parsedRequestBody.imp[0].ext.data.pbadslot).to.equal('/a/b/c'); }); }); describe('GAM ad unit config', function () { - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1696,31 +1722,33 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is undefined', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is empty string', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adServer: { - adSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1730,18 +1758,20 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should send both \"adslot\" and \"name\" from \"imp.ext.context.data.adserver\" if \"fpd.context.adserver.adSlot\" and \"fpd.context.adserver.name\" values are non-empty strings', function () { + it('should send both \"adslot\" and \"name\" from \"imp.ext.data.adserver\" if \"ortb2Imp.ext.data.adserver.adslot\" and \"ortb2Imp.ext.data.adserver.name\" values are non-empty strings', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adserver: { - adSlot: '/a/b/c', - name: 'adserverName1' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '/a/b/c', + name: 'adserverName1' + } } } }; @@ -1751,10 +1781,10 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.adslot'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.name'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.adslot).to.equal('/a/b/c'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.name).to.equal('adserverName1'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.adslot'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.name'); + expect(parsedRequestBody.imp[0].ext.data.adserver.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0].ext.data.adserver.name).to.equal('adserverName1'); }); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 8c25d97dada..36890a2891b 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -827,27 +827,39 @@ describe('the rubicon adapter', function () { }); it('should merge first party data from getConfig with the bid params, if present', () => { - const context = { + const site = { keywords: 'e,f', rating: '4-star', - data: { - page: 'home' + ext: { + data: { + page: 'home' + } } }; const user = { + data: [{ + 'name': 'www.dataprovider1.com', + 'ext': { 'taxonomyname': 'IAB Audience Taxonomy' }, + 'segment': [ + { 'id': '687' }, + { 'id': '123' } + ] + }], gender: 'M', yob: '1984', geo: {country: 'ca'}, keywords: 'd', - data: { - age: 40 + ext: { + data: { + age: 40 + } } }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -861,8 +873,9 @@ describe('the rubicon adapter', function () { 'tg_v.likes': 'sports,video games', 'tg_v.gender': 'M', 'tg_v.age': '40', + 'tg_v.iab': '687,123', 'tg_v.yob': '1984', - 'tg_i.rating': '5-star', + 'tg_i.rating': '4-star,5-star', 'tg_i.page': 'home', 'tg_i.prodtype': 'tech,mobile', }; @@ -1287,12 +1300,12 @@ describe('the rubicon adapter', function () { describe('Prebid AdSlot', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1300,8 +1313,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1310,10 +1323,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '' + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1324,10 +1339,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: 'abc' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abc' + } } } @@ -1339,12 +1356,14 @@ describe('the rubicon adapter', function () { expect(data['tg_i.pbadslot']).to.equal('abc'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } - }; + } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1358,12 +1377,12 @@ describe('the rubicon adapter', function () { describe('GAM ad unit', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1371,8 +1390,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1381,11 +1400,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '' + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1397,11 +1418,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'abc' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'abc' + } } } } @@ -1414,11 +1437,13 @@ describe('the rubicon adapter', function () { expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'a/b/c' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'a/b/c' + } } } }; @@ -1871,27 +1896,36 @@ describe('the rubicon adapter', function () { it('should include first party data', () => { createVideoBidderRequest(); - const context = { - data: { - page: 'home' + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] }, keywords: 'e,f', - rating: '4-star' + rating: '4-star', + data: [{foo: 'bar'}] }; const user = { - data: { - age: 31 + ext: { + data: { + age: 31 + } }, keywords: 'd', gender: 'M', yob: '1984', - geo: {country: 'ca'} + geo: {country: 'ca'}, + data: [{foo: 'bar'}] }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -1901,19 +1935,19 @@ describe('the rubicon adapter', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const expected = { - site: Object.assign({}, context, context.data, bidderRequest.bids[0].params.inventory), - user: Object.assign({}, user, user.data, bidderRequest.bids[0].params.visitor) + site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), + user: Object.assign({}, user), + siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), + userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), }; - delete expected.site.data; - delete expected.user.data; - delete expected.site.keywords; - delete expected.user.keywords; + delete request.data.site.page; + delete request.data.site.content.language; expect(request.data.site.keywords).to.deep.equal('a,b,c'); expect(request.data.user.keywords).to.deep.equal('d'); - expect(request.data.site.ext.data).to.deep.equal(expected.site); - expect(request.data.user.ext.data).to.deep.equal(expected.user); + expect(request.data.site.ext.data).to.deep.equal(expected.siteData); + expect(request.data.user.ext.data).to.deep.equal(expected.userData); }); it('should include storedAuctionResponse in video bid request', function () { @@ -1932,29 +1966,33 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); - it('should include pbAdSlot in bid request', function () { + it('should include pbadslot in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '1234567890' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } - }; + } sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); }); it('should include GAM ad unit in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '1234567890', - name: 'adServerName1' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } } } }; @@ -1964,8 +2002,8 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.context.data.adserver.name).to.equal('adServerName1'); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); }); it('should use the integration type provided in the config instead of the default', () => { diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 6af0a855800..1bc77fc9572 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -42,7 +42,7 @@ const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; -const context = { +const site = { keywords: 'power tools,drills' }; @@ -439,8 +439,8 @@ describe('smaatoBidAdapterTest', () => { it('sends fp data', () => { this.sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index eb410c2525d..30377ec0a5d 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -143,10 +143,10 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, - fpd: { - context: { - pbAdSlot: 'homepage-top-rect', + ortb2Imp: { + ext: { data: { + pbAdSlot: 'homepage-top-rect', adUnitSpecificAttribute: 123 } } @@ -663,11 +663,13 @@ describe('triplelift adapter', function () { const sens = null; const category = ['news', 'weather', 'hurricane']; const pmp_elig = 'true'; - const fpd = { - context: { + const ortb2 = { + site: { pmp_elig: pmp_elig, - data: { - category: category + ext: { + data: { + category: category + } } }, user: { @@ -676,7 +678,7 @@ describe('triplelift adapter', function () { } sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd + ortb2 }; return utils.deepAccess(config, key); }); @@ -688,8 +690,8 @@ describe('triplelift adapter', function () { }); it('should send ad unit fpd if kvps are available', function() { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - expect(request.data.imp[0].fpd.context).to.haveOwnProperty('pbAdSlot'); expect(request.data.imp[0].fpd.context).to.haveOwnProperty('data'); + expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('pbAdSlot'); expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); expect(request.data.imp[1].fpd).to.not.exist; }); From 15cf2f0cf9f863c6c88e4890e867e628f4c95ff0 Mon Sep 17 00:00:00 2001 From: Ryan Schweitzer <50628828+r-schweitzer@users.noreply.github.com> Date: Mon, 8 Mar 2021 11:57:29 +0000 Subject: [PATCH 211/325] PBS Bid Adapter: fix s2s alias collision with built-in adapter aliasing (#6379) * fixed overwriting of aliases for s2s * made change * added fix --- modules/prebidServerBidAdapter/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 20cf93caae5..088b5430f46 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -724,7 +724,7 @@ const OPEN_RTB_PROTOCOL = { } if (!utils.isEmpty(aliases)) { - request.ext.prebid.aliases = aliases; + request.ext.prebid.aliases = {...request.ext.prebid.aliases, ...aliases}; } const bidUserIdAsEids = utils.deepAccess(bidRequests, '0.bids.0.userIdAsEids'); From 83e82229398724ac2c38936e5b8afa3f7de66dd4 Mon Sep 17 00:00:00 2001 From: Pierre Turpin Date: Mon, 8 Mar 2021 16:03:08 +0100 Subject: [PATCH 212/325] Clean side-effect when checking that local storage is enabled (#6323) --- src/storageManager.js | 9 +++++-- test/spec/unit/core/storageManager_spec.js | 29 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/storageManager.js b/src/storageManager.js index 60e5a7706d0..66a0cf68cbf 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,4 +1,4 @@ -import { hook } from './hook.js'; +import {hook} from './hook.js'; import * as utils from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -110,7 +110,12 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { try { localStorage.setItem('prebid.cookieTest', '1'); return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) {} + } catch (error) { + } finally { + try { + localStorage.removeItem('prebid.cookieTest'); + } catch (error) {} + } } return false; } diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index de09df5b196..5bb766217f5 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -46,16 +46,17 @@ describe('storage manager', function() { describe('localstorage forbidden access in 3rd-party context', function() { let errorLogSpy; - const originalLocalStorage = { get: () => window.localStorage }; + let originalLocalStorage; const localStorageMock = { get: () => { throw Error } }; beforeEach(function() { + originalLocalStorage = window.localStorage; Object.defineProperty(window, 'localStorage', localStorageMock); errorLogSpy = sinon.spy(utils, 'logError'); }); afterEach(function() { - Object.defineProperty(window, 'localStorage', originalLocalStorage); + Object.defineProperty(window, 'localStorage', { get: () => originalLocalStorage }); errorLogSpy.restore(); }) @@ -70,4 +71,28 @@ describe('storage manager', function() { sinon.assert.calledThrice(errorLogSpy); }) }) + + describe('localstorage is enabled', function() { + let localStorage; + + beforeEach(function() { + localStorage = window.localStorage; + localStorage.clear(); + }); + + afterEach(function() { + localStorage.clear(); + }) + + it('should remove side-effect after checking', function () { + const storage = getStorageManager(); + + localStorage.setItem('unrelated', 'dummy'); + const val = storage.localStorageIsEnabled(); + + expect(val).to.be.true; + expect(localStorage.length).to.be.eq(1); + expect(localStorage.getItem('unrelated')).to.be.eq('dummy'); + }); + }); }); From 3129a96f57141727c759f82189a348f6c37da19b Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Tue, 9 Mar 2021 05:18:34 -0500 Subject: [PATCH 213/325] updates docs and demo for fpd changes (#6302) Co-authored-by: karimJWP --- .../gpt/jwplayerRtdProvider_example.html | 48 +++++++++---------- modules/jwplayerRtdProvider.md | 12 +++-- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html index 75eb85a2d8c..41c27b70ece 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -10,32 +10,30 @@ var PREBID_TIMEOUT = 1000; var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - fpd: { - context: { - data: { - jwTargeting: { - // Note: the following Ids are placeholders and should be replaced with your Ids. - playerID: '123', - mediaID: 'abc' + code: 'div-gpt-ad-1460505748561-0', + ortb2Imp: { + ext: { + data: { + jwTargeting: { + // Note: the following Ids are placeholders and should be replaced with your Ids. + playerID: '123', + mediaID: 'abc' + } + } } - }, - } - }, - - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'appnexus', - params: { - placementId: 13144370 - } - }] - + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] }]; var pbjs = pbjs || {}; diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index ae09277979a..0723e8cbb6c 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -25,14 +25,14 @@ pbjs.setConfig({ } }); ``` -Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `fpd.context.data`: +Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `ortb2Imp.ext.data`: ```javascript const adUnit = { code: '/19968336/prebid_native_example_1', ... - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { // Note: the following Ids are placeholders and should be replaced with your Ids. @@ -52,7 +52,7 @@ pbjs.que.push(function() { }); ``` -**Note**: You may also include `jwTargeting` information in the prebid config's `fpd.context.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. +**Note**: You may also include `jwTargeting` information in the prebid config's `ortb2.site.ext.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. ##Prefetching In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var and set `waitForIt` to `true`: @@ -117,3 +117,7 @@ To view an example: `http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. + +#Maintainer info + +Maintained by JW Player. For any questions, comments or feedback please contact Karim Mourra, karim@jwplayer.com From 9b2d3c0872b946f70e19d1160200c69ff83e505f Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Tue, 9 Mar 2021 16:50:53 +0530 Subject: [PATCH 214/325] Changed net revenue to True (#6387) --- modules/pubmaticBidAdapter.js | 2 +- test/spec/modules/pubmaticBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 07e5b62ac26..41ca642c869 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -101,7 +101,7 @@ const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ } ] -const NET_REVENUE = false; +const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', 5: 'PREF', diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 37deb0bca9c..09573fe9f85 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2614,7 +2614,7 @@ describe('PubMatic adapter', function () { } expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); expect(response[0].currency).to.equal('USD'); - expect(response[0].netRevenue).to.equal(false); + expect(response[0].netRevenue).to.equal(true); expect(response[0].ttl).to.equal(300); expect(response[0].meta.networkId).to.equal(123); expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); @@ -2638,7 +2638,7 @@ describe('PubMatic adapter', function () { } expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid); expect(response[1].currency).to.equal('USD'); - expect(response[1].netRevenue).to.equal(false); + expect(response[1].netRevenue).to.equal(true); expect(response[1].ttl).to.equal(300); expect(response[1].meta.networkId).to.equal(422); expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); From a2282be216fae97323bc65a618099b49e1b4807d Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 9 Mar 2021 16:29:02 +0100 Subject: [PATCH 215/325] adagioBidAdapter: add Native support (#6368) --- modules/adagioBidAdapter.js | 116 +++++++++++++- modules/adagioBidAdapter.md | 60 ++++++- test/spec/modules/adagioBidAdapter_spec.js | 173 ++++++++++++++++++++- 3 files changed, 340 insertions(+), 9 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 6f7feec59c9..892411837dd 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -8,16 +8,16 @@ import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { createEidsArray } from './userId/eids.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import { OUTSTREAM } from '../src/video.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.6.0'; +export const VERSION = '2.7.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; -export const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +export const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; export const GVLID = 617; @@ -687,6 +687,112 @@ function _renderer(bid) { }); } +function _parseNativeBidResponse(bid) { + if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { + utils.logError(`${LOG_PREFIX} Invalid native response`); + return; + } + + const native = {} + + function addAssetDataValue(data) { + const map = { + 1: 'sponsoredBy', // sponsored + 2: 'body', // desc + 3: 'rating', + 4: 'likes', + 5: 'downloads', + 6: 'price', + 7: 'salePrice', + 8: 'phone', + 9: 'address', + 10: 'body2', // desc2 + 11: 'displayUrl', + 12: 'cta' + } + if (map.hasOwnProperty(data.type) && typeof data.value === 'string') { + native[map[data.type]] = data.value; + } + } + + // assets + bid.admNative.assets.forEach(asset => { + if (asset.title) { + native.title = asset.title.text + } else if (asset.data) { + addAssetDataValue(asset.data) + } else if (asset.img) { + switch (asset.img.type) { + case 1: + native.icon = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + default: + native.image = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + } + } + }); + + if (bid.admNative.link) { + if (bid.admNative.link.url) { + native.clickUrl = bid.admNative.link.url; + } + if (Array.isArray(bid.admNative.link.clickTrackers)) { + native.clickTrackers = bid.admNative.link.clickTrackers + } + } + + if (Array.isArray(bid.admNative.eventtrackers)) { + native.impressionTrackers = []; + bid.admNative.eventtrackers.forEach(tracker => { + // Only Impression events are supported. Prebid does not support Viewability events yet. + if (tracker.event !== 1) { + return; + } + + // methods: + // 1: image + // 2: js + // note: javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + switch (tracker.method) { + case 1: + native.impressionTrackers.push(tracker.url); + break; + case 2: + native.javascriptTrackers = ``; + break; + } + }); + } else { + native.impressionTrackers = Array.isArray(bid.admNative.imptrackers) ? bid.admNative.imptrackers : []; + if (bid.admNative.jstracker) { + native.javascriptTrackers = bid.admNative.jstracker; + } + } + + if (bid.admNative.privacy) { + native.privacyLink = bid.admNative.privacy; + } + + if (bid.admNative.ext) { + native.ext = {} + + if (bid.admNative.ext.bvw) { + native.ext.adagio_bvw = bid.admNative.ext.bvw; + } + } + + bid.native = native +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -873,6 +979,10 @@ export const spec = { } } + if (bidObj.mediaType === NATIVE) { + _parseNativeBidResponse(bidObj); + } + bidObj.site = bidReq.params.site; bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index aa79338d79e..46656d88d37 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -38,8 +38,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value // Optional debug mode, used to get a bid response with expected cpm. debug: { enabled: true, @@ -78,8 +78,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value video: { skip: 0 // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. @@ -91,6 +91,58 @@ Connects to Adagio demand source to fetch bids. } } }] + }, + { + code: 'article_native', + mediaTypes: { + native: { + // generic Prebid options + title: { + required: true, + len: 80 + }, + // … + // Custom Adagio data assets + ext: { + adagio_bvw: { + required: false + } + } + } + }, + bids: [{ + bidder: 'adagio', // Required + params: { + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_native', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. + environment: 'mobile', // Recommended. Environment where the page is displayed. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false, // Optional. Use it in case of Post-bid integration only. + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value + // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. + native: { + context: 1, + plcmttype: 2 + }, + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 + } + } + }] } ]; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 0a585caaa1a..999773f1a1f 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,5 +1,5 @@ import find from 'core-js-pure/features/array/find.js'; -import { expect } from 'chai'; +import { expect, util } from 'chai'; import { _features, internal as adagio, @@ -13,7 +13,8 @@ import { } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; -import { config } from 'src/config.js'; +import { config } from '../../../src/config.js'; +import { NATIVE } from '../../../src/mediaTypes.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { @@ -879,6 +880,174 @@ describe('Adagio bid adapter', () => { expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); }); + + describe('Response with native add', () => { + const serverResponseWithNative = utils.deepClone(serverResponse) + serverResponseWithNative.body.bids[0].mediaType = 'native'; + serverResponseWithNative.body.bids[0].admNative = { + ver: '1.2', + link: { + url: 'https://i.am.a.click.url', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ] + }, + privacy: 'http://www.myprivacyurl.url', + ext: { + bvw: 'test' + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventrack.local/impression' + }, + { + event: 1, + method: 2, + url: 'https://eventrack.local/impression' + }, + { + event: 2, + method: 1, + url: 'https://eventrack.local/viewable-mrc50' + } + ], + assets: [ + { + required: 1, + title: { + text: 'My title' + } + }, + { + img: { + url: 'https://images.local/image.jpg', + w: 100, + h: 250 + } + }, + { + img: { + type: 1, + url: 'https://images.local/icon.png', + w: 40, + h: 40 + } + }, + { + data: { + type: 1, // sponsored + value: 'Adagio' + } + }, + { + data: { + type: 2, // desc / body + value: 'The super ad text' + } + }, + { + data: { + type: 3, // rating + value: '10 from 10' + } + }, + { + data: { + type: 11, // displayUrl + value: 'https://i.am.a.display.url' + } + } + ] + }; + + const bidRequestNative = utils.deepClone(bidRequest) + bidRequestNative.mediaTypes = { + native: { + sendTargetingKeys: false, + + clickUrl: { + required: true, + }, + title: { + required: true, + }, + body: { + required: true, + }, + sponsoredBy: { + required: false + }, + image: { + required: true + }, + icon: { + required: true + }, + privacyLink: { + required: false + }, + ext: { + adagio_bvw: {} + } + } + }; + + it('Should ignore native parsing due to missing raw admNative property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + delete alternateServerResponse.body.bids[0].admNative + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should ignore native parsing due to invalid raw admNative.assets property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + alternateServerResponse.body.bids[0].admNative.assets = { title: { text: 'test' } }; + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should handle and return a formated Native ad', () => { + const r = spec.interpretResponse(serverResponseWithNative, bidRequestNative); + const expected = { + displayUrl: 'https://i.am.a.display.url', + sponsoredBy: 'Adagio', + body: 'The super ad text', + rating: '10 from 10', + clickUrl: 'https://i.am.a.click.url', + title: 'My title', + impressionTrackers: [ + 'https://eventrack.local/impression' + ], + javascriptTrackers: '', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ], + image: { + url: 'https://images.local/image.jpg', + width: 100, + height: 250 + }, + icon: { + url: 'https://images.local/icon.png', + width: 40, + height: 40 + }, + ext: { + adagio_bvw: 'test' + }, + privacyLink: 'http://www.myprivacyurl.url' + } + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).ok; + expect(r[0].native).to.deep.equal(expected); + }); + }); }); describe('getUserSyncs()', function() { From 7635af1c9b2e143401284fbc511d18e748a6c749 Mon Sep 17 00:00:00 2001 From: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Date: Wed, 10 Mar 2021 13:02:42 +0100 Subject: [PATCH 216/325] EIDS Support Update (#6394) --- modules/connectadBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index d1811a1b7d1..4fa2a56a004 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -72,7 +72,7 @@ export const spec = { // EIDS Support if (validBidRequests[0].userId) { - data.user.ext.eids = createEidsArray(validBidRequests[0].userId); + utils.deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); } validBidRequests.map(bid => { From 89d5d410bc4b62c2934f64cec10a6e1948329318 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Wed, 10 Mar 2021 15:03:49 +0300 Subject: [PATCH 217/325] TheMediaGridNM Bid Adapter: fix trouble with alias (#6371) * Added TheMediaGridNM Bid Adapter * Updated required params for TheMediaGridNM Bid Adapter * Update TheMediGridNM Bid Adapter * Fix tests for TheMediaGridNM Bid Adapter * Fixes after review for TheMediaGridNM Bid Adapter * Add support of multi-format in TheMediaGrid Bid Adapter * Update sync url for grid and gridNM Bid Adapters * TheMediaGrid Bid Adapter: added keywords adUnit parameter * Update TheMediaGrid Bid Adapter to support keywords from config * Implement new request format for TheMediaGrid Bid Adapter * Fix jwpseg params for TheMediaGrid Bid Adapter * Update unit tests for The Media Grid Bid Adapter * Fix typo in TheMediaGrid Bid Adapter * Added test for jwTargeting in TheMediaGrid Bid Adapter * The new request format was made by default in TheMediaGrid Bid Adapter * Update userId format in ad request for TheMediaGrid Bid Adapter * Added bidFloor parameter for TheMediaGrid Bid Adapter * Fix for review TheMediaGrid Bid Adapter * Support floorModule in TheMediaGrid Bid Adapter * Fix empty bidfloor for TheMediaGrid Bid Adapter * Some change to restart autotests * Fix userIds format for TheMediaGrid Bid Adapter * Remove digitrust userId from TheMediaGrid Bid Adapter * Protocols was added in video section in ad request for TheMediaGrid Bid Adapter * TheMediaGrid: fix trouble with alias using * TheMediaGridNM: fix trouble with alias --- modules/gridNMBidAdapter.js | 1 - test/spec/modules/gridNMBidAdapter_spec.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index ffd6c1b250c..af1e9f84f43 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -134,7 +134,6 @@ export const spec = { } const bidResponse = { requestId: bid.bidId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index 0dbaac0c526..2aec9713000 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -300,7 +300,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': 11, 'width': 300, 'height': 250, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -317,7 +316,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, From 20f3cd8972079231f81a02ad4aa24b317e4032dd Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 10 Mar 2021 07:28:35 -0800 Subject: [PATCH 218/325] Grab sourceAgnostic IDs first, then fallback to regular IDs (#6400) --- modules/rubiconAnalyticsAdapter.js | 4 +- .../modules/rubiconAnalyticsAdapter_spec.js | 44 ++++++++++++++++--- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 08ae8bf2dd8..f6724ffcc7a 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -482,8 +482,8 @@ function subscribeToGamSlots() { // these come in as `null` from Gpt, which when stringified does not get removed // so set explicitly to undefined when not a number 'advertiserId', advertiserId => utils.isNumber(advertiserId) ? advertiserId : undefined, - 'creativeId', creativeId => utils.isNumber(creativeId) ? creativeId : undefined, - 'lineItemId', lineItemId => utils.isNumber(lineItemId) ? lineItemId : undefined, + 'creativeId', creativeId => utils.isNumber(event.sourceAgnosticCreativeId) ? event.sourceAgnosticCreativeId : utils.isNumber(creativeId) ? creativeId : undefined, + 'lineItemId', lineItemId => utils.isNumber(event.sourceAgnosticLineItemId) ? event.sourceAgnosticLineItemId : utils.isNumber(lineItemId) ? lineItemId : undefined, 'adSlot', () => event.slot.getAdUnitPath(), 'isSlotEmpty', () => event.isEmpty || undefined ]); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 136f5ef240a..a9c1eeef4de 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1436,8 +1436,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot0, isEmpty: false, advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333 + sourceAgnosticCreativeId: 2222, + sourceAgnosticLineItemId: 3333 } }; @@ -1448,8 +1448,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot1, isEmpty: false, advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666 + sourceAgnosticCreativeId: 5555, + sourceAgnosticLineItemId: 6666 } }; }); @@ -1515,8 +1515,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot1, isEmpty: false, advertiserId: 0, - creativeId: 0, - lineItemId: 0 + sourceAgnosticCreativeId: 0, + sourceAgnosticLineItemId: 0 } }]); expect(server.requests.length).to.equal(1); @@ -1540,6 +1540,38 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); + it('should pick backup Ids if no sourceAgnostic available first', function () { + performStandardAuction([gptSlotRenderEnded0, { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: false, + advertiserId: 0, + lineItemId: 1234, + creativeId: 5678 + } + }]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 0, + creativeId: 5678, + lineItemId: 1234, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + it('should correctly set adUnit for associated slots', function () { performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); expect(server.requests.length).to.equal(1); From b65ea7371bb4e0d91197c550c13526cb54b2d3a5 Mon Sep 17 00:00:00 2001 From: David Reischer Date: Wed, 10 Mar 2021 16:16:18 +0000 Subject: [PATCH 219/325] Permutive - add AC support for TrustX (#6393) --- .../gpt/permutiveRtdProvider_example.html | 19 ++- modules/permutiveRtdProvider.js | 7 + modules/permutiveRtdProvider.md | 1 + .../spec/modules/permutiveRtdProvider_spec.js | 147 ++++++++++++++++-- 4 files changed, 153 insertions(+), 21 deletions(-) diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html index 0814dcece5b..a06430bcdfa 100644 --- a/integrationExamples/gpt/permutiveRtdProvider_example.html +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -50,7 +50,7 @@ params: { placementId: 13144370, keywords: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -64,7 +64,7 @@ area: ['home'] }, visitor: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -78,12 +78,21 @@ { settings: {}, targeting: { - inline_kvs: ['1', '2', '3', '4'] + test_kv: ['true'] } } ], ozoneData: {} } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } } ] }, @@ -127,13 +136,13 @@ pbjs.setConfig({ debug: true, realTimeData: { - auctionDelay: 50, // maximum time for RTD modules to respond + auctionDelay: 80, // maximum time for RTD modules to respond dataProviders: [ { name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], maxSegs: 500, overwrites: { rubicon: function (bid, data, acEnabled, utils, defaultFn) { diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 8ec215d3cca..db431ed45a7 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -110,6 +110,13 @@ function getDefaultBidderFn (bidder) { deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac) } + return bid + }, + trustx: function (bid, data, acEnabled) { + if (acEnabled && data.ac && data.ac.length) { + deepSetValue(bid, 'params.keywords.p_standard', data.ac) + } + return bid } } diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 55bdf6420cf..fe8c34c1b5c 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -33,6 +33,7 @@ The below bidders are currently support by the Permutive RTD module. Please reac | Xandr | `appnexus` | Yes | Yes | | Magnite | `rubicon` | Yes | Yes | | Ozone | `ozone` | No | Yes | +| TrustX | `trustx` | No | Yes | * **First-party segments:** When enabling the respective Activation for a segment in Permutive, this module will automatically attach that segment to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in Permutive. Permutive segments will be sent in the `permutive` key-value. diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index d55bbc58056..cf1f3861eab 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -54,7 +54,7 @@ describe('permutiveRtdProvider', function () { }) } }) - it('sets segment targeting for Rubicon', function () { + it('sets segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() const config = getConfig() @@ -93,10 +93,29 @@ describe('permutiveRtdProvider', function () { }) } }) + it('sets segment targeting for TrustX', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) }) describe('Custom segment targeting', function () { - it('sets custom segment targeting for Rubicon', function () { + it('sets custom segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() const config = getConfig() @@ -129,6 +148,81 @@ describe('permutiveRtdProvider', function () { }) }) + describe('Existing key-value targeting', function () { + it('doesn\'t overwrite existing key-values for Xandr', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Magnite', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Ozone', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for TrustX', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + }) + describe('Permutive on page', function () { it('checks if Permutive is on page', function () { expect(isPermutiveOnPage()).to.equal(false) @@ -168,7 +262,7 @@ function getConfig () { name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], maxSegs: 500 } } @@ -197,15 +291,20 @@ function getTargetingData () { } function getAdUnits () { + const div_1_sizes = [ + [300, 250], + [300, 600] + ] + const div_2_sizes = [ + [728, 90], + [970, 250] + ] return [ { code: '/19968336/header-bid-tag-0', mediaTypes: { banner: { - sizes: [ - [300, 250], - [300, 600] - ] + sizes: div_1_sizes } }, bids: [ @@ -214,7 +313,7 @@ function getAdUnits () { params: { placementId: 13144370, keywords: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -228,7 +327,7 @@ function getAdUnits () { area: ['home'] }, visitor: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -242,12 +341,21 @@ function getAdUnits () { { settings: {}, targeting: { - inline_kvs: ['1', '2', '3', '4'] + test_kv: ['true'] } } ], ozoneData: {} } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } } ] }, @@ -255,17 +363,17 @@ function getAdUnits () { code: '/19968336/header-bid-tag-1', mediaTypes: { banner: { - sizes: [ - [728, 90], - [970, 250] - ] + sizes: div_2_sizes } }, bids: [ { bidder: 'appnexus', params: { - placementId: 13144370 + placementId: 13144370, + keywords: { + test_kv: ['true'] + } } }, { @@ -273,7 +381,14 @@ function getAdUnits () { params: { publisherId: 'OZONEGMG0001', siteId: '4204204209', - placementId: '0420420500' + placementId: '0420420500', + customData: [ + { + targeting: { + test_kv: ['true'] + } + } + ] } } ] From cdfc9dfeecd48fb9c7cdd116fc7ae976871f11c0 Mon Sep 17 00:00:00 2001 From: evanmsmrtb Date: Wed, 10 Mar 2021 12:56:49 -0600 Subject: [PATCH 220/325] SmartRTB Bid Adapter: add alias and update valid opts (#6365) * Add alias, update valid opts * Update bidder tests --- modules/smartrtbBidAdapter.js | 6 ++---- test/spec/modules/smartrtbBidAdapter_spec.js | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/modules/smartrtbBidAdapter.js b/modules/smartrtbBidAdapter.js index 12d5a0ae7da..de303f9e4b2 100644 --- a/modules/smartrtbBidAdapter.js +++ b/modules/smartrtbBidAdapter.js @@ -17,11 +17,9 @@ function getDomain () { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ 'banner', 'video' ], - aliases: ['smrtb'], + aliases: ['rdigital'], isBidRequestValid: function(bid) { - return (bid.params.pubId !== null && - bid.params.medId !== null && - bid.params.zoneId !== null); + return (bid.params.pubId !== null || bid.params.zoneId !== null); }, buildRequests: function(validBidRequests, bidderRequest) { let stack = (bidderRequest.refererInfo && diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js index cb5ceee0870..a7f30bdec6e 100644 --- a/test/spec/modules/smartrtbBidAdapter_spec.js +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -69,9 +69,6 @@ describe('SmartRTBBidAdapter', function () { it('should return a bidder code of smartrtb', function () { expect(spec.code).to.equal('smartrtb') }) - it('should alias smrtb', function () { - expect(spec.aliases.length > 0 && spec.aliases[0] === 'smrtb').to.be.true - }) }) describe('isBidRequestValid', function () { @@ -79,8 +76,8 @@ describe('SmartRTBBidAdapter', function () { expect(spec.isBidRequestValid(bannerRequest)).to.be.true }) - it('should return false if any zone id missing', function () { - expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { zoneId: null } }))).to.be.false + it('should return false if any zone id and pub id missing', function () { + expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { pubId: null, zoneId: null } }))).to.be.false }) }) From f1c45cd3a702f9b7b77da8d7be523756173809be Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 10 Mar 2021 13:17:06 -0800 Subject: [PATCH 221/325] Prebid 4.30.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dfd8a60b06d..befc6c79fbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.30.0-pre", + "version": "4.30.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 41b8607a004c111093a8143992b0191f1ff3bd44 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 10 Mar 2021 13:36:31 -0800 Subject: [PATCH 222/325] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index befc6c79fbd..7943e00f3c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.30.0", + "version": "4.31.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f88a55addb04ebdcaa31d43cf121895ea89461e3 Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Thu, 11 Mar 2021 04:00:03 -0500 Subject: [PATCH 223/325] Sonobi Bid Adapter: send eids in bid request. (#6364) * unwrapping id5id uid. Added new eid param for user id modules * set userid to new variable * fixed spelling mistake in unit test Co-authored-by: Scott Menzer * copying userid object so the referenced object does not get updated. * using deepClone instead of spreading the top userId object Co-authored-by: Scott Menzer --- modules/sonobiBidAdapter.js | 16 +++- test/spec/modules/sonobiBidAdapter_spec.js | 85 +++++++++++++++++++++- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index a55992cec22..0e4bfb37829 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,10 +1,9 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { userSync } from '../src/userSync.js'; - const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); @@ -117,7 +116,18 @@ export const spec = { payload.schain = JSON.stringify(validBidRequests[0].schain) } if (deepAccess(validBidRequests[0], 'userId') && Object.keys(validBidRequests[0].userId).length > 0) { - payload.userid = JSON.stringify(validBidRequests[0].userId); + const userIds = deepClone(validBidRequests[0].userId); + + if (userIds.id5id) { + userIds.id5id = deepAccess(userIds, 'id5id.uid'); + } + + payload.userid = JSON.stringify(userIds); + } + + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (Array.isArray(eids) && eids.length > 0) { + payload.eids = JSON.stringify(eids); } let keywords = validBidRequests[0].params.keywords; // a CSV of keywords diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 52821072a21..d1ac200394c 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -414,15 +414,92 @@ describe('SonobiBidAdapter', function () { expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids as a JSON-encoded set of eids', function () { + bidRequest[0].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + bidRequest[1].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(JSON.parse(bidRequests.data.eids)).to.eql([ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]); + }); + it('should return a properly formatted request with userid as a JSON-encoded set of User ID results', function () { - bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; - bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; + bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': {'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': {'linkType': 2}}}; + bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': {'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': {'linkType': 2}}}; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); expect(bidRequests.method).to.equal('GET'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; - expect(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}); + expect(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ'}); }); it('should return a properly formatted request with userid omitted if there are no userIds', function () { @@ -469,7 +546,7 @@ describe('SonobiBidAdapter', function () { ]; const bidRequests = spec.buildRequests(bRequest, bidderRequests); expect(bidRequests.url).to.equal('https://iad-2-apex.go.sonobi.com/trinity.json'); - }) + }); }); describe('.interpretResponse', function () { From 8e764f666530bf8ea57f1438907adb2951aa6d6f Mon Sep 17 00:00:00 2001 From: thuyhq <61451682+thuyhq@users.noreply.github.com> Date: Fri, 12 Mar 2021 16:48:38 +0700 Subject: [PATCH 224/325] Apacdex Bid Adapter: userId module support, show demo ads in debug mode & other maintenance (#6378) * Upgrade and maintenance apacdexBidAdapter * fix error and add unit test --- modules/apacdexBidAdapter.js | 145 ++++++++++++++++---- test/spec/modules/apacdexBidAdapter_spec.js | 101 +++++++++++++- 2 files changed, 221 insertions(+), 25 deletions(-) diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index 2582e4788c1..62ae3f54125 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'apacdex'; const CONFIG = { @@ -49,11 +50,34 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { + let siteId; + let schain; + let eids; + let geo; + let test; + var bids = JSON.parse(JSON.stringify(validBidRequests)) bidderConfig = CONFIG[bids[0].bidder]; - const payload = {}; + + test = config.getConfig('debug'); bids.forEach(bidReq => { + siteId = siteId || bidReq.params.siteId; + + if (bidReq.schain) { + schain = schain || bidReq.schain + } + + if (bidReq.userIdAsEids) { + eids = eids || bidReq.userIdAsEids + } + + if (bidReq.params && bidReq.params.geo) { + if (validateGeoObject(bidReq.params.geo)) { + geo = bidReq.params.geo; + } + } + var targetKey = 0; if (bySlotTargetKey[bidReq.adUnitCode] != undefined) { targetKey = bySlotTargetKey[bidReq.adUnitCode]; @@ -73,36 +97,55 @@ export const spec = { bidReq.targetKey = targetKey; }); + const payload = {}; + payload.tmax = bidderRequest.timeout; + if (test) { + payload.test = 1; + } + payload.device = {}; payload.device.ua = navigator.userAgent; - payload.device.height = window.top.innerHeight; - payload.device.width = window.top.innerWidth; + payload.device.height = window.screen.width; + payload.device.width = window.screen.height; payload.device.dnt = _getDoNotTrack(); payload.device.language = navigator.language; + var pageUrl = _extractTopWindowUrlFromBidderRequest(bidderRequest); payload.site = {}; - payload.site.id = bids[0].params.siteId; - payload.site.page = _extractTopWindowUrlFromBidderRequest(bidderRequest); + payload.site.id = siteId; + payload.site.page = pageUrl payload.site.referrer = _extractTopWindowReferrerFromBidderRequest(bidderRequest); - payload.site.hostname = window.top.location.hostname; + payload.site.hostname = getDomain(pageUrl); // Apply GDPR parameters to request. - payload.gdpr = {}; if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = {}; payload.gdpr.gdprApplies = !!bidderRequest.gdprConsent.gdprApplies; if (bidderRequest.gdprConsent.consentString) { payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; } } - // Apply schain. - if (bids[0].schain) { - payload.schain = bids[0].schain - } + // Apply us_privacy. if (bidderRequest && bidderRequest.uspConsent) { payload.us_privacy = bidderRequest.uspConsent; } + // Apply schain. + if (schain) { + payload.schain = schain + } + + // Apply eids. + if (eids) { + payload.eids = eids + } + + // Apply geo + if (geo) { + payload.geo = geo; + } + payload.bids = bids; return { @@ -115,12 +158,12 @@ export const spec = { }, interpretResponse: function (serverResponse, bidRequest) { const serverBody = serverResponse.body; - const serverBids = serverBody.bids; - // check overall response - if (!serverBody || typeof serverBody !== 'object') { + if (!serverBody || !utils.isPlainObject(serverBody)) { return []; } - if (!serverBids || typeof serverBids !== 'object') { + + const serverBids = serverBody.bids; + if (!serverBids || !utils.isArray(serverBids)) { return []; } @@ -192,15 +235,25 @@ function _getBiggestSize(sizes) { } function _getDoNotTrack() { - if (window.top.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack) { - if (window.top.doNotTrack == '1' || navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') { + try { + if (window.top.doNotTrack && window.top.doNotTrack == '1') { return 1; - } else { - return 0; } - } else { - return 0; - } + } catch (e) { } + + try { + if (navigator.doNotTrack && (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1')) { + return 1; + } + } catch (e) { } + + try { + if (navigator.msDoNotTrack && navigator.msDoNotTrack == '1') { + return 1; + } + } catch (e) { } + + return 0 } /** @@ -210,8 +263,11 @@ function _getDoNotTrack() { * @returns {string} */ function _extractTopWindowUrlFromBidderRequest(bidderRequest) { - if (bidderRequest && utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl')) { - return bidderRequest.refererInfo.canonicalUrl; + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + if (utils.deepAccess(bidderRequest, 'refererInfo.referer')) { + return bidderRequest.refererInfo.referer; } try { @@ -239,4 +295,45 @@ function _extractTopWindowReferrerFromBidderRequest(bidderRequest) { } } +/** + * Extracts the domain from given page url + * + * @param {string} url + * @returns {string} + */ +export function getDomain(pageUrl) { + if (config.getConfig('publisherDomain')) { + var publisherDomain = config.getConfig('publisherDomain'); + return publisherDomain.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; + } + + if (!pageUrl) { + return pageUrl; + } + + return pageUrl.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; +} + +/** + * Validate geo object + * + * @param {Object} geo + * @returns {boolean} + */ +export function validateGeoObject(geo) { + if (!utils.isPlainObject(geo)) { + return false; + } + if (!geo.lat) { + return false; + } + if (!geo.lon) { + return false; + } + if (!geo.accuracy) { + return false; + } + return true; +} + registerBidder(spec); diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index da9a050a8de..c3d0d025c0c 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -1,7 +1,8 @@ import { expect } from 'chai' -import { spec } from 'modules/apacdexBidAdapter.js' +import { spec, validateGeoObject, getDomain } from '../../../modules/apacdexBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' import { userSync } from '../../../src/userSync.js'; +import { config } from 'src/config.js'; describe('ApacdexBidAdapter', function () { const adapter = newBidder(spec) @@ -199,11 +200,34 @@ describe('ApacdexBidAdapter', function () { 'bidder': 'apacdex', 'params': { 'siteId': '1a2b3c4d5e6f1a2b3c4d', + 'geo': {'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60} }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'targetKey': 0, 'bidId': '30b31c1838de1f', + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'p0cCLF9JazY1ZUFjazJRb3NKbEprVTcwZ0IwRUlGalBjOG9laUZNbFJ0ZGpOSnVFbE9VMjBNMzNBTzladGt4cUVGQzBybDY2Y1FqT1dkUkFsMmJIWDRHNjlvNXJjbiUyQlZDd1dOTmt6VlV2TDhRd0F0RTlBcmpyZU5WRHBPU25GQXpyMnlT', + 'atype': 1 + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': '2ae366c2-2576-45e5-bd21-72ed10598f17', + 'atype': 1 + }] + }, { + 'source': 'sharedid.org', + 'uids': [{ + 'id': '01EZXQDVAPER4KE1VBS29XKV4Z', + 'atype': 1, + 'ext': { + 'third': '01EZXQDVAPER4KE1VBS29XKV4Z' + } + }] + }], }, { 'bidder': 'apacdex', @@ -300,10 +324,23 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.schain).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) + }); + it('should return a properly formatted request with geo defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.geo).to.deep.equal(bidRequest[0].params.geo) + }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); }); + it('should return a properly formatted request with pbjs_debug is true', function () { + config.setConfig({debug: true}); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.test).to.equal(1) + }); }); describe('.interpretResponse', function () { @@ -601,4 +638,66 @@ describe('ApacdexBidAdapter', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); }); }); + + describe('validateGeoObject', function () { + it('should return true if the geo object is valid', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(true); + }); + + it('should return false if the geo object is not plain object', () => { + let geoObject = [{ + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }]; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lat attribute', () => { + let geoObject = { + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lon attribute', () => { + let geoObject = { + lat: 123.5624234, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing accuracy attribute', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + }); + + describe('getDomain', function () { + it('should return valid domain from publisherDomain config', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: pageUrl}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return valid domain from pageUrl argument', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return undefined if pageUrl and publisherDomain not config', () => { + let pageUrl; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal(pageUrl); + }); + }); }); From 72820960581e8298fc8580e3098e462105aee671 Mon Sep 17 00:00:00 2001 From: novatiq <79258366+novatiq@users.noreply.github.com> Date: Fri, 12 Mar 2021 16:01:35 +0200 Subject: [PATCH 225/325] Novatiq ID System: add snowflake userId submodule (#6350) * Novatiq snowflake userId submodule Novatiq snowflake userId submodule initial release * change request updates added novatiq info /modules/userId/userId.md added novatiq info /modules/userId/eids.md added novatiq eids /modules/userId/eids.js added novatiq module in /modules/.submodules.json removed unnecessary value from getId response * Update novatiqIdSystem_spec.js removed unnecessary srcid value * Update novatiqIdSystem.md Novatiq ID System: updated novatiq snowflake ID description --- modules/.submodules.json | 3 +- modules/novatiqIdSystem.js | 88 +++++++++++++++++++++++ modules/novatiqIdSystem.md | 36 ++++++++++ modules/userId/eids.js | 9 +++ modules/userId/eids.md | 7 ++ modules/userId/userId.md | 4 ++ test/spec/modules/novatiqIdSystem_spec.js | 70 ++++++++++++++++++ 7 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 modules/novatiqIdSystem.js create mode 100644 modules/novatiqIdSystem.md create mode 100644 test/spec/modules/novatiqIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index fc69dd276a3..a7cf1f54426 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -20,7 +20,8 @@ "fabrickIdSystem", "verizonMediaIdSystem", "pubProvidedIdSystem", - "tapadIdSystem" + "tapadIdSystem", + "novatiqIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js new file mode 100644 index 00000000000..fbfa6ca8abc --- /dev/null +++ b/modules/novatiqIdSystem.js @@ -0,0 +1,88 @@ +/** + * This module adds novatiqId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/novatiqIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +/** @type {Submodule} */ +export const novatiqIdSubmodule = { + +/** +* used to link submodule with config +* @type {string} +*/ + name: 'novatiq', + + /** +* decode the stored id value for passing to bid requests +* @function +* @returns {novatiq: {snowflake: string}} +*/ + decode(novatiqId, config) { + let responseObj = { + novatiq: { + snowflake: novatiqId + } + }; + return responseObj; + }, + + /** +* performs action to obtain id and return a value in the callback's response argument +* @function +* @param {SubmoduleConfig} config +* @returns {id: string} +*/ + getId(config) { + function snowflakeId(placeholder) { + return placeholder + ? (placeholder ^ Math.random() * 16 >> placeholder / 4).toString(16) + : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11 + 1e3).replace(/[018]/g, snowflakeId); + } + + const configParams = config.params || {}; + const srcId = this.getSrcId(configParams); + utils.logInfo('NOVATIQ Sync request used sourceid param: ' + srcId); + + let partnerhost; + partnerhost = window.location.hostname; + utils.logInfo('NOVATIQ partner hostname: ' + partnerhost); + + const novatiqId = snowflakeId(); + const url = 'https://spadsync.com/sync?sptoken=' + novatiqId + '&sspid=' + srcId + '&ssphost=' + partnerhost; + ajax(url, undefined, undefined, { method: 'GET', withCredentials: false }); + + utils.logInfo('NOVATIQ snowflake: ' + novatiqId); + return { 'id': novatiqId } + }, + + getSrcId(configParams) { + utils.logInfo('NOVATIQ Configured sourceid param: ' + configParams.sourceid); + + function isHex(str) { + var a = parseInt(str, 16); + return (a.toString(16) === str) + } + + let srcId; + if (typeof configParams.sourceid === 'undefined' || configParams.sourceid === null || configParams.sourceid === '') { + srcId = '000'; + utils.logInfo('NOVATIQ sourceid param set to value 000 due to undefined parameter or missing value in config section'); + } else if (configParams.sourceid.length < 3 || configParams.sourceid.length > 3) { + srcId = '001'; + utils.logInfo('NOVATIQ sourceid param set to value 001 due to wrong size in config section 3 chars max e.g. 1ab'); + } else if (isHex(configParams.sourceid) == false) { + srcId = '002'; + utils.logInfo('NOVATIQ sourceid param set to value 002 due to wrong format in config section expecting hex value only'); + } else { + srcId = configParams.sourceid; + } + return srcId + } +}; +submodule('userId', novatiqIdSubmodule); diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md new file mode 100644 index 00000000000..ce561a696e3 --- /dev/null +++ b/modules/novatiqIdSystem.md @@ -0,0 +1,36 @@ +# Novatiq Snowflake ID + +Novatiq proprietary dynamic snowflake ID is a unique, non sequential and single use identifier for marketing activation. Our in network solution matches verification requests to telco network IDs, safely and securely inside telecom infrastructure. Novatiq snowflake ID can be used for identity validation and as a secured 1st party data delivery mechanism. + +## Novatiq Snowflake ID Configuration + +Enable by adding the Novatiq submodule to your Prebid.js package with: + +``` +gulp build --modules=novatiqIdSystem,userId +``` + +Module activation and configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'novatiq', + params: { + sourceid '1a3', // change to the Partner Number you received from Novatiq + } + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | Module identification: `"novatiq"` | `"novatiq"` | +| params | Required | Object | Configuration specifications for the Novatiq module. | | +| params.sourceid | Required | String | This is the Novatiq Partner Number obtained via Novatiq registration. | `1a3` | + +If you have any questions, please reach out to us at prebid@novatiq.com. diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 80750ccaae8..a7e5eaf6061 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -172,6 +172,15 @@ const USER_IDS_CONFIG = { 'tapadId': { source: 'tapad.com', atype: 1 + }, + + // Novatiq Snowflake + 'novatiq': { + getValue: function(data) { + return data.snowflake + }, + source: 'novatiq.com', + atype: 1 } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index b69c4b9bd5e..404066d53e4 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -148,6 +148,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 1 }] + }, + { + source: 'novatiq.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] } ] ``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 267b3a60cea..a7f98fb39a0 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -182,6 +182,10 @@ pbjs.setConfig({ { name: "criteo", value: { "criteoId": "wK-fkF8zaEIlMkZMbHl3eFo4NEtoNmZaeXJtYkFjZlVuWjBhcjJMaTRYd3pZNSUyQnlKRHNGRXlpdzdjd3pjVzhjcSUyQmY4eTFzN3VSZjV1ZyUyRlA0U2ZiR0UwN2I4bDZRJTNEJTNE" } + }, + { + name: "novatiq", + value: { "snowflake": "81b001ec-8914-488c-a96e-8c220d4ee08895ef" } }], syncDelay: 5000 } diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js new file mode 100644 index 00000000000..60c82626450 --- /dev/null +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -0,0 +1,70 @@ +import { novatiqIdSubmodule } from 'modules/novatiqIdSystem.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('novatiqIdSystem', function () { + describe('getSrcId', function() { + it('getSrcId should set srcId value to 000 due to undefined parameter in config section', function() { + const config = { params: { } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set srcId value to 000 due to missing value in config section', function() { + const config = { params: { sourceid: '' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 000 due to null value in config section', function() { + const config = { params: { sourceid: null } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 001 due to wrong length in config section max 3 chars', function() { + const config = { params: { sourceid: '1234' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('001'); + }); + + it('getSrcId should set value to 002 due to wrong format in config section', function() { + const config = { params: { sourceid: '1xc' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('002'); + }); + }); + + describe('getId', function() { + it('should log message if novatiqId has wrong format', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).to.have.length(40); + }); + + it('should log message if novatiqId not provided', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).should.be.not.empty; + }); + }); + + describe('decode', function() { + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).to.have.length(40); + }); + + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).should.be.not.empty; + }); + }); +}) From 4b0779a665d42eae01c8ef6bc02d0fc687da9565 Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Mon, 15 Mar 2021 16:24:53 +0200 Subject: [PATCH 226/325] Rise Bid Adapter: add session_id & is_wrapper params to adapter (#6407) * add new params to rise adapter * add unit tests for isWrapper and sessionId adapter params --- modules/riseBidAdapter.js | 3 ++- test/spec/modules/riseBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index a891e0aa883..e3265ad5d3e 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 176437c4f27..b3257cbda9d 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('riseAdapter', function () { bidderCode: 'rise', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -83,6 +85,22 @@ describe('riseAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('sends bid request to test ENDPOINT via GET', function () { const requests = spec.buildRequests(testModeBidRequests, bidderRequest); for (const request of requests) { From da7872851f04e813eb165ea9c5923520421bc8ff Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Mon, 15 Mar 2021 16:25:27 +0200 Subject: [PATCH 227/325] IronSource Bid Adapter: add session_id & is_wrapper params to adapter (#6408) * add new params * add unit tests for isWrapper and sessionId adapter params --- modules/ironsourceBidAdapter.js | 3 ++- test/spec/modules/ironsourceBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/ironsourceBidAdapter.js b/modules/ironsourceBidAdapter.js index ba510e86e7f..5b8531d7a85 100644 --- a/modules/ironsourceBidAdapter.js +++ b/modules/ironsourceBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION diff --git a/test/spec/modules/ironsourceBidAdapter_spec.js b/test/spec/modules/ironsourceBidAdapter_spec.js index 93c3a6fb7b9..cca928ff28b 100644 --- a/test/spec/modules/ironsourceBidAdapter_spec.js +++ b/test/spec/modules/ironsourceBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('ironsourceAdapter', function () { bidderCode: 'ironsource', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -98,6 +100,22 @@ describe('ironsourceAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('should send the correct width and height', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { From dc67fe8987886809ee1165a5b145fd7781dcd9d7 Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Mon, 15 Mar 2021 14:29:25 -0400 Subject: [PATCH 228/325] AMX Bid Adapter: add or update general adapter support and code refactoring (#6403) * AMX Bid adapter improvements * fix eslint issues (breaking CI) --- modules/amxBidAdapter.js | 115 +++++++++++------------- test/spec/modules/amxBidAdapter_spec.js | 87 ++++++++++++------ 2 files changed, 114 insertions(+), 88 deletions(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 497c2142b9b..5794b52dce0 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel } from '../src/utils.js'; +import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel, isFn, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -9,7 +9,6 @@ const storage = getStorageManager(737, BIDDER_CODE); const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; const VERSION = 'pba1.2.1'; -const xmlDTDRxp = /^\s*<\?xml[^\?]+\?>/; const VAST_RXP = /^\s*<\??(?:vast|xml)/i; const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/'; const AMUID_KEY = '__amuidpb'; @@ -45,11 +44,16 @@ function flatMap(input, mapFn) { .reduce((acc, item) => item != null && acc.concat(item), []) } -const generateDTD = (xmlDocument) => - ``; - const isVideoADM = (html) => html != null && VAST_RXP.test(html); -const getMediaType = (bid) => isVideoADM(bid.adm) ? VIDEO : BANNER; + +function getMediaType(bid) { + if (isVideoADM(bid.adm)) { + return VIDEO; + } + + return BANNER; +} + const nullOrType = (value, type) => value == null || (typeof value) === type // eslint-disable-line valid-typeof @@ -103,6 +107,32 @@ const trackEvent = (eventName, data) => eid: getUniqueIdentifierStr(), })}`); +const DEFAULT_MIN_FLOOR = 0; + +function ensureFloor(floorValue) { + return typeof floorValue === 'number' && isFinite(floorValue) && floorValue > 0.0 + ? floorValue : DEFAULT_MIN_FLOOR; +} + +function getFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.floor', DEFAULT_MIN_FLOOR); + } + + try { + const floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + bidRequest: bid + }); + return floor.floor; + } catch (e) { + logError('call to getFloor failed: ', e); + return DEFAULT_MIN_FLOOR; + } +} + function convertRequest(bid) { const size = largestSize(bid.sizes, bid.mediaTypes) || [0, 0]; const isVideoBid = bid.mediaType === VIDEO || VIDEO in bid.mediaTypes @@ -116,16 +146,21 @@ function convertRequest(bid) { bid.sizes, deepAccess(bid, `mediaTypes.${BANNER}.sizes`, []) || [], deepAccess(bid, `mediaTypes.${VIDEO}.sizes`, []) || [], - ] + ]; + + const videoData = deepAccess(bid, `mediaTypes.${VIDEO}`, {}) || {}; const params = { au, av, + vd: videoData, vr: isVideoBid, ms: multiSizes, aw: size[0], ah: size[1], tf: 0, + sc: bid.schain || {}, + f: ensureFloor(getFloor(bid)) }; if (typeof tid === 'string' && tid.length > 0) { @@ -143,52 +178,6 @@ function decorateADM(bid) { return bid.adm + impressions; } -function transformXmlSimple(bid) { - const pixels = [] - _each([bid.nurl].concat(bid.ext != null && bid.ext.himp != null ? bid.ext.himp : []), (pixel) => { - if (pixel != null) { - pixels.push(``) - } - }); - // find the current "Impression" here & slice ours in - const impressionIndex = bid.adm.indexOf(' url != null); - - _each(pixels, (pxl) => { - const imagePixel = doc.createElement('Impression'); - const cdata = doc.createCDATASection(pxl); - imagePixel.appendChild(cdata); - root.appendChild(imagePixel); - }); - - const dtdMatch = xmlDTDRxp.exec(bid.adm); - return (dtdMatch != null ? dtdMatch[0] : generateDTD(doc)) + getOuterHTML(doc.documentElement); -} - function resolveSize(bid, request, bidId) { if (bid.w != null && bid.w > 1 && bid.h != null && bid.h > 1) { return [bid.w, bid.h]; @@ -212,14 +201,16 @@ function values(source) { }); } +const isTrue = (boolValue) => + boolValue === true || boolValue === 1 || boolValue === 'true'; + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid(bid) { return nullOrType(deepAccess(bid, 'params.endpoint', null), 'string') && - nullOrType(deepAccess(bid, 'params.tagId', null), 'string') && - nullOrType(deepAccess(bid, 'params.testMode', null), 'boolean'); + nullOrType(deepAccess(bid, 'params.tagId', null), 'string') }, buildRequests(bidRequests, bidderRequest) { @@ -239,8 +230,9 @@ export const spec = { brc: fbid.bidderRequestsCount || 0, bwc: fbid.bidderWinsCount || 0, trc: fbid.bidRequestsCount || 0, - tm: testMode, + tm: isTrue(testMode), V: '$prebid.version$', + vg: '$$PREBID_GLOBAL$$', i: (testMode && tagId != null) ? tagId : getID(loc), l: {}, f: 0.01, @@ -259,7 +251,8 @@ export const spec = { d: '', m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, - fpd: config.getLegacyFpd(config.getConfig('ortb2')), + fpd2: config.getConfig('ortb2'), + tmax: config.getConfig('bidderTimeout'), eids: values(bidRequests.reduce((all, bid) => { // we only want unique ones in here if (bid == null || bid.userIdAsEids == null) { @@ -306,7 +299,6 @@ export const spec = { }, interpretResponse(serverResponse, request) { - // validate the body/response const response = serverResponse.body; if (response == null || typeof response === 'string') { return []; @@ -320,13 +312,14 @@ export const spec = { return flatMap(response.r[bidID], (siteBid) => siteBid.b.map((bid) => { const mediaType = getMediaType(bid); - // let ad = null; - let ad = mediaType === BANNER ? decorateADM(bid) : decorateVideoADM(bid); + const ad = mediaType === BANNER ? decorateADM(bid) : bid.adm; + if (ad == null) { return null; } const size = resolveSize(bid, request.data, bidID); + const defaultExpiration = mediaType === BANNER ? 240 : 300; return ({ requestId: bidID, @@ -341,7 +334,7 @@ export const spec = { advertiserDomains: bid.adomain, mediaType, }, - ttl: mediaType === VIDEO ? 90 : 70 + ttl: typeof bid.exp === 'number' ? bid.exp : defaultExpiration, }); })).filter((possibleBid) => possibleBid != null); }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 0658fe9f33c..863a8a1d0fc 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -31,20 +31,6 @@ const sampleFPD = { } }; -const legacySampleFPD = { - context: { - keywords: 'sample keywords', - data: { - pageType: 'article' - - } - }, - user: { - gender: 'O', - yob: 1982, - } -}; - const stubConfig = (withStub) => { const stub = sinon.stub(config, 'getConfig').callsFake( (arg) => arg === 'ortb2' ? sampleFPD : null @@ -74,6 +60,15 @@ const sampleBidRequestBase = { endpoint: 'https://httpbin.org/post', }, sizes: [[320, 50]], + getFloor(params) { + if (params.size == null || params.currency == null || params.mediaType == null) { + throw new Error(`getFloor called with incomplete params: ${JSON.stringify(params)}`) + } + return { + floor: 0.5, + currency: 'USD' + } + }, mediaTypes: { [BANNER]: { sizes: [[300, 250]] @@ -85,13 +80,28 @@ const sampleBidRequestBase = { auctionId: utils.getUniqueIdentifierStr(), }; +const schainConfig = { + ver: '1.0', + nodes: [{ + asi: 'greatnetwork.exchange', + sid: '000001', + hp: 1, + rid: 'bid_request_1', + domain: 'publisher.com' + }] +}; + const sampleBidRequestVideo = { ...sampleBidRequestBase, bidId: sampleRequestId + '_video', sizes: [[300, 150]], + schain: schainConfig, mediaTypes: { [VIDEO]: { - sizes: [[360, 250]] + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' } } }; @@ -120,6 +130,7 @@ const sampleServerResponse = { 'h': 600, 'id': '2014691335735134254', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 300 }, @@ -139,6 +150,7 @@ const sampleServerResponse = { 'h': 1, 'id': '7735706981389902829', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 1 }, @@ -160,8 +172,11 @@ describe('AmxBidAdapter', () => { expect(spec.isBidRequestValid({params: { tagId: 'test' }})).to.equal(true) }); - it('testMode is an optional boolean', () => { - expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(false) + it('testMode is an optional truthy value', () => { + expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(true) + expect(spec.isBidRequestValid({params: { testMode: 'true' }})).to.equal(true) + // ignore invalid values (falsy) + expect(spec.isBidRequestValid({params: { testMode: 'non-truthy-invalid-value' }})).to.equal(true) expect(spec.isBidRequestValid({params: { testMode: false }})).to.equal(true) }); @@ -195,6 +210,17 @@ describe('AmxBidAdapter', () => { expect(url).to.equal('https://prebid.a-mo.net/a/c') }); + it('will read the prebid version & global', () => { + const { data: { V: prebidVersion, vg: prebidGlobal } } = spec.buildRequests([{ + ...sampleBidRequestBase, + params: { + testMode: true + } + }], sampleBidderRequest); + expect(prebidVersion).to.equal('$prebid.version$') + expect(prebidGlobal).to.equal('$$PREBID_GLOBAL$$') + }); + it('reads test mode from the first bid request', () => { const { data } = spec.buildRequests([{ ...sampleBidRequestBase, @@ -269,7 +295,7 @@ describe('AmxBidAdapter', () => { it('will forward first-party data', () => { stubConfig(() => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd).to.deep.equal(legacySampleFPD) + expect(data.fpd2).to.deep.equal(sampleFPD) }); }); @@ -315,20 +341,24 @@ describe('AmxBidAdapter', () => { expect(data.m[sampleRequestId]).to.deep.equal({ av: true, au: 'div-gpt-ad-example', + vd: {}, ms: [ [[320, 50]], [[300, 250]], [] ], aw: 300, + sc: {}, ah: 250, tf: 0, + f: 0.5, vr: false }); expect(data.m[sampleRequestId + '_2']).to.deep.equal({ av: true, aw: 300, au: 'div-gpt-ad-example', + sc: {}, ms: [ [[320, 50]], [[300, 250]], @@ -336,7 +366,9 @@ describe('AmxBidAdapter', () => { ], i: 'example', ah: 250, + vd: {}, tf: 0, + f: 0.5, vr: false, }); }); @@ -354,7 +386,15 @@ describe('AmxBidAdapter', () => { av: true, aw: 360, ah: 250, + sc: schainConfig, + vd: { + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' + }, tf: 0, + f: 0.5, vr: true }); }); @@ -401,7 +441,7 @@ describe('AmxBidAdapter', () => { }, width: 300, height: 600, // from the bid itself - ttl: 70, + ttl: 90, ad: sampleDisplayAd( `` + `` @@ -412,20 +452,13 @@ describe('AmxBidAdapter', () => { it('can parse a video ad', () => { const parsed = spec.interpretResponse({ body: sampleServerResponse }, baseRequest) expect(parsed.length).to.equal(2) - - // we should have display, video, display - const xml = parsed[1].vastXml - delete parsed[1].vastXml - - expect(xml).to.have.string(``) - expect(xml).to.have.string(``) - expect(parsed[1]).to.deep.equal({ ...baseBidResponse, meta: { ...baseBidResponse.meta, mediaType: VIDEO, }, + vastXml: sampleVideoAd(''), width: 300, height: 250, ttl: 90, From 670ab08c3e030cf04b94e324719c3ad159ebf309 Mon Sep 17 00:00:00 2001 From: hybrid-ai <58724131+hybrid-ai@users.noreply.github.com> Date: Tue, 16 Mar 2021 07:25:34 +0300 Subject: [PATCH 229/325] VOX Bid adapter, Hybrid Bid adapter: fix global var name to avoid conflicts with astraOne adapter. (#6416) Co-authored-by: Petrov Denis --- modules/hybridBidAdapter.js | 2 +- modules/voxBidAdapter.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index dd55483ef33..e7281086a92 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -164,7 +164,7 @@ function wrapAd(bid, bidData) { parentDocument.style.width = "100%"; } var _content = "${encodeURIComponent(JSON.stringify(bid.inImageContent))}"; - window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_content))); + window._hyb_prebid_ssp.registerInImage(JSON.parse(decodeURIComponent(_content))); `; diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 450f270db31..73df9bb8b9b 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -129,7 +129,7 @@ function wrapInImageBanner(bid, bidData) { var s = document.getElementById("prebidrenderer"); s.onload = function () { var _html = "${encodeURIComponent(JSON.stringify(bid))}"; - window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); + window._hyb_prebid_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); } s.src = "https://st.hybrid.ai/prebidrenderer.js?t=" + Date.now(); if (parent.window.frames[window.name]) { @@ -157,7 +157,7 @@ function wrapBanner(bid, bidData) { var s = document.getElementById("prebidrenderer"); s.onload = function () { var _html = "${encodeURIComponent(JSON.stringify(bid))}"; - window._ao_ssp.registerAds(JSON.parse(decodeURIComponent(_html))); + window._hyb_prebid_ssp.registerAds(JSON.parse(decodeURIComponent(_html))); } s.src = "https://st.hybrid.ai/prebidrenderer.js?t=" + Date.now(); From 20bea36f5fc6cc5b8d5d0a7c82fa852e7e060eee Mon Sep 17 00:00:00 2001 From: Yevhenii Melnyk Date: Tue, 16 Mar 2021 05:31:55 +0100 Subject: [PATCH 230/325] LiveIntent Id System: fix for parsing response twice (#6418) * Don't parse response twice in LiveIntent Id submodule * Update the liveintent module test to have the 204 response --- modules/liveIntentIdSystem.js | 10 +--------- test/spec/modules/liveIntentIdSystem_spec.js | 17 ++++++++++------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index f87d67aae8e..5a955eefa92 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -166,15 +166,7 @@ export const liveIntentIdSubmodule = { const result = function(callback) { liveConnect.resolve( response => { - let responseObj = {}; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - } - callback(responseObj); + callback(response); }, error => { utils.logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3b4e4b9d9a7..f1de2f3bf93 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -36,7 +36,7 @@ describe('LiveIntentId', function() { resetLiveIntentIdSubmodule(); }); - it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function() { + it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function () { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, @@ -47,12 +47,16 @@ describe('LiveIntentId', function() { submoduleCallback(callBackSpy); let request = server.requests[1]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); + const response = { + unifiedId: 'a_unified_id', + segments: [123, 234] + } request.respond( 200, responseHeader, - JSON.stringify({}) + JSON.stringify(response) ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith(response)).to.be.true; }); it('should fire an event when getId', function() { @@ -131,11 +135,10 @@ describe('LiveIntentId', function() { let request = server.requests[1]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); request.respond( - 200, - responseHeader, - JSON.stringify({}) + 204, + responseHeader ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith({})).to.be.true; }); it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { From b9cd1a4e6a7c46d1095b8886f1e64c7d021603a3 Mon Sep 17 00:00:00 2001 From: Aleksa Trajkovic Date: Tue, 16 Mar 2021 13:54:50 +0100 Subject: [PATCH 231/325] PBJS Core: use mediaType renderer when backupOnly and no bid.renderer (#6419) * use mediaType renderer when backupOnly and no bid.renderer * check if necessary renderer properties are defined --- src/auction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auction.js b/src/auction.js index 217a50be3d6..7005d56827e 100644 --- a/src/auction.js +++ b/src/auction.js @@ -533,9 +533,9 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { var renderer = null; // the renderer for the mediaType takes precendence - if (mediaTypeRenderer && mediaTypeRenderer.url && !(mediaTypeRenderer.backupOnly === true && mediaTypeRenderer.render)) { + if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { renderer = mediaTypeRenderer; - } else if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly === true && bid.renderer)) { + } else if (adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render && !(adUnitRenderer.backupOnly === true && bid.renderer)) { renderer = adUnitRenderer; } From 70a41a51ac945b90c47224c294e64fc35742d604 Mon Sep 17 00:00:00 2001 From: susyt Date: Tue, 16 Mar 2021 07:56:52 -0700 Subject: [PATCH 232/325] GumGum Bid Adapter: pass bidfloor currency in bidrequest (#6391) * adds support for zone and pubId params * adds support for iriscat field * sets mediatype depending on product id * Update doc for mediaType needed for video products * makes slot and invideo products avail for pubId * updates gumgum doc * lint * adds missing comma in gumgum doc * adds currency in ad request, adds unit test * readd the previous irisid changes * remove the only in testing --- modules/gumgumBidAdapter.js | 24 +++++++++++----------- test/spec/modules/gumgumBidAdapter_spec.js | 4 ++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 2b0f7e03d22..9a01cd21fa4 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -208,25 +208,24 @@ function _getVidParams (attributes) { * @param {Object} bid * @returns {Number} floor */ -function _getFloor (mediaTypes, bidfloor, bid) { +function _getFloor (mediaTypes, staticBidfloor, bid) { const curMediaType = Object.keys(mediaTypes)[0] || 'banner'; - let floor = bidfloor || 0; + const bidFloor = { floor: 0, currency: 'USD' }; if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', + const { currency, floor } = bid.getFloor({ mediaType: curMediaType, size: '*' }); + floor && (bidFloor.floor = floor); + currency && (bidFloor.currency = currency); - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); + if (staticBidfloor && floor && currency === 'USD') { + bidFloor.floor = Math.max(staticBidfloor, parseFloat(floor)); } } - return floor; + return bidFloor; } /** @@ -250,7 +249,7 @@ function buildRequests (validBidRequests, bidderRequest) { transactionId, userId = {} } = bidRequest; - const bidFloor = _getFloor(mediaTypes, params.bidfloor, bidRequest); + const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); let sizes = [1, 1]; let data = {}; @@ -265,8 +264,9 @@ function buildRequests (validBidRequests, bidderRequest) { data.pv = pageViewId; } - if (bidFloor) { - data.fp = bidFloor; + if (floor) { + data.fp = floor; + data.fpc = currency; } if (params.iriscat && typeof params.iriscat === 'string') { diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 0d37c8b1d25..3f5d32bcef7 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -258,6 +258,10 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.fp).to.equal(bidfloor); }); + it('should return a floor currency', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.fpc).to.equal(floorTestData.currency); + }) }); it('sends bid request to ENDPOINT via GET', function () { From 46ecf2c41400d933ecdf77ba0c1e5513e1982d97 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 16 Mar 2021 21:37:28 -0700 Subject: [PATCH 233/325] Documentation: fixed a typo and sentence structure (#6421) --- modules/geoedgeRtdProvider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md index e4aa046a97d..5414606612c 100644 --- a/modules/geoedgeRtdProvider.md +++ b/modules/geoedgeRtdProvider.md @@ -4,7 +4,7 @@ Module Name: Geoedge Rtd provider Module Type: Rtd Provider Maintainer: guy.books@geoedge.com -The Geoedge Realtime module let pusblishers to block bad ads such as automatic redirects, malware, offensive creatives and landing pages. +The Geoedge Realtime module lets publishers block bad ads such as automatic redirects, malware, offensive creatives and landing pages. To use this module, you'll need to work with [Geoedge](https://www.geoedge.com/publishers-real-time-protection/) to get an account and cutomer key. ## Integration From 8beade60241293e09d745bec932719b59134b2f5 Mon Sep 17 00:00:00 2001 From: Daniel Liebner Date: Wed, 17 Mar 2021 01:03:06 -0400 Subject: [PATCH 234/325] Bid Glass Bid Adapter: pass options in bid request (#6424) * Added bidglass adapter + test * PR Review Updates: - Added formal params to getUserSyncs function definition - getUserSyncs now always returns an array - Improved unit test coverage * PR Review Updates: - Removed unused methods: getUserSyncs, onTimeout, onBidWon, onSetTargeting - Removed getUserSyncs unit test - Removed "dead code" - Removed some unnecessary comments - Fixed usage of parseInt * Bid Glass Bid Adapter: pass options in bid request --- modules/bidglassBidAdapter.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 6db35f184ca..3162228ce58 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { return window === window.top ? window.location.href : window.parent === window.top ? document.referrer : null; }; let getOrigins = function() { - var ori = ['https://' + window.location.hostname]; + var ori = [window.location.protocol + '//' + window.location.hostname]; if (window.location.ancestorOrigins) { for (var i = 0; i < window.location.ancestorOrigins.length; i++) { @@ -56,7 +56,7 @@ export const spec = { // Derive the parent origin var parts = document.referrer.split('/'); - ori.push('https://' + parts[2]); + ori.push(parts[0] + '//' + parts[2]); if (window.parent !== window.top) { // Additional unknown origins exist @@ -71,11 +71,15 @@ export const spec = { bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); bid.sizes = bid.sizes.filter(size => utils.isArray(size)); - // Stuff to send: [bid id, sizes, adUnitId] + var options = utils.deepClone(bid.params); + delete options.adUnitId; + + // Stuff to send: [bid id, sizes, adUnitId, options] imps.push({ bidId: bid.bidId, sizes: bid.sizes, - adUnitId: utils.getBidIdParameter('adUnitId', bid.params) + adUnitId: utils.getBidIdParameter('adUnitId', bid.params), + options: options }); }); From 990d287e3f158c2dca366c39c2f58d509137d8d1 Mon Sep 17 00:00:00 2001 From: Nick Peceniak Date: Wed, 17 Mar 2021 00:27:41 -0600 Subject: [PATCH 235/325] Spotx Bid Adapter: add publisher support for cache.ignoreBidderCacheKey (#6413) * Support ignoreBidderCacheKey in spotxBidAdapter * Update spotxBidAdapter_spec.js * Update spotxBidAdapter_spec.js Co-authored-by: Nick Peceniak --- modules/spotxBidAdapter.js | 16 ++++++++++--- test/spec/modules/spotxBidAdapter_spec.js | 28 +++++++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index c85a836435e..05e4e0ba1ef 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -215,6 +215,12 @@ export const spec = { } }; + // If the publisher asks to ignore the bidder cache key we need to return the full vast xml + // so that it can be cached on the publishes specified server. + if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { + requestPayload['ext']['wrap_response'] = 0; + } + if (utils.getBidIdParameter('number_of_ads', bid.params)) { requestPayload['ext']['number_of_ads'] = utils.getBidIdParameter('number_of_ads', bid.params); } @@ -336,14 +342,18 @@ export const spec = { ttl: 360, netRevenue: true, channel_id: serverResponseBody.id, - cache_key: spotxBid.ext.cache_key, - vastUrl: 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key, - videoCacheKey: spotxBid.ext.cache_key, mediaType: VIDEO, width: spotxBid.w, height: spotxBid.h }; + if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { + bid.vastXml = spotxBid.adm; + } else { + bid.cache_key = spotxBid.ext.cache_key; + bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key + } + bid.meta = bid.meta || {}; if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) { bid.meta.advertiserDomains = spotxBid.adomain; diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 927599ac986..873914441aa 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -379,6 +379,32 @@ describe('the spotx adapter', function () { expect(request.data.site.page).to.equal('prebid.js'); }); + + it('should set ext.wrap_response to 0 when cache url is set and ignoreBidderCacheKey is true', function() { + var request; + + var origGetConfig = config.getConfig; + sinon.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'cache') { + return { + url: 'prebidCacheLocation', + ignoreBidderCacheKey: true + }; + } + if (key === 'cache.url') { + return 'prebidCacheLocation'; + } + if (key === 'cache.ignoreBidderCacheKey') { + return true; + } + return origGetConfig.apply(config, arguments); + }); + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.ext.wrap_response).to.equal(0); + config.getConfig.restore(); + }); }); describe('interpretResponse', function() { @@ -469,7 +495,6 @@ describe('the spotx adapter', function () { expect(responses[0].requestId).to.equal(123); expect(responses[0].ttl).to.equal(360); expect(responses[0].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); - expect(responses[0].videoCacheKey).to.equal('cache123'); expect(responses[0].width).to.equal(400); expect(responses[1].cache_key).to.equal('cache124'); expect(responses[1].channel_id).to.equal(12345); @@ -483,7 +508,6 @@ describe('the spotx adapter', function () { expect(responses[1].requestId).to.equal(124); expect(responses[1].ttl).to.equal(360); expect(responses[1].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache124'); - expect(responses[1].videoCacheKey).to.equal('cache124'); expect(responses[1].width).to.equal(200); }); }); From a75f2f48356fb74aedf825971e1b06a619984443 Mon Sep 17 00:00:00 2001 From: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Wed, 17 Mar 2021 12:57:21 +0200 Subject: [PATCH 236/325] update prebid adapter. Add at, ccpa, gdpr and coppa support (#6405) --- modules/bizzclickBidAdapter.js | 19 ++++++++++++ test/spec/modules/bizzclickBidAdapter_spec.js | 29 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 80d2f6b5395..2af9a7afed2 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -81,10 +81,12 @@ export const spec = { let data = { id: bidRequest.bidId, test: config.getConfig('debug') ? 1 : 0, + at: 1, cur: ['USD'], device: { w: winTop.screen.width, h: winTop.screen.height, + dnt: utils.getDNT() ? 1 : 0, language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', }, site: { @@ -94,9 +96,26 @@ export const spec = { source: { tid: bidRequest.transactionId }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + user: { + ext: {} + }, tmax: bidRequest.timeout, imp: [impObject], }; + if (bidRequest) { + if (bidRequest.gdprConsent && bidRequest.gdprConsent.gdprApplies) { + utils.deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); + utils.deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); + } + + if (bidRequest.uspConsent !== undefined) { + utils.deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); + } + } bids.push(data) } return { diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js index 39ad4ae39c9..e0698c9eda8 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/bizzclickBidAdapter.js'; +import {config} from 'src/config.js'; const NATIVE_BID_REQUEST = { code: 'native_example', @@ -53,7 +54,11 @@ const BANNER_BID_REQUEST = { accountId: 'accountId' }, timeout: 1000, - + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: 1, + }, + uspConsent: 'uspConsent' } const bidRequest = { @@ -172,6 +177,22 @@ const NATIVE_BID_RESPONSE = { }; describe('BizzclickAdapter', function() { + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); + expect(serverRequest.data[0].regs.coppa).to.equal(1); + }); + }); + describe('isBidRequestValid', function() { it('should return true when required params found', function () { expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); @@ -225,6 +246,12 @@ describe('BizzclickAdapter', function() { expect(request.method).to.equal('POST'); }); + it('check consent and ccpa string is set properly', function() { + expect(request.data[0].regs.ext.gdpr).to.equal(1); + expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); + expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); + }) + it('Returns valid URL', function () { expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); }); From b98707c541e473db84b8891ec67cdd59bda72233 Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Thu, 18 Mar 2021 00:25:10 +0700 Subject: [PATCH 237/325] Qwarry Bid Adapter: add referer detection (#6412) * qwarry bid adapter * formatting fixes * fix tests for qwarry * qwarry bid adapter * add header for qwarry bid adapter * bid requests fix * fix tests * response fix * fix tests for Qwarry bid adapter * add pos parameter to qwarry bid adapter * qwarryBidAdapter onBidWon hotfix * Change bidder endpoint url for Qwarry adapter * add referer JS detection * use bidderRequest.refererInfo * fix tests Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 2 +- test/spec/modules/qwarryBidAdapter_spec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index 7cb83520979..5ff48ec53f6 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -27,7 +27,7 @@ export const spec = { return { method: 'POST', url: ENDPOINT, - data: { requestId: bidderRequest.bidderRequestId, bids }, + data: { requestId: bidderRequest.bidderRequestId, bids, referer: bidderRequest.refererInfo.referer }, options: { contentType: 'application/json', customHeaders: { diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 91e3cf4bfdf..bc776f7ebe7 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -70,11 +70,12 @@ describe('qwarryBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [REQUEST] - const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123' }) + const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123', refererInfo: { referer: 'http://test.com/path.html' } }) it('sends bid request to ENDPOINT via POST', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') + expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') From 24114d136537ff9f2f162f85adc45ef2e4a41433 Mon Sep 17 00:00:00 2001 From: SmartyAdsSSP <41569976+SmartyAdsSSP@users.noreply.github.com> Date: Wed, 17 Mar 2021 19:35:58 +0200 Subject: [PATCH 238/325] Smartyads Bid Adapter: add coppa field from config (#6402) * update adapter. Add coppa field from config * move stubs and restores for coppa tests --- modules/smartyadsBidAdapter.js | 2 ++ test/spec/modules/smartyadsBidAdapter_spec.js | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index ff60d08e48b..610617155ed 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; import * as utils from '../src/utils.js'; const BIDDER_CODE = 'smartyads'; @@ -49,6 +50,7 @@ export const spec = { 'secure': 1, 'host': location.host, 'page': location.pathname, + 'coppa': config.getConfig('coppa') === true ? 1 : 0, 'placements': placements }; request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 2780e88255d..8804050134a 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from '../../../modules/smartyadsBidAdapter.js'; +import { config } from '../../../src/config.js'; describe('SmartyadsAdapter', function () { let bid = { @@ -38,9 +39,10 @@ describe('SmartyadsAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); + expect(data.coppa).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); @@ -57,6 +59,23 @@ describe('SmartyadsAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([bid]); + expect(serverRequest.data.coppa).to.equal(1); + }); + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { From e0bd8e27d979ad9e7c4b9fe0be28a5f12d5f9a06 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:15:24 -0400 Subject: [PATCH 239/325] PBS Bid Adapter: Fpd2.0 bug fix for first party data issue (#6428) * Bug fix for PBS data from FPD2.0 update: Merging request.site and request.user with site and user object in FPD. --- modules/prebidServerBidAdapter/index.js | 4 ++-- test/spec/modules/prebidServerBidAdapter_spec.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 088b5430f46..d3bbb347720 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -769,10 +769,10 @@ const OPEN_RTB_PROTOCOL = { const commonFpd = getConfig('ortb2') || {}; if (commonFpd.site) { - utils.deepSetValue(request, 'site', commonFpd.site); + utils.mergeDeep(request, {site: commonFpd.site}); } if (commonFpd.user) { - utils.deepSetValue(request, 'user', commonFpd.user); + utils.mergeDeep(request, {user: commonFpd.user}); } addBidderFirstPartyDataToRequest(request); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index babee7e10d7..45d96b96d66 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1631,13 +1631,14 @@ describe('S2S Adapter', function () { } } })); + const commonContextExpected = utils.mergeDeep({'page': 'http://mytestpage.com', 'publisher': {'id': '1'}}, commonContext); config.setConfig({ fpd: { context: commonContext, user: commonUser } }); config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user } } }); adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); - expect(parsedRequestBody.site).to.deep.equal(commonContext); + expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); expect(parsedRequestBody.user).to.deep.equal(commonUser); }); From 04e49740cdd511cd024649d00bb74d6cc681b534 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 17 Mar 2021 15:26:26 -0400 Subject: [PATCH 240/325] Prebid 4.31.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7943e00f3c6..5af93962114 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.31.0-pre", + "version": "4.31.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 76a78287df25c4914ee22e3516cb0f103bf88024 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 17 Mar 2021 15:46:06 -0400 Subject: [PATCH 241/325] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5af93962114..ed13b6ae7da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.31.0", + "version": "4.32.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c245959f925283eb5e356a2826dead5ca72f2c32 Mon Sep 17 00:00:00 2001 From: Junus Date: Thu, 18 Mar 2021 08:19:43 +0600 Subject: [PATCH 242/325] a4g Bid Adapter: delete adid and use crid if it exists (#6409) * Deleted adid * set crid if it's exist and added unit tests --- modules/a4gBidAdapter.js | 3 +- test/spec/modules/a4gBidAdapter_spec.js | 50 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js index b7d8722e9f9..1961dba1f10 100644 --- a/modules/a4gBidAdapter.js +++ b/modules/a4gBidAdapter.js @@ -70,8 +70,7 @@ export const spec = { if (response.cpm > 0) { const bidResponse = { requestId: response.id, - creativeId: response.id, - adId: response.id, + creativeId: response.crid || response.id, cpm: response.cpm, width: response.width, height: response.height, diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js index 3dccbb28426..cb05fa62ab6 100644 --- a/test/spec/modules/a4gBidAdapter_spec.js +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/a4gBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('a4gAdapterTests', function () { describe('bidRequestValidity', function () { @@ -139,15 +140,54 @@ describe('a4gAdapterTests', function () { const bidResponse = { body: [{ - 'id': 'div-gpt-ad-1460505748561-0', + 'id': '51ef8751f9aead', 'ad': 'test ad', 'width': 320, 'height': 250, - 'cpm': 5.2 + 'cpm': 5.2, + 'crid': '111' }], headers: {} }; + it('should get correct bid response for banner ad', function () { + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '111', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set creativeId to default value if not provided', function () { + const bidResponseWithoutCrid = utils.deepClone(bidResponse); + delete bidResponseWithoutCrid.body[0].crid; + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '51ef8751f9aead', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponseWithoutCrid, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }) + it('required keys', function () { const result = spec.interpretResponse(bidResponse, bidRequest); @@ -169,5 +209,11 @@ describe('a4gAdapterTests', function () { expect(requiredKeys.indexOf(key) !== -1).to.equal(true); }); }) + + it('adId should not be equal to requestId', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].requestId).to.not.equal(result[0].adId); + }) }); }); From 595fc0a08b5f97a319b81f8099394623218efe7c Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Thu, 18 Mar 2021 07:47:26 -0500 Subject: [PATCH 243/325] Mediawallah ID System: add openlink userId submodule (#5921) * My first commit * Removed unnecessary await operation * Bug fixes and compliance fixes * Fixing some formatting and naming * Updating code based on automated feedback. * Parking refactoring change for team review * update mwOpenLink module * remove .git 2 folder * Trying to force a change * update the PR comments * applying the changes * update submodules.json and userId.md * fix typo of module names * update module decode function and test code * update test codes * apply the suggestions from Prebid * fix count of modules. Co-authored-by: Eric Brown Co-authored-by: hanna Co-authored-by: hannapanova190119 <71532550+hannapanova190119@users.noreply.github.com> --- modules/.submodules.json | 1 + modules/mwOpenLinkIdSystem.js | 142 +++++++++++++++++++ modules/mwOpenLinkIdSystem.md | 43 ++++++ modules/userId/eids.js | 6 +- modules/userId/eids.md | 7 + modules/userId/userId.md | 7 + test/spec/modules/mwOpenLinkIdSystem_spec.js | 20 +++ test/spec/modules/userId_spec.js | 71 +++++++--- 8 files changed, 280 insertions(+), 17 deletions(-) create mode 100644 modules/mwOpenLinkIdSystem.js create mode 100644 modules/mwOpenLinkIdSystem.md create mode 100644 test/spec/modules/mwOpenLinkIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index a7cf1f54426..0f62627822a 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -20,6 +20,7 @@ "fabrickIdSystem", "verizonMediaIdSystem", "pubProvidedIdSystem", + "mwOpenLinkIdSystem", "tapadIdSystem", "novatiqIdSystem" ], diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js new file mode 100644 index 00000000000..b2381836d5d --- /dev/null +++ b/modules/mwOpenLinkIdSystem.js @@ -0,0 +1,142 @@ +/** + * This module adds MediaWallah OpenLink to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/mwOpenLinkIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const openLinkID = { + name: 'mwol', + cookie_expiration: (86400 * 1000 * 365 * 1) // 1 year +} + +const storage = getStorageManager(); + +function getExpirationDate() { + return (new Date(utils.timestamp() + openLinkID.cookie_expiration)).toGMTString(); +} + +function isValidConfig(configParams) { + if (!configParams) { + utils.logError('User ID - mwOlId submodule requires configParams'); + return false; + } + if (!configParams.accountId) { + utils.logError('User ID - mwOlId submodule requires accountId to be defined'); + return false; + } + if (!configParams.partnerId) { + utils.logError('User ID - mwOlId submodule requires partnerId to be defined'); + return false; + } + return true; +} + +function deserializeMwOlId(mwOlIdStr) { + const mwOlId = {}; + const mwOlIdArr = mwOlIdStr.split(','); + + mwOlIdArr.forEach(function(value) { + const pair = value.split(':'); + // unpack a value of 1 as true + mwOlId[pair[0]] = +pair[1] === 1 ? true : pair[1]; + }); + + return mwOlId; +} + +function serializeMwOlId(mwOlId) { + let components = []; + + if (mwOlId.eid) { + components.push('eid:' + mwOlId.eid); + } + if (mwOlId.ibaOptout) { + components.push('ibaOptout:1'); + } + if (mwOlId.ccpaOptout) { + components.push('ccpaOptout:1'); + } + + return components.join(','); +} + +function readCookie(name) { + if (!name) name = openLinkID.name; + const mwOlIdStr = storage.getCookie(name); + if (mwOlIdStr) { + return deserializeMwOlId(decodeURIComponent(mwOlIdStr)); + } + return null; +} + +function writeCookie(mwOlId) { + if (mwOlId) { + const mwOlIdStr = encodeURIComponent(serializeMwOlId(mwOlId)); + storage.setCookie(openLinkID.name, mwOlIdStr, getExpirationDate(), 'lax'); + } +} + +function register(configParams, olid) { + const { accountId, partnerId, uid } = configParams; + const url = 'https://ol.mediawallahscript.com/?account_id=' + accountId + + '&partner_id=' + partnerId + + '&uid=' + uid + + '&olid=' + olid + + '&cb=' + Math.random() + ; + ajax(url); +} + +function setID(configParams) { + if (!isValidConfig(configParams)) return undefined; + const mwOlId = readCookie(); + const newMwOlId = mwOlId ? utils.deepClone(mwOlId) : {eid: utils.generateUUID()}; + writeCookie(newMwOlId); + register(configParams, newMwOlId.eid); + return { + id: newMwOlId + }; +}; + +/* End MW */ + +export { writeCookie }; + +/** @type {Submodule} */ +export const mwOpenLinkIdSubModule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'mwOpenLinkId', + /** + * decode the stored id value for passing to bid requests + * @function + * @param {MwOlId} mwOlId + * @return {(Object|undefined} + */ + decode(mwOlId) { + const id = mwOlId && utils.isPlainObject(mwOlId) ? mwOlId.eid : undefined; + return id ? { 'mwOpenLinkId': id } : undefined; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [submoduleParams] + * @returns {id:MwOlId | undefined} + */ + getId(submoduleConfig) { + const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; + if (!isValidConfig(submoduleConfigParams)) return undefined; + return setID(submoduleConfigParams); + } +}; + +submodule('userId', mwOpenLinkIdSubModule); diff --git a/modules/mwOpenLinkIdSystem.md b/modules/mwOpenLinkIdSystem.md new file mode 100644 index 00000000000..f55913f2983 --- /dev/null +++ b/modules/mwOpenLinkIdSystem.md @@ -0,0 +1,43 @@ +## MediaWallah openLink User ID Submodule + +OpenLink ID User ID Module generates a UUID that can be utilized to improve user matching. This module enables timely synchronization which handles MediaWallah optout. You must have a pre-existing relationship with MediaWallah prior to integrating. + +### Building Prebid with openLink Id Support +Your Prebid build must include the modules for both **userId** and **mwOpenLinkId** submodule. Follow the build instructions for Prebid as +explained in the top level README.md file of the Prebid source tree. + +ex: $ gulp build --modules=userId,mwOpenLinkIdSystem + +##$ MediaWallah openLink ID Example Configuration + +When the module is included, it's automatically enabled and saves an id to both cookie with an expiration time of 1 year. + +### Prebid Params + +Individual params may be set for the MediaWallah openLink User ID Submodule. At least accountId and partnerId must be set in the params. + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'mwOpenLinkId', + params: { + accountId: '1000', + partnerId: '1001', + uid: 'u-123xyz' + } + }] + } +}); +``` + +### Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the MediaWallah OpenLink ID User ID Module integration. + +| Params under usersync.userIds[]| Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `'mwOpenLinkId'` | +| params | Required | Object | Details for mwOLID syncing. | | +| params.accountId | Required | String | The MediaWallah assigned Account Id | `1000` | +| params.partnerId | Required | String | The MediaWallah assign Partner Id | `1001` | +| params.uid | Optional | String | Your unique Id for the user or browser. Used for matching | `u-123xyz` | \ No newline at end of file diff --git a/modules/userId/eids.js b/modules/userId/eids.js index a7e5eaf6061..9b26eff2ebf 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -169,11 +169,15 @@ const USER_IDS_CONFIG = { source: 'neustar.biz', atype: 1 }, + // MediaWallah OpenLink + 'mwOpenLinkId': { + source: 'mediawallahscript.com', + atype: 1 + }, 'tapadId': { source: 'tapad.com', atype: 1 }, - // Novatiq Snowflake 'novatiq': { getValue: function(data) { diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 404066d53e4..bd14ea0b9e7 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -142,6 +142,13 @@ userIdAsEids = [ atype: 1 }] }, + { + source: 'mediawallahscript.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { source: 'tapad.com', uids: [{ diff --git a/modules/userId/userId.md b/modules/userId/userId.md index a7f98fb39a0..828b2993e40 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -77,6 +77,13 @@ pbjs.setConfig({ name: '_criteoId', expires: 1 } + }, { + name: 'mwOpenLinkId', + params: { + accountId: 0000, + partnerId: 0000, + uid: '12345xyz' + } }], syncDelay: 5000, auctionDelay: 1000 diff --git a/test/spec/modules/mwOpenLinkIdSystem_spec.js b/test/spec/modules/mwOpenLinkIdSystem_spec.js new file mode 100644 index 00000000000..fb082b8cd16 --- /dev/null +++ b/test/spec/modules/mwOpenLinkIdSystem_spec.js @@ -0,0 +1,20 @@ +import { writeCookie, mwOpenLinkIdSubModule } from 'modules/mwOpenLinkIdSystem.js'; + +const P_CONFIG_MOCK = { + params: { + accountId: '123', + partnerId: '123' + } +}; + +describe('mwOpenLinkId module', function () { + beforeEach(function() { + writeCookie(''); + }); + + it('getId() should return a MediaWallah openLink Id when the MediaWallah openLink first party cookie exists', function () { + writeCookie({eid: 'XX-YY-ZZ-123'}); + const id = mwOpenLinkIdSubModule.getId(P_CONFIG_MOCK); + expect(id).to.be.deep.equal({id: {eid: 'XX-YY-ZZ-123'}}); + }); +}); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 8a7be41b2b5..95279380232 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -38,6 +38,7 @@ import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {haloIdSubmodule} from 'modules/haloIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; +import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; import {getPrebidInternal} from 'src/utils.js'; @@ -458,7 +459,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -466,14 +467,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -484,7 +485,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -501,15 +502,15 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 14 configurations should result in 14 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + it('config with 15 configurations should result in 15 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -549,17 +550,19 @@ describe('User ID', function () { name: 'zeotapIdPlus' }, { name: 'criteo' + }, { + name: 'mwOpenLinkId' }, { name: 'tapadId', storage: {name: 'tapad_id', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 14 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 15 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -574,7 +577,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -589,7 +592,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -1622,7 +1625,27 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId and Criteo have data to pass', function (done) { + it('test hook from mwOpenLinkId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([mwOpenLinkIdSubModule]); + init(config); + config.setConfig(getConfigMock(['mwOpenLinkId', 'mwol', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + }); + }); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId, Criteo and mwOpenLinkId have data to pass', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1637,8 +1660,9 @@ describe('User ID', function () { }), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1651,6 +1675,7 @@ describe('User ID', function () { ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'])); requestBidsHook(function () { @@ -1691,8 +1716,11 @@ describe('User ID', function () { // also check that criteo id was copied to bid expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); + // also check that mwOpenLink id was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - expect(bid.userIdAsEids.length).to.equal(11); + expect(bid.userIdAsEids.length).to.equal(12); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1706,11 +1734,12 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId and haloId have their modules added before and after init', function (done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId, haloId and mwOpenLinkId have their modules added before and after init', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1725,6 +1754,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -1744,6 +1774,7 @@ describe('User ID', function () { attachIdSystem(zeotapIdPlusSubmodule); attachIdSystem(haloIdSubmodule); attachIdSystem(criteoIdSubmodule); + attachIdSystem(mwOpenLinkIdSubModule); attachIdSystem(tapadIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], @@ -1757,6 +1788,7 @@ describe('User ID', function () { ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'])); requestBidsHook(function () { @@ -1800,7 +1832,11 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); - expect(bid.userIdAsEids.length).to.equal(11); + // also check that mwOpenLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + + expect(bid.userIdAsEids.length).to.equal(12); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1814,6 +1850,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -2129,6 +2166,7 @@ describe('User ID', function () { // also check that haloId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.haloId'); expect(bid.userId.haloId).to.equal('testHaloId'); + expect(bid.userIdAsEids.length).to.equal(10); }); }); @@ -2143,6 +2181,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); From a1380fe5d93de9d7af4f92d040c54fa17d34719b Mon Sep 17 00:00:00 2001 From: Skylinar <53079123+Skylinar@users.noreply.github.com> Date: Thu, 18 Mar 2021 17:06:19 +0100 Subject: [PATCH 244/325] Documentation: Adjust desired bitrate examples smartx adapter (#6438) --- modules/smartxBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index b25ce68bb6e..223e51763b9 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -40,7 +40,7 @@ This adapter requires setup and approval from the smartclip team. skipOffset: 0, startOpen: true, endingScreen: true, - desiredBitrate: 1600, + desiredBitrate: 800, }, } }], @@ -75,7 +75,7 @@ This adapter requires setup and approval from the smartclip team. skipOffset: 0, startOpen: true, endingScreen: true, - desiredBitrate: 1600, + desiredBitrate: 800, }, user: { data: [{ From 3eb1216281f25485fc22a5dfa4d61c6a4dfe9e21 Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Thu, 18 Mar 2021 19:14:28 +0100 Subject: [PATCH 245/325] Remove adId (autogenerated by Prebid) (#6441) --- modules/improvedigitalBidAdapter.js | 3 +-- test/spec/modules/improvedigitalBidAdapter_spec.js | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 0432adf2b2d..dc0911ff5da 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -11,7 +11,7 @@ const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; export const spec = { - version: '7.2.0', + version: '7.3.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -126,7 +126,6 @@ export const spec = { } // Common properties - bid.adId = bidObject.id; bid.cpm = parseFloat(bidObject.price); bid.creativeId = bidObject.crid; bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 4d0da695d95..f34a75ef8f3 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -750,7 +750,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBid = [ { 'ad': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', @@ -767,7 +766,6 @@ describe('Improve Digital Adapter Tests', function () { expectedBid[0], { 'ad': '', - 'adId': '1234', 'creativeId': '422033', 'cpm': 1.23, 'currency': 'USD', @@ -783,7 +781,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidNative = [ { mediaType: 'native', - adId: '33e9500b21129f', creativeId: '422031', cpm: 1.45888594164456, currency: 'USD', @@ -832,7 +829,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidInstreamVideo = [ { 'vastXml': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', From 24c4fb8b4b502777f8fc3b3b5f96c0e68aecb8dd Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Fri, 19 Mar 2021 02:00:32 +0100 Subject: [PATCH 246/325] RichAudience Bid Adapter: add render video in banner (#6392) --- modules/richaudienceBidAdapter.js | 62 +++++++----- modules/richaudienceBidAdapter.md | 2 +- .../modules/richaudienceBidAdapter_spec.js | 99 ++++++++++++++++--- 3 files changed, 121 insertions(+), 42 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 07de3e40594..37a9554e9a4 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -14,20 +14,20 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], /*** - * Determines whether or not the given bid request is valid - * - * @param {bidRequest} bid The bid params to validate. - * @returns {boolean} True if this is a valid bid, and false otherwise - */ + * Determines whether or not the given bid request is valid + * + * @param {bidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ isBidRequestValid: function (bid) { return !!(bid.params && bid.params.pid && bid.params.supplyType); }, /*** - * Build a server request from the list of valid BidRequests - * @param {validBidRequests} is an array of the valid bids - * @param {bidderRequest} bidder request object - * @returns {ServerRequest} Info describing the request to the server - */ + * Build a server request from the list of valid BidRequests + * @param {validBidRequests} is an array of the valid bids + * @param {bidderRequest} bidder request object + * @returns {ServerRequest} Info describing the request to the server + */ buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bid => { var payload = { @@ -77,11 +77,11 @@ export const spec = { }); }, /*** - * Read the response from the server and build a list of bids - * @param {serverResponse} Response from the server. - * @param {bidRequest} Bid request object - * @returns {bidResponses} Array of bids which were nested inside the server - */ + * Read the response from the server and build a list of bids + * @param {serverResponse} Response from the server. + * @param {bidRequest} Bid request object + * @returns {bidResponses} Array of bids which were nested inside the server + */ interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; // try catch @@ -103,10 +103,16 @@ export const spec = { if (response.media_type === 'video') { bidResponse.vastXml = response.vastXML; try { - if (JSON.parse(bidRequest.data).videoData.format == 'outstream') { - bidResponse.renderer = Renderer.install({ - url: 'https://cdn3.richaudience.com/prebidVideo/player.js' - }); + if (bidResponse.vastXml != null) { + if (JSON.parse(bidRequest.data).videoData.format == 'outstream' || JSON.parse(bidRequest.data).videoData.format == 'banner') { + bidResponse.renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: response.media_type, + url: 'https://cdn3.richaudience.com/prebidVideo/player.js' + }); + } bidResponse.renderer.setRender(renderer); } } catch (e) { @@ -121,13 +127,13 @@ export const spec = { return bidResponses }, /*** - * User Syncs - * - * @param {syncOptions} Publisher prebid configuration - * @param {serverResponses} Response from the server - * @param {gdprConsent} GPDR consent object - * @returns {Array} - */ + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @param {gdprConsent} GPDR consent object + * @returns {Array} + */ getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { const syncs = []; @@ -197,6 +203,10 @@ function raiGetVideoInfo(bid) { playerSize: bid.mediaTypes.video.playerSize, mimes: bid.mediaTypes.video.mimes }; + } else { + videoData = { + format: 'banner' + } } return videoData; } diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index fbf59a0208a..f888117b166 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -39,7 +39,7 @@ Please reach out to your account manager for more information. "pid":"ADb1f40rmo", "supplyType":"site", "bidfloor":0.40, - "keywords": "bici=scott;coche=audi;coche=mercedes;" + "keywords": "key1=value1;key2=value2;key3=value3;" } }] } diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 3b9f07b6efc..5deb2463523 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -20,7 +20,7 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.5, pid: 'ADb1f40rmi', supplyType: 'site', - keywords: 'coche=mercedes;coche=audi' + keywords: 'key1=value1;key2=value2' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -75,26 +75,19 @@ describe('Richaudience adapter tests', function () { user: {} }]; - var DEFAULT_PARAMS_VIDEO_OUT_PARAMS = [{ + var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'] + banner: { + sizes: [[300, 250], [600, 300]] } }, bidder: 'richaudience', params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site', - player: { - init: 'close', - end: 'close', - skin: 'dark' - } + supplyType: 'site' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -242,7 +235,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('numIframes').and.to.equal(0); expect(typeof requestContent.scr_rsl === 'string') expect(typeof requestContent.cpuc === 'number') - expect(requestContent).to.have.property('kws').and.to.equal('coche=mercedes;coche=audi'); + expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) it('Verify build request to prebid video inestream', function() { @@ -262,8 +255,6 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('640'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('480'); }) it('Verify build request to prebid video outstream', function() { @@ -281,6 +272,7 @@ describe('Richaudience adapter tests', function () { expect(request[0]).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); }) @@ -623,9 +615,19 @@ describe('Richaudience adapter tests', function () { }); const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); expect(bid.mediaType).to.equal('video'); expect(bid.vastXml).to.equal(''); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); }); it('no banner media response outstream', function () { @@ -640,6 +642,35 @@ describe('Richaudience adapter tests', function () { } }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('banner media and response VAST', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); const bid = bids[0]; expect(bid.mediaType).to.equal('video'); @@ -656,6 +687,16 @@ describe('Richaudience adapter tests', function () { expect(spec.aliases[0]).to.equal('ra'); }); + it('Verifies bidder gvlid', function () { + expect(spec.gvlid).to.equal(108); + }); + + it('Verifies bidder supportedMediaTypes', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); + it('Verifies if bid request is valid', function () { expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); @@ -717,6 +758,34 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.50, } })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + bidfloor: 0.50, + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); }); it('Verifies user syncs iframe', function () { From cd6f1a87b63ba4d0fd4ae42e6a4d3217a2da9d31 Mon Sep 17 00:00:00 2001 From: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Date: Fri, 19 Mar 2021 02:58:20 -0700 Subject: [PATCH 247/325] Integration Example: ID import library example (#6434) * ID Import Library example * Fixing review comments Co-authored-by: skocheri --- .../gpt/idImportLibrary_example.html | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 integrationExamples/gpt/idImportLibrary_example.html diff --git a/integrationExamples/gpt/idImportLibrary_example.html b/integrationExamples/gpt/idImportLibrary_example.html new file mode 100644 index 00000000000..da1581d1c89 --- /dev/null +++ b/integrationExamples/gpt/idImportLibrary_example.html @@ -0,0 +1,351 @@ + + + + + + + + + + + + + +Prebid + +

ID Import Library Example

+

Steps before logging in:

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

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

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

Prebid.js Test

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

Prebid Display/Video Merged Auction with Adloox Integration

+ +

div-1

+
+ +
+ +

div-2

+
+ +
+ +

video-1

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