From 81fc1ce9b8471cadb33ebc10a270e8c6d001ea98 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 7 Apr 2021 04:13:06 -0400 Subject: [PATCH 01/57] PBJS Core: canBidderRegisterSync ignoring iframe sync disabled by default (#6535) * Update eids.js * Update eids_spec.js * Update eids.js * Update pubmaticBidAdapter_spec.js * Update eids.js * Update eids_spec.js * Update conversantBidAdapter_spec.js * Update rubiconBidAdapter_spec.js * Update conversantBidAdapter_spec.js * Delete test/spec/adapters directory * Update userId_spec.js * Update userSync.js * Update userSync_spec.js --- src/userSync.js | 2 +- test/spec/userSync_spec.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/userSync.js b/src/userSync.js index fceeb1d722d..f653880fa29 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -225,7 +225,7 @@ export function newUserSync(userSyncDependencies) { } return checkForFiltering[filterType](biddersToFilter, bidder); } - return false; + return !permittedPixels[type]; } /** diff --git a/test/spec/userSync_spec.js b/test/spec/userSync_spec.js index 2b68349ca31..55b613ce929 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -475,6 +475,24 @@ describe('user sync', function () { }); expect(userSync.canBidderRegisterSync('iframe', 'otherTestBidder')).to.equal(false); }); + it('should return false for iframe if there is no iframe filterSettings', function () { + const userSync = newUserSync({ + config: { + syncEnabled: true, + filterSettings: { + image: { + bidders: '*', + filter: 'include' + } + }, + syncsPerBidder: 5, + syncDelay: 3000, + auctionDelay: 0 + } + }); + + expect(userSync.canBidderRegisterSync('iframe', 'otherTestBidder')).to.equal(false); + }); it('should return true if filter settings does allow it', function () { const userSync = newUserSync({ config: { From 987c08289b2328864dc7ea1b893b9716afbe8569 Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Wed, 7 Apr 2021 14:57:15 +0200 Subject: [PATCH 02/57] Added automatic tzo and targetId to adserver request. (#6534) --- modules/adnuntiusBidAdapter.js | 11 ++++++----- test/spec/modules/adnuntiusBidAdapter_spec.js | 9 +++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 16594b3453c..f250f6e334d 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'adnuntius'; -const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo=-60&format=json'; +const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo='; export const spec = { code: BIDDER_CODE, @@ -14,6 +14,7 @@ export const spec = { const networks = {}; const bidRequests = {}; const requests = []; + const tzo = new Date().getTimezoneOffset(); for (var i = 0; i < validBidRequests.length; i++) { const bid = validBidRequests[i] @@ -23,7 +24,7 @@ export const spec = { networks[network] = networks[network] || {}; networks[network].adUnits = networks[network].adUnits || []; - networks[network].adUnits.push({ ...bid.params.targeting, auId: bid.params.auId }); + networks[network].adUnits.push({ ...bid.params.targeting, auId: bid.params.auId, targetId: bid.bidId }); } const networkKeys = Object.keys(networks) @@ -31,7 +32,7 @@ export const spec = { const network = networkKeys[j]; requests.push({ method: 'POST', - url: ENDPOINT_URL, + url: ENDPOINT_URL + tzo + '&format=json', data: JSON.stringify(networks[network]), bid: bidRequests[network] }); @@ -50,11 +51,11 @@ export const spec = { const bid = adUnit.ads[0]; bidResponses.push({ requestId: bidRequest.bid[k].bidId, - cpm: (bid.cpm) ? bid.cpm.amount : 0, + cpm: (bid.bid) ? bid.bid.amount : 0, width: Number(bid.creativeWidth), height: Number(bid.creativeHeight), creativeId: bid.creativeId, - currency: (bid.cpm) ? bid.cpm.currency : 'EUR', + currency: (bid.bid) ? bid.bid.currency : 'EUR', netRevenue: false, ttl: 360, ad: adUnit.html diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 54ff038c083..f4b0306451c 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -4,7 +4,8 @@ import { spec } from 'modules/adnuntiusBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; describe('adnuntiusBidAdapter', function () { - const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo=-60&format=json'; + const tzo = new Date().getTimezoneOffset(); + const ENDPOINT_URL = `https://delivery.adnuntius.com/i?tzo=${tzo}&format=json`; const adapter = newBidder(spec); const bidRequests = [ { @@ -47,8 +48,8 @@ describe('adnuntiusBidAdapter', function () { 'destination': 'http://google.com' }, 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, - 'bid': { 'amount': 0.005, 'currency': 'NOK' }, - 'cost': { 'amount': 0.005, 'currency': 'NOK' }, + 'bid': { 'amount': 5.0, 'currency': 'NOK' }, + 'cost': { 'amount': 5.0, 'currency': 'NOK' }, 'impressionTrackingUrls': [], 'impressionTrackingUrlsEsc': [], 'adId': 'adn-id-1347343135', @@ -96,7 +97,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\"}]}'); + expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\"}]}'); }); }); From 0ab09644cc13fd9f7c7f2394d6b1a2f48c286cbd Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Wed, 7 Apr 2021 15:12:21 +0200 Subject: [PATCH 03/57] Tappx Bid Adapter: minor updates and universal id added (#6504) * UPDATE: add initial UID * UPDATE: UID change user ext * UPDATE: UID clean logs * UPDATE: add host info * UPDATE: tappx bid adapter universal id * UPDATE: fix bidder param * UPDATE: tappxBidAdapter tests * tappxBidAdapter - fix spacing * tappxBidAdapter: add test user eids array * tappxBidAdapter: update eids array * FIX: package-lock.json Co-authored-by: marc_tappx --- modules/tappxBidAdapter.js | 48 +++++++- package-lock.json | 2 +- test/spec/modules/tappxBidAdapter_spec.js | 141 ++++++++++++++++++---- 3 files changed, 163 insertions(+), 28 deletions(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 7782f151802..3677c3ce4c9 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -8,6 +8,8 @@ import { config } from '../src/config.js'; const BIDDER_CODE = 'tappx'; const TTL = 360; const CUR = 'USD'; +const TAPPX_BIDDER_VERSION = '0.1.10329'; +const TYPE_CNN = 'prebidjs'; var HOST; var hostDomain; @@ -134,7 +136,9 @@ function interpretBannerBid(serverBid, request) { */ function buildOneRequest(validBidRequests, bidderRequest) { HOST = utils.deepAccess(validBidRequests, 'params.host'); - hostDomain = HOST.split('/', 1)[0]; + let hostInfo = getHostInfo(HOST) + // hostDomain = HOST.split('/', 1)[0]; + hostDomain = hostInfo.domain; const ENDPOINT = utils.deepAccess(validBidRequests, 'params.endpoint'); const TAPPXKEY = utils.deepAccess(validBidRequests, 'params.tappxkey'); @@ -206,6 +210,14 @@ function buildOneRequest(validBidRequests, bidderRequest) { imp.secure = 1; imp.bidfloor = utils.deepAccess(validBidRequests, 'params.bidfloor'); + + let bidder = {}; + bidder.tappxkey = TAPPXKEY; + bidder.endpoint = ENDPOINT; + bidder.host = hostInfo.url; + + imp.ext = {}; + imp.ext.bidder = bidder; // < Imp object // > Device object @@ -230,8 +242,6 @@ function buildOneRequest(validBidRequests, bidderRequest) { // > Params let params = {}; params.host = 'tappx.com'; - params.tappxkey = TAPPXKEY; - params.endpoint = ENDPOINT; params.bidfloor = BIDFLOOR; // < Params @@ -253,6 +263,14 @@ function buildOneRequest(validBidRequests, bidderRequest) { if (config.getConfig('coppa') === true) { regs.coppa = config.getConfig('coppa') === true ? 1 : 0; } + + // Universal ID + const eidsArr = utils.deepAccess(validBidRequests, 'userIdAsEids'); + payload.user = { + ext: { + eids: eidsArr + } + }; // < GDPR // > Payload @@ -270,7 +288,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { return { method: 'POST', - url: `https://${HOST}/${ENDPOINT}?type_cnn=prebidjs`, + url: `https://${HOST}/${ENDPOINT}?type_cnn=${TYPE_CNN}&v=${TAPPX_BIDDER_VERSION}`, data: JSON.stringify(payload), bids: validBidRequests }; @@ -286,4 +304,26 @@ function getOs() { 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'; } } +function getHostInfo(hostParam) { + let domainInfo = {}; + + domainInfo.domain = hostParam.split('/', 1)[0]; + domainInfo.url = hostParam; + + let regexNewEndpoints = new RegExp(`^(zz.*|testing)\.ssp\.tappx\.com$`, 'i'); + let regexClassicEndpoints = new RegExp(`^[a-z]{3}\.[a-z]{3}\.tappx\.com$`, 'i'); + + if (regexNewEndpoints.test(domainInfo.domain)) { + let endpoint = domainInfo.domain.split('.', 1)[0] + if (endpoint.toUpperCase().indexOf('TESTING') === -1) { + domainInfo.endpoint = endpoint + domainInfo.new_endpoint = true; + } + } else if (regexClassicEndpoints.test(domainInfo.domain)) { + domainInfo.new_endpoint = false; + } + + return domainInfo; +} + registerBidder(spec); diff --git a/package-lock.json b/package-lock.json index 330298cbaac..5131b5f4d23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24613,4 +24613,4 @@ } } } -} +} \ No newline at end of file diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index c4410d8ce5e..1d3f9676d09 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -1,36 +1,126 @@ import { assert } from 'chai'; -import {spec} from 'modules/tappxBidAdapter'; +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 } }; +const c_BIDREQUEST = { + data: { + }, + bids: [ + { + bidder: 'tappx', + params: { + host: 'testing.ssp.tappx.com\/rtb\/v2\/', + tappxkey: 'pub-1234-android-1234', + endpoint: 'ZZ1234PBJS', + bidfloor: 0.05 + }, + 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 c_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: {} +}; +const c_CONSENTSTRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; +const c_VALIDBIDREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000x179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6eFJ7otPYix179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6e', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_rEXbz6UYtYEJelYrDaZOLkh8WcF9J0ZHmEHFKZEBlLXsgP6xqXU3BCj4Ay0Z6fw_jSOaHxMHwd-voRHqFA4Q9NwAxFcVLyPWnNGZ9VbcSAPos1wupq7Xu3MIm-Bw_0vxjhZdWNy4chM9x3i', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': 'xTtLUY7GwqX2MMqSHo9RQ2YUOIBFhlASOR43I9KjvgtcrxIys3RxME96M02LTjWR', 'parrableId': {'eid': '02.YoqC9lWZh8.C8QTSiJTNgI6Pp0KCM5zZgEgwVMSsVP5W51X8cmiUHQESq9WRKB4nreqZJwsWIcNKlORhG4u25Wm6lmDOBmQ0B8hv0KP6uVQ97aouuH52zaz2ctVQTORUKkErPRPcaCJ7dKFcrNoF2i6WOR0S5Nk'}, 'pubcid': 'b1254-152f-12F5-5698-dI1eljK6C7WA', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_VALIDBIDAPPREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1, 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}, {'source': 'intentiq.com', 'uids': [{'id': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'atype': 1}]}, {'source': 'crwdcntrl.net', 'uids': [{'id': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'atype': 1}]}, {'source': 'parrable.com', 'uids': [{'id': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0', 'atype': 1}]}, {'source': 'pubcid.org', 'uids': [{'id': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'atype': 1}]}, {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_BIDDERREQUEST = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'referer': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_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}; +describe('Tappx bid adapter', function () { + /** + * IS REQUEST VALID + */ + describe('isBidRequestValid', function () { it('should return true when required params found', function () { - assert(spec.isBidRequestValid(bid)); + assert.isTrue(spec.isBidRequestValid(c_BIDREQUEST.bids[0]), JSON.stringify(c_BIDREQUEST)); }); it('should return false when required params are missing', function () { - const bid = { - host: 'testing.ssp.tappx.com' - }; - assert.isFalse(spec.isBidRequestValid(bid)); + let badBidRequest = c_BIDREQUEST; + delete badBidRequest.bids[0].params.tappxkey; + delete badBidRequest.bids[0].params.endpoint; + assert.isFalse(spec.isBidRequestValid(badBidRequest.bids[0])); }); }); + /** + * BUILD REQUEST TEST + */ 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}] + let validBidRequests = c_VALIDBIDREQUESTS; // 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}; + let validAppBidRequests = c_VALIDBIDAPPREQUESTS; + + let bidderRequest = c_BIDDERREQUEST; 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.consent).to.exist.and.to.equal(c_CONSENTSTRING); expect(payload.regs.ext.us_privacy).to.exist; }); @@ -48,6 +138,14 @@ describe('Tappx adapter tests', function () { expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); }); + it('should set user eids array', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + const data = JSON.parse(request[0].data); + expect(data.user.ext.eids, data).to.not.be.null; + expect(data.user.ext.eids[0]).to.have.keys(['source', 'uids']); + }); + 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\/.+/); @@ -63,23 +161,20 @@ describe('Tappx adapter tests', function () { }); }); + /** + * INTERPRET RESPONSE TESTS + */ 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 bids = spec.interpretResponse(c_SERVERRESPONSE, c_BIDREQUEST); const bid = bids[0]; expect(bid.cpm).to.exist; expect(bid.ad).to.match(/^', + 'adUrl': 'adUrl', 'requestId': '2e41f65424c87c', 'creative_id': 342516, 'cmpId': 342516, @@ -171,6 +173,7 @@ const displayEqResponse = [{ netRevenue: true, currency: 'USD', ad: '', + adUrl: 'adUrl', height: 250, width: 300, ttl: 300, From 9eff9d55daa0e89dd365c597ecb6f0197958560e Mon Sep 17 00:00:00 2001 From: BrightMountainMedia <69471268+BrightMountainMediaInc@users.noreply.github.com> Date: Tue, 13 Apr 2021 18:38:18 +0530 Subject: [PATCH 31/57] Bright Mountain Media Bid Adapter: Change Endpoint URL (#6576) --- modules/brightMountainMediaBidAdapter.js | 2 +- test/spec/modules/brightMountainMediaBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js index 6434d8b2b81..b57e696148f 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -3,7 +3,7 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; const BIDDER_CODE = 'bmtm'; -const AD_URL = 'https://console.brightmountainmedia.com/hb/bid'; +const AD_URL = 'https://one.elitebidder.com/api/hb'; export const spec = { code: BIDDER_CODE, diff --git a/test/spec/modules/brightMountainMediaBidAdapter_spec.js b/test/spec/modules/brightMountainMediaBidAdapter_spec.js index 7511b5bf239..bc7f69f0baf 100644 --- a/test/spec/modules/brightMountainMediaBidAdapter_spec.js +++ b/test/spec/modules/brightMountainMediaBidAdapter_spec.js @@ -55,7 +55,7 @@ describe('brightMountainMediaBidAdapter_spec', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://console.brightmountainmedia.com/hb/bid'); + expect(serverRequest.url).to.equal('https://one.elitebidder.com/api/hb'); }); it('Returns valid data if array of bids is valid', function () { From e0007c24baabd294151f510d2e69bbf3eed7257b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Tue, 13 Apr 2021 17:03:59 +0200 Subject: [PATCH 32/57] Outbrain Bid Adapter: only trigger nurl ajax call if nurl is present (#6579) --- modules/outbrainBidAdapter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 402d4515abc..304cdf2088c 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -164,7 +164,11 @@ export const spec = { return syncs; }, onBidWon: (bid) => { - ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server + // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute + if (bid.nurl) { + ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + } } }; From c83bc53a8239bc828e81622d4567174a82a29b48 Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Tue, 13 Apr 2021 18:00:32 +0200 Subject: [PATCH 33/57] LiveWrapper Bid Adapter: add US privacy and Coppa support (#6569) * Livewrapped bid and analytics adapter * Fixed some tests for browser compatibility * Fixed some tests for browser compatibility * Changed analytics adapter code name * Fix double quote in debug message * modified how gdpr is being passed * Added support for Publisher Common ID Module * Corrections for ttr in analytics * ANalytics updates * Auction start time stamp changed * Detect recovered ad blocked requests Make it possible to pass dynamic parameters to adapter * Collect info on ad units receiving any valid bid * Support for ID5 Pass metadata from adapter * Typo in test + eids on wrong level * Fix for Prebid 3.0 * Fix get referer * http -> https in tests * Native support * Read sizes from mediatype.banner * Revert accidental commit * Support native data collection + minor refactorings * Set analytics endpoint * Support for app parameters * Fix issue where adunits with bids were not counted on reload * Send debug info from adapter to external debugger * SChain support * Send GDPR data in analytics request * video support Video support * Report back floor via analytic * Send auction id and adunit/bidder connection id * Criteo id support * Updated example * livewrapped Analytics Adapter info file * Livewrapped gvlid * Us Privacy and Coppa support --- modules/livewrappedBidAdapter.js | 7 ++ .../modules/livewrappedBidAdapter_spec.js | 73 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 2840da8dda6..93552638007 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -81,6 +81,8 @@ export const spec = { version: VERSION, gdprApplies: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : undefined, gdprConsent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : undefined, + coppa: getCoppa(), + usPrivacy: bidderRequest.uspConsent, cookieSupport: !utils.isSafariBrowser() && storage.cookiesAreEnabled(), rcv: getAdblockerRecovered(), adRequests: [...adRequests], @@ -309,4 +311,9 @@ function getDeviceHeight() { return window.innerHeight; } +function getCoppa() { + if (typeof config.getConfig('coppa') === 'boolean') { + return config.getConfig('coppa'); + } +} registerBidder(spec); diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index fa00a359191..9435d9131fa 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -625,6 +625,79 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); + it('should pass us privacy parameter', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + let testRequest = clone(bidderRequest); + testRequest.uspConsent = '1---'; + let result = spec.buildRequests(testRequest.bids, testRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + usPrivacy: '1---', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should pass coppa parameter', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'coppa') { + return true; + } + return origGetConfig.apply(config, arguments); + }); + + let result = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + coppa: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + it('should pass no cookie support', function() { sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => false); sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); From 41e050a28003a4c0a23967694c3a327c3dba62be Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 13 Apr 2021 15:28:17 -0700 Subject: [PATCH 34/57] Rubicon Analytics: Handle PBS Sending Bid ID 0 (#6584) --- modules/rubiconAnalyticsAdapter.js | 5 +-- .../modules/rubiconAnalyticsAdapter_spec.js | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 90575cf8cf1..74f098c0292 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -363,8 +363,9 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { const height = bid.height || bid.playerHeight; return (width && height) ? {width, height} : undefined; }, - 'pbsBidId', - 'seatBidId', + // Handling use case where pbs sends back 0 or '0' bidIds + 'pbsBidId', pbsBidId => pbsBidId == 0 ? utils.generateUUID() : pbsBidId, + 'seatBidId', seatBidId => seatBidId == 0 ? utils.generateUUID() : seatBidId, 'floorValue', () => utils.deepAccess(bid, 'floorData.floorValue'), 'floorRuleValue', () => utils.deepAccess(bid, 'floorData.floorRuleValue'), 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined, diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 0239eff5883..84315dd1eba 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1773,6 +1773,40 @@ describe('rubicon analytics adapter', function () { expect(message.bidsWon[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); }); + it('should correctly generate new bidId if it is 0', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let seatBidResponse = utils.deepClone(BID4); + seatBidResponse.pbsBidId = '0'; + + const setTargeting = { + [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting + }; + + const bidWon = Object.assign({}, seatBidResponse, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal(STUBBED_UUID); + expect(message.bidsWon[0].bidId).to.equal(STUBBED_UUID); + }); + it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { // Only want one bid request in our mock auction let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); From e25678894b54f01615beb72033be5a20902096a9 Mon Sep 17 00:00:00 2001 From: PWyrembak Date: Wed, 14 Apr 2021 12:27:40 +0300 Subject: [PATCH 35/57] TrustX Bid Adapter: support new format request (#6556) * Add trustx adapter and tests for it * update integration example * Update trustx adapter * Post-review fixes of Trustx adapter * Code improvement for trustx adapter: changed default price type from gross to net * Update TrustX adapter to support the 1.0 version * Make requested changes for TrustX adapter * Updated markdown file for TrustX adapter * Fix TrustX adapter and spec file * Update TrustX adapter: r parameter was added to ad request as cache buster * Add support of gdpr to Trustx Bid Adapter * Add wtimeout to ad request params for TrustX Bid Adapter * TrustX Bid Adapter: remove last ampersand in the ad request * Update TrustX Bid Adapter to support identical uids in parameters * Update TrustX Bid Adapter to ignore bids that sizes do not match the size of the request * Update TrustX Bid Adapter to support instream and outstream video * Added wrapperType and wrapperVersion parameters in ad request for TrustX Bid Adapter * Update TrustX Bid Adapter to use refererInfo instead depricated function utils.getTopWindowUrl * HOTFIX for referrer encodind in TrustX Bid Adapter * Fix test for TrustX Bid Adapter * TrustX Bid Adapter: added keywords passing support * TrustX Bid Adapter: added us_privacy parameter in bid request * TrustX Bid Adapter: fix us_privacy parameter in bid request * Fix alias error for TrustX Bid Adapter * TrustX Bid Adapter: added new request format * TrustX Bid adapter: fix new format endpoint * TrustX Bid Adapter: update md file to support useNewFormat parameter --- modules/trustxBidAdapter.js | 556 ++++++++++++++++----- modules/trustxBidAdapter.md | 14 +- test/spec/modules/trustxBidAdapter_spec.js | 465 ++++++++++++++++- 3 files changed, 880 insertions(+), 155 deletions(-) diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index a86ac1f2874..3116400edf7 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -2,9 +2,11 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = 'https://sofia.trustx.org/hb'; +const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -40,98 +42,30 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; const bids = validBidRequests || []; - let priceType = 'net'; - let pageKeywords; - let reqId; - + const newFormatBids = []; + const oldFormatBids = []; + const requests = []; bids.forEach(bid => { - if (bid.params.priceType === 'gross') { - priceType = 'gross'; - } - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - const keywords = utils.transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - pageKeywords = keywords; - } - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + if (bid.params.useNewFormat) { + newFormatBids.push(bid); } else { - slotsMap[adUnitCode].bids.push(bid); + oldFormatBids.push(bid); } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); }); - - const payload = { - pt: priceType, - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); - } - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; + if (newFormatBids.length) { + const newFormatRequests = newFormatRequest(newFormatBids, bidderRequest); + if (newFormatRequests) { + requests.push(newFormatRequests); } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; + } + if (oldFormatBids.length) { + const oldFormatRequests = oldFormatRequest(oldFormatBids, bidderRequest); + if (oldFormatRequests) { + requests.push(oldFormatRequests); } } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, - }; + return requests; }, /** * Unpack the response from the server into a list of bids. @@ -143,8 +77,6 @@ export const spec = { interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; - const bidsMap = bidRequest.bidsMap; - const priceType = bidRequest.data.pt; let errorMessage; @@ -155,7 +87,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst); }); } if (errorMessage) utils.logError(errorMessage); @@ -192,66 +124,75 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { +function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - const slot = awaitingBids[sizeId][0]; - - const bid = slot.bids.shift(); - const bidResponse = { - requestId: bid.bidId, // bid.bidderRequestId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId, - currency: 'USD', - netRevenue: priceType !== 'gross', - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid - }; - if (serverBid.content_type === 'video') { - bidResponse.vastXml = serverBid.adm; - bidResponse.mediaType = VIDEO; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }, RendererConst); - } - } else { - bidResponse.ad = serverBid.adm; - bidResponse.mediaType = BANNER; + const { bidsMap, priceType, newFormat } = bidRequest; + let bid; + let slot; + if (newFormat) { + bid = bidsMap[serverBid.impid]; + } else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + const sizeId = `${serverBid.w}x${serverBid.h}`; + if (awaitingBids[sizeId]) { + slot = awaitingBids[sizeId][0]; + bid = slot.bids.shift(); } + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } - bidResponses.push(bidResponse); - - if (!slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } - }); + if (!errorMessage && bid) { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: newFormat ? false : priceType !== 'gross', + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid + }; + if (serverBid.content_type === 'video') { + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = VIDEO; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL + }, RendererConst); } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + + bidResponses.push(bidResponse); + } + + if (slot && !slot.bids.length) { + slot.parents.forEach(({parent, key, uid}) => { + const index = parent[key].indexOf(slot); + if (index > -1) { + parent[key].splice(index, 1); + } + if (!parent[key].length) { + delete parent[key]; + if (!utils.getKeys(parent).length) { + delete bidsMap[uid]; + } + } + }); } } if (errorMessage) { @@ -284,4 +225,343 @@ function createRenderer (bid, rendererParams, RendererConst) { return rendererInst; } +function createVideoRequest(bid, mediaType) { + const {playerSize, mimes, durationRangeSec, protocols} = mediaType; + const size = (playerSize || bid.sizes || [])[0]; + if (!size) return; + + let result = utils.parseGPTSingleSizeArrayToRtbSize(size); + + if (mimes) { + result.mimes = mimes; + } + + if (durationRangeSec && durationRangeSec.length === 2) { + result.minduration = durationRangeSec[0]; + result.maxduration = durationRangeSec[1]; + } + + if (protocols && protocols.length) { + result.protocols = protocols; + } + + return result; +} + +function createBannerRequest(bid, mediaType) { + const sizes = mediaType.sizes || bid.sizes; + if (!sizes || !sizes.length) return; + + let format = sizes.map((size) => utils.parseGPTSingleSizeArrayToRtbSize(size)); + let result = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]); + + if (format.length) { + result.format = format + } + return result; +} + +/** + * Gets bidfloor + * @param {Object} mediaTypes + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor (mediaTypes, bid) { + const curMediaType = mediaTypes.video ? 'video' : 'banner'; + let floor = bid.params.bidFloor || 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: bid.sizes.map(([w, h]) => ({w, h})) + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + +function newFormatRequest(validBidRequests, bidderRequest) { + if (!validBidRequests.length) { + return null; + } + let pageKeywords = null; + let jwpseg = null; + 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 || {}; + + const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const imp = []; + const bidsMap = {}; + + validBidRequests.forEach((bid) => { + if (!bidderRequestId) { + bidderRequestId = bid.bidderRequestId; + } + if (!auctionId) { + auctionId = bid.auctionId; + } + if (!schain) { + schain = bid.schain; + } + if (!userId) { + userId = bid.userId; + } + if (!userIdAsEids) { + userIdAsEids = bid.userIdAsEids; + } + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; + bidsMap[bidId] = bid; + if (!pageKeywords && !utils.isEmpty(keywords)) { + pageKeywords = utils.transformBidderParamKeywords(keywords); + } + const bidFloor = _getFloor(mediaTypes || {}, bid); + const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; + if (jwTargeting) { + if (!jwpseg && jwTargeting.segments) { + jwpseg = jwTargeting.segments; + } + if (!content && jwTargeting.content) { + content = jwTargeting.content; + } + } + let impObj = { + id: bidId, + tagid: uid.toString(), + ext: { + divid: adUnitCode + } + }; + + if (bidFloor) { + impObj.bidfloor = bidFloor; + } + + if (!mediaTypes || mediaTypes[BANNER]) { + const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); + if (banner) { + impObj.banner = banner; + } + } + if (mediaTypes && mediaTypes[VIDEO]) { + const video = createVideoRequest(bid, mediaTypes[VIDEO]); + if (video) { + impObj.video = video; + } + } + + if (impObj.banner || impObj.video) { + imp.push(impObj); + } + }); + + const source = { + tid: auctionId, + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' + } + }; + + if (schain) { + source.ext.schain = schain; + } + + const bidderTimeout = config.getConfig('bidderTimeout') || timeout; + const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + + let request = { + id: bidderRequestId, + site: { + page: referer + }, + tmax, + source, + imp + }; + + if (content) { + request.site.content = content; + } + + if (jwpseg && jwpseg.length) { + user = { + data: [{ + name: 'iow_labs_pub_data', + segment: jwpseg.map((seg) => { + return {name: 'jwpseg', value: seg}; + }) + }] + }; + } + + if (gdprConsent && gdprConsent.consentString) { + userExt = {consent: gdprConsent.consentString}; + } + + if (userIdAsEids && userIdAsEids.length) { + userExt = userExt || {}; + userExt.eids = [...userIdAsEids]; + } + + if (userExt && Object.keys(userExt).length) { + user = user || {}; + user.ext = userExt; + } + + if (user) { + request.user = user; + } + + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + + if (pageKeywords) { + request.ext = { + keywords: pageKeywords + }; + } + + if (gdprConsent && gdprConsent.gdprApplies) { + request.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 + } + } + } + + if (uspConsent) { + if (!request.regs) { + request.regs = {ext: {}}; + } + request.regs.ext.us_privacy = uspConsent; + } + + return { + method: 'POST', + url: NEW_ENDPOINT_URL, + data: JSON.stringify(request), + newFormat: true, + bidsMap + }; +} + +function oldFormatRequest(validBidRequests, bidderRequest) { + const auids = []; + const bidsMap = {}; + const slotsMapByUid = {}; + const sizeMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let pageKeywords; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + reqId = bid.bidderRequestId; + const {params: {uid}, adUnitCode} = bid; + auids.push(uid); + const sizesId = utils.parseSizesInput(bid.sizes); + + if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { + const keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + pageKeywords = keywords; + } + + if (!slotsMapByUid[uid]) { + slotsMapByUid[uid] = {}; + } + const slotsMap = slotsMapByUid[uid]; + if (!slotsMap[adUnitCode]) { + slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + } else { + slotsMap[adUnitCode].bids.push(bid); + } + const slot = slotsMap[adUnitCode]; + + sizesId.forEach((sizeId) => { + sizeMap[sizeId] = true; + if (!bidsMap[uid]) { + bidsMap[uid] = {}; + } + + if (!bidsMap[uid][sizeId]) { + bidsMap[uid][sizeId] = [slot]; + } else { + bidsMap[uid][sizeId].push(slot); + } + slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); + }); + }); + + const payload = { + pt: priceType, + auids: auids.join(','), + sizes: utils.getKeys(sizeMap).join(','), + r: reqId, + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$' + }; + + if (pageKeywords) { + payload.keywords = JSON.stringify(pageKeywords); + } + + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + payload.u = bidderRequest.refererInfo.referer; + } + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap, + priceType + }; +} + registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md index a72f1ba85aa..e891df8f161 100644 --- a/modules/trustxBidAdapter.md +++ b/modules/trustxBidAdapter.md @@ -52,6 +52,18 @@ TrustX Bid Adapter supports Banner and Video (instream and outstream). } } ] + },{ + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "trustx", + params: { + uid: '58851', + useNewFormat: true + } + } + ] } ]; -``` \ No newline at end of file +``` diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index e9daaa83b5d..c0155f87ab6 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/trustxBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; describe('TrustXAdapter', function () { const adapter = newBidder(spec); @@ -92,7 +93,7 @@ describe('TrustXAdapter', function () { ]; it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -105,7 +106,7 @@ describe('TrustXAdapter', function () { }); it('sizes must not be duplicated', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -117,7 +118,7 @@ describe('TrustXAdapter', function () { it('pt parameter must be "gross" if params.priceType === "gross"', function () { bidRequests[1].params.priceType = 'gross'; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -130,7 +131,7 @@ describe('TrustXAdapter', function () { it('pt parameter must be "net" or "gross"', function () { bidRequests[1].params.priceType = 'some'; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -143,7 +144,7 @@ describe('TrustXAdapter', function () { it('if gdprConsent is present payload must have gdpr params', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); @@ -152,7 +153,7 @@ describe('TrustXAdapter', function () { it('if gdprApplies is false gdpr_applies must be 0', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: false}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); @@ -161,7 +162,7 @@ describe('TrustXAdapter', function () { it('if gdprApplies is undefined gdpr_applies must be 1', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA'}}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); @@ -170,7 +171,7 @@ describe('TrustXAdapter', function () { it('if usPrivacy is present payload must have us_privacy param', function () { const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('us_privacy', '1YNN'); @@ -197,7 +198,7 @@ describe('TrustXAdapter', function () { } ); - const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest)[0]; expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.keywords).to.be.an('string'); @@ -226,6 +227,438 @@ describe('TrustXAdapter', function () { }); }); + describe('buildRequests with new format', function () { + function parseRequest(data) { + return JSON.parse(data); + } + const bidderRequest = { + refererInfo: {referer: 'https://example.com'}, + bidderRequestId: '22edbae2733bf6', + auctionId: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + timeout: 3000 + }; + const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + + let bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '43', + 'bidFloor': 1.25, + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '44', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '45', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [[400, 600]], + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '41', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [[400, 600]], + 'protocols': [1, 2, 3] + }, + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + } + ]; + + it('should attach valid params to the tag', function () { + const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); + }); + + it('make possible to process request without mediaTypes', function () { + const request = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); + }); + + it('should attach valid params to the video tag', function () { + const request = spec.buildRequests(bidRequests.slice(0, 3), bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }] + }); + }); + + it('should support mixed mediaTypes', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + } + }, { + 'id': bidRequests[3].bidId, + 'tagid': bidRequests[3].params.uid, + 'ext': {'divid': bidRequests[3].adUnitCode}, + 'banner': { + 'w': 728, + 'h': 90, + 'format': [{'w': 728, 'h': 90}] + }, + 'video': { + 'w': 400, + 'h': 600, + 'protocols': [1, 2, 3] + } + }] + }); + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); + const request = spec.buildRequests(bidRequests, gdprBidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext).to.have.property('consent', 'AAA'); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + + it('if usPrivacy is present payload must have us_privacy param', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); + }); + + it('if userId is present payload must have user.ext param with right keys', function () { + const eids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + const bidRequestsWithUserIds = bidRequests.map((bid) => { + return Object.assign({ + userIdAsEids: eids + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest)[0]; + 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 () { + const schain = { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + }; + const bidRequestsWithSChain = bidRequests.map((bid) => { + return Object.assign({ + schain: schain + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithSChain, bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('source'); + expect(payload.source).to.have.property('ext'); + expect(payload.source.ext).to.have.property('schain'); + expect(payload.source.ext.schain).to.deep.equal(schain); + }); + + it('if content and segment is present in jwTargeting, payload must have right params', function () { + const jsContent = {id: 'test_jw_content_id'}; + const jsSegments = ['test_seg_1', 'test_seg_2']; + const bidRequestsWithJwTargeting = bidRequests.map((bid) => { + return Object.assign({ + rtd: { + jwplayer: { + targeting: { + segments: jsSegments, + content: jsContent + } + } + } + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user.data).to.deep.equal([{ + name: 'iow_labs_pub_data', + segment: [ + {name: 'jwpseg', value: jsSegments[0]}, + {name: 'jwpseg', value: jsSegments[1]} + ] + }]); + expect(payload).to.have.property('site'); + expect(payload.site.content).to.deep.equal(jsContent); + }); + + 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)[0]; + 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); + const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.tmax).to.equal(2000); + getConfigStub.restore(); + }); + it('shold be right tmax when timeout in bidderRequest is less then timeout in config', function() { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'bidderTimeout' ? 5000 : null); + const request = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.tmax).to.equal(3000); + getConfigStub.restore(); + }); + + describe('floorModule', function () { + const floorTestData = { + 'currency': 'USD', + 'floor': 1.50 + }; + const bidRequest = Object.assign({ + getFloor: (_) => { + return floorTestData; + } + }, bidRequests[1]); + it('should return the value from getFloor if present', function () { + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].bidfloor).to.equal(floorTestData.floor); + }); + it('should return the getFloor.floor value if it is greater than bidfloor', function () { + const bidfloor = 0.80; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + const request = spec.buildRequests([bidRequestsWithFloor], bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].bidfloor).to.equal(floorTestData.floor); + }); + it('should return the bidfloor value if it is greater than getFloor.floor', function () { + const bidfloor = 1.80; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + const request = spec.buildRequests([bidRequestsWithFloor], bidderRequest)[0]; + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].bidfloor).to.equal(bidfloor); + }); + }); + }); + describe('interpretResponse', function () { const responses = [ {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, @@ -252,7 +685,7 @@ describe('TrustXAdapter', function () { 'auctionId': '1cbd2feafe5e8b', } ]; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests)[0]; const expectedResponse = [ { 'requestId': '659423fff799cb', @@ -309,7 +742,7 @@ describe('TrustXAdapter', function () { 'auctionId': '1fa09aee5c8c99', } ]; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests)[0]; const expectedResponse = [ { 'requestId': '300bfeb0d71a5b', @@ -392,7 +825,7 @@ describe('TrustXAdapter', function () { 'auctionId': '1fa09aee5c84d34', } ]; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests)[0]; const result = spec.interpretResponse({'body': {'seatbid': responses.slice(3)}}, request); expect(result.length).to.equal(0); }); @@ -462,7 +895,7 @@ describe('TrustXAdapter', function () { 'auctionId': '32a1f276cb87cb8', } ]; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests)[0]; const expectedResponse = [ { 'requestId': '2164be6358b9', @@ -562,7 +995,7 @@ describe('TrustXAdapter', function () { 'auctionId': '35bcbc0f7e79c', } ]; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests)[0]; const expectedResponse = [ { 'requestId': '5126e301f4be', @@ -636,7 +1069,7 @@ describe('TrustXAdapter', function () { {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video'}], 'seat': '2'} ]; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests)[0]; const expectedResponse = [ { 'requestId': '57dfefb80eca', @@ -715,7 +1148,7 @@ describe('TrustXAdapter', function () { {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 52, content_type: 'video', w: 300, h: 250}], 'seat': '2'} ]; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests)[0]; const expectedResponse = [ { 'requestId': 'e6e65553fc8', From 5fbef6063ccee3dddd77fddca4a2bc922e56c4f7 Mon Sep 17 00:00:00 2001 From: couchcrew-thomas Date: Wed, 14 Apr 2021 11:34:03 +0200 Subject: [PATCH 36/57] FeedAd Bid Adapter: add support for GDPR/TCF 2.0 & remove video support (#6538) * added file scaffold * added isBidRequestValid implementation * added local prototype of ad integration * added implementation for placement ID validation * fixed video context filter * applied lint to feedad bid adapter * added unit test for bid request validation * added buildRequest unit test * added unit tests for timeout and bid won callbacks * updated bid request to FeedAd API * added parsing of feedad api bid response * added transmisison of tracking events to FeedAd Api * code cleanup * updated feedad unit tests for buildRequest method * added unit tests for event tracking implementation * added unit test for interpretResponse method * added adapter documentation * added dedicated feedad example page * updated feedad adapter to use live system * updated FeedAd adapter placement ID regex * removed groups from FeedAd adapter placement ID regex * removed dedicated feedad example page * updated imports in FeedAd adapter file to use relative paths * updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() * added GDPR fields to the FeedAd bid request * removed video from supported media types of the FeedAd adapter * increased version code of FeedAd adapter to 1.0.2 * removed unnecessary check of bidder request * fixed unit test testing for old FeedAd version * removed video media type example from documentation file * added gvlid to FeedAd adapter --- modules/feedadBidAdapter.js | 16 +++++++-- modules/feedadBidAdapter.md | 3 -- test/spec/modules/feedadBidAdapter_spec.js | 41 ++++++++++++++++++++-- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 3992f2db5e0..54a4ef0c998 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -1,13 +1,13 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; /** * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.0'; +const VERSION = '1.0.2'; /** * @typedef {object} FeedAdApiBidRequest @@ -61,6 +61,11 @@ const VERSION = '1.0.0'; * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ +/** + * The IAB TCF 2.0 vendor ID for the FeedAd GmbH + */ +const TCF_VENDOR_ID = 781; + /** * Bidder network identity code * @type {string} @@ -71,7 +76,7 @@ const BIDDER_CODE = 'feedad'; * The media types supported by FeedAd * @type {MediaType[]} */ -const MEDIA_TYPES = [VIDEO, BANNER]; +const MEDIA_TYPES = [BANNER]; /** * Tag for logging @@ -204,6 +209,10 @@ function buildRequests(validBidRequests, bidderRequest) { referer: data.refererInfo.referer, transactionId: bid.transactionId }); + if (bidderRequest.gdprConsent) { + data.consentIabTcf = bidderRequest.gdprConsent.consentString; + data.gdprApplies = bidderRequest.gdprConsent.gdprApplies; + } return { method: 'POST', url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`, @@ -279,6 +288,7 @@ function trackingHandlerFactory(klass) { */ export const spec = { code: BIDDER_CODE, + gvlid: TCF_VENDOR_ID, supportedMediaTypes: MEDIA_TYPES, isBidRequestValid, buildRequests, diff --git a/modules/feedadBidAdapter.md b/modules/feedadBidAdapter.md index fd57025c29e..6f705df36b5 100644 --- a/modules/feedadBidAdapter.md +++ b/modules/feedadBidAdapter.md @@ -18,9 +18,6 @@ Prebid.JS adapter that connects to the FeedAd demand sources. mediaTypes: { banner: { // supports all banner sizes sizes: [[300, 250]], - }, - video: { // supports only outstream video - context: 'outstream' } }, bids: [ diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 066ab6b21f6..6b75af0d55d 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -13,7 +13,7 @@ describe('FeedAdAdapter', function () { it('should only support video and banner ads', function () { expect(spec.supportedMediaTypes).to.be.a('array'); expect(spec.supportedMediaTypes).to.include(BANNER); - expect(spec.supportedMediaTypes).to.include(VIDEO); + expect(spec.supportedMediaTypes).not.to.include(VIDEO); expect(spec.supportedMediaTypes).not.to.include(NATIVE); }); it('should export the BidderSpec functions', function () { @@ -23,6 +23,9 @@ describe('FeedAdAdapter', function () { expect(spec.onTimeout).to.be.a('function'); expect(spec.onBidWon).to.be.a('function'); }); + it('should export the TCF vendor ID', function () { + expect(spec.gvlid).to.equal(781); + }) }); describe('isBidRequestValid', function () { @@ -248,6 +251,40 @@ describe('FeedAdAdapter', function () { let result = spec.buildRequests([bid, bid, bid]); expect(result).to.be.empty; }); + it('should not include GDPR data if the bidder request has none available', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.gdprApplies).to.be.undefined; + expect(result.data.consentIabTcf).to.be.undefined; + }); + it('should include GDPR data if the bidder requests contains it', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let request = Object.assign({}, bidderRequest, { + gdprConsent: { + consentString: 'the consent string', + gdprApplies: true + } + }); + let result = spec.buildRequests([bid], request); + expect(result.data.gdprApplies).to.equal(request.gdprConsent.gdprApplies); + expect(result.data.consentIabTcf).to.equal(request.gdprConsent.consentString); + }); }); describe('interpretResponse', function () { @@ -404,7 +441,7 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.0' + sdk_version: '1.0.2' }; subject(data); expect(server.requests.length).to.equal(1); From 1aeca20155c134b8e7a4da01403940564ef1e114 Mon Sep 17 00:00:00 2001 From: mamatic <52153441+mamatic@users.noreply.github.com> Date: Wed, 14 Apr 2021 12:08:12 +0200 Subject: [PATCH 37/57] Ats optional 3p endpoint - keep default behavior the same (#6586) * ATS-identityLinkIdSystem - add use3P config property to control firing of 3P envelope endpoint * ATS-identityLinkIdSystem - add notUse3P config property to control firing of 3P envelope endpoint --- .../gpt/idImportLibrary_example.html | 3 +-- integrationExamples/gpt/userId_example.html | 2 +- modules/identityLinkIdSystem.js | 2 +- modules/userId/index.js | 2 +- modules/userId/userId.md | 4 ++-- .../spec/modules/identityLinkIdSystem_spec.js | 20 +++---------------- 6 files changed, 9 insertions(+), 24 deletions(-) diff --git a/integrationExamples/gpt/idImportLibrary_example.html b/integrationExamples/gpt/idImportLibrary_example.html index b0088131858..a3ef3f168c0 100644 --- a/integrationExamples/gpt/idImportLibrary_example.html +++ b/integrationExamples/gpt/idImportLibrary_example.html @@ -46,8 +46,7 @@ name: 'identityLink', params: { pid: '14', // Set your real identityLink placement ID here - // use3P: false // true/false - If you want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will not be fired. By default this property is undefined and 3p request will not be fired. - }, + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this propertt is undefined and 3p request will be fired.}, storage: { type: 'html5', name: 'idl_env', diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 9d170254084..72657258ad5 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -230,7 +230,7 @@ name: 'identityLink', params: { pid: '14', // Set your real identityLink placement ID here - // use3P: false // true/false - If you want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will not be fired. By default this property is undefined and 3p request will not be fired. + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this property is undefined and 3p request will be fired. }, storage: { type: 'cookie', diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index 7c04118e1fc..df7b03b4e6e 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -98,7 +98,7 @@ function getEnvelope(url, callback, configParams) { } }; - if (configParams.use3P && !storage.getCookie('_lr_retry_request')) { + if (!configParams.notUse3P && !storage.getCookie('_lr_retry_request')) { setRetryCookie(); utils.logInfo('identityLink: A 3P retrieval is attempted!'); setEnvelopeSource(false); diff --git a/modules/userId/index.js b/modules/userId/index.js index 0dce727ca53..8822e2e11ea 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -101,7 +101,7 @@ * @property {(LiveIntentCollectConfig|undefined)} liCollectConfig - the config for LiveIntent's collect requests * @property {(string|undefined)} pd - publisher provided data for reconciling ID5 IDs * @property {(string|undefined)} emailHash - if provided, the hashed email address of a user - * @property {(string|undefined)} use3P - use to retrieve envelope from 3p endpoint + * @property {(string|undefined)} notUse3P - use to retrieve envelope from 3p endpoint */ /** diff --git a/modules/userId/userId.md b/modules/userId/userId.md index d9379ea9c85..79f547b179f 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -44,7 +44,7 @@ pbjs.setConfig({ name: 'identityLink', params: { pid: '999', // Set your real identityLink placement ID here - // use3P: false // true/false - If you want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will not be fired. By default this propertt is undefined and 3p request will not be fired. + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this property is undefined and 3p request will be fired. }, storage: { type: 'cookie', @@ -146,7 +146,7 @@ pbjs.setConfig({ name: 'identityLink', params: { pid: '999', // Set your real identityLink placement ID here - // use3P: false // true/false - If you want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will not be fired. By default this property is undefined and 3p request will not be fired. + // notUse3P: true // true/false - If you do not want to use 3P endpoint to retrieve envelope. If you do not set this property to true, 3p endpoint will be fired. By default this property is undefined and 3p request will be fired. }, storage: { type: 'html5', diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index ac79b2e3916..a31270c86c7 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -36,7 +36,6 @@ describe('IdentityLinkId tests', function () { it('should call the LiveRamp envelope endpoint', function () { let callBackSpy = sinon.spy(); - defaultConfigParams.params.use3P = true; let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; @@ -65,7 +64,6 @@ describe('IdentityLinkId tests', function () { }); it('should call the LiveRamp envelope endpoint with IAB consent string v1', function () { - defaultConfigParams.params.use3P = true; let callBackSpy = sinon.spy(); let consentData = { gdprApplies: true, @@ -84,7 +82,6 @@ describe('IdentityLinkId tests', function () { }); it('should call the LiveRamp envelope endpoint with IAB consent string v2', function () { - defaultConfigParams.params.use3P = true; let callBackSpy = sinon.spy(); let consentData = { gdprApplies: true, @@ -106,7 +103,6 @@ describe('IdentityLinkId tests', function () { }); it('should not throw Uncaught TypeError when envelope endpoint returns empty response', function () { - defaultConfigParams.params.use3P = true; let callBackSpy = sinon.spy(); let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); @@ -123,7 +119,6 @@ describe('IdentityLinkId tests', function () { }); it('should log an error and continue to callback if ajax request errors', function () { - defaultConfigParams.params.use3P = true; let callBackSpy = sinon.spy(); let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); @@ -148,8 +143,7 @@ describe('IdentityLinkId tests', function () { expect(request).to.be.eq(undefined); }); - it('should call the LiveRamp envelope endpoint if cookie _lr_retry_request does not exist and use3P config property was set to true', function () { - defaultConfigParams.params.use3P = true; + it('should call the LiveRamp envelope endpoint if cookie _lr_retry_request does not exist and notUse3P config property was not set', function () { let callBackSpy = sinon.spy(); let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); @@ -163,16 +157,8 @@ describe('IdentityLinkId tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should not call the LiveRamp envelope endpoint if config property use3P is set to false', function () { - defaultConfigParams.params.use3P = false; - let callBackSpy = sinon.spy(); - let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; - submoduleCallback(callBackSpy); - let request = server.requests[0]; - expect(request).to.be.eq(undefined); - }); - - it('should not call the LiveRamp envelope endpoint if config property use3P is undefined', function () { + it('should not call the LiveRamp envelope endpoint if config property notUse3P is set to true', function () { + defaultConfigParams.params.notUse3P = true; let callBackSpy = sinon.spy(); let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); From d51d6ebf0920632137cbf8f9385db60069ce515a Mon Sep 17 00:00:00 2001 From: vincentproxistore <56686565+vincentproxistore@users.noreply.github.com> Date: Wed, 14 Apr 2021 12:31:32 +0200 Subject: [PATCH 38/57] use another uri for cookieless (#6572) --- modules/proxistoreBidAdapter.js | 2 +- test/spec/modules/proxistoreBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index 9b1675722be..ff07ee9ede9 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -57,7 +57,7 @@ function _createServerRequest(bidRequests, bidderRequest) { const endPointUri = payload.gdpr.consentGiven || !payload.gdpr.applies ? `https://abs.proxistore.com/${payload.language}/v3/rtb/prebid/multi` - : `https://abs.proxistore.com/${payload.language}/v3/rtb/prebid/multi/cookieless`; + : `https://abs.cookieless-proxistore.com/${payload.language}/v3/rtb/prebid/multi`; return { method: 'POST', diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index e7db01828e0..bdcdca06183 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -54,7 +54,7 @@ describe('ProxistoreBidAdapter', function () { const url = { cookieBase: 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi', cookieLess: - 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi/cookieless', + 'https://abs.cookieless-proxistore.com/fr/v3/rtb/prebid/multi', }; let request = spec.buildRequests([bid], bidderRequest); it('should return a valid object', function () { From 6a6268a4ada20f45cb06aaeaf46e32f7807fb02d Mon Sep 17 00:00:00 2001 From: Matt Felstead Date: Wed, 14 Apr 2021 12:16:45 +0100 Subject: [PATCH 39/57] Pbjs Core: add new API to return the highest unused bid for a given adunit (#6342) * feat: add new API to return the highest unused bid for a given adunit code * feat: add unit tests for new API --- src/auctionManager.js | 8 +++++ src/prebid.js | 22 ++++++++++++-- test/spec/api_spec.js | 4 +++ test/spec/unit/pbjs_api_spec.js | 52 +++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/auctionManager.js b/src/auctionManager.js index 3d4bd0afe99..1aca1277aa8 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -9,6 +9,7 @@ * * @property {function(): Array} getBidsRequested - returns consolidated bid requests * @property {function(): Array} getBidsReceived - returns consolidated bid received + * @property {function(): Array} getAllBidsForAdUnitCode - returns consolidated bid received for a given adUnit * @property {function(): Array} getAdUnits - returns consolidated adUnits * @property {function(): Array} getAdUnitCodes - returns consolidated adUnitCodes * @property {function(): Object} createAuction - creates auction instance and stores it for future reference @@ -66,6 +67,13 @@ export function newAuctionManager() { .filter(bid => bid); }; + auctionManager.getAllBidsForAdUnitCode = function(adUnitCode) { + return _auctions.map((auction) => { + return auction.getBidsReceived(); + }).reduce(flatten, []) + .filter(bid => bid && bid.adUnitCode === adUnitCode) + }; + auctionManager.getAdUnits = function() { return _auctions.map(auction => auction.getAdUnits()) .reduce(flatten, []); diff --git a/src/prebid.js b/src/prebid.js index 6565c1610d8..7211371366e 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,12 +1,12 @@ /** @module pbjs */ import { getGlobal } from './prebidGlobal.js'; -import { adUnitsFilter, flatten, isArrayOfNums, isGptPubadsDefined, uniques } from './utils.js'; +import { adUnitsFilter, flatten, getHighestCpm, isArrayOfNums, isGptPubadsDefined, uniques } from './utils.js'; import { listenMessagesFromCreative } from './secureCreatives.js'; import { userSync } from './userSync.js'; import { config } from './config.js'; import { auctionManager } from './auctionManager.js'; -import { targeting } from './targeting.js'; +import { filters, targeting } from './targeting.js'; import { hook } from './hook.js'; import { sessionLoader } from './debugging.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -206,6 +206,24 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { } }; +/** + * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param adUnitCode {string} adUnitCode to get the bid responses for + * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode + * @returns {Object} returnObj return bid + */ +$$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { + if (adunitCode) { + const bid = auctionManager.getAllBidsForAdUnitCode(adunitCode) + .filter(filters.isUnusedBid) + .filter(filters.isBidNotExpired) + + return bid.length ? bid.reduce(getHighestCpm) : {} + } else { + utils.logMessage('Need to call getHighestUnusedBidResponseForAdUnitCode with adunitCode'); + } +}; + /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index 6d67565056f..cb6c7aa87a3 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -82,5 +82,9 @@ describe('Publisher API', function () { it('should have function $$PREBID_GLOBAL$$.getAllWinningBids', function () { assert.isFunction($$PREBID_GLOBAL$$.getAllWinningBids); }); + + it('should have function $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode', function () { + assert.isFunction($$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode); + }); }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index afad1d665fd..805eae9e3bc 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -2575,6 +2575,58 @@ describe('Unit: Prebid Module', function () { }); }); + describe('getHighestUnusedBidResponseForAdUnitCode', () => { + afterEach(() => { + resetAuction(); + }) + + it('returns an empty object if there is no bid for the given adUnitCode', () => { + const highestBid = $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode('stallone'); + expect(highestBid).to.deep.equal({}); + }) + + it('returns undefined if adUnitCode is provided', () => { + const highestBid = $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode(); + expect(highestBid).to.be.undefined; + }) + + it('should ignore bids that have already been used (\'rendered\')', () => { + const _bidsReceived = getBidResponses().slice(0, 3); + _bidsReceived[0].cpm = 11 + _bidsReceived[1].cpm = 13 + _bidsReceived[2].cpm = 12 + + _bidsReceived.forEach((bid) => { + bid.adUnitCode = '/19968336/header-bid-tag-0'; + }); + + auction.getBidsReceived = function() { return _bidsReceived }; + const highestBid1 = $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0'); + expect(highestBid1).to.deep.equal(_bidsReceived[1]) + _bidsReceived[1].status = CONSTANTS.BID_STATUS.RENDERED + const highestBid2 = $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0'); + expect(highestBid2).to.deep.equal(_bidsReceived[2]) + }) + + it('should ignore expired bids', () => { + const _bidsReceived = getBidResponses().slice(0, 3); + _bidsReceived[0].cpm = 11 + _bidsReceived[1].cpm = 13 + _bidsReceived[2].cpm = 12 + + _bidsReceived.forEach((bid) => { + bid.adUnitCode = '/19968336/header-bid-tag-0'; + }); + + auction.getBidsReceived = function() { return _bidsReceived }; + + bidExpiryStub.restore(); + bidExpiryStub = sinon.stub(filters, 'isBidNotExpired').callsFake((bid) => bid.cpm !== 13); + const highestBid = $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0'); + expect(highestBid).to.deep.equal(_bidsReceived[2]) + }) + }) + describe('getHighestCpm', () => { after(() => { resetAuction(); From 93ba601438117954e9e0fa685cce00924f420585 Mon Sep 17 00:00:00 2001 From: Sergio Date: Wed, 14 Apr 2021 13:59:26 +0200 Subject: [PATCH 40/57] Adpone Bid Adapter: remove usersync and pass gpdr via querystring (#6326) * remove usersync and add gpdr via querystring * wrap tests under describe adponeBidAdapter * remove bid.meta.advertiserDomains * support adomain * fix adpone advertiserDomains * fix test --- modules/adponeBidAdapter.js | 56 +++--- test/spec/modules/adponeBidAdapter_spec.js | 203 ++++++++++----------- 2 files changed, 123 insertions(+), 136 deletions(-) diff --git a/modules/adponeBidAdapter.js b/modules/adponeBidAdapter.js index f128785afff..49c0365fc87 100644 --- a/modules/adponeBidAdapter.js +++ b/modules/adponeBidAdapter.js @@ -4,34 +4,20 @@ import {triggerPixel} from '../src/utils.js'; const ADPONE_CODE = 'adpone'; const ADPONE_ENDPOINT = 'https://rtb.adpone.com/bid-request'; -const ADPONE_SYNC_ENDPOINT = 'https://eu-ads.adpone.com'; const ADPONE_REQUEST_METHOD = 'POST'; const ADPONE_CURRENCY = 'EUR'; -function _createSync() { - return { - type: 'iframe', - url: ADPONE_SYNC_ENDPOINT - } -} - -function getUserSyncs(syncOptions) { - return (syncOptions && syncOptions.iframeEnabled) ? _createSync() : ([]); -} - export const spec = { code: ADPONE_CODE, supportedMediaTypes: [BANNER], - getUserSyncs, - isBidRequestValid: bid => { return !!bid.params.placementId && !!bid.bidId && bid.bidder === 'adpone' }, - buildRequests: bidRequests => { + buildRequests: (bidRequests, bidderRequest) => { return bidRequests.map(bid => { - const url = ADPONE_ENDPOINT + '?pid=' + bid.params.placementId; + let url = ADPONE_ENDPOINT + '?pid=' + bid.params.placementId; const data = { at: 1, id: bid.bidId, @@ -49,6 +35,11 @@ export const spec = { withCredentials: true }; + if (bidderRequest && bidderRequest.gdprConsent) { + url += '&gdpr_applies=' + bidderRequest.gdprConsent.gdprApplies; + url += '&consentString=' + bidderRequest.gdprConsent.consentString; + } + return { method: ADPONE_REQUEST_METHOD, url, @@ -67,18 +58,27 @@ export const spec = { serverResponse.body.seatbid.forEach(seatbid => { if (seatbid.bid.length) { - answer = [...answer, ...seatbid.bid.filter(bid => bid.price > 0).map(bid => ({ - id: bid.id, - requestId: bidRequest.data.id, - cpm: bid.price, - ad: bid.adm, - width: bid.w || 0, - height: bid.h || 0, - currency: serverResponse.body.cur || ADPONE_CURRENCY, - netRevenue: true, - ttl: 300, - creativeId: bid.crid || 0 - }))]; + answer = [...answer, ...seatbid.bid.filter(bid => bid.price > 0).map(adponeBid => { + const bid = { + id: adponeBid.id, + requestId: bidRequest.data.id, + cpm: adponeBid.price, + ad: adponeBid.adm, + width: adponeBid.w || 0, + height: adponeBid.h || 0, + currency: serverResponse.body.cur || ADPONE_CURRENCY, + netRevenue: true, + ttl: 300, + creativeId: adponeBid.crid || 0 + }; + + if (adponeBid.meta && adponeBid.meta.adomain && adponeBid.meta.adomain.length > 0) { + bid.meta = {}; + bid.meta.advertiserDomains = adponeBid.meta.adomain; + } + + return bid + })]; } }); diff --git a/test/spec/modules/adponeBidAdapter_spec.js b/test/spec/modules/adponeBidAdapter_spec.js index 737f1c284e1..92fd672df47 100644 --- a/test/spec/modules/adponeBidAdapter_spec.js +++ b/test/spec/modules/adponeBidAdapter_spec.js @@ -110,122 +110,109 @@ describe('adponeBidAdapter', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); -}); - -describe('interpretResponse', function () { - let serverResponse; - let bidRequest = { data: {id: '1234'} }; - - beforeEach(function () { - serverResponse = { - body: { - id: '2579e20c0bb89', - seatbid: [ - { - bid: [ - { - id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', - impid: '2579e20c0bb89_0', - price: 1, - adm: '', - adomain: [ - 'www.addomain.com' - ], - iurl: 'https://localhost11', - crid: 'creative111', - h: 250, - w: 300, - ext: { - dspid: 6 + describe('interpretResponse', function () { + let serverResponse; + let bidRequest = { data: {id: '1234'} }; + + beforeEach(function () { + serverResponse = { + body: { + id: '2579e20c0bb89', + seatbid: [ + { + bid: [ + { + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + impid: '2579e20c0bb89_0', + price: 1, + adm: '', + meta: { + adomain: [ + 'adpone.com' + ] + }, + iurl: 'https://localhost11', + crid: 'creative111', + h: 250, + w: 300, + ext: { + dspid: 6 + } } - } - ], - seat: 'adpone' - } - ], - cur: 'USD' - }, - }; - }); - - it('validate_response_params', function() { - const newResponse = spec.interpretResponse(serverResponse, bidRequest); - expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); - expect(newResponse[0].requestId).to.be.equal('1234'); - expect(newResponse[0].cpm).to.be.equal(1); - expect(newResponse[0].width).to.be.equal(300); - expect(newResponse[0].height).to.be.equal(250); - expect(newResponse[0].currency).to.be.equal('USD'); - expect(newResponse[0].netRevenue).to.be.equal(true); - expect(newResponse[0].ttl).to.be.equal(300); - expect(newResponse[0].ad).to.be.equal(''); - }); + ], + seat: 'adpone' + } + ], + cur: 'USD' + }, + }; + }); - it('should correctly reorder the server response', function () { - const newResponse = spec.interpretResponse(serverResponse, bidRequest); - expect(newResponse.length).to.be.equal(1); - expect(newResponse[0]).to.deep.equal({ - id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', - requestId: '1234', - cpm: 1, - width: 300, - height: 250, - creativeId: 'creative111', - currency: 'USD', - netRevenue: true, - ttl: 300, - ad: '' + it('validate_response_params', function() { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); + expect(newResponse[0].requestId).to.be.equal('1234'); + expect(newResponse[0].cpm).to.be.equal(1); + expect(newResponse[0].width).to.be.equal(300); + expect(newResponse[0].height).to.be.equal(250); + expect(newResponse[0].currency).to.be.equal('USD'); + expect(newResponse[0].netRevenue).to.be.equal(true); + expect(newResponse[0].ttl).to.be.equal(300); + expect(newResponse[0].ad).to.be.equal(''); }); - }); - it('should not add responses if the cpm is 0 or null', function () { - serverResponse.body.seatbid[0].bid[0].price = 0; - let response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.deep.equal([]); + it('should correctly reorder the server response', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + meta: { + advertiserDomains: [ + 'adpone.com' + ] + }, + requestId: '1234', + cpm: 1, + width: 300, + height: 250, + creativeId: 'creative111', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '' + }); + }); - serverResponse.body.seatbid[0].bid[0].price = null; - response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.deep.equal([]) - }); - it('should add responses if the cpm is valid', function () { - serverResponse.body.seatbid[0].bid[0].price = 0.5; - let response = spec.interpretResponse(serverResponse, bidRequest); - expect(response).to.not.deep.equal([]); - }); -}); + it('should not add responses if the cpm is 0 or null', function () { + serverResponse.body.seatbid[0].bid[0].price = 0; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); -describe('getUserSyncs', function () { - it('Verifies that getUserSyncs is a function', function () { - expect((typeof (spec.getUserSyncs)).should.equals('function')); - }); - it('Verifies getUserSyncs returns expected result', function () { - expect((typeof (spec.getUserSyncs)).should.equals('function')); - expect(spec.getUserSyncs({iframeEnabled: true})).to.deep.equal({ - type: 'iframe', - url: 'https://eu-ads.adpone.com' + serverResponse.body.seatbid[0].bid[0].price = null; + response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]) + }); + it('should add responses if the cpm is valid', function () { + serverResponse.body.seatbid[0].bid[0].price = 0.5; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.not.deep.equal([]); }); }); - it('Verifies that iframeEnabled: false returns an empty array', function () { - expect(spec.getUserSyncs({iframeEnabled: false})).to.deep.equal(EMPTY_ARRAY); - }); - it('Verifies that iframeEnabled: null returns an empty array', function () { - expect(spec.getUserSyncs(null)).to.deep.equal(EMPTY_ARRAY); - }); -}); -describe('test onBidWon function', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel'); - }); - afterEach(function() { - utils.triggerPixel.restore(); - }); - it('exists and is a function', () => { - expect(spec.onBidWon).to.exist.and.to.be.a('function'); - }); - it('should return nothing', function () { - var response = spec.onBidWon({}); - expect(response).to.be.an('undefined') - expect(utils.triggerPixel.called).to.equal(true); + describe('test onBidWon function', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); }); }); From fddb1353a4f1969d72443174d1281e1dcd63be74 Mon Sep 17 00:00:00 2001 From: ym-abaranov <78230460+ym-abaranov@users.noreply.github.com> Date: Wed, 14 Apr 2021 07:45:49 -0700 Subject: [PATCH 41/57] Yieldmo bid adapter: set outstream renderer. (#6566) - adapter maintainer email: opensource@yieldmo.com --- modules/yieldmoBidAdapter.js | 60 +++++++++++++++++++++++++++++------- modules/yieldmoBidAdapter.md | 27 ++++++++++++++++ 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 4981534412f..9bd0d1c7f6a 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,8 +1,8 @@ import * as utils from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; import includes from 'core-js-pure/features/array/includes'; -import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'yieldmo'; const CURRENCY = 'USD'; @@ -10,10 +10,14 @@ const TIME_TO_LIVE = 300; const NET_REVENUE = true; const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; +const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js'; const OPENRTB_VIDEO_BIDPARAMS = ['placement', 'startdelay', 'skipafter', 'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos']; const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords']; -const localWindow = utils.getWindowTop(); +const LOCAL_WINDOW = utils.getWindowTop(); +const DEFAULT_PLAYBACK_METHOD = 2; +const DEFAULT_START_DELAY = 0; +const VAST_TIMEOUT = 15000; export const spec = { code: BIDDER_CODE, @@ -48,12 +52,12 @@ export const spec = { page_url: bidderRequest.refererInfo.referer, bust: new Date().getTime().toString(), pr: bidderRequest.refererInfo.referer, - scrd: localWindow.devicePixelRatio || 0, + scrd: LOCAL_WINDOW.devicePixelRatio || 0, dnt: getDNT(), description: getPageDescription(), - title: localWindow.document.title || '', - w: localWindow.innerWidth, - h: localWindow.innerHeight, + title: LOCAL_WINDOW.document.title || '', + w: LOCAL_WINDOW.innerWidth, + h: LOCAL_WINDOW.innerHeight, userConsent: JSON.stringify({ // case of undefined, stringify will remove param gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', @@ -203,8 +207,9 @@ function createNewBannerBid(response) { * @param bidRequest server request */ function createNewVideoBid(response, bidRequest) { - const imp = find((utils.deepAccess(bidRequest, 'data.imp') || []), imp => imp.id === response.impid); - return { + const imp = (utils.deepAccess(bidRequest, 'data.imp') || []).find(imp => imp.id === response.impid); + + let result = { requestId: imp.id, cpm: response.price, width: imp.video.w, @@ -220,6 +225,35 @@ function createNewVideoBid(response, bidRequest) { mediaType: VIDEO, }, }; + + if (imp.placement && imp.placement !== 1) { + const renderer = Renderer.install({ + url: OUTSTREAM_VIDEO_PLAYER_URL, + config: { + width: result.width, + height: result.height, + vastTimeout: VAST_TIMEOUT, + maxAllowedVastTagRedirects: 5, + allowVpaid: true, + autoPlay: true, + preload: true, + mute: true + }, + id: imp.tagid, + loaded: false, + }); + + renderer.setRender(function (bid) { + bid.renderer.push(() => { + const { id, config } = bid.renderer; + window.YMoutstreamPlayer(bid, id, config); + }); + }); + + result.renderer = renderer; + } + + return result; } /** @@ -306,10 +340,14 @@ function openRtbImpression(bidRequest) { .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) .forEach(param => imp.video[param] = videoParams[param]); - if (videoParams.skippable) { - imp.video.skip = 1; + if (videoParams.skippable) imp.video.skip = 1; + if (videoParams.placement !== 1) { + imp.video = { + ...imp.video, + startdelay: DEFAULT_START_DELAY, + playbackmethod: [ DEFAULT_PLAYBACK_METHOD ] + } } - return imp; } diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md index 1b8b7b1b741..54be295a1a1 100644 --- a/modules/yieldmoBidAdapter.md +++ b/modules/yieldmoBidAdapter.md @@ -69,3 +69,30 @@ var adUnits = [{ // Video adUnit }] }]; ``` + +Sample out-stream video ad unit config: +```javascript +var videoAdUnit = [{ + code: 'div-video-ad-1234567890', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'outstream', + mimes: ['video/mp4'] // required, array of strings + } + }, + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1524592390382976659', // required + video: { + placement: 3, // required, integer ( 3,4,5 ) + maxduration: 30, // required, integer + protocols: [2, 3], // required, array of integers + api: [2, 3], // required, array of integers + playbackmethod: [1,2] // required, array of integers + } + } + }] +}]; +``` From 42e36dec98c755a2b73f1e300642998840990566 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Wed, 14 Apr 2021 18:57:26 +0200 Subject: [PATCH 42/57] Richaudience Bid Adapter: add new config UserSync (#6523) * Add new config UserSync * Solved lint errors * Solved lint errors 2 * Solved lint errors 3 * Add changes * Add changes 2 * Add changes 3 * Add changes 4 * Add changes 5 * Solved issues Co-authored-by: sgimenez --- integrationExamples/gpt/gdpr_hello_world.html | 26 +- modules/richaudienceBidAdapter.js | 49 ++- .../modules/richaudienceBidAdapter_spec.js | 393 +++++++++++++++--- 3 files changed, 389 insertions(+), 79 deletions(-) diff --git a/integrationExamples/gpt/gdpr_hello_world.html b/integrationExamples/gpt/gdpr_hello_world.html index de0630178f1..2d70af8d34f 100644 --- a/integrationExamples/gpt/gdpr_hello_world.html +++ b/integrationExamples/gpt/gdpr_hello_world.html @@ -82,7 +82,7 @@ - + + + - +

Prebid.js Test

Div-1
diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 37a9554e9a4..5e2a5e1bff5 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -31,7 +31,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bid => { var payload = { - bidfloor: bid.params.bidfloor, + bidfloor: raiGetFloor(bid, config), ifa: bid.params.ifa, pid: bid.params.pid, supplyType: bid.params.supplyType, @@ -141,11 +141,15 @@ export const spec = { var syncUrl = ''; var consent = ''; + var raiSync = {}; + + raiSync = raiGetSyncInclude(config); + if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') { consent = `consentString=${gdprConsent.consentString}` } - if (syncOptions.iframeEnabled) { + if (syncOptions.iframeEnabled && raiSync.raiIframe != 'exclude') { syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand if (consent != '') { syncUrl += `&${consent}` @@ -156,7 +160,7 @@ export const spec = { }); } - if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0) { + if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0 && raiSync.raiImage != 'exclude') { syncUrl = `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=${REFERER}`; if (consent != '') { syncUrl += `&${consent}` @@ -263,3 +267,42 @@ function raiGetResolution() { } return resolution; } + +function raiGetSyncInclude(config) { + try { + let raConfig = null; + let raiSync = {}; + if (config.getConfig('userSync').filterSettings != null && typeof config.getConfig('userSync').filterSettings != 'undefined') { + raConfig = config.getConfig('userSync').filterSettings + if (raConfig.iframe != null && typeof raConfig.iframe != 'undefined') { + raiSync.raiIframe = raConfig.iframe.bidders == 'richaudience' || raConfig.iframe.bidders == '*' ? raConfig.iframe.filter : 'exclude'; + } + if (raConfig.image != null && typeof raConfig.image != 'undefined') { + raiSync.raiImage = raConfig.image.bidders == 'richaudience' || raConfig.image.bidders == '*' ? raConfig.image.filter : 'exclude'; + } + } + return raiSync; + } catch (e) { + return null; + } +} + +function raiGetFloor(bid, config) { + try { + let raiFloor; + if (bid.params.bidfloor != null) { + raiFloor = bid.params.bidfloor; + } else if (typeof bid.getFloor == 'function') { + let floorSpec = bid.getFloor({ + currency: config.getConfig('currency.adServerCurrency'), + mediaType: bid.mediaType.banner ? 'banner' : 'video', + size: '*' + }) + + raiFloor = floorSpec.floor; + } + return raiFloor + } catch (e) { + return 0 + } +} diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 5deb2463523..72410b71fb2 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -788,76 +788,343 @@ describe('Richaudience adapter tests', function () { })).to.equal(true); }); - it('Verifies user syncs iframe', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true + describe('userSync', function () { + it('Verifies user syncs iframe include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); }); + it('Verifies user syncs iframe exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}}} + }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - syncs = spec.getUserSyncs({ - iframeEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); }); - expect(syncs).to.have.lengthOf(0); - syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(1); + it('Verifies user syncs image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); - syncs = spec.getUserSyncs({ - iframeEnabled: false - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); - config.setConfig({ - consentManagement: { - cmpApi: 'iab', - timeout: 5000, - allowAuctionWithoutConsent: true, + syncs = spec.getUserSyncs({ + iframeEnabled: false, pixelEnabled: true - } + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); }); - }); - it('Verifies user syncs image', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: '', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [], { - consentString: null, - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - }); + it('Verifies user syncs image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe exclude / image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe include / image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + }) }); From 05d87735631c76a90b658453640b3c86d983a0d8 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 14 Apr 2021 19:48:10 +0200 Subject: [PATCH 43/57] Prebid 4.35.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b8a4dc5fb4..67394ebdb33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.35.0-pre", + "version": "4.35.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a3790c27cf71d288cc3e96959110cbce88a0b9c8 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 14 Apr 2021 20:15:04 +0200 Subject: [PATCH 44/57] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 67394ebdb33..58ea0d7ba30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.35.0", + "version": "4.36.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From caead3ccccc448e4cd09d074fd9f8833f56fe9b3 Mon Sep 17 00:00:00 2001 From: Sourabh Gandhe Date: Thu, 15 Apr 2021 13:12:55 +0530 Subject: [PATCH 45/57] Deepintent ID System: add new ID module (#6537) * initial commit * feat(deepintent-dpes): adds deepintent user id module * chore(code-cleanup): removed console logs * eids config added * fix for passing the eids * docs added with minor change * tests added * remaining conflict resolution * kick off circle-ci tests manually * fix linting error * changed the atype to 3 * tests added for eids_spec.js * Change the language * added cacheIdObject signature * changed test cases * eIds passing added to adapter * docs changed removed params not required * doc added * docs added in userId base * user id tests added * lint fixes * lint fixes * code review comments fix Co-authored-by: Sourabh Gandhe Co-authored-by: ChinmoyDebnath Co-authored-by: Chris Huie --- modules/.submodules.json | 1 + modules/deepintentBidAdapter.js | 9 ++ modules/deepintentDpesIdSystem.js | 45 ++++++++ modules/deepintentDpesIdSystem.md | 43 +++++++ modules/userId/eids.js | 5 +- modules/userId/eids.md | 7 ++ modules/userId/userId.md | 14 +++ .../modules/deepintentDpesIdsystem_spec.js | 76 ++++++++++++ test/spec/modules/eids_spec.js | 12 ++ test/spec/modules/userId_spec.js | 108 +++++++++++++++--- 10 files changed, 302 insertions(+), 18 deletions(-) create mode 100644 modules/deepintentDpesIdSystem.js create mode 100644 modules/deepintentDpesIdSystem.md create mode 100644 test/spec/modules/deepintentDpesIdsystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index ffa3f9df353..7ad4bedde5c 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -16,6 +16,7 @@ "zeotapIdPlusIdSystem", "haloIdSystem", "quantcastIdSystem", + "deepintentDpesIdSystem", "nextrollIdSystem", "idxIdSystem", "fabrickIdSystem", diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index c4dc23cf912..9ec6c8e5bc2 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -49,6 +49,8 @@ export const spec = { utils.deepSetValue(openRtbBidRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } + injectEids(openRtbBidRequest, validBidRequests); + return { method: 'POST', url: BIDDER_ENDPOINT, @@ -128,6 +130,13 @@ function buildUser(bid) { } } +function injectEids(openRtbBidRequest, validBidRequests) { + const bidUserIdAsEids = utils.deepAccess(validBidRequests, '0.userIdAsEids'); + if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + utils.deepSetValue(openRtbBidRequest, 'user.eids', bidUserIdAsEids); + } +} + function buildBanner(bid) { if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Get Sizes from MediaTypes Object, Will always take first size, will be overrided by params for exact w,h diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js new file mode 100644 index 00000000000..375c8c07ed1 --- /dev/null +++ b/modules/deepintentDpesIdSystem.js @@ -0,0 +1,45 @@ +/** + * This module adds DPES to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/deepintentDpesSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'deepintentId'; +export const storage = getStorageManager(null, MODULE_NAME); + +/** @type {Submodule} */ +export const deepintentDpesSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{value:string}} value + * @returns {{deepintentId:Object}} + */ + decode(value, config) { + return value ? { 'deepintentId': value } : undefined; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @param {ConsentData|undefined} consentData + * @param {Object} cacheIdObj - existing id, if any + * @return {{id: string | undefined} | undefined} + */ + getId(config, consentData, cacheIdObj) { + return cacheIdObj; + } + +}; + +submodule('userId', deepintentDpesSubmodule); diff --git a/modules/deepintentDpesIdSystem.md b/modules/deepintentDpesIdSystem.md new file mode 100644 index 00000000000..2af0fe7446e --- /dev/null +++ b/modules/deepintentDpesIdSystem.md @@ -0,0 +1,43 @@ +# Deepintent DPES ID + +The Deepintent Id is a shared, healthcare identifier which helps publisher in absence of the 3rd Party cookie matching. This lets publishers set and bid with healthcare identity . Deepintent lets users protect their privacy through advertising value chain, where Healthcare identity when setting the identity takes in consideration of users choices, as well as when passing identity on the cookie itself privacy consent strings are checked. The healthcare identity when set is not stored on Deepintent's servers but is stored on users browsers itself. User can still opt out of the ads by https://option.deepintent.com/adchoices. + +## Deepintent DPES ID Registration + +The Deepintent DPES ID is free to use, but requires a simple registration with Deepintent. Please reach to prebid@deepintent.com to get started. +Once publisher registers with deepintents platform for healthcare identity Deepintent provides the Tag code to be placed on the page, this tag code works to capture and store information as per publishers and users agreement. DPES User ID module uses this stored id and passes it on the deepintent prebid adapter. + + +## Deepintent DPES ID Configuration + +First, make sure to add the Deepintent submodule to your Prebid.js package with: + +``` +gulp build --modules=deepintentDpesIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'deepintentId', + storage: { + type: 'cookie', + name: '_dpes_id', + expires: 90 // storage lasts for 90 days, optional if storage type is html5 + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module: `"deepintentId"` | `"deepintentId"` | +| storage | Required | Object | Storage settings for how the User Id module will cache the Deepintent ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. Deepintent`"html5"` or `"cookie"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. | `"_dpes_id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. Deepintent recommends `90`. | `90` | \ No newline at end of file diff --git a/modules/userId/eids.js b/modules/userId/eids.js index a38417683ba..93d2ead15ea 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -207,7 +207,10 @@ const USER_IDS_CONFIG = { return data.id; } }, - + 'deepintentId': { + source: 'deepintent.com', + atype: 3 + }, // Admixer Id 'admixerId': { source: 'admixer.net', diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 93783a2db4d..a00aedcc52e 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -184,6 +184,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 3 }] + }, + { + source: 'deepintent.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] } ] ``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 79f547b179f..aef50eeccdf 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -222,6 +222,20 @@ pbjs.setConfig({ name: 'admixerId', expires: 30 } + },{ + name: "deepintentId", + storage: { + type: "html5", + name: "_dpes_id", + expires: 90 + } + },{ + name: "deepintentId", + storage: { + type: "cookie", + name: "_dpes_id", + expires: 90 + } }], syncDelay: 5000 } diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js new file mode 100644 index 00000000000..7ea5553393c --- /dev/null +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { storage, deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { config } from 'src/config.js'; + +const DI_COOKIE_NAME = '_dpes_id'; +const DI_COOKIE_STORED = '{"id":"2cf40748c4f7f60d343336e08f80dc99"}'; +const DI_COOKIE_OBJECT = {id: '2cf40748c4f7f60d343336e08f80dc99'}; + +const cookieConfig = { + name: 'deepintentId', + storage: { + type: 'cookie', + name: '_dpes_id', + expires: 28 + } +}; + +const html5Config = { + name: 'deepintentId', + storage: { + type: 'html5', + name: '_dpes_id', + expires: 28 + } +} + +describe('Deepintent DPES System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let getCookieStub, cookiesAreEnabledStub; + + beforeEach(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + cookiesAreEnabledStub.restore(); + }); + + describe('Deepintent Dpes Sytsem: test "getId" method', () => { + it('Wrong config should fail the tests', () => { + // no config + expect(deepintentDpesSubmodule.getId()).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({ })).to.be.eq(undefined); + + expect(deepintentDpesSubmodule.getId({params: {}, storage: {}})).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({params: {}, storage: {type: 'cookie'}})).to.be.eq(undefined); + expect(deepintentDpesSubmodule.getId({params: {}, storage: {name: '_dpes_id'}})).to.be.eq(undefined); + }); + + it('Get value stored in cookie for getId', () => { + getCookieStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); + let diId = deepintentDpesSubmodule.getId(cookieConfig, undefined, DI_COOKIE_OBJECT); + expect(diId).to.deep.equal(DI_COOKIE_OBJECT); + }); + + it('provides the stored deepintentId if cookie is absent but present in local storage', () => { + getDataFromLocalStorageStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); + let idx = deepintentDpesSubmodule.getId(html5Config, undefined, DI_COOKIE_OBJECT); + expect(idx).to.deep.equal(DI_COOKIE_OBJECT); + }); + }); + + describe('Deepintent Dpes System : test "decode" method', () => { + it('Get the correct decoded value for dpes id', () => { + expect(deepintentDpesSubmodule.decode(DI_COOKIE_OBJECT, cookieConfig)).to.deep.equal({'deepintentId': {'id': '2cf40748c4f7f60d343336e08f80dc99'}}); + }); + }); +}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 86a6dff2205..1ccaab2b302 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -189,6 +189,18 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('deepintentId', function() { + const userId = { + deepintentId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + it('NetId', function() { const userId = { netId: 'some-random-id-value' diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 3c852f3af5c..d61d919a5ef 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -44,6 +44,7 @@ import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; import {getPrebidInternal} from 'src/utils.js'; import {uid2IdSubmodule} from 'modules/uid2IdSystem.js'; import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; +import {deepintentDpesSubmodule} from 'modules/deepintentDpesIdSystem.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -462,7 +463,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, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -470,14 +471,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, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); 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, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig({ userSync: { @@ -488,7 +489,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, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig({ userSync: { @@ -505,7 +506,7 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); @@ -513,7 +514,7 @@ describe('User ID', function () { }); it('config with 17 configurations should result in 18 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig({ userSync: { @@ -565,14 +566,17 @@ describe('User ID', function () { }, { name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} + }, { + name: 'deepintentId', + storage: {name: 'deepintentId', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 18 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 19 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig({ userSync: { @@ -587,7 +591,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig({ userSync: { @@ -602,7 +606,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig({ userSync: { @@ -1702,6 +1706,53 @@ describe('User ID', function () { }, {adUnits}); }); + it('test hook from deepintentId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([deepintentDpesSubmodule]); + init(config); + config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userId.deepintentId).to.deep.equal('testdeepintentId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'testdeepintentId', atype: 3}] + }); + }); + }); + coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook from deepintentId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('deepintentId', 'testdeepintentId'); + localStorage.setItem('deepintentId_exp', ''); + + setSubmoduleRegistry([deepintentDpesSubmodule]); + init(config); + config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'html5'])); + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'testdeepintentId', atype: 3}] + }); + }); + }); + localStorage.removeItem('deepintentId'); + done(); + }, {adUnits}); + }); + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId, Criteo, UID 2.0, admixerId 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())); @@ -1720,8 +1771,9 @@ describe('User ID', function () { 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())); coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1737,7 +1789,8 @@ describe('User ID', function () { ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'], ['uid2', 'uid2id', 'cookie'], - ['admixerId', 'admixerId', 'cookie'])); + ['admixerId', 'admixerId', 'cookie'], + ['deepintentId', 'deepintentId', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1787,7 +1840,11 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.admixerId'); expect(bid.userId.admixerId).to.equal('testadmixerId'); - expect(bid.userIdAsEids.length).to.equal(14); + // also check that deepintentId was copied to bid + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userId.deepintentId).to.equal('testdeepintentId'); + + expect(bid.userIdAsEids.length).to.equal(15); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1804,6 +1861,7 @@ describe('User ID', function () { coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('deepintentId', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -1826,6 +1884,7 @@ describe('User ID', function () { 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())); coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -1849,6 +1908,7 @@ describe('User ID', function () { attachIdSystem(tapadIdSubmodule); attachIdSystem(uid2IdSubmodule); attachIdSystem(admixerIdSubmodule); + attachIdSystem(deepintentDpesSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1864,7 +1924,8 @@ describe('User ID', function () { ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'], ['uid2', 'uid2id', 'cookie'], - ['admixerId', 'admixerId', 'cookie'])); + ['admixerId', 'admixerId', 'cookie'], + ['deepintentId', 'deepintentId', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1917,8 +1978,11 @@ describe('User ID', function () { // also check that admixerId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.admixerId'); expect(bid.userId.admixerId).to.equal('testadmixerId'); + // also check that deepintentId was copied to bid + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userId.deepintentId).to.equal('testdeepintentId'); - expect(bid.userIdAsEids.length).to.equal(14); + expect(bid.userIdAsEids.length).to.equal(15); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1935,6 +1999,7 @@ describe('User ID', function () { coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -2192,10 +2257,11 @@ 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('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); 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, uid2IdSubmodule, admixerIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule]); init(config); config.setConfig({ @@ -2227,6 +2293,8 @@ describe('User ID', function () { name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }, { name: 'uid2' + }, { + name: 'deepintentId', storage: {name: 'deepintentId', type: 'cookie'} }] } }); @@ -2291,7 +2359,12 @@ describe('User ID', function () { // also check that admixerId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.admixerId'); expect(bid.userId.admixerId).to.equal('testadmixerId'); - expect(bid.userIdAsEids.length).to.equal(12); + + // also check that deepintentId was copied to bid + expect(bid).to.have.deep.nested.property('userId.deepintentId'); + expect(bid.userId.deepintentId).to.equal('testdeepintentId'); + + expect(bid.userIdAsEids.length).to.equal(13); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -2305,6 +2378,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); From bea2261291ffbb4b89100174975f100ad373440a Mon Sep 17 00:00:00 2001 From: Nick Peceniak Date: Thu, 15 Apr 2021 06:00:55 -0600 Subject: [PATCH 46/57] Update spotxBidAdpter renderer url to ensure onLoad is always called. (#6592) Co-authored-by: Nick Peceniak --- modules/spotxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 05e4e0ba1ef..4c9b50ca9db 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -365,7 +365,7 @@ export const spec = { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: '//', + url: '/', config: { adText: 'SpotX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], From d18c6a874bf1465cf9f8427f65a2fa21c05a2454 Mon Sep 17 00:00:00 2001 From: Michael Kuryshev Date: Thu, 15 Apr 2021 17:00:24 +0300 Subject: [PATCH 47/57] VIS.X: add onSetTargeting, onBidWon & onTimeout handlers (#6532) --- modules/visxBidAdapter.js | 20 +++++++++++++++-- test/spec/modules/visxBidAdapter_spec.js | 28 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index 725482d07c3..a5829b9cd9c 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -2,10 +2,14 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'visx'; -const ENDPOINT_URL = 'https://t.visx.net/hb'; +const BASE_URL = 'https://t.visx.net'; +const ENDPOINT_URL = BASE_URL + '/hb'; const TIME_TO_LIVE = 360; const DEFAULT_CUR = 'EUR'; -const ADAPTER_SYNC_URL = 'https://t.visx.net/push_sync'; +const ADAPTER_SYNC_URL = BASE_URL + '/push_sync'; +const TRACK_WIN_URL = BASE_URL + '/track/win'; +const TRACK_PENDING_URL = BASE_URL + '/track/pending'; +const TRACK_TIMEOUT_URL = BASE_URL + '/track/bid_timeout'; const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -170,6 +174,18 @@ export const spec = { url: ADAPTER_SYNC_URL + (query.length ? '?' + query.join('&') : '') }]; } + }, + onSetTargeting: function(bid) { + // Call '/track/pending' with the corresponding bid.requestId + utils.triggerPixel(TRACK_PENDING_URL + '?requestId=' + bid.requestId); + }, + onBidWon: function(bid) { + // Call '/track/win' with the corresponding bid.requestId + utils.triggerPixel(TRACK_WIN_URL + '?requestId=' + bid.requestId); + }, + onTimeout: function(timeoutData) { + // Call '/track/bid_timeout' with timeout data + utils.triggerPixel(TRACK_TIMEOUT_URL + '?data=' + JSON.stringify(timeoutData)); } }; diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index a06f530e145..db885ad314e 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/visxBidAdapter.js'; import { config } from 'src/config.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; describe('VisxAdapter', function () { const adapter = newBidder(spec); @@ -656,4 +657,31 @@ describe('VisxAdapter', function () { expect(result).to.deep.equal(expectedResponse); }); }); + describe('check trackers', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('onSetTargeting', function () { + const requestId = '111'; + spec.onSetTargeting({ requestId }); + expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/pending?requestId=' + requestId)).to.equal(true); + }); + + it('onBidWon', function () { + const requestId = '111'; + spec.onBidWon({ requestId }); + expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/win?requestId=' + requestId)).to.equal(true); + }); + + it('onTimeout', function () { + const data = { timeout: 3000, bidId: '23423', params: { uid: 1 } }; + spec.onTimeout(data); + expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/bid_timeout?data=' + JSON.stringify(data))).to.equal(true); + }); + }); }); From 6ff2cf7792beaf8b1c2577ed9b9360beb35054b0 Mon Sep 17 00:00:00 2001 From: Kajan Umakanthan Date: Thu, 15 Apr 2021 08:05:40 -0700 Subject: [PATCH 48/57] Index Exchange Bid Adapter: resolve negative size bug (#6582) --- modules/ixBidAdapter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 7b972aa37e6..0f7f967ef6b 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -492,7 +492,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { msd = impressions[transactionIds[i]].missingCount; } - trimImpressions(impressions[transactionIds[i]], MAX_REQ_SIZE - BASE_REQ_SIZE); + if (BASE_REQ_SIZE < MAX_REQ_SIZE) { + trimImpressions(impressions[transactionIds[i]], MAX_REQ_SIZE - BASE_REQ_SIZE); + } else { + utils.logError('ix bidder: Base request size has exceeded maximum request size.'); + } if (impressions[transactionIds[i]].hasOwnProperty('missingImps')) { msi = impressions[transactionIds[i]].missingImps.length; From 170c82d1ef3aadbf9ff3ffcde9594abeaf708ee5 Mon Sep 17 00:00:00 2001 From: Skylinar <53079123+Skylinar@users.noreply.github.com> Date: Thu, 15 Apr 2021 17:26:26 +0200 Subject: [PATCH 49/57] smartx Bid Adapter: Outstream render bugfix numeric elementId (#6588) --- modules/smartxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index b95b31934a2..6b34e499a99 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -379,7 +379,7 @@ function createOutstreamScript(bid) { smartPlayObj.adResponse = bid.vastContent; - const divID = '#' + elementId; + const divID = '[id="' + elementId + '"]'; var script = document.createElement('script'); script.src = 'https://dco.smartclip.net/?plc=7777778'; script.type = 'text/javascript'; From de4098269bb2b15e285fb1b7c9b1b383d2cd19f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20DEYM=C3=88S?= <47388595+MaxSmileWanted@users.noreply.github.com> Date: Fri, 16 Apr 2021 03:46:50 +0200 Subject: [PATCH 50/57] Update cookie sync call (#6567) --- modules/smilewantedBidAdapter.js | 31 ++++++++++++++----- .../modules/smilewantedBidAdapter_spec.js | 10 +++--- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index f965310abdd..fb05298a230 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -108,16 +108,31 @@ export const spec = { * @param {*} serverResponses A successful response from the server. * @return {Syncs[]} An array of syncs that should be executed. */ - getUserSyncs: function(syncOptions, serverResponses) { - const syncs = [] - if (syncOptions.iframeEnabled && serverResponses.length > 0) { - if (serverResponses[0].body.cSyncUrl === 'https://csync.smilewanted.com') { - syncs.push({ - type: 'iframe', - url: serverResponses[0].body.cSyncUrl - }); + getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { + let params = ''; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `?gdpr_consent=${gdprConsent.consentString}`; } } + + if (uspConsent) { + params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + } + + const syncs = [] + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://csync.smilewanted.com' + params + }); + } + return syncs; } } diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 0ac242ce0e1..d0d8b65a42f 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -305,12 +305,12 @@ describe('smilewantedBidAdapterTests', function () { }); it('SmileWanted - Verify user sync', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE_DISPLAY]); + var syncs = spec.getUserSyncs({iframeEnabled: true}, {}, { + consentString: 'foo' + }, '1NYN'); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://csync.smilewanted.com'); + expect(syncs[0].url).to.equal('https://csync.smilewanted.com?gdpr_consent=foo&us_privacy=1NYN'); syncs = spec.getUserSyncs({ iframeEnabled: false @@ -320,6 +320,6 @@ describe('smilewantedBidAdapterTests', function () { syncs = spec.getUserSyncs({ iframeEnabled: true }, []); - expect(syncs).to.have.lengthOf(0); + expect(syncs).to.have.lengthOf(1); }); }); From 8745d462ad2e8c95e2a90fd80926c3a7dfcdcdf6 Mon Sep 17 00:00:00 2001 From: Nick Peceniak Date: Fri, 16 Apr 2021 03:52:49 -0600 Subject: [PATCH 51/57] Add videoCacheKey back to bid response when using spotx as cache server (#6605) Co-authored-by: Nick Peceniak --- modules/spotxBidAdapter.js | 1 + test/spec/modules/spotxBidAdapter_spec.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 4c9b50ca9db..f3728058d18 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -352,6 +352,7 @@ export const spec = { } else { bid.cache_key = spotxBid.ext.cache_key; bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key + bid.videoCacheKey = spotxBid.ext.cache_key; } bid.meta = bid.meta || {}; diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 873914441aa..cc94e37fdaa 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -495,6 +495,7 @@ 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); @@ -508,6 +509,7 @@ 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 f65fe744bc4b80990d83e699e00b92436ce92f3e Mon Sep 17 00:00:00 2001 From: jsfledd Date: Fri, 16 Apr 2021 07:48:09 -0700 Subject: [PATCH 52/57] Nativo Bid Adapter: add new bid adapter (#6542) * Initial nativoBidAdapter document creation (js, md and spec) * Fulling working prebid using nativoBidAdapter. Support for GDPR and CCPA in user syncs. * Added defult size settings based on the largest ad unit. Added response body validation. Added consent to request url qs params. * Changed bidder endpoint url * Changed double quotes to single quotes. * Reverted package-json.lock to remove modifications from PR * Added optional bidder param 'url' so the ad server can force- match an existing placement * Lint fix. Added space after if. --- modules/nativoBidAdapter.js | 307 +++++++++++++++++++++ modules/nativoBidAdapter.md | 40 +++ test/spec/modules/nativoBidAdapter_spec.js | 227 +++++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 modules/nativoBidAdapter.js create mode 100644 modules/nativoBidAdapter.md create mode 100644 test/spec/modules/nativoBidAdapter_spec.js diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js new file mode 100644 index 00000000000..d396bd4d495 --- /dev/null +++ b/modules/nativoBidAdapter.js @@ -0,0 +1,307 @@ +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' + +const BIDDER_CODE = 'nativo' +const BIDDER_ENDPOINT = 'https://exchange.postrelease.com/prebid' + +const TIME_TO_LIVE = 360 + +const SUPPORTED_AD_TYPES = [BANNER] + +const bidRequestMap = {} + +// Prebid adapter referrence doc: https://docs.prebid.org/dev-docs/bidder-adaptor.html + +export const spec = { + code: BIDDER_CODE, + aliases: ['ntv'], // short code + supportedMediaTypes: SUPPORTED_AD_TYPES, + + /** + * 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 bid.params && !!bid.params.placementId + }, + + /** + * Called when the page asks Prebid.js for bids + * Make a server request from the list of BidRequests + * + * @param {Array} validBidRequests - An array of bidRequest objects, one for each AdUnit that your module is involved in. This array has been processed for special features like sizeConfig, so it’s the list that you should be looping through + * @param {Object} bidderRequest - The master bidRequest object. This object is useful because it carries a couple of bid parameters that are global to all the bids. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const placementIds = [] + const placmentBidIdMap = {} + let placementId, pageUrl + validBidRequests.forEach((request) => { + pageUrl = pageUrl || request.params.url // Use the first url value found + placementId = request.params.placementId + placementIds.push(placementId) + placmentBidIdMap[placementId] = { + bidId: request.bidId, + size: getLargestSize(request.sizes), + } + }) + bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap + + if (!pageUrl) pageUrl = bidderRequest.refererInfo.referer + + let params = [ + { key: 'ntv_ptd', value: placementIds.toString() }, + { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, + { + key: 'ntv_url', + value: encodeURIComponent(pageUrl), + }, + ] + + if (bidderRequest.gdprConsent) { + // Put on the beginning of the qs param array + params.unshift({ + key: 'ntv_gdpr_consent', + value: bidderRequest.gdprConsent.consentString, + }) + } + + if (bidderRequest.uspConsent) { + // Put on the beginning of the qs param array + params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) + } + + let serverRequest = { + method: 'GET', + url: BIDDER_ENDPOINT + arrayToQS(params), + } + + return serverRequest + }, + + /** + * Will be called when the browser has received the response from your server. + * The function will parse the response and create a bidResponse object containing one or more bids. + * The adapter should indicate no valid bids by returning an empty array. + * + * @param {Object} response - Data returned from the bidding server request endpoint + * @param {Object} request - The request object used to call the server request endpoint + * @return {Array} An array of bids which were nested inside the server. + */ + interpretResponse: function (response, request) { + // If the bid response was empty, return [] + if (!response || !response.body || utils.isEmpty(response.body)) return [] + + try { + const body = + typeof response.body === 'string' + ? JSON.parse(response.body) + : response.body + + const bidResponses = [] + const seatbids = body.seatbid + + // Step through and grab pertinent data + let bidResponse, adUnit + seatbids.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + adUnit = this.getRequestId(body.id, bid.impid) + bidResponse = { + requestId: adUnit.bidId, + cpm: bid.price, + currency: body.cur, + width: bid.w || adUnit.size[0], + height: bid.h || adUnit.size[1], + creativeId: bid.crid, + dealId: bid.id, + netRevenue: true, + ttl: bid.ttl || TIME_TO_LIVE, + ad: bid.adm, + meta: { + advertiserDomains: bid.adomain, + }, + } + + bidResponses.push(bidResponse) + }) + }) + + // Don't need the map anymore as it was unique for one request/response + delete bidRequestMap[body.id] + + return bidResponses + } catch (error) { + // If there is an error, return [] + return [] + } + }, + + /** + * All user ID sync activity should be done using the getUserSyncs callback of the BaseAdapter model. + * Given an array of all the responses from the server, getUserSyncs is used to determine which user syncs should occur. + * The order of syncs in the serverResponses array matters. The most important ones should come first, since publishers may limit how many are dropped on their page. + * @param {Object} syncOptions - Which user syncs are allowed? + * @param {Array} serverResponses - Array of server's responses + * @param {Object} gdprConsent - GDPR consent data + * @param {Object} uspConsent - USP consent data + * @return {Array} The user syncs which should be dropped. + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + // Generate consent qs string + let params = '' + // GDPR + if (gdprConsent) { + params = appendQSParamString( + params, + 'gdpr', + gdprConsent.gdprApplies ? 1 : 0 + ) + params = appendQSParamString( + params, + 'gdpr_consent', + encodeURIComponent(gdprConsent.consentString || '') + ) + } + // CCPA + if (uspConsent) { + params = appendQSParamString( + params, + 'us_privacy', + encodeURIComponent(uspConsent.uspConsent) + ) + } + + // Get sync urls from the respnse and inject cinbsent params + const types = { + iframe: syncOptions.iframeEnabled, + image: syncOptions.pixelEnabled, + } + const syncs = [] + + let body + serverResponses.forEach((response) => { + // If the bid response was empty, return [] + if (!response || !response.body || utils.isEmpty(response.body)) { + return syncs + } + + body = + typeof response.body === 'string' + ? JSON.parse(response.body) + : response.body + + // Make sure we have valid content + if (!body || !body.seatbid || body.seatbid.length === 0) return + + body.seatbid.forEach((seatbid) => { + // Grab the syncs for each seatbid + seatbid.syncUrls.forEach((sync) => { + if (types[sync.type]) { + if (sync.url.trim() !== '') { + syncs.push({ + type: sync.type, + url: sync.url.replace('{GDPR_params}', params), + }) + } + } + }) + }) + }) + + return syncs + }, + + /** + * Will be called when an adpater timed out for an auction. + * Adapter can fire a ajax or pixel call to register a timeout at thier end. + * @param {Object} timeoutData - Timeout specific data + */ + onTimeout: function (timeoutData) {}, + + /** + * Will be called when a bid from the adapter won the auction. + * @param {Object} bid - The bid that won the auction + */ + onBidWon: function (bid) {}, + + /** + * Will be called when the adserver targeting has been set for a bid from the adapter. + * @param {Object} bidder - The bid of which the targeting has been set + */ + onSetTargeting: function (bid) {}, + + /** + * Maps Prebid's bidId to Nativo's placementId values per unique bidderRequestId + * @param {String} bidderRequestId - The unique ID value associated with the bidderRequest + * @param {String} placementId - The placement ID value from Nativo + * @returns {String} - The bidId value associated with the corresponding placementId + */ + getRequestId: function (bidderRequestId, placementId) { + return ( + bidRequestMap[bidderRequestId] && + bidRequestMap[bidderRequestId][placementId] + ) + }, +} +registerBidder(spec) + +// Utils +/** + * Append QS param to existing string + * @param {String} str - String to append to + * @param {String} key - Key to append + * @param {String} value - Value to append + * @returns + */ +function appendQSParamString(str, key, value) { + return str + `${str.length ? '&' : ''}${key}=${value}` +} + +/** + * Convert an object to query string parameters + * @param {Object} obj - Object to convert + * @returns + */ +function arrayToQS(arr) { + return ( + '?' + + arr.reduce((value, obj) => { + return appendQSParamString(value, obj.key, obj.value) + }, '') + ) +} + +/** + * Get the largest size array + * @param {Array} sizes - Array of size arrays + * @returns Size array with the largest area + */ +function getLargestSize(sizes, method = area) { + if (!sizes || sizes.length === 0) return [] + if (sizes.length === 1) return sizes[0] + + return sizes.reduce((prev, current) => { + if (method(current) > method(prev)) { + return current + } else { + return prev + } + }) +} + +/** + * Calculate the area + * @param {Array} size - [width, height] + * @returns The calculated area + */ +const area = (size) => size[0] * size[1] diff --git a/modules/nativoBidAdapter.md b/modules/nativoBidAdapter.md new file mode 100644 index 00000000000..ec0980aae50 --- /dev/null +++ b/modules/nativoBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Nativo Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebiddev@nativo.com +``` + +# Description + +Module that connects to Nativo's demand sources + +# Dev + +gulp serve --modules=nativoBidAdapter + +# Test Parameters + +``` +var adUnits = [ + { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'nativo', + params: { + placementId: 1125609, + url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html' + } + }] + + } + ]; + +``` diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js new file mode 100644 index 00000000000..e1132bf1b74 --- /dev/null +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -0,0 +1,227 @@ +import { expect } from 'chai' +import { spec } from 'modules/nativoBidAdapter.js' +// import { newBidder } from 'src/adapters/bidderFactory.js' +// import * as bidderFactory from 'src/adapters/bidderFactory.js' +// import { deepClone } from 'src/utils.js' +// import { config } from 'src/config.js' + +describe('nativoBidAdapterTests', function () { + describe('isBidRequestValid', function () { + let bid = { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + } + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + let bid2 = Object.assign({}, bid) + delete bid2.params + bid2.params = {} + expect(spec.isBidRequestValid(bid2)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + transactionId: '3b36e7e0-0c3e-4006-a279-a741239154ff', + }, + ] + + it('url should contain query string parameters', function () { + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + + expect(request.url).to.exist + expect(request.url).to.be.a('string') + + expect(request.url).to.include('?') + expect(request.url).to.include('ntv_url') + expect(request.url).to.include('ntv_ptd') + }) + }) +}) + +describe('interpretResponse', function () { + let response = { + id: '126456', + seatbid: [ + { + seat: 'seat_0', + bid: [ + { + id: 'f70362ac-f3cf-4225-82a5-948b690927a6', + impid: '1', + price: 3.569, + adm: '', + h: 300, + w: 250, + cat: [], + adomain: ['test.com'], + crid: '1060_72_6760217', + }, + ], + }, + ], + cur: 'USD', + } + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '1F254428-AB11-4D5E-9887-567B3F952CA5', + cpm: 3.569, + currency: 'USD', + width: 300, + height: 250, + creativeId: '1060_72_6760217', + dealId: 'f70362ac-f3cf-4225-82a5-948b690927a6', + netRevenue: true, + ttl: 360, + ad: '', + meta: { + advertiserDomains: ['test.com'], + }, + }, + ] + + let bidderRequest = { + id: 123456, + bids: [ + { + params: { + placementId: 1 + } + }, + ], + } + + // mock + spec.getRequestId = () => 123456 + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(Object.keys(result[0])).to.have.deep.members( + Object.keys(expectedResponse[0]) + ) + }) + + it('handles nobid responses', function () { + let response = {} + let bidderRequest + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(result.length).to.equal(0) + }) +}) + +describe('getUserSyncs', function () { + const response = [ + { + body: { + cur: 'USD', + id: 'a136dbd8-4387-48bf-b8e4-ff9c1d6056ee', + seatbid: [ + { + bid: [{}], + seat: 'seat_0', + syncUrls: [ + { + type: 'image', + url: 'pixel-tracker-test-url/?{GDPR_params}', + }, + { + type: 'iframe', + url: 'iframe-tracker-test-url/?{GDPR_params}', + }, + ], + }, + ], + }, + }, + ] + + const gdprConsent = { + gdprApplies: true, + consentString: '111111' + } + + const uspConsent = { + uspConsent: '1YYY' + } + + it('Returns empty array if no supported user syncs', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(0) + }) + + it('Returns valid iframe user sync', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: true, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + 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('iframe') + expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + }) + + it('Returns valid URL and type', function () { + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: true, + }, + response, + gdprConsent, + uspConsent + ) + 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.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + }) +}) From 1b28481afff549fbe11e08b138ae58644ffe5d6d Mon Sep 17 00:00:00 2001 From: Nick Peceniak Date: Fri, 16 Apr 2021 09:58:50 -0600 Subject: [PATCH 53/57] Spotx Bid Adapter: Update endpoint to indicate request is from Prebid (#6593) --- modules/spotxBidAdapter.js | 3 ++- test/spec/modules/spotxBidAdapter_spec.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index f3728058d18..b60d25db4d6 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -291,9 +291,10 @@ export const spec = { if (!utils.isEmpty(userExt)) { requestPayload.user = { ext: userExt }; } + const urlQueryParams = 'src_sys=prebid' return { method: 'POST', - url: URL + channelId, + url: URL + channelId + '?' + urlQueryParams, data: requestPayload, bidRequest: bidderRequest }; diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index cc94e37fdaa..5d7b32eaeeb 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -102,7 +102,7 @@ describe('the spotx adapter', function () { it('should build a very basic request', function() { var request = spec.buildRequests([bid], bidRequestObj)[0]; expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://search.spotxchange.com/openrtb/2.3/dados/12345'); + expect(request.url).to.equal('https://search.spotxchange.com/openrtb/2.3/dados/12345?src_sys=prebid'); expect(request.bidRequest).to.equal(bidRequestObj); expect(request.data.id).to.equal(12345); expect(request.data.ext.wrap_response).to.equal(1); From dd64734e46b5f9907cd14a8e2b287ac1cfe12aad Mon Sep 17 00:00:00 2001 From: Mathieu Pheulpin Date: Fri, 16 Apr 2021 13:16:53 -0700 Subject: [PATCH 54/57] Sharethrough Bid Adapter: add support for COPPA (#6602) * Pass COPPA flag to Ad Server [#177598971] * Send true instead of 1 * Upgrade adapter version number --- modules/sharethroughBidAdapter.js | 7 +++++- .../modules/sharethroughBidAdapter_spec.js | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 24be8673615..eef18288b17 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,7 +1,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; -const VERSION = '3.3.1'; +const VERSION = '3.3.2'; const BIDDER_CODE = 'sharethrough'; const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; const DEFAULT_SIZE = [1, 1]; @@ -48,6 +49,10 @@ export const sharethroughAdapterSpec = { query.us_privacy = bidderRequest.uspConsent } + if (config.getConfig('coppa') === true) { + query.coppa = true + } + if (bidRequest.schain) { query.schain = JSON.stringify(bidRequest.schain); } diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index b3451a09dde..57306580ecc 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -2,6 +2,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'; +import { config } from 'src/config'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); const bidRequests = [ @@ -441,6 +442,29 @@ describe('sharethrough adapter spec', function() { const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data).to.not.include.any.keys('bidfloor'); }); + + describe('coppa', function() { + it('should add coppa to request if enabled', function() { + config.setConfig({coppa: true}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.eq(true); + }); + + it('should not add coppa to request if disabled', function() { + config.setConfig({coppa: false}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.be.undefined; + }); + + it('should not add coppa to request if unknown value', function() { + config.setConfig({coppa: 'something'}); + const bidRequest = Object.assign({}, bidRequests[0]); + const builtBidRequest = spec.buildRequests([bidRequest])[0]; + expect(builtBidRequest.data.coppa).to.be.undefined; + }); + }); }); describe('.interpretResponse', function() { From ef00f9b681b7b2f4d507fd6e359d01a726facbbc Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Fri, 16 Apr 2021 22:27:47 +0200 Subject: [PATCH 55/57] tappx Bid Adapter: add video instream support and update testing (#6580) * tappxBidAdapter : update tests adding video * tappxBidAdapter : add video instream * tappxBidAdapter : update tappx md doc * tappxBidAdapter: Fix Newline required eol-last * tappxBidAdapter: update tests User sync and video reqs * tappxBidAdapter: Extra space after fix Co-authored-by: marc_tappx --- modules/tappxBidAdapter.js | 81 +++++++++++++---- modules/tappxBidAdapter.md | 33 +++++++ test/spec/modules/tappxBidAdapter_spec.js | 102 ++++++++++++++++++++-- 3 files changed, 195 insertions(+), 21 deletions(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 3677c3ce4c9..1228fbafaad 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -2,20 +2,22 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'tappx'; const TTL = 360; const CUR = 'USD'; -const TAPPX_BIDDER_VERSION = '0.1.10329'; +const TAPPX_BIDDER_VERSION = '0.1.10413'; const TYPE_CNN = 'prebidjs'; +const VIDEO_SUPPORT = ['instream']; + var HOST; var hostDomain; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -24,11 +26,7 @@ export const spec = { * @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; + return validBasic(bid) && validMediaType(bid) }, /** @@ -63,7 +61,7 @@ export const spec = { const bids = []; responseBody.seatbid.forEach(serverSeatBid => { serverSeatBid.bid.forEach(serverBid => { - bids.push(interpretBannerBid(serverBid, originalRequest)); + bids.push(interpretBid(serverBid, originalRequest)); }); }); @@ -106,25 +104,66 @@ export const spec = { } } +function validBasic(bid) { + if ( + (bid.params == null) || + (bid.params.endpoint == null) || + (bid.params.tappxkey == null)) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters.`); + return false; + } + return true; +} + +function validMediaType(bid) { + const video = utils.deepAccess(bid, 'mediaTypes.video'); + + // Video validations + if (typeof video != 'undefined') { + if (VIDEO_SUPPORT.indexOf(video.context) === -1) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters for Video. Only "instream" is suported.`); + return false; + } + } + + return true; +} + /** * 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 { +function interpretBid(serverBid, request) { + let bidReturned = { 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, } + + if (typeof serverBid.dealId != 'undefined') { bidReturned.dealId = serverBid.dealId } + + if (typeof request.bids.mediaTypes != 'undefined' && typeof request.bids.mediaTypes.video != 'undefined') { + bidReturned.vastXml = serverBid.adm; + bidReturned.vastUrl = serverBid.lurl; + bidReturned.ad = serverBid.adm; + bidReturned.mediaType = VIDEO; + } else { + bidReturned.ad = serverBid.adm; + bidReturned.mediaType = BANNER; + } + + if (typeof bidReturned.adomain != 'undefined' || bidReturned.adomain != null) { + bidReturned.meta = { advertiserDomains: request.bids.adomain }; + } + + return bidReturned; } /** @@ -136,14 +175,14 @@ function interpretBannerBid(serverBid, request) { */ function buildOneRequest(validBidRequests, bidderRequest) { HOST = utils.deepAccess(validBidRequests, 'params.host'); - let hostInfo = getHostInfo(HOST) - // hostDomain = HOST.split('/', 1)[0]; + let hostInfo = getHostInfo(HOST); hostDomain = hostInfo.domain; 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 videoMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.video'); const { refererInfo } = bidderRequest; // let requests = []; @@ -205,6 +244,18 @@ function buildOneRequest(validBidRequests, bidderRequest) { imp.banner = banner; } + if (videoMediaType) { + let video = {}; + w = videoMediaType.playerSize[0][0]; + h = videoMediaType.playerSize[0][1]; + video.w = w; + video.h = h; + + video.mimes = videoMediaType.mimes; + + imp.video = video; + } + imp.id = validBidRequests.bidId; imp.tagid = tagid; imp.secure = 1; diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md index d9ffd98b6c5..e6581a67d06 100644 --- a/modules/tappxBidAdapter.md +++ b/modules/tappxBidAdapter.md @@ -7,6 +7,7 @@ Maintainer: prebid@tappx.com # Description Module that connects to :tappx demand sources. +Suppots Banner and Instream Video. Please use ```tappx``` as the bidder code. Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] @@ -35,3 +36,35 @@ Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] } ]; ``` + + +# Video Test Parameters +``` + var adUnits = [ + { + code: 'video-ad-div', + renderer: { + options: { + text: "Tappx instream Video" + } + }, + mediaTypes: { + video: { + context: "instream", + mimes : [ "video/mp4", "application/javascript" ], + playerSize: [320, 250] + } + }, + bids: [{ + bidder: 'tappx', + params: { + host: "testing.ssp.tappx.com/rtb/v2/", + tappxkey: "pub-1234-desktop-1234", + endpoint: "VZ12TESTCTV", + bidfloor: 0.005, + test: true + } + }] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index 1d3f9676d09..378b391a4eb 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -53,7 +53,7 @@ const c_BIDREQUEST = { } ] }; -const c_SERVERRESPONSE = { +const c_SERVERRESPONSE_B = { body: { id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', bidid: 'bid3811165568213389257', @@ -82,10 +82,40 @@ const c_SERVERRESPONSE = { }, headers: {} }; + +const c_SERVERRESPONSE_V = { + body: { + id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + bidid: 'bid3811165568213389257', + seatbid: [ + { + seat: '1', + group: 0, + bid: [ + { + id: '3811165568213389257', + impid: 1, + price: 0.05, + adm: "Tappx<\/AdSystem>Tappx<\/AdTitle><\/Impression><\/Error>00:00:22<\/Duration><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/Tracking><\/TrackingEvents><\/ClickThrough><\/ClickTracking><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + 'lurl': 'https:\/\/ssp.api.tappx.com\/rtb\/RTBv2Loss?id=5001829913749291152&ep=VZ12TESTCTV&au=test&bu=localhost&sz=6x6&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: {} +}; + const c_CONSENTSTRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; const c_VALIDBIDREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000x179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6eFJ7otPYix179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6e', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_rEXbz6UYtYEJelYrDaZOLkh8WcF9J0ZHmEHFKZEBlLXsgP6xqXU3BCj4Ay0Z6fw_jSOaHxMHwd-voRHqFA4Q9NwAxFcVLyPWnNGZ9VbcSAPos1wupq7Xu3MIm-Bw_0vxjhZdWNy4chM9x3i', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': 'xTtLUY7GwqX2MMqSHo9RQ2YUOIBFhlASOR43I9KjvgtcrxIys3RxME96M02LTjWR', 'parrableId': {'eid': '02.YoqC9lWZh8.C8QTSiJTNgI6Pp0KCM5zZgEgwVMSsVP5W51X8cmiUHQESq9WRKB4nreqZJwsWIcNKlORhG4u25Wm6lmDOBmQ0B8hv0KP6uVQ97aouuH52zaz2ctVQTORUKkErPRPcaCJ7dKFcrNoF2i6WOR0S5Nk'}, 'pubcid': 'b1254-152f-12F5-5698-dI1eljK6C7WA', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; const c_VALIDBIDAPPREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1, 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}, {'source': 'intentiq.com', 'uids': [{'id': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'atype': 1}]}, {'source': 'crwdcntrl.net', 'uids': [{'id': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'atype': 1}]}, {'source': 'parrable.com', 'uids': [{'id': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0', 'atype': 1}]}, {'source': 'pubcid.org', 'uids': [{'id': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'atype': 1}]}, {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; -const c_BIDDERREQUEST = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'referer': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_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}; +const c_BIDDERREQUEST_B = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'referer': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_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}; +const c_BIDDERREQUEST_V = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"e807363f-3095-43a8-a4a6-f44196cb7318","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"w":320,"h":250,"mimes":["video/mp4","application/javascript"]},"id":"28f49c71b13f2f","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"VZ12TESTCTV","host":"testing.ssp.tappx.com/rtb/v2/"}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'context': 'instream', 'mimes': ['video/mp4', 'application/javascript'], 'playerSize': [[320, 250]]}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} describe('Tappx bid adapter', function () { /** @@ -102,6 +132,16 @@ describe('Tappx bid adapter', function () { delete badBidRequest.bids[0].params.endpoint; assert.isFalse(spec.isBidRequestValid(badBidRequest.bids[0])); }); + + it('should return false for not instream requests', function () { + let badBidRequest_v = c_BIDDERREQUEST_V; + delete badBidRequest_v.bids.mediaTypes.banner; + badBidRequest_v.bids.mediaTypes.video = {}; + badBidRequest_v.bids.mediaTypes.video.context = 'outstream'; + badBidRequest_v.bids.mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; + badBidRequest_v.bids.mediaTypes.video.playerSize = [320, 250]; + assert.isFalse(spec.isBidRequestValid(badBidRequest_v.bids)); + }); }); /** @@ -110,10 +150,12 @@ describe('Tappx bid adapter', function () { describe('buildRequest', function () { // Web Test let validBidRequests = c_VALIDBIDREQUESTS; + let validBidRequests_V = c_VALIDBIDREQUESTS; // App Test let validAppBidRequests = c_VALIDBIDAPPREQUESTS; - let bidderRequest = c_BIDDERREQUEST; + let bidderRequest = c_BIDDERREQUEST_B; + let bidderRequest_V = c_BIDDERREQUEST_V; it('should add gdpr/usp consent information to the request', function () { const request = spec.buildRequests(validBidRequests, bidderRequest); @@ -138,6 +180,20 @@ describe('Tappx bid adapter', function () { expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); }); + it('should properly build a video request', function () { + const request = spec.buildRequests(validBidRequests_V, bidderRequest_V); + 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 set user eids array', function () { const request = spec.buildRequests(validBidRequests, bidderRequest); @@ -165,17 +221,51 @@ describe('Tappx bid adapter', function () { * INTERPRET RESPONSE TESTS */ describe('interpretResponse', function () { - it('receive reponse with single placement', function () { - const bids = spec.interpretResponse(c_SERVERRESPONSE, c_BIDREQUEST); + it('receive banner reponse with single placement', function () { + const bids = spec.interpretResponse(c_SERVERRESPONSE_B, c_BIDDERREQUEST_B); const bid = bids[0]; expect(bid.cpm).to.exist; expect(bid.ad).to.match(/^