From 876172b19af70ff4f8c96b7c555a94a958d86076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20=C5=A0t=C5=A1epelin?= Date: Tue, 26 Apr 2022 15:43:05 +0300 Subject: [PATCH 01/44] domain change (#8318) --- modules/cointrafficBidAdapter.js | 2 +- test/spec/modules/cointrafficBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index e3d3c65a4f0..f61d58664ca 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.js' const BIDDER_CODE = 'cointraffic'; -const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; +const ENDPOINT_URL = 'https://apps-pbd.ctengine.io/pb/tmp'; const DEFAULT_CURRENCY = 'EUR'; const ALLOWED_CURRENCIES = [ 'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', 'RUB', 'TRY', diff --git a/test/spec/modules/cointrafficBidAdapter_spec.js b/test/spec/modules/cointrafficBidAdapter_spec.js index 3755ddc4c4a..24570de5083 100644 --- a/test/spec/modules/cointrafficBidAdapter_spec.js +++ b/test/spec/modules/cointrafficBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec } from 'modules/cointrafficBidAdapter.js'; import { config } from 'src/config.js' import * as utils from 'src/utils.js' -const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; +const ENDPOINT_URL = 'https://apps-pbd.ctengine.io/pb/tmp'; describe('cointrafficBidAdapter', function () { describe('isBidRequestValid', function () { From 801c2d758ca3844ca4cf0504fe57f6366cb795c0 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Tue, 26 Apr 2022 17:26:46 +0200 Subject: [PATCH 02/44] nexx360 Bid Adapter : gvlid added and params updated with bidfloor and keywords support (#8285) * video added to nexx360 * gvlid added and params updated with bidfloor and keywords support * tests added to isBidRequestValid --- modules/nexx360BidAdapter.js | 18 ++++++-- test/spec/modules/nexx360BidAdapter_spec.js | 49 +++++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 0689d3b04ce..814a2f55299 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -1,5 +1,6 @@ import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; +import { transformBidderParamKeywords } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -8,8 +9,11 @@ const BIDDER_URL = 'https://fast.nexx360.io/prebid'; const CACHE_URL = 'https://fast.nexx360.io/cache'; const METRICS_TRACKER_URL = 'https://fast.nexx360.io/track-imp'; +const GVLID = 965; + export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: ['revenuemaker'], // short code supportedMediaTypes: [BANNER, VIDEO], /** @@ -19,6 +23,9 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { + if (!!bid.params.bidfloorCurrency && !['EUR', 'USD'].includes(bid.params.bidfloorCurrency)) return false; + if (!!bid.params.bidfloor && typeof bid.params.bidfloor !== 'number') return false; + if (!!bid.params.keywords && typeof bid.params.keywords !== 'object') return false; return !!(bid.params.account && bid.params.tagId); }, /** @@ -34,7 +41,7 @@ export const spec = { let userEids = null; Object.keys(validBidRequests).forEach(key => { adunitValue = validBidRequests[key]; - adUnits.push({ + const foo = { account: adunitValue.params.account, tagId: adunitValue.params.tagId, videoExt: adunitValue.params.videoExt, @@ -42,8 +49,12 @@ export const spec = { bidId: adunitValue.bidId, auctionId: adunitValue.auctionId, transactionId: adunitValue.transactionId, - mediatypes: adunitValue.mediaTypes - }); + mediatypes: adunitValue.mediaTypes, + bidfloor: adunitValue.params.bidfloor || 0, + bidfloorCurrency: adunitValue.params.bidfloorCurrency || 'USD', + keywords: adunitValue.params.keywords ? transformBidderParamKeywords(adunitValue.params.keywords) : [], + } + adUnits.push(foo); if (adunitValue.userIdAsEids) userEids = adunitValue.userIdAsEids; }); const payload = { @@ -78,7 +89,6 @@ export const spec = { */ interpretResponse: function(serverResponse, bidRequest) { const serverBody = serverResponse.body; - // const headerValue = serverResponse.headers.get('some-response-header'); const bidResponses = []; let bidResponse = null; let value = null; diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 3d5b2554cda..1a695e5f8b3 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -146,6 +146,37 @@ describe('Nexx360 bid adapter tests', function () { }] }, }; + + it('We verify isBidRequestValid with uncorrect bidfloorCurrency', function() { + const bid = { params: { + 'account': '1067', + 'tagId': 'luvxjvgn', + 'bidfloorCurrency': 'AAA', + }}; + expect(spec.isBidRequestValid(bid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with uncorrect bidfloor', function() { + const bid = { params: { + 'account': '1067', + 'tagId': 'luvxjvgn', + 'bidfloorCurrency': 'EUR', + 'bidfloor': 'EUR', + }}; + expect(spec.isBidRequestValid(bid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with uncorrect keywords', function() { + const bid = { params: { + 'account': '1067', + 'tagId': 'luvxjvgn', + 'bidfloorCurrency': 'EUR', + 'bidfloor': 0.8, + 'keywords': 'test', + }}; + expect(spec.isBidRequestValid(bid)).to.be.equal(false); + }); + it('Verify banner build request', function () { const request = spec.buildRequests(DISPLAY_BID_REQUEST, DEFAULT_OPTIONS); expect(request).to.have.property('url').and.to.equal('https://fast.nexx360.io/prebid'); @@ -162,6 +193,24 @@ describe('Nexx360 bid adapter tests', function () { expect(requestContent.adUnits[0]).to.have.property('auctionId').and.to.equal('05e0a3a1-9f57-41f6-bbcb-2ba9c9e3d2d5'); expect(requestContent.adUnits[0]).to.have.property('mediatypes').exist; expect(requestContent.adUnits[0].mediatypes).to.have.property('banner').exist; + expect(requestContent.adUnits[0]).to.have.property('bidfloor').and.to.equal(0); + expect(requestContent.adUnits[0]).to.have.property('bidfloorCurrency').and.to.equal('USD'); + expect(requestContent.adUnits[0]).to.have.property('keywords'); + expect(requestContent.adUnits[0].keywords.length).to.be.eql(0); + }); + + it('We add bidfloor and keywords', function() { + const DISPLAY_BID_REQUEST_2 = [ ...DISPLAY_BID_REQUEST ]; + DISPLAY_BID_REQUEST_2[0].params.keywords = { interest: 'cars' }; + DISPLAY_BID_REQUEST_2[0].params.bidfloor = 2.1; + const request = spec.buildRequests(DISPLAY_BID_REQUEST, DEFAULT_OPTIONS); + const requestContent = JSON.parse(request.data); + expect(requestContent.adUnits[0]).to.have.property('bidfloor').and.to.equal(2.1); + expect(requestContent.adUnits[0]).to.have.property('bidfloorCurrency').and.to.equal('USD'); + expect(requestContent.adUnits[0]).to.have.property('keywords'); + expect(requestContent.adUnits[0].keywords.length).to.be.eql(1); + expect(requestContent.adUnits[0].keywords[0].key).to.be.eql('interest'); + expect(requestContent.adUnits[0].keywords[0].value[0]).to.be.eql('cars'); }); it('Verify banner parse response', function () { From d953eaac97b3e889e162a0e4bf5bb90f721c2c91 Mon Sep 17 00:00:00 2001 From: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Date: Wed, 27 Apr 2022 01:37:20 +0300 Subject: [PATCH 03/44] Evolution Bid Adapter: add id5id (#8324) * updates for Prebid v5 * add id5id * update tests --- modules/e_volutionBidAdapter.js | 20 ++++- .../spec/modules/e_volutionBidAdapter_spec.js | 77 ++++++++++++++++--- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 884c4f0c067..63332db8725 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -41,6 +41,19 @@ function getBidFloor(bid) { } } +function getUserId(eids, id, source, uidExt) { + if (id) { + var uid = { id }; + if (uidExt) { + uid.ext = uidExt; + } + eids.push({ + source, + uids: [ uid ] + }); + } +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -86,7 +99,12 @@ export const spec = { const placement = { placementId: bid.params.placementId, bidId: bid.bidId, - bidfloor: getBidFloor(bid) + bidfloor: getBidFloor(bid), + eids: [] + } + + if (bid.userId) { + getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); } if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js index 1f60edda0ef..d488048060a 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -2,7 +2,7 @@ import {expect} from 'chai'; import {spec} from '../../../modules/e_volutionBidAdapter.js'; describe('EvolutionTechBidAdapter', function () { - let bid = { + let bids = [{ bidId: '23fhj33i987f', bidder: 'e_volution', params: { @@ -12,21 +12,55 @@ describe('EvolutionTechBidAdapter', function () { banner: { sizes: [[300, 250]], } + }, + userId: { + id5id: 'id5id' + } + }, { + bidId: '23fhj33i987f', + bidder: 'e_volution', + params: { + placementId: 0 + }, + mediaTypes: { + video: { + playerSize: [300, 250] + } + }, + userId: { + id5id: 'id5id' + } + }, { + bidId: '23fhj33i987f', + bidder: 'e_volution', + params: { + placementId: 0 + }, + mediaTypes: { + native: {} + }, + userId: { + id5id: 'id5id' } + }]; + + const bidderRequest = { + uspConsent: 'uspConsent', + gdprConsent: 'gdprConsent' }; describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and placementId parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + delete bids[0].params.placementId; + expect(spec.isBidRequestValid(bids[0])).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); + let serverRequest = spec.buildRequests(bids, bidderRequest); it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -42,18 +76,35 @@ describe('EvolutionTechBidAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); + expect(data.ccpa).to.be.equal('uspConsent'); + expect(data.gdpr).to.be.equal('gdprConsent'); + let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'bidfloor'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'bidfloor', 'eids'); expect(placement.placementId).to.equal(0); expect(placement.bidId).to.equal('23fhj33i987f'); expect(placement.traffic).to.equal('banner'); + + placement = data['placements'][1]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'bidfloor', 'eids', 'wPlayer', 'hPlayer', + 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', + 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal('video'); + + placement = data['placements'][2]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'bidfloor', 'eids', 'native'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal('native'); }); it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); @@ -76,7 +127,9 @@ describe('EvolutionTechBidAdapter', function () { netRevenue: true, currency: 'USD', dealId: '1', - meta: {} + meta: { + adomain: [ 'example.com' ] + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -106,7 +159,9 @@ describe('EvolutionTechBidAdapter', function () { netRevenue: true, currency: 'USD', dealId: '1', - meta: {} + meta: { + adomain: [ 'example.com' ] + } }] }; let videoResponses = spec.interpretResponse(video); @@ -139,7 +194,9 @@ describe('EvolutionTechBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - meta: {} + meta: { + adomain: [ 'example.com' ] + } }] }; let nativeResponses = spec.interpretResponse(native); From 5504cda3b122d1036640a4fc2444c9410d95e25f Mon Sep 17 00:00:00 2001 From: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> Date: Thu, 28 Apr 2022 16:58:24 +0300 Subject: [PATCH 04/44] MobfoxPB Adapter: update adapter to comply with Prebid 5 changes (#8297) * initial * use util for win top * use util for win top * updates for prebid v5 * update import Co-authored-by: Aiholkin --- modules/mobfoxpbBidAdapter.js | 135 ++++++++ modules/mobfoxpbBidAdapter.md | 6 +- test/spec/modules/mobfoxpbBidAdapter_spec.js | 307 +++++++++++++++++++ 3 files changed, 445 insertions(+), 3 deletions(-) create mode 100644 modules/mobfoxpbBidAdapter.js create mode 100644 test/spec/modules/mobfoxpbBidAdapter_spec.js diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js new file mode 100644 index 00000000000..a4af7133370 --- /dev/null +++ b/modules/mobfoxpbBidAdapter.js @@ -0,0 +1,135 @@ +import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'mobfoxpb'; +const AD_URL = 'https://bes.mobfox.com/pbjs'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && bid.params.placementId); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + const winTop = getWindowTop(); + const location = winTop.location; + const placements = []; + const request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + bidfloor: getBidFloor(bid) + }; + const mediaType = bid.mediaTypes + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.traffic = BANNER; + placement.sizes = mediaType[BANNER].sizes; + } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { + placement.traffic = VIDEO; + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + placement.playerSize = mediaType[VIDEO].playerSize; + placement.minduration = mediaType[VIDEO].minduration; + placement.maxduration = mediaType[VIDEO].maxduration; + placement.mimes = mediaType[VIDEO].mimes; + placement.protocols = mediaType[VIDEO].protocols; + placement.startdelay = mediaType[VIDEO].startdelay; + placement.placement = mediaType[VIDEO].placement; + placement.skip = mediaType[VIDEO].skip; + placement.skipafter = mediaType[VIDEO].skipafter; + placement.minbitrate = mediaType[VIDEO].minbitrate; + placement.maxbitrate = mediaType[VIDEO].maxbitrate; + placement.delivery = mediaType[VIDEO].delivery; + placement.playbackmethod = mediaType[VIDEO].playbackmethod; + placement.api = mediaType[VIDEO].api; + placement.linearity = mediaType[VIDEO].linearity; + } else if (mediaType && mediaType[NATIVE]) { + placement.traffic = NATIVE; + placement.native = mediaType[NATIVE]; + } + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + resItem.meta = resItem.meta || {}; + resItem.meta.advertiserDomains = resItem.adomain || []; + + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/mobfoxpbBidAdapter.md b/modules/mobfoxpbBidAdapter.md index 6eb549919d7..f434b2792a9 100644 --- a/modules/mobfoxpbBidAdapter.md +++ b/modules/mobfoxpbBidAdapter.md @@ -24,7 +24,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testBanner' } } ] @@ -41,7 +41,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testVideo' } } ] @@ -63,7 +63,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testNative' } } ] diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js new file mode 100644 index 00000000000..766f8d1a848 --- /dev/null +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -0,0 +1,307 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/mobfoxpbBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; + +describe('MobfoxHBBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'mobfoxpb', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 783, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://bes.mobfox.com/pbjs'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain', 'bidfloor'); + expect(placement.placementId).to.equal(783); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + expect(placement.bidfloor).to.equal(0); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'playerSize', 'wPlayer', 'hPlayer', 'schain', 'bidfloor', + 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', + 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns valid data for mediatype native', function () { + const native = { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + }; + + bid.mediaTypes = {}; + bid.params.traffic = NATIVE; + bid.mediaTypes[NATIVE] = native; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain', 'bidfloor'); + expect(placement.traffic).to.equal(NATIVE); + expect(placement.native).to.equal(native); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From e1f3239bf5315411e1b023a46a624cdfaed23f75 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 28 Apr 2022 18:17:29 +0000 Subject: [PATCH 05/44] Prebid 6.22.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0abb6fd3da8..c83f06c0a39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.22.0-pre", + "version": "6.22.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6ba4acd692d..bb8bac6187c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.22.0-pre", + "version": "6.22.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 061eb3c9c295d1e9bb9af62e1ba2370781f0ad17 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 28 Apr 2022 18:17:29 +0000 Subject: [PATCH 06/44] Increment version to 6.23.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c83f06c0a39..c4e86036f99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.22.0", + "version": "6.23.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index bb8bac6187c..9e5d68ac5e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.22.0", + "version": "6.23.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From d90b454fbed6cdf5444573c32e654a67e448ce43 Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Fri, 29 Apr 2022 18:08:17 +0200 Subject: [PATCH 07/44] tmax & addtl consent improvements (#8346) --- modules/improvedigitalBidAdapter.js | 16 ++++++---- .../modules/improvedigitalBidAdapter_spec.js | 32 ++++++++++++++++--- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index c696dabc64a..d1c35f63f9a 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -117,16 +117,20 @@ export const spec = { if (additionalConsent && additionalConsent.indexOf('~') !== -1) { // Google Ad Tech Provider IDs const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1); - deepSetValue( - request, - 'user.ext.consented_providers_settings.consented_providers', - atpIds.split('.').map(id => parseInt(id, 10)) - ); + if (atpIds) { + deepSetValue( + request, + 'user.ext.consented_providers_settings.consented_providers', + atpIds.split('.').map(id => parseInt(id, 10)) + ); + } } } // Timeout - deepSetValue(request, 'tmax', bidderRequest.timeout); + if (bidderRequest.timeout) { + request.tmax = parseInt(bidderRequest.timeout); + } // US Privacy if (typeof bidderRequest.uspConsent !== typeof undefined) { deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 9dcc11f5aa1..d721c2d20bc 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/improvedigitalBidAdapter.js'; import { config } from 'src/config.js'; -import * as utils from 'src/utils.js'; +import { deepClone } from 'src/utils.js'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; describe('Improve Digital Adapter Tests', function () { @@ -34,7 +34,7 @@ describe('Improve Digital Adapter Tests', function () { skipafter: 30 } - const instreamBidRequest = utils.deepClone(simpleBidRequest); + const instreamBidRequest = deepClone(simpleBidRequest); instreamBidRequest.mediaTypes = { video: { context: 'instream', @@ -42,7 +42,7 @@ describe('Improve Digital Adapter Tests', function () { } }; - const outstreamBidRequest = utils.deepClone(simpleBidRequest); + const outstreamBidRequest = deepClone(simpleBidRequest); outstreamBidRequest.mediaTypes = { video: { context: 'outstream', @@ -50,7 +50,7 @@ describe('Improve Digital Adapter Tests', function () { } }; - const multiFormatBidRequest = utils.deepClone(simpleBidRequest); + const multiFormatBidRequest = deepClone(simpleBidRequest); multiFormatBidRequest.mediaTypes = { banner: { sizes: [[300, 250], [160, 600]] @@ -294,6 +294,14 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]); }); + it('should not add consented providers when empty', function () { + const bidderRequestGdprEmptyAddtl = deepClone(bidderRequestGdpr); + bidderRequestGdprEmptyAddtl.gdprConsent.addtlConsent = '1~'; + const bidRequest = Object.assign({}, simpleBidRequest); + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdprEmptyAddtl)[0].data); + expect(payload.user.ext.consented_providers_settings).to.not.exist; + }); + it('should add CCPA consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); const request = spec.buildRequests([bidRequest], {...bidderRequest, ...{ uspConsent: '1YYY' }}); @@ -308,6 +316,20 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.site.page).to.equal('https://blah.com/test.html'); }); + it('should add timeout', function () { + const bidderRequestTimeout = deepClone(bidderRequest); + // Int + bidderRequestTimeout.timeout = 300; + const bidRequest = Object.assign({}, simpleBidRequest); + let request = spec.buildRequests([bidRequest], bidderRequestTimeout)[0]; + expect(JSON.parse(request.data).tmax).to.equal(300); + + // String + bidderRequestTimeout.timeout = '500'; + request = spec.buildRequests([bidRequest], bidderRequestTimeout)[0]; + expect(JSON.parse(request.data).tmax).to.equal(500); + }); + it('should not add video params for banner', function () { const bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.params.video = videoParams; @@ -916,7 +938,7 @@ describe('Improve Digital Adapter Tests', function () { } ]; - const expectedBidOutstreamVideo = utils.deepClone(expectedBidInstreamVideo); + const expectedBidOutstreamVideo = deepClone(expectedBidInstreamVideo); expectedBidOutstreamVideo[0].adResponse = { content: expectedBidOutstreamVideo[0].vastXml }; From b72829893604c98e8b58786d9f573d1fb33481cc Mon Sep 17 00:00:00 2001 From: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Date: Sat, 30 Apr 2022 00:10:48 +0800 Subject: [PATCH 08/44] Jixie Bid adapter: support more userids (#8314) * Adapter does not seem capable of supporting advertiserDomains #6650 added response comment and some trivial code. * removed a blank line at the end of file added a space behind the // in comments * in response to comment from reviewer. add the aspect of advertiserdomain in unit tests * added the code to get the keywords from the meta tags if available. * adding jixie adaptor to send tid and uid2 and publisher ids * changed to send all the ids to our backend. added a little comment * send the config info of price granularity to our backend * corrected code formatting violations that failed some checks --- modules/jixieBidAdapter.js | 29 +++++++ modules/jixieBidAdapter.md | 1 + test/spec/modules/jixieBidAdapter_spec.js | 96 +++++++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index de509853fed..700d3276e06 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -6,6 +6,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { Renderer } from '../src/Renderer.js'; +import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'jixie'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -121,6 +122,17 @@ function getMiscDims_() { return ret; } +/* function addUserId(eids, id, source, rti) { + if (id) { + if (rti) { + eids.push({ source, id, rti_partner: rti }); + } else { + eids.push({ source, id }); + } + } + return eids; +} */ + // easier for replacement in the unit test export const internal = { getDevice: getDevice_, @@ -163,7 +175,22 @@ export const spec = { } let ids = fetchIds_(); + let eids = []; let miscDims = internal.getMiscDims(); + + // all available user ids are sent to our backend in the standard array layout: + if (validBidRequests[0].userId) { + let eids1 = createEidsArray(validBidRequests[0].userId); + if (eids1.length) { + eids = eids1; + } + } + // we want to send this blob of info to our backend: + let pg = config.getConfig('priceGranularity'); + if (!pg) { + pg = {}; + } + let transformedParams = Object.assign({}, { auctionid: bidderRequest.auctionId, timeout: bidderRequest.timeout, @@ -174,6 +201,8 @@ export const spec = { pageurl: miscDims.pageurl, mkeywords: miscDims.mkeywords, bids: bids, + eids: eids, + pricegranularity: pg, cfg: jixieCfgBlob }, ids); return Object.assign({}, { diff --git a/modules/jixieBidAdapter.md b/modules/jixieBidAdapter.md index d9c1f19541d..c0a1a965e87 100644 --- a/modules/jixieBidAdapter.md +++ b/modules/jixieBidAdapter.md @@ -7,6 +7,7 @@ Maintainer: contact@jixie.io # Description Module that connects to Jixie demand source to fetch bids. +All prebid-supported user ids are sent to Jixie endpoint, if available. # Test Parameters ``` diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index 68de5c7a8fd..7af0372c22c 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -240,6 +240,102 @@ describe('jixie Adapter', function () { getLocalStorageStub.restore(); miscDimsStub.restore(); });// it + + it('it should popular the pricegranularity when info is available', function () { + let content = { + 'ranges': [{ + 'max': 12, + 'increment': 0.5 + }, + { + 'max': 5, + 'increment': 0.1 + }], + precision: 1 + }; + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.callsFake(function fakeFn(prop) { + if (prop == 'priceGranularity') { + return content; + } + return null; + }); + + const oneSpecialBidReq = Object.assign({}, bidRequests_[0]); + const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + const payload = JSON.parse(request.data); + getConfigStub.restore(); + expect(payload.pricegranularity).to.deep.include(content); + }); + + it('should populate eids when supported userIds are available', function () { + const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { + userId: { + tdid: '11111111-2222-3333-4444-555555555555', + uid2: { id: 'AbCdEfGhIjKlMnO9qdQBW7qtMw8f1WTUvtkHe6u+fqLfhbtsqrJ697Z6YoI3IB9klGUv1wvlFIbwH7ELDlqQBGtj8AC1v7fMJ/Q45E7W90dts7UQLTDMLNmtHBRDXVb0Fpas4Vh3yN1jGVQNhzXC/RpGIVtZE8dCxcjfa7VfcTNcvxxxxx==' }, + pubProvidedId: [{ + source: 'puburl1.com', + uids: [{ + id: 'pubid1', + atype: 1, + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'puburl2.com', + uids: [{ + id: 'pubid2' + }] + }] + } + }); + const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + const payload = JSON.parse(request.data); + expect(payload.eids).to.deep.include({ + 'source': 'adserver.org', + 'uids': [ + { + 'id': '11111111-2222-3333-4444-555555555555', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + }); + expect(payload.eids).to.deep.include({ + 'source': 'uidapi.com', + 'uids': [ + { + 'id': 'AbCdEfGhIjKlMnO9qdQBW7qtMw8f1WTUvtkHe6u+fqLfhbtsqrJ697Z6YoI3IB9klGUv1wvlFIbwH7ELDlqQBGtj8AC1v7fMJ/Q45E7W90dts7UQLTDMLNmtHBRDXVb0Fpas4Vh3yN1jGVQNhzXC/RpGIVtZE8dCxcjfa7VfcTNcvxxxxx==', + 'atype': 3 + } + ] + }); + + expect(payload.eids).to.deep.include({ + 'source': 'puburl1.com', + 'uids': [ + { + 'id': 'pubid1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + }); + + expect(payload.eids).to.deep.include({ + 'source': 'puburl2.com', + 'uids': [ + { + 'id': 'pubid2' + } + ] + }); + }); });// describe /** From 0095646a89562b57d037a3a0ad8b787c25b89156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Fri, 29 Apr 2022 18:28:03 +0200 Subject: [PATCH 09/44] Criteo : get rid of getLegacyFpd call (#8344) --- modules/criteoBidAdapter.js | 40 ++++++------- test/spec/modules/criteoBidAdapter_spec.js | 66 +++++++++++----------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 75d41d970a9..1ad693fcaab 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,11 +1,11 @@ -import {deepAccess, getUniqueIdentifierStr, isArray, logError, logInfo, logWarn, parseUrl} from '../src/utils.js'; -import {loadExternalScript} from '../src/adloader.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {find} from '../src/polyfill.js'; -import {verify} from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 -import {getStorageManager} from '../src/storageManager.js'; +import { deepAccess, getUniqueIdentifierStr, isArray, logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { find } from '../src/polyfill.js'; +import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 +import { getStorageManager } from '../src/storageManager.js'; const GVLID = 91; export const ADAPTER_VERSION = 34; @@ -13,7 +13,7 @@ const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; export const PROFILE_ID_PUBLISHERTAG = 185; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); const LOG_PREFIX = 'Criteo: '; /* @@ -35,7 +35,7 @@ const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDe export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [ BANNER, VIDEO, NATIVE ], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** f * @param {object} bid @@ -65,11 +65,11 @@ export const spec = { buildRequests: (bidRequests, bidderRequest) => { let url; let data; - let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + let fpd = config.getConfig('ortb2') || {}; Object.assign(bidderRequest, { - publisherExt: fpd.context, - userExt: fpd.user, + publisherExt: fpd.site?.ext, + userExt: fpd.user?.ext, ceh: config.getConfig('criteo.ceh') }); @@ -265,11 +265,11 @@ function checkNativeSendId(bidRequest) { return !(bidRequest.nativeParams && ( (bidRequest.nativeParams.image && ((bidRequest.nativeParams.image.sendId !== true || bidRequest.nativeParams.image.sendTargetingKeys === true))) || - (bidRequest.nativeParams.icon && ((bidRequest.nativeParams.icon.sendId !== true || bidRequest.nativeParams.icon.sendTargetingKeys === true))) || - (bidRequest.nativeParams.clickUrl && ((bidRequest.nativeParams.clickUrl.sendId !== true || bidRequest.nativeParams.clickUrl.sendTargetingKeys === true))) || - (bidRequest.nativeParams.displayUrl && ((bidRequest.nativeParams.displayUrl.sendId !== true || bidRequest.nativeParams.displayUrl.sendTargetingKeys === true))) || - (bidRequest.nativeParams.privacyLink && ((bidRequest.nativeParams.privacyLink.sendId !== true || bidRequest.nativeParams.privacyLink.sendTargetingKeys === true))) || - (bidRequest.nativeParams.privacyIcon && ((bidRequest.nativeParams.privacyIcon.sendId !== true || bidRequest.nativeParams.privacyIcon.sendTargetingKeys === true))) + (bidRequest.nativeParams.icon && ((bidRequest.nativeParams.icon.sendId !== true || bidRequest.nativeParams.icon.sendTargetingKeys === true))) || + (bidRequest.nativeParams.clickUrl && ((bidRequest.nativeParams.clickUrl.sendId !== true || bidRequest.nativeParams.clickUrl.sendTargetingKeys === true))) || + (bidRequest.nativeParams.displayUrl && ((bidRequest.nativeParams.displayUrl.sendId !== true || bidRequest.nativeParams.displayUrl.sendTargetingKeys === true))) || + (bidRequest.nativeParams.privacyLink && ((bidRequest.nativeParams.privacyLink.sendId !== true || bidRequest.nativeParams.privacyLink.sendTargetingKeys === true))) || + (bidRequest.nativeParams.privacyIcon && ((bidRequest.nativeParams.privacyIcon.sendId !== true || bidRequest.nativeParams.privacyIcon.sendTargetingKeys === true))) )); } @@ -285,7 +285,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { const request = { publisher: { url: context.url, - ext: bidderRequest.publisherExt + ext: bidderRequest.publisherExt, }, slots: bidRequests.map(bidRequest => { networkId = bidRequest.params.networkId || networkId; @@ -405,7 +405,7 @@ function hasValidVideoMediaType(bidRequest) { var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api', 'skip', 'placement', 'playbackmethod']; - requiredMediaTypesParams.forEach(function(param) { + requiredMediaTypesParams.forEach(function (param) { if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { isValid = false; logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 8793d9351d4..57276759a35 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -23,7 +23,7 @@ describe('The Criteo bidding adapter', function () { sandbox = sinon.sandbox.create(); }); - afterEach(function() { + afterEach(function () { global.Criteo = undefined; utilsMock.restore(); sandbox.restore(); @@ -457,7 +457,7 @@ describe('The Criteo bidding adapter', function () { params: { zoneId: 123, publisherSubId: '123', - nativeCallback: function() {}, + nativeCallback: function () { }, integrationMode: 'amp' }, }, @@ -521,7 +521,7 @@ describe('The Criteo bidding adapter', function () { } }, params: { - nativeCallback: function() {} + nativeCallback: function () { } }, }, ]; @@ -540,7 +540,7 @@ describe('The Criteo bidding adapter', function () { } }, params: { - nativeCallback: function() {} + nativeCallback: function () { } }, }, ]; @@ -852,7 +852,7 @@ describe('The Criteo bidding adapter', function () { sizes: [[300, 250]], mediaTypes: { video: { - playerSize: [ [300, 250] ], + playerSize: [[300, 250]], mimes: ['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg'], minduration: 1, maxduration: 30, @@ -970,7 +970,7 @@ describe('The Criteo bidding adapter', function () { }); it('should properly build a request with first party data', function () { - const contextData = { + const siteData = { keywords: ['power tools'], ext: { data: { @@ -1015,7 +1015,7 @@ describe('The Criteo bidding adapter', function () { sandbox.stub(config, 'getConfig').callsFake(key => { const config = { ortb2: { - site: contextData, + site: siteData, user: userData } }; @@ -1023,8 +1023,8 @@ describe('The Criteo bidding adapter', function () { }); const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.publisher.ext).to.deep.equal({keywords: ['power tools'], data: {pageType: 'article'}}); - expect(request.data.user.ext).to.deep.equal({gender: 'M', data: {registered: true}}); + expect(request.data.publisher.ext).to.deep.equal({ data: { pageType: 'article' } }); + expect(request.data.user.ext).to.deep.equal({ data: { registered: true } }); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { @@ -1171,13 +1171,13 @@ describe('The Criteo bidding adapter', function () { 'advertiser': { 'description': 'sponsor', 'domain': 'criteo.com', - 'logo': {'url': 'https://www.criteo.com/images/criteo-logo.svg', 'height': 300, 'width': 300} + 'logo': { 'url': 'https://www.criteo.com/images/criteo-logo.svg', 'height': 300, 'width': 300 } }, 'privacy': { 'optout_click_url': 'https://info.criteo.com/privacy/informations', 'optout_image_url': 'https://static.criteo.net/flash/icon/nai_small.png', }, - 'impression_pixels': [{'url': 'https://my-impression-pixel/test/impression'}, {'url': 'https://cas.com/lg.com'}] + 'impression_pixels': [{ 'url': 'https://my-impression-pixel/test/impression' }, { 'url': 'https://cas.com/lg.com' }] } }], }, @@ -1201,7 +1201,7 @@ describe('The Criteo bidding adapter', function () { }); it('should warn only once if sendTargetingKeys set to true on required fields for native bidRequest', () => { - const bidderRequest = { }; + const bidderRequest = {}; const bidRequests = [ { bidder: 'criteo', @@ -1211,7 +1211,7 @@ describe('The Criteo bidding adapter', function () { params: { zoneId: 123, publisherSubId: '123', - nativeCallback: function() {} + nativeCallback: function () { } }, }, { @@ -1222,7 +1222,7 @@ describe('The Criteo bidding adapter', function () { params: { zoneId: 456, publisherSubId: '456', - nativeCallback: function() {} + nativeCallback: function () { } }, }, ]; @@ -1276,7 +1276,7 @@ describe('The Criteo bidding adapter', function () { .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') .exactly(nativeParamsWithSendTargetingKeys.length * bidRequests.length); nativeParamsWithSendTargetingKeys.forEach(nativeParams => { - let transformedBidRequests = {...bidRequests}; + let transformedBidRequests = { ...bidRequests }; transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; spec.buildRequests(transformedBidRequests, bidderRequest); }); @@ -1440,7 +1440,7 @@ describe('The Criteo bidding adapter', function () { describe('when pubtag prebid adapter is not available', function () { it('should not warn if sendId is provided on required fields for native bidRequest', () => { - const bidderRequest = { }; + const bidderRequest = {}; const bidRequestsWithSendId = [ { bidder: 'criteo', @@ -1450,7 +1450,7 @@ describe('The Criteo bidding adapter', function () { params: { zoneId: 123, publisherSubId: '123', - nativeCallback: function() {} + nativeCallback: function () { } }, nativeParams: { image: { @@ -1481,7 +1481,7 @@ describe('The Criteo bidding adapter', function () { }); it('should warn only once if sendId is not provided on required fields for native bidRequest', () => { - const bidderRequest = { }; + const bidderRequest = {}; const bidRequests = [ { bidder: 'criteo', @@ -1491,7 +1491,7 @@ describe('The Criteo bidding adapter', function () { params: { zoneId: 123, publisherSubId: '123', - nativeCallback: function() {} + nativeCallback: function () { } }, }, { @@ -1502,7 +1502,7 @@ describe('The Criteo bidding adapter', function () { params: { zoneId: 456, publisherSubId: '456', - nativeCallback: function() {} + nativeCallback: function () { } }, }, ]; @@ -1556,7 +1556,7 @@ describe('The Criteo bidding adapter', function () { .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') .exactly(nativeParamsWithoutSendId.length * bidRequests.length); nativeParamsWithoutSendId.forEach(nativeParams => { - let transformedBidRequests = {...bidRequests}; + let transformedBidRequests = { ...bidRequests }; transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; spec.buildRequests(transformedBidRequests, bidderRequest); }); @@ -1569,10 +1569,10 @@ describe('The Criteo bidding adapter', function () { const response = {}; const request = {}; - const adapter = { interpretResponse: function() {} }; + const adapter = { interpretResponse: function () { } }; const adapterMock = sinon.mock(adapter); adapterMock.expects('interpretResponse').withExactArgs(response, request).once().returns('ok'); - const prebidAdapter = { GetAdapter: function() {} }; + const prebidAdapter = { GetAdapter: function () { } }; const prebidAdapterMock = sinon.mock(prebidAdapter); prebidAdapterMock.expects('GetAdapter').withExactArgs(request).once().returns(adapter); @@ -1592,10 +1592,10 @@ describe('The Criteo bidding adapter', function () { it('should forward bid to pubtag when calling onBidWon', () => { const bid = { auctionId: 123 }; - const adapter = { handleBidWon: function() {} }; + const adapter = { handleBidWon: function () { } }; const adapterMock = sinon.mock(adapter); adapterMock.expects('handleBidWon').withExactArgs(bid).once(); - const prebidAdapter = { GetAdapter: function() {} }; + const prebidAdapter = { GetAdapter: function () { } }; const prebidAdapterMock = sinon.mock(prebidAdapter); prebidAdapterMock.expects('GetAdapter').withExactArgs(bid.auctionId).once().returns(adapter); @@ -1615,10 +1615,10 @@ describe('The Criteo bidding adapter', function () { it('should forward bid to pubtag when calling onSetTargeting', () => { const bid = { auctionId: 123 }; - const adapter = { handleSetTargeting: function() {} }; + const adapter = { handleSetTargeting: function () { } }; const adapterMock = sinon.mock(adapter); adapterMock.expects('handleSetTargeting').withExactArgs(bid).once(); - const prebidAdapter = { GetAdapter: function() {} }; + const prebidAdapter = { GetAdapter: function () { } }; const prebidAdapterMock = sinon.mock(prebidAdapter); prebidAdapterMock.expects('GetAdapter').withExactArgs(bid.auctionId).once().returns(adapter); @@ -1638,10 +1638,10 @@ describe('The Criteo bidding adapter', function () { it('should forward bid to pubtag when calling onTimeout', () => { const timeoutData = [{ auctionId: 123 }]; - const adapter = { handleBidTimeout: function() {} }; + const adapter = { handleBidTimeout: function () { } }; const adapterMock = sinon.mock(adapter); adapterMock.expects('handleBidTimeout').once(); - const prebidAdapter = { GetAdapter: function() {} }; + const prebidAdapter = { GetAdapter: function () { } }; const prebidAdapterMock = sinon.mock(prebidAdapter); prebidAdapterMock.expects('GetAdapter').withExactArgs(timeoutData[0].auctionId).once().returns(adapter); @@ -1659,15 +1659,15 @@ describe('The Criteo bidding adapter', function () { }); it('should return a POST method with url & data from pubtag', () => { - const bidRequests = { }; - const bidderRequest = { }; + const bidRequests = {}; + const bidderRequest = {}; - const prebidAdapter = { buildCdbUrl: function() {}, buildCdbRequest: function() {} }; + const prebidAdapter = { buildCdbUrl: function () { }, buildCdbRequest: function () { } }; const prebidAdapterMock = sinon.mock(prebidAdapter); prebidAdapterMock.expects('buildCdbUrl').once().returns('cdbUrl'); prebidAdapterMock.expects('buildCdbRequest').once().returns('cdbRequest'); - const adapters = { Prebid: function() {} }; + const adapters = { Prebid: function () { } }; const adaptersMock = sinon.mock(adapters); adaptersMock.expects('Prebid').withExactArgs(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$').once().returns(prebidAdapter); From 75b8f20c2e3002de7520881403ecba020ee8d571 Mon Sep 17 00:00:00 2001 From: Patrick Loughrey Date: Fri, 29 Apr 2022 13:59:13 -0400 Subject: [PATCH 10/44] Triplelift Bid Adapter: Updating Prebid floors logic and unit test coverage (#8348) * TL-19850 Finished log error logic around floors functionality * deprecates getlegacyFpd * remove console log * TL-19850 Changed functionlity of tests * restore logErrorSpy aftereach Co-authored-by: Dan Goldin Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> Co-authored-by: Patrick Loughrey Co-authored-by: Nick Llerandi --- modules/tripleliftBidAdapter.js | 22 +++++++++++-------- .../spec/modules/tripleliftBidAdapter_spec.js | 14 ++++++++++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 830d26eda61..7e964661db6 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -1,4 +1,4 @@ -import { tryAppendQueryString, logMessage, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js'; +import { tryAppendQueryString, logMessage, logError, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; @@ -175,14 +175,18 @@ function _getORTBVideo(bidRequest) { function _getFloor (bid) { let floor = null; if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: _isInstreamBidRequest(bid) ? 'video' : 'banner', - size: '*' - }); - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { - floor = parseFloat(floorInfo.floor); + try { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: _isInstreamBidRequest(bid) ? 'video' : 'banner', + size: '*' + }); + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } catch (err) { + logError('Triplelift: getFloor threw an error: ', err); } } return floor !== null ? floor : bid.params.floor; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 164188804a3..fc33a7cd676 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -11,8 +11,7 @@ const GDPR_CONSENT_STR = 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7V describe('triplelift adapter', function () { const adapter = newBidder(tripleliftAdapterSpec); - let bid, instreamBid; - let sandbox; + let bid, instreamBid, sandbox, logErrorSpy; this.beforeEach(() => { bid = { @@ -379,9 +378,11 @@ describe('triplelift adapter', function () { }, }; sandbox = sinon.sandbox.create(); + logErrorSpy = sinon.spy(utils, 'logError'); }); afterEach(() => { sandbox.restore(); + utils.logError.restore(); }); it('exists and is an object', function () { @@ -787,6 +788,15 @@ describe('triplelift adapter', function () { size: '*' })).to.be.true; }); + it('should catch error if getFloor throws error', function() { + bidRequests[0].getFloor = () => { + throw new Error('An exception!'); + }; + + tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + + expect(logErrorSpy.calledOnce).to.equal(true); + }); it('should send global config fpd if kvps are available', function() { const sens = null; const category = ['news', 'weather', 'hurricane']; From f8c8fca03ed502ba084d57cbe6d7fe8e9f0e55fb Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Mon, 2 May 2022 09:36:42 -0500 Subject: [PATCH 11/44] 33Across user id sub-module: initial release (#8335) * wip * Rename lexicon param url to apiUrl and remove envelope param * Rename gvlid spec title - lexicon id system * add missing semicolon * apply code review feedback lexicon id system * Unit test coverage for the callback execution - lexicon id system * Add lexicon to user id integration example * Add lexicon is system markdown documentation * Update modules/lexiconIdSystem.js add colon to error message Co-authored-by: macinjosh32 * Send privacy strings in the lexicon id system request * apply code review feedback markdown lexicon documentation * Rename files with references to lexicon * rename 33across ID from lexicon to 33acrossId * fix failing unit tests incorrect URL and error message * Add 33acrossId reference in submodules & documentation Co-authored-by: macinjosh32 --- integrationExamples/gpt/userId_example.html | 12 + modules/.submodules.json | 1 + modules/33acrossIdSystem.js | 115 ++++++ modules/33acrossIdSystem.md | 53 +++ modules/userId/eids.js | 9 + modules/userId/eids.md | 7 + modules/userId/userId.md | 25 +- test/spec/modules/33acrossIdSystem_spec.js | 413 ++++++++++++++++++++ test/spec/modules/eids_spec.js | 17 + 9 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 modules/33acrossIdSystem.js create mode 100644 modules/33acrossIdSystem.md create mode 100644 test/spec/modules/33acrossIdSystem_spec.js diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index e11a0b626c9..52578ebcada 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -77,6 +77,7 @@ "301": true, // zeotapIdPlus "91": true, // criteo "737": true, // amxId + "58": true, // 33acrossId } } } @@ -128,6 +129,17 @@ "expires": 30 } }, + { + "name": "33acrossId", + "params": { + "pid": '0' + }, + "storage": { + "type": 'html5', + "name": '33acrossId', + "expires": 90 + } + }, { "name": "intentIqId", "params": { diff --git a/modules/.submodules.json b/modules/.submodules.json index 85e4658cc61..b0f19aa0bae 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -1,5 +1,6 @@ { "userId": [ + "33acrossIdSystem", "admixerIdSystem", "adtelligentIdSystem", "akamaiDAPIdSystem", diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js new file mode 100644 index 00000000000..3763fee5124 --- /dev/null +++ b/modules/33acrossIdSystem.js @@ -0,0 +1,115 @@ +/** + * This module adds 33acrossId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/33acrossIdSystem + * @requires module:modules/userId + */ + +import { logMessage, logError } from '../src/utils.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { uspDataHandler } from '../src/adapterManager.js'; + +const MODULE_NAME = '33acrossId'; +const API_URL = 'https://lexicon.33across.com/v1/envelope'; +const AJAX_TIMEOUT = 10000; + +function getEnvelope(response) { + if (!response.succeeded) { + logError(`${MODULE_NAME}: Unsuccessful response`); + + return; + } + + if (!response.data.envelope) { + logMessage(`${MODULE_NAME}: No envelope was received`); + + return; + } + + return response.data.envelope; +} + +function calculateQueryStringParams(pid, gdprConsentData) { + const uspString = uspDataHandler.getConsentData(); + const gdprApplies = Boolean(gdprConsentData?.gdprApplies); + const params = { + pid, + gdpr: Number(gdprApplies), + }; + + if (uspString) { + params.us_privacy = uspString; + } + + if (gdprApplies) { + params.gdpr_consent = gdprConsentData.consentString || ''; + } + + return params; +} + +/** @type {Submodule} */ +export const thirthyThreeAcrossIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + gvlid: 58, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} id + * @returns {{'33acrossId':{ envelope: string}}} + */ + decode(id) { + return { + [MODULE_NAME]: { + envelope: id + } + }; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @returns {IdResponse|undefined} + */ + getId({ params = { } }, gdprConsentData) { + if (typeof params.pid !== 'string') { + logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`); + + return; + } + + const { pid, apiUrl = API_URL } = params; + + return { + callback(cb) { + ajaxBuilder(AJAX_TIMEOUT)(apiUrl, { + success(response) { + let envelope; + + try { + envelope = getEnvelope(JSON.parse(response)) + } catch (err) { + logError(`${MODULE_NAME}: ID reading error:`, err); + } + cb(envelope); + }, + error(err) { + logError(`${MODULE_NAME}: ID error response`, err); + + cb(); + } + }, calculateQueryStringParams(pid, gdprConsentData), { method: 'GET', withCredentials: true }); + } + }; + } +}; + +submodule('userId', thirthyThreeAcrossIdSubmodule); diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md new file mode 100644 index 00000000000..1e4af89344f --- /dev/null +++ b/modules/33acrossIdSystem.md @@ -0,0 +1,53 @@ +# 33ACROSS ID + +For help adding this submodule, please contact [PrebidUIM@33across.com](PrebidUIM@33across.com). + +### Prebid Configuration + +You can configure this submodule in your `userSync.userIds[]` configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "33acrossId", + storage: { + name: "33acrossId", + type: "html5", + expires: 90, + refreshInSeconds: 8*3600 + }, + params: { + pid: "0010b00002GYU4eBAH", + }, + }, + ], + }, +}); +``` + +| Parameters under `userSync.userIds[]` | Scope | Type | Description | Example | +| ---| --- | --- | --- | --- | +| name | Required | String | Name for the 33Across ID submodule | `"33acrossId"` | | +| storage | Required | Object | Configures how to cache User IDs locally in the browser | See [storage settings](#storage-settings) | +| params | Required | Object | Parameters for 33Across ID submodule | See [params](#params) | + +### Storage Settings + +The following settings are available for the `storage` property in the `userSync.userIds[]` object: + +| Param name | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"33acrossId"` | +| type | Required | String | `"html5"` (preferred) or `"cookie"` | `"html5"` | +| expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `90`. | `90` | +| refreshInSeconds | Strongly Recommended | Number | The interval (in seconds) for refreshing the user ID. 33Across recommends no more than 8 hours between refreshes. | `8*3600` | + +### Params + +The following settings are available in the `params` property in `userSync.userIds[]` object: + +| Param name | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| pid | Required | String | Partner ID provided by 33Across | `"0010b00002GYU4eBAH"` | diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 5d138795bd8..9c995a52fe3 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -305,6 +305,15 @@ export const USER_IDS_CONFIG = { source: 'impact-ad.jp', atype: 1 }, + + // 33across ID + '33acrossId': { + source: '33across.com', + atype: 1, + getValue: function(data) { + return data.envelope; + } + }, }; // this function will create an eid object for the given UserId sub-module diff --git a/modules/userId/eids.md b/modules/userId/eids.md index a61ef66c56c..45237ac5f26 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -2,6 +2,13 @@ ``` userIdAsEids = [ + { + source: '33across.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { source: 'trustpid.com', uids: [{ diff --git a/modules/userId/userId.md b/modules/userId/userId.md index ee49211e4cb..bc0dad6462a 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -6,6 +6,17 @@ Example showing `cookie` storage for user id data for each of the submodules pbjs.setConfig({ userSync: { userIds: [{ + name: "33acrossId", + storage: { + type: "cookie", + name: "33acrossId", + expires: 90, + refreshInSeconds: 8*3600 + }, + params: { + pid: "0010b00002GYU4eBAH" // Example ID + } + }, { name: "pubCommonId", storage: { type: "cookie", @@ -166,8 +177,18 @@ Example showing `localStorage` for user id data for some submodules ``` pbjs.setConfig({ userSync: { - userIds: [ - { + userIds: [{ + name: "33acrossId", + storage: { + type: "html5", + name: "33acrossId", + expires: 90, + refreshInSeconds: 8*3600 + }, + params: { + pid: "0010b00002GYU4eBAH" // Example ID + } + }, { name: 'trustpid', params: { maxDelayTime: 2500 diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js new file mode 100644 index 00000000000..8198bd75381 --- /dev/null +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -0,0 +1,413 @@ +import { thirthyThreeAcrossIdSubmodule } from 'modules/33acrossIdSystem.js'; +import * as utils from 'src/utils.js'; + +import { server } from 'test/mocks/xhr.js'; +import { uspDataHandler } from 'src/adapterManager.js'; + +describe('33acrossIdSystem', () => { + describe('name', () => { + it('should expose the name of the submodule', () => { + expect(thirthyThreeAcrossIdSubmodule.name).to.equal('33acrossId'); + }); + }); + + describe('gvlid', () => { + it('should expose the vendor id', () => { + expect(thirthyThreeAcrossIdSubmodule.gvlid).to.equal(58); + }); + }); + + describe('getId', () => { + it('should call endpoint and handle valid response', () => { + const completeCallback = sinon.spy(); + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo' + }, + expires: 1645667805067 + })); + + expect(request.method).to.equal('GET'); + expect(request.withCredentials).to.be.true; + expect(request.url).to.contain('https://lexicon.33across.com/v1/envelope?pid=12345'); + expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; + }); + + context('when GDPR applies', () => { + it('should call endpoint with \'gdpr=1\'', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }, { + gdprApplies: true + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('gdpr=1'); + }); + + context('and the consent string is given', () => { + it('should call endpoint with the GDPR consent string', () => { + [ + { consentString: '', expected: '' }, + { consentString: undefined, expected: '' }, + { consentString: 'foo', expected: 'foo' } + ].forEach(({ consentString, expected }, index) => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }, { + gdprApplies: true, + consentString + }); + + callback(completeCallback); + + expect(server.requests[index].url).to.contain(`gdpr_consent=${expected}`); + }); + }); + }); + }); + + context('when GDPR doesn\'t apply', () => { + it('should call endpoint with \'gdpr=0\' and no GDPR consent string parameter', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }, { + gdprApplies: false, + consentString: 'foo' + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('gdpr=0'); + expect(request.url).not.to.contain('gdpr_consent'); + }); + }); + + context('when a valid US Privacy string is given', () => { + it('should call endpoint with the US Privacy parameter', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + sinon.stub(uspDataHandler, 'getConsentData').returns('1YYY'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('us_privacy=1YYY'); + + uspDataHandler.getConsentData.restore(); + }); + }); + + context('when an invalid US Privacy is given', () => { + it('should call endpoint without the US Privacy parameter', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + // null or any other falsy value is considered invalid. + sinon.stub(uspDataHandler, 'getConsentData').returns(null); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).not.to.contain('us_privacy'); + + uspDataHandler.getConsentData.restore(); + }); + }); + + context('when the partner ID is not given', () => { + it('should log an error', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + + thirthyThreeAcrossIdSubmodule.getId({ + params: { /* No 'pid' param */ } + }); + + expect(logErrorSpy.calledOnceWithExactly('33acrossId: Submodule requires a partner ID to be defined')).to.be.true; + + logErrorSpy.restore(); + }); + }); + + context('when the partner ID has an incorrect format', () => { + it('should log an error', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + + thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: 123456 // PID must be a string + } + }); + + expect(logErrorSpy.calledOnceWithExactly('33acrossId: Submodule requires a partner ID to be defined')).to.be.true; + + logErrorSpy.restore(); + }); + }); + + context('when the server JSON is invalid', () => { + it('should log an error', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, 'invalid response'); + + expect(logErrorSpy.lastCall.args[0]).to.eq(`${thirthyThreeAcrossIdSubmodule.name}: ID reading error:`); + + logErrorSpy.restore(); + }); + + it('should execute complete callback with undefined value', () => { + const completeCallback = sinon.spy(); + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, 'invalid response'); + + expect(completeCallback.calledOnceWithExactly(undefined)).to.be.true; + }); + }); + + context('when an endpoint override is given', () => { + it('should call that endpoint', () => { + const completeCallback = sinon.spy(); + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + apiUrl: 'https://staging-lexicon.33across.com/v1/envelope' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo' + }, + expires: 1645667805067 + })); + + expect(request.url).to.contain('https://staging-lexicon.33across.com/v1/envelope?pid=12345'); + }); + }); + + context('when the server returns an unsuccessful response', () => { + it('should log an error', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: false, + error: 'foo' + })); + + expect(logErrorSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: Unsuccessful response`)).to.be.true; + + logErrorSpy.restore(); + }); + + it('should execute complete callback with undefined value', () => { + const completeCallback = sinon.spy(); + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: false, + error: 'foo' + })); + + expect(completeCallback.calledOnceWithExactly(undefined)).to.be.true; + }); + }); + + context('when the server returns a successful response but without ID', () => { + it('should log a message', () => { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: {} + })); + + expect(logMessageSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: No envelope was received`)).to.be.true; + + logMessageSpy.restore(); + }); + + it('should execute complete callback with undefined value', () => { + const completeCallback = sinon.spy(); + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: {} + })); + + expect(completeCallback.calledOnceWithExactly(undefined)).to.be.true; + }); + }); + + context('when the server returns an error status code', () => { + it('should log an error', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(404); + + expect(logErrorSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: ID error response`, 'Not Found')).to.be.true; + + logErrorSpy.restore(); + }); + + it('should execute complete callback without any value', () => { + const completeCallback = sinon.spy(); + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(404); + + expect(completeCallback.calledOnceWithExactly()).to.be.true; + }); + }) + }) + + describe('decode', () => { + it('should wrap the given value inside an object literal', () => { + expect(thirthyThreeAcrossIdSubmodule.decode('foo')).to.deep.equal({ + [thirthyThreeAcrossIdSubmodule.name]: { + envelope: 'foo' + } + }); + }); + }); +}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 9edda3c9e95..a92f7145747 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -363,6 +363,23 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + + it('33acrossId', function() { + const userId = { + '33acrossId': { + envelope: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: '33across.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); }); describe('Negative case', function() { it('eids array generation for UN-known sub-module', function() { From 506b07002c371cc77b20ef77a21ebe619e705037 Mon Sep 17 00:00:00 2001 From: Olivier Date: Mon, 2 May 2022 20:04:49 +0200 Subject: [PATCH 12/44] Adagio Bid Adapter: Price floor support improvements (#8355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * AdagioBidAdapter: improve price floor module support * AdagioBidAdapter: add access to prebid global instance at adUnit level Co-authored-by: Jérémy LAMBERT --- modules/adagioBidAdapter.js | 50 +++++++++++++++++++--- test/spec/modules/adagioBidAdapter_spec.js | 13 +++++- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index a24bc889411..f3060c5598b 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -31,6 +31,7 @@ import {createEidsArray} from './userId/eids.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {OUTSTREAM} from '../src/video.js'; +import { getGlobal } from '../src/prebidGlobal.js'; const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; @@ -46,7 +47,6 @@ const MAX_SESS_DURATION = 30 * 60 * 1000; const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; const ADAGIO_PUBKEY_E = 65537; const CURRENCY = 'USD'; -const DEFAULT_FLOOR = 0.1; // This provide a whitelist and a basic validation // of OpenRTB 2.5 options used by the Adagio SSP. @@ -589,13 +589,13 @@ function _getFloors(bidRequest) { const info = bidRequest.getFloor({ currency: CURRENCY, mediaType, - size: [] + size }); floors.push(cleanObj({ mt: mediaType, s: isArray(size) ? `${size[0]}x${size[1]}` : undefined, - f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : DEFAULT_FLOOR + f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : undefined })); } @@ -869,7 +869,9 @@ function storeRequestInAdagioNS(bidRequest) { }], auctionId: bidRequest.auctionId, pageviewId: internal.getPageviewId(), - printNumber + printNumber, + localPbjs: '$$PREBID_GLOBAL$$', + localPbjsRef: getGlobal() }); // (legacy) Store internal adUnit information @@ -937,7 +939,45 @@ export const spec = { }); // Handle priceFloors module - bidRequest.floors = _getFloors(bidRequest); + const computedFloors = _getFloors(bidRequest); + if (isArray(computedFloors) && computedFloors.length) { + bidRequest.floors = computedFloors + + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + const bannerObj = bidRequest.mediaTypes.banner + + const computeNewSizeArray = (sizeArr = []) => { + const size = { size: sizeArr, floor: null } + const bannerFloors = bidRequest.floors.filter(floor => floor.mt === BANNER) + const BannerSizeFloor = bannerFloors.find(floor => floor.s === sizeArr.join('x')) + size.floor = (bannerFloors) ? (BannerSizeFloor) ? BannerSizeFloor.f : bannerFloors[0].f : null + return size + } + + // `bannerSizes`, internal property name + bidRequest.mediaTypes.banner.bannerSizes = (isArray(bannerObj.sizes[0])) + ? bannerObj.sizes.map(sizeArr => { + return computeNewSizeArray(sizeArr) + }) + : computeNewSizeArray(bannerObj.sizes) + } + + if (deepAccess(bidRequest, 'mediaTypes.video')) { + const videoObj = bidRequest.mediaTypes.video + const videoFloors = bidRequest.floors.filter(floor => floor.mt === VIDEO); + const playerSize = (videoObj.playerSize && isArray(videoObj.playerSize[0])) ? videoObj.playerSize[0] : videoObj.playerSize + const videoSizeFloor = (playerSize) ? videoFloors.find(floor => floor.s === playerSize.join('x')) : undefined + + bidRequest.mediaTypes.video.floor = (videoFloors) ? videoSizeFloor ? videoSizeFloor.f : videoFloors[0].f : null + } + + if (deepAccess(bidRequest, 'mediaTypes.native')) { + const nativeFloors = bidRequest.floors.filter(floor => floor.mt === NATIVE); + if (nativeFloors.length) { + bidRequest.mediaTypes.native.floor = nativeFloors[0].f + } + } + } if (deepAccess(bidRequest, 'mediaTypes.video')) { _buildVideoBidRequest(bidRequest); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 945cd0565a7..a3e05697597 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -677,6 +677,11 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 1, mt: 'banner', s: '300x250'}); expect(requests[0].data.adUnits[0].floors[1]).to.deep.equal({f: 1, mt: 'banner', s: '300x600'}); expect(requests[0].data.adUnits[0].floors[2]).to.deep.equal({f: 1, mt: 'video', s: '600x480'}); + + expect(requests[0].data.adUnits[0].mediaTypes.banner.sizes.length).to.equal(2); + expect(requests[0].data.adUnits[0].mediaTypes.banner.bannerSizes[0]).to.deep.equal({size: [300, 250], floor: 1}); + expect(requests[0].data.adUnits[0].mediaTypes.banner.bannerSizes[1]).to.deep.equal({size: [300, 600], floor: 1}); + expect(requests[0].data.adUnits[0].mediaTypes.video.floor).to.equal(1); }); it('should get and set floor by mediatype if no size provided (ex native, video)', function() { @@ -700,6 +705,9 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].floors.length).to.equal(2); expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 1, mt: 'video'}); expect(requests[0].data.adUnits[0].floors[1]).to.deep.equal({f: 1, mt: 'native'}); + + expect(requests[0].data.adUnits[0].mediaTypes.video.floor).to.equal(1); + expect(requests[0].data.adUnits[0].mediaTypes.native.floor).to.equal(1); }); it('should get and set floor with default value if no floors found', function() { @@ -713,12 +721,13 @@ describe('Adagio bid adapter', () => { }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); bid01.getFloor = () => { - return { floor: NaN, currency: 'USD' } + return { floor: NaN, currency: 'USD', mt: 'video' } } const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.adUnits[0].floors.length).to.equal(1); - expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 0.1, mt: 'video'}); + expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({mt: 'video'}); + expect(requests[0].data.adUnits[0].mediaTypes.video.floor).to.be.undefined; }); }); }); From 880b089bf66701d19a6284a61847fb05a059b270 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Mon, 2 May 2022 14:29:18 -0400 Subject: [PATCH 13/44] Prebid Server Adapter: add native asset id parameter (#8317) * add missing native asset id param for PBS requests * use id if it exists * update id counter logic --- modules/prebidServerBidAdapter/index.js | 5 ++++- test/spec/modules/prebidServerBidAdapter_spec.js | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 62f550eb216..b1b54d9e313 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -563,13 +563,16 @@ Object.assign(ORTB2.prototype, { const nativeParams = adUnit.nativeParams; let nativeAssets; if (nativeParams) { + let idCounter = -1; try { nativeAssets = nativeAssetCache[impressionId] = Object.keys(nativeParams).reduce((assets, type) => { let params = nativeParams[type]; function newAsset(obj) { + idCounter++; return Object.assign({ - required: params.required ? 1 : 0 + required: params.required ? 1 : 0, + id: (isNumber(params.id)) ? idCounter = params.id : idCounter }, obj ? cleanObj(obj) : {}); } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 895099a4748..3edb89dd573 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1279,12 +1279,14 @@ describe('S2S Adapter', function () { 'assets': [ { 'required': 1, + 'id': 0, 'title': { 'len': 800 } }, { 'required': 1, + 'id': 1, 'img': { 'type': 3, 'w': 989, @@ -1293,6 +1295,7 @@ describe('S2S Adapter', function () { }, { 'required': 1, + 'id': 2, 'img': { 'type': 1, 'wmin': 10, @@ -1304,6 +1307,7 @@ describe('S2S Adapter', function () { }, { 'required': 1, + 'id': 3, 'data': { 'type': 1 } From 49118b835f9564b03a20b4909668ab292f83bc1b Mon Sep 17 00:00:00 2001 From: guiann Date: Tue, 3 May 2022 16:16:04 +0200 Subject: [PATCH 14/44] AdYouLike Bidder: clean on video ad treatment (#8352) * add required clickurl in every native adrequest * allows the native response to be given as is to prebid if possible * add unit tests on new Native case * Handle meta object in bid response with default addomains array * fix icon retrieval in Native case * Update priorities in case of multiple mediatypes given * improve robustness and fix associated unit test on picture urls * add support for params.size parameter * add unit test on new size format * Makes sure the playerSize format is consistent * enable Vast response on bidder adapter * fix lint errors * add test on Vast format case * add userId to bidrequest * revert package-lock.json changes * improve multiple mediatype handling * Expose adyoulike GVL id * fix icurl issue when retreiving icon for Native mediatype * update unit tests on icon url in native mediatype * target video endpoint when video mediatype is present * add unit test on video endpoint * detect if bid request has video * remove console log * Add size information in Video bid + unit tests * Remove unused method (old video retrieval) --- modules/adyoulikeBidAdapter.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 6710448f00e..6fcce753596 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -350,14 +350,6 @@ function getTrackers(eventsArray, jsTrackers) { return result; } -function getVideoAd(response) { - var adJson = {}; - if (typeof response.Ad === 'string' && response.Ad.indexOf('\/\*PREBID\*\/') > 0) { - adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]); - return deepAccess(adJson, 'Content.MainVideo.Vast'); - } -} - function getNativeAssets(response, nativeConfig) { if (typeof response.Native === 'object') { return response.Native; @@ -486,7 +478,7 @@ function createBid(response, bidRequests) { }; // retreive video response if present - const vast64 = response.Vast || getVideoAd(response); + const vast64 = response.Vast; if (vast64) { bid.width = response.Width; bid.height = response.Height; From 0a5fc0570d5e10f74f41451481a5676c547f2cae Mon Sep 17 00:00:00 2001 From: Kanchika - Automatad Date: Tue, 3 May 2022 19:54:50 +0530 Subject: [PATCH 15/44] Automatad Bid Adapter: make placementId param optional (#8269) * added automatad bid adapter * added automatad bid adapter readme * added automatad bidder adapter unit test * updated maintainer email id for automatad adapter * refactored automatadBidAdapter js * refactored automatadBidAdapter unit test * refactored automatadBidAdapter unit test * added usersync code to automatad bid adapter * Added unit test for onBidWon in automatadBidAdapter_spec * removed trailing spaces * removed trailing space * changes for getUserSync function * lint error fixes * updated usersync url * additional test for onBidWon function added * added ajax stub in test * updated winurl params * lint fixes * added adunitCode in bid request * added test for adunit code * add placement in impression object * added code to interpret multiple bid response in seatbid * added bid meta with advertiserDomains * endpoint url changes * added format changes * macro substitution change added * make placementId optional * lint the code --- modules/automatadBidAdapter.js | 35 +++++++++++------ modules/automatadBidAdapter.md | 4 +- test/spec/modules/automatadBidAdapter_spec.js | 39 +++++++++++++------ 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index 726bbef9bd6..e3d0d87783a 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -18,7 +18,7 @@ export const spec = { isBidRequestValid: function (bid) { // will receive request bid. check if have necessary params for bidding - return (bid && bid.hasOwnProperty('params') && bid.params.hasOwnProperty('siteId') && bid.params.hasOwnProperty('placementId') && bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty('banner')) + return (bid && bid.hasOwnProperty('params') && bid.params.hasOwnProperty('siteId') && bid.params.siteId != null && bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty('banner') && typeof bid.mediaTypes.banner == 'object') }, buildRequests: function (validBidRequests, bidderRequest) { @@ -29,16 +29,29 @@ export const spec = { const siteId = validBidRequests[0].params.siteId const impressions = validBidRequests.map(bidRequest => { - return { - id: bidRequest.bidId, - adUnitCode: bidRequest.adUnitCode, - placement: bidRequest.params.placementId, - banner: { - format: bidRequest.sizes.map(sizeArr => ({ - w: sizeArr[0], - h: sizeArr[1], - })) - }, + if (bidRequest.params.hasOwnProperty('placementId')) { + return { + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + placement: bidRequest.params.placementId, + banner: { + format: bidRequest.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1], + })) + }, + } + } else { + return { + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + banner: { + format: bidRequest.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1], + })) + }, + } } }) diff --git a/modules/automatadBidAdapter.md b/modules/automatadBidAdapter.md index 56a4b53c067..94bc707c75b 100644 --- a/modules/automatadBidAdapter.md +++ b/modules/automatadBidAdapter.md @@ -25,8 +25,8 @@ var adUnits = [ bids: [{ bidder: 'automatad', params: { - siteId: 'someValue', - placementId: 'someValue' + siteId: 'someValue', // required + placementId: 'someValue' // optional } }] } diff --git a/test/spec/modules/automatadBidAdapter_spec.js b/test/spec/modules/automatadBidAdapter_spec.js index 4a15209822b..09bfb96e8b9 100644 --- a/test/spec/modules/automatadBidAdapter_spec.js +++ b/test/spec/modules/automatadBidAdapter_spec.js @@ -5,7 +5,25 @@ import { newBidder } from 'src/adapters/bidderFactory.js' describe('automatadBidAdapter', function () { const adapter = newBidder(spec) - let bidRequest = { + let bidRequestRequiredParams = { + bidder: 'automatad', + params: {siteId: '123ad'}, + mediaTypes: { + banner: { + sizes: [[300, 600]], + } + }, + adUnitCode: 'some-ad-unit-code', + transactionId: '1465569e-52cc-4c36-88a1-7174cfef4b44', + sizes: [[300, 600]], + bidId: '123abc', + bidderRequestId: '3213887463c059', + auctionId: 'abc-123', + src: 'client', + bidRequestsCount: 1 + } + + let bidRequestAllParams = { bidder: 'automatad', params: {siteId: '123ad', placementId: '123abc345'}, mediaTypes: { @@ -59,10 +77,14 @@ describe('automatadBidAdapter', function () { }) describe('isBidRequestValid', function () { - let inValidBid = Object.assign({}, bidRequest) + let inValidBid = Object.assign({}, bidRequestRequiredParams) delete inValidBid.params it('should return true if all params present', function () { - expect(spec.isBidRequestValid(bidRequest)).to.equal(true) + expect(spec.isBidRequestValid(bidRequestAllParams)).to.equal(true) + }) + + it('should return true if only required params present', function() { + expect(spec.isBidRequestValid(bidRequestRequiredParams)).to.equal(true) }) it('should return false if any parameter missing', function () { @@ -71,7 +93,7 @@ describe('automatadBidAdapter', function () { }) describe('buildRequests', function () { - let req = spec.buildRequests([ bidRequest ], { refererInfo: { } }) + let req = spec.buildRequests([ bidRequestRequiredParams ], { refererInfo: { } }) let rdata it('should return request object', function () { @@ -87,9 +109,9 @@ describe('automatadBidAdapter', function () { expect(rdata.imp.length).to.equal(1) }) - it('should include placement', function () { + it('should include siteId', function () { let r = rdata.imp[0] - expect(r.placement !== null).to.be.true + expect(r.siteId !== null).to.be.true }) it('should include media types', function () { @@ -97,11 +119,6 @@ describe('automatadBidAdapter', function () { expect(r.media_types !== null).to.be.true }) - it('should include all publisher params', function () { - let r = rdata.imp[0] - expect(r.siteID !== null && r.placementID !== null).to.be.true - }) - it('should include adunit code', function () { let r = rdata.imp[0] expect(r.adUnitCode !== null).to.be.true From 21e73bb6c9a618abde47afb788cadb9ec7c34b7c Mon Sep 17 00:00:00 2001 From: Steven Ho Date: Wed, 4 May 2022 01:36:57 +0800 Subject: [PATCH 16/44] Aseal Bid Adapter: add session id & update request payload (#8231) --- modules/asealBidAdapter.js | 76 +++++++-- test/spec/modules/asealBidAdapter_spec.js | 199 +++++++++++++++------- 2 files changed, 196 insertions(+), 79 deletions(-) diff --git a/modules/asealBidAdapter.js b/modules/asealBidAdapter.js index 559afefa94b..855aee65f34 100644 --- a/modules/asealBidAdapter.js +++ b/modules/asealBidAdapter.js @@ -1,30 +1,77 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { generateUUID, getWindowTop, getWindowSelf } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; export const BIDDER_CODE = 'aseal'; -const SUPPORTED_AD_TYPES = [BANNER]; +export const SUPPORTED_AD_TYPES = [BANNER]; export const API_ENDPOINT = 'https://tkprebid.aotter.net/prebid/adapter'; -export const HEADER_AOTTER_VERSION = 'prebid_0.0.1'; +export const WEB_SESSION_ID_KEY = '__tkwsid'; +export const HEADER_AOTTER_VERSION = 'prebid_0.0.2'; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +const getTrekWebSessionId = () => { + let wsid = + storage.localStorageIsEnabled() && + storage.getDataFromLocalStorage(WEB_SESSION_ID_KEY); + + if (!wsid) { + wsid = generateUUID(); + setTrekWebSessionId(wsid); + } + + return wsid; +}; + +const setTrekWebSessionId = (wsid) => { + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(WEB_SESSION_ID_KEY, wsid); + } +}; + +const canAccessTopWindow = () => { + try { + return !!getWindowTop().location.href; + } catch (errro) { + return false; + } +}; export const spec = { code: BIDDER_CODE, aliases: ['aotter', 'trek'], supportedMediaTypes: SUPPORTED_AD_TYPES, - - isBidRequestValid: (bid) => !!bid.params.placeUid && typeof bid.params.placeUid === 'string', - + isBidRequestValid: (bid) => + !!bid.params.placeUid && typeof bid.params.placeUid === 'string', buildRequests: (validBidRequests, bidderRequest) => { if (validBidRequests.length === 0) { return []; } - const clientId = - config.getConfig('aseal.clientId') || ''; + const clientId = config.getConfig('aseal.clientId') || ''; + + const windowTop = getWindowTop(); + const windowSelf = getWindowSelf(); + + const w = canAccessTopWindow() ? windowTop : windowSelf; const data = { bids: validBidRequests, refererInfo: bidderRequest.refererInfo, + device: { + webSessionId: getTrekWebSessionId(), + }, + payload: { + meta: { + dr: w.document.referrer, + drs: windowSelf.document.referrer, + drt: (canAccessTopWindow() && windowTop.document.referrer) || '', + dt: w.document.title, + dl: w.location.href, + }, + }, }; const options = { @@ -36,14 +83,15 @@ export const spec = { }, }; - return [{ - method: 'POST', - url: API_ENDPOINT, - data, - options, - }]; + return [ + { + method: 'POST', + url: API_ENDPOINT, + data, + options, + }, + ]; }, - interpretResponse: (serverResponse, bidRequest) => { if (!Array.isArray(serverResponse.body)) { return []; diff --git a/test/spec/modules/asealBidAdapter_spec.js b/test/spec/modules/asealBidAdapter_spec.js index ef74dd3fc05..2dc1b47b7d0 100644 --- a/test/spec/modules/asealBidAdapter_spec.js +++ b/test/spec/modules/asealBidAdapter_spec.js @@ -1,120 +1,187 @@ import { expect } from 'chai'; -import { spec, BIDDER_CODE, API_ENDPOINT, HEADER_AOTTER_VERSION } from 'modules/asealBidAdapter.js'; +import { + spec, + BIDDER_CODE, + API_ENDPOINT, + HEADER_AOTTER_VERSION, + WEB_SESSION_ID_KEY, +} from 'modules/asealBidAdapter.js'; +import { getRefererInfo } from 'src/refererDetection.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { storage } from 'modules/asealBidAdapter.js'; -const TEST_CLIENT_ID = 'TEST_CLIENT_ID' +const TEST_CLIENT_ID = 'TEST_CLIENT_ID'; +const TEST_WEB_SESSION_ID = 'TEST_WEB_SESSION_ID'; describe('asealBidAdapter', () => { const adapter = newBidder(spec); + let localStorageIsEnabledStub; + let getDataFromLocalStorageStub; + let sandbox; + let w; + + beforeEach((done) => { + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getDataFromLocalStorageStub = sinon.stub( + storage, + 'getDataFromLocalStorage' + ); + + w = { + document: { + title: 'Aseal', + referrer: 'https://aseal.in/', + href: 'https://aseal.in/', + }, + location: { + href: 'https://aseal.in/', + }, + }; + + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'getWindowTop').returns(w); + sandbox.stub(utils, 'getWindowSelf').returns(w); + done(); + }); + + afterEach(() => { + localStorageIsEnabledStub.restore(); + getDataFromLocalStorageStub.restore(); + sandbox.restore(); + config.resetConfig(); + }); + describe('inherited functions', () => { it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function') - }) + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); }); describe('isBidRequestValid', () => { const bid = { bidder: 'aseal', params: { - placeUid: '123' - } + placeUid: '123', + }, }; it('should return true when required params found', () => { - expect(spec.isBidRequestValid(bid)).to.equal(true) + expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when required param placeUid is not passed', () => { bid.params = { - placeUid: '' - } - expect(spec.isBidRequestValid(bid)).to.equal(false) + placeUid: '', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when required param placeUid is wrong type', () => { bid.params = { - placeUid: null - } - expect(spec.isBidRequestValid(bid)).to.equal(false) + placeUid: null, + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when required params are not passed', () => { let bid = Object.assign({}, bid); delete bid.params; bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', () => { - afterEach(() => { - config.resetConfig(); - }); - it('should return an empty array when there are no bid requests', () => { - const bidRequests = [] - const request = spec.buildRequests(bidRequests) + const bidRequests = []; + const request = spec.buildRequests(bidRequests); - expect(request).to.be.an('array').that.is.empty + expect(request).to.be.an('array').that.is.empty; }); it('should send `x-aotter-clientid` header as empty string when user not set config `clientId`', () => { - const bidRequests = [{ - bidder: BIDDER_CODE, - params: { - placeUid: '123' - } - }] - - const bidderRequest = {} + const bidRequests = [ + { + bidder: BIDDER_CODE, + params: { + placeUid: '123', + }, + }, + ]; + + const bidderRequest = {}; const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.options.customHeaders['x-aotter-clientid']).equal('') - }) + expect(request.options.customHeaders['x-aotter-clientid']).equal(''); + }); it('should send bid requests to ENDPOINT via POST', () => { - const bidRequests = [{ - bidder: BIDDER_CODE, - params: { - placeUid: '123' - } - }] + const bidRequests = [ + { + bidder: BIDDER_CODE, + params: { + placeUid: '123', + }, + }, + ]; const bidderRequest = { - refererInfo: { - referer: 'https://aseal.in/', - } - } + refererInfo: getRefererInfo(), + }; config.setConfig({ aseal: { - clientId: TEST_CLIENT_ID - } + clientId: TEST_CLIENT_ID, + }, }); + + localStorageIsEnabledStub.returns(true); + getDataFromLocalStorageStub + .withArgs(WEB_SESSION_ID_KEY) + .returns(TEST_WEB_SESSION_ID); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + + sandbox.stub(utils, 'getWindowTop').returns(w); + sandbox.stub(utils, 'getWindowSelf').returns(w); + + const payload = { + meta: { + dr: w.document.referrer, + drs: w.document.referrer, + drt: w.document.referrer, + dt: w.document.title, + dl: w.location.href, + }, + }; + expect(request.url).to.equal(API_ENDPOINT); - expect(request.method).to.equal('POST') + expect(request.method).to.equal('POST'); expect(request.options).deep.equal({ contentType: 'application/json', withCredentials: true, customHeaders: { 'x-aotter-clientid': TEST_CLIENT_ID, 'x-aotter-version': HEADER_AOTTER_VERSION, - } + }, }); - expect(request.data).deep.equal({ - bids: bidRequests, - refererInfo: bidderRequest.refererInfo, + expect(request.data.bids).deep.equal(bidRequests); + expect(request.data.payload).deep.equal(payload); + expect(request.data.device).deep.equal({ + webSessionId: TEST_WEB_SESSION_ID, }); }); }); describe('interpretResponse', () => { it('should return an empty array when there are no bids', () => { - const serverResponse = {} + const serverResponse = {}; const response = spec.interpretResponse(serverResponse); expect(response).is.an('array').that.is.empty; @@ -122,23 +189,25 @@ describe('asealBidAdapter', () => { it('should get correct bid response', () => { const serverResponse = { - body: [{ - requestId: '2ef08f145b7a4f', - cpm: 3, - width: 300, - height: 250, - creativeId: '123abc', - dealId: '123abc', - currency: 'USD', - netRevenue: false, - mediaType: 'banner', - ttl: 300, - ad: '' - }] - } + body: [ + { + requestId: '2ef08f145b7a4f', + cpm: 3, + width: 300, + height: 250, + creativeId: '123abc', + dealId: '123abc', + currency: 'USD', + netRevenue: false, + mediaType: 'banner', + ttl: 300, + ad: '', + }, + ], + }; const response = spec.interpretResponse(serverResponse); expect(response).deep.equal(serverResponse.body); }); - }) + }); }); From 0561d2ade1f977598d12ba30189c6dd091fa753f Mon Sep 17 00:00:00 2001 From: PubNX <44992681+prebid-pubnx@users.noreply.github.com> Date: Wed, 4 May 2022 00:32:09 +0530 Subject: [PATCH 17/44] removed PubNX bid adaptor (#8353) Co-authored-by: Mohit Patil --- modules/pubnxBidAdapter.md | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 modules/pubnxBidAdapter.md diff --git a/modules/pubnxBidAdapter.md b/modules/pubnxBidAdapter.md deleted file mode 100644 index 6c843322402..00000000000 --- a/modules/pubnxBidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: PubNX Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid-team@pubnx.com -``` - -# Description - -Connects to PubNX exchange for bids. -PubNX Bidder adapter supports Banner ads. -Use bidder code ```pubnx``` for all PubNX traffic. - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - sizes: [[300, 250], [300,600]], // a display size(s) - bids: [{ - bidder: 'pubnx', - params: { - placementId: 'PNX-HB-G396432V4809F3' - } - }] - }, -]; -``` - From ceaa4bad0437a2e96b6e41086f2fbf5f1c5c768a Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Tue, 3 May 2022 21:34:43 +0200 Subject: [PATCH 18/44] JwPlayer RTD Module: Support cids (#8349) * supports cids * updates documentation --- modules/jwplayerRtdProvider.js | 36 ++++++++--- modules/jwplayerRtdProvider.md | 8 ++- test/spec/modules/jwplayerRtdProvider_spec.js | 64 +++++++++++++++---- 3 files changed, 82 insertions(+), 26 deletions(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 814aed59d85..400ee1a5f8a 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -155,8 +155,10 @@ export function enrichAdUnits(adUnits) { if (!vat) { return; } - const contentId = getContentId(vat.mediaID); - const contentData = getContentData(vat.segments); + const mediaId = vat.mediaID; + const contentId = getContentId(mediaId); + const contentSegments = getContentSegments(vat.segments); + const contentData = getContentData(mediaId, contentSegments); const targeting = formatTargetingResponse(vat); enrichBids(adUnit.bids, targeting, contentId, contentData); }; @@ -263,7 +265,7 @@ export function getContentId(mediaID) { return 'jw_' + mediaID; } -export function getContentData(segments) { +export function getContentSegments(segments) { if (!segments || !segments.length) { return; } @@ -276,13 +278,29 @@ export function getContentData(segments) { return convertedSegments; }, []); - return { - name: 'jwplayer', - ext: { - segtax: 502 - }, - segment: formattedSegments + return formattedSegments; +} + +export function getContentData(mediaId, segments) { + if (!mediaId && !segments) { + return; + } + + const contentData = { + name: 'jwplayer.com', + ext: {} }; + + if (mediaId) { + contentData.ext.cids = [mediaId]; + } + + if (segments) { + contentData.segment = segments; + contentData.ext.segtax = 502; + } + + return contentData; } export function addOrtbSiteContent(bid, contentId, contentData) { diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 77f65909040..479829196ed 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -95,9 +95,10 @@ Example: content: { id: 'jw_abc123', data: [{ - name: 'jwplayer', + name: 'jwplayer.com', ext: { - segtax: 502 + segtax: 502, + cids: ['abc123'] }, segment: [{ id: '123' @@ -117,8 +118,9 @@ where: - `content` is an object containing metadata for the media. It may contain the following information: - `id` is a unique identifier for the specific media asset - `data` is an array containing segment taxonomy objects that have the following parameters: - - `name` is the `jwplayer` string indicating the provider name + - `name` is the `jwplayer.com` string indicating the provider name - `ext.segtax` whose `502` value is the unique identifier for JW Player's proprietary taxonomy + - `ext.cids` is an array containing the list of extended content ids as defined in [oRTB's community extensions](https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/extended-content-ids.md#example---content-id-and-seller-defined-context). - `segment` is an array containing the segment taxonomy values as an object where: - `id` is the string representation of the data segment value. diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index db17d18864b..0775676aefe 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -1,6 +1,6 @@ import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams, formatTargetingResponse, getVatFromPlayer, enrichAdUnits, addTargetingToBid, - fetchTargetingInformation, jwplayerSubmodule, getContentId, getContentData } from 'modules/jwplayerRtdProvider.js'; + fetchTargetingInformation, jwplayerSubmodule, getContentId, getContentSegments, getContentData } from 'modules/jwplayerRtdProvider.js'; import { server } from 'test/mocks/xhr.js'; import {addOrtbSiteContent} from '../../../modules/jwplayerRtdProvider'; @@ -498,26 +498,62 @@ describe('jwplayerRtdProvider', function() { }); }); - describe('Get Content Data', function () { + describe('Get Content Segments', function () { it('returns undefined when segments are empty', function () { - let data = getContentData(null); - expect(data).to.be.undefined; - data = getContentData(undefined); - expect(data).to.be.undefined; - data = getContentData([]); - expect(data).to.be.undefined; + let contentSegments = getContentSegments(null); + expect(contentSegments).to.be.undefined; + contentSegments = getContentSegments(undefined); + expect(contentSegments).to.be.undefined; + contentSegments = getContentSegments([]); + expect(contentSegments).to.be.undefined; }); it('returns proper format', function () { const segment1 = 'segment1'; const segment2 = 'segment2'; const segment3 = 'segment3'; - const data = getContentData([segment1, segment2, segment3]); - expect(data).to.have.property('name', 'jwplayer'); - expect(data.ext).to.have.property('segtax', 502); - expect(data.segment[0]).to.deep.equal({ id: segment1, value: segment1 }); - expect(data.segment[1]).to.deep.equal({ id: segment2, value: segment2 }); - expect(data.segment[2]).to.deep.equal({ id: segment3, value: segment3 }); + const contentSegments = getContentSegments([segment1, segment2, segment3]); + expect(contentSegments[0]).to.deep.equal({ id: segment1, value: segment1 }); + expect(contentSegments[1]).to.deep.equal({ id: segment2, value: segment2 }); + expect(contentSegments[2]).to.deep.equal({ id: segment3, value: segment3 }); + }); + }); + + describe('Get Content Data', function () { + it('should return proper format', function () { + const testMediaId = 'test_media_id'; + const testSegments = [{ id: 1 }, { id: 2 }]; + const contentData = getContentData(testMediaId, testSegments); + expect(contentData).to.have.property('name', 'jwplayer.com'); + expect(contentData.ext).to.have.property('segtax', 502); + expect(contentData.ext).to.have.property('cids'); + expect(contentData.ext.cids).to.have.length(1); + expect(contentData.ext.cids[0]).to.equal(testMediaId); + expect(contentData.segment).to.deep.equal(testSegments); + }); + + it('should only set segtax and segment when segments are provided', function () { + const testMediaId = 'test_media_id'; + const contentData = getContentData(testMediaId); + expect(contentData).to.have.property('name', 'jwplayer.com'); + expect(contentData.ext.segtax).to.be.undefined; + expect(contentData.ext).to.have.property('cids'); + expect(contentData.ext.cids).to.have.length(1); + expect(contentData.ext.cids[0]).to.equal(testMediaId); + expect(contentData.segment).to.be.undefined; + }); + + it('should only set cids when a media id is provided', function () { + const testSegments = [{ id: 1 }, { id: 2 }]; + const contentData = getContentData(null, testSegments); + expect(contentData).to.have.property('name', 'jwplayer.com'); + expect(contentData.ext).to.have.property('segtax', 502); + expect(contentData.ext).to.not.have.property('cids'); + expect(contentData.segment).to.deep.equal(testSegments); + }); + + it('should return undefined when no params are provided', function () { + expect(getContentData()).to.be.undefined; }); }); From 32f4a5c7923c9aa07d1381b4e37c304f0e9ad35c Mon Sep 17 00:00:00 2001 From: Eugene Vigonny <79149590+EugeneVigonny@users.noreply.github.com> Date: Wed, 4 May 2022 01:36:02 +0600 Subject: [PATCH 19/44] Insticator Bid adapter: added support for additional attributes (#8342) * feat: added support for yob, gender, instl, secure, pos * fix: fix getStorageManager * refactor: changed the order of vars --- modules/insticatorBidAdapter.js | 18 +++++++-- .../spec/modules/insticatorBidAdapter_spec.js | 39 ++++++++++++------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 4122166a151..c967f530e75 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -72,17 +72,25 @@ function buildImpression(bidRequest) { ext.gpid = gpid; } + const instl = deepAccess(bidRequest, 'ortb2Imp.instl') + const secure = location.protocol === 'https:' ? 1 : 0; + const pos = deepAccess(bidRequest, 'mediaTypes.banner.pos'); + return { id: bidRequest.bidId, tagid: bidRequest.adUnitCode, + instl, + secure, banner: { format, + pos, }, ext, }; } function buildDevice() { + const deviceConfig = config.getConfig('device'); const device = { w: window.innerWidth, h: window.innerHeight, @@ -93,8 +101,6 @@ function buildDevice() { }, }; - const deviceConfig = config.getConfig('device'); - if (typeof deviceConfig === 'object') { Object.assign(device, deviceConfig); } @@ -115,13 +121,17 @@ function buildRegs(bidderRequest) { return {}; } -function buildUser() { +function buildUser(bid) { const userId = getUserId() || generateUUID(); + const yob = deepAccess(bid, 'params.user.yob') + const gender = deepAccess(bid, 'params.user.gender') setUserId(userId); return { id: userId, + yob, + gender, }; } @@ -158,7 +168,7 @@ function buildRequest(validBidRequests, bidderRequest) { }, device: buildDevice(), regs: buildRegs(bidderRequest), - user: buildUser(), + user: buildUser(validBidRequests[0]), imp: validBidRequests.map((bidRequest) => buildImpression(bidRequest)), ext: { insticator: { diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index e14207ba3e0..03e034f060a 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec, storage } from '../../../modules/insticatorBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js' -import { userSync } from '../../../src/userSync.js'; const USER_ID_KEY = 'hb_insticator_uid'; const USER_ID_DUMMY_VALUE = '74f78609-a92d-4cf1-869f-1b244bbfb5d2'; @@ -18,15 +17,21 @@ describe('InsticatorBidAdapter', function () { adUnitCode: 'adunit-code', params: { adUnitId: '1a2b3c4d5e6f1a2b3c4d', + user: { + yob: 1984, + gender: 'M' + }, }, sizes: [[300, 250], [300, 600]], mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [[300, 250], [300, 600]], + pos: 4, } }, bidId: '30b31c1838de1e', ortb2Imp: { + instl: 1, ext: { gpid: '1111/homepage' } @@ -91,16 +96,16 @@ describe('InsticatorBidAdapter', function () { }); it('should return false if there is no adUnitId param', () => { - expect(spec.isBidRequestValid({...bidRequest, ...{params: {}}})).to.be.false; + expect(spec.isBidRequestValid({ ...bidRequest, ...{ params: {} } })).to.be.false; }); it('should return false if there is no mediaTypes', () => { - expect(spec.isBidRequestValid({...bidRequest, ...{mediaTypes: {}}})).to.be.false; + expect(spec.isBidRequestValid({ ...bidRequest, ...{ mediaTypes: {} } })).to.be.false; }); it('should return false if there are no banner sizes and no sizes', () => { bidRequest.mediaTypes.banner = {}; - expect(spec.isBidRequestValid({...bidRequest, ...{sizes: {}}})).to.be.false; + expect(spec.isBidRequestValid({ ...bidRequest, ...{ sizes: {} } })).to.be.false; }); it('should return true if there is sizes and no banner sizes', () => { @@ -109,7 +114,7 @@ describe('InsticatorBidAdapter', function () { it('should return true if there is banner sizes and no sizes', () => { bidRequest.mediaTypes.banner.sizes = [[300, 250], [300, 600]]; - expect(spec.isBidRequestValid({...bidRequest, ...{sizes: {}}})).to.be.true; + expect(spec.isBidRequestValid({ ...bidRequest, ...{ sizes: {} } })).to.be.true; }); }); @@ -207,6 +212,10 @@ describe('InsticatorBidAdapter', function () { expect(data.regs.ext.gdprConsentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.user).to.be.an('object'); expect(data.user.id).to.equal(USER_ID_DUMMY_VALUE); + expect(data.user).to.have.property('yob'); + expect(data.user.yob).to.equal(1984); + expect(data.user).to.have.property('gender'); + expect(data.user.gender).to.equal('M'); expect(data.user.ext).to.have.property('eids'); expect(data.user.ext.eids).to.deep.equal([ { @@ -223,10 +232,12 @@ describe('InsticatorBidAdapter', function () { expect(data.imp).to.deep.equal([{ id: bidRequest.bidId, tagid: bidRequest.adUnitCode, + instl: 1, + secure: 0, banner: { format: [ - {w: 300, h: 250}, - {w: 300, h: 600}, + { w: 300, h: 250 }, + { w: 300, h: 600 }, ] }, ext: { @@ -256,7 +267,7 @@ describe('InsticatorBidAdapter', function () { expect(data.user.id).to.equal(USER_ID_STUBBED); }); it('should return empty regs object if no gdprConsent is passed', function () { - const requests = spec.buildRequests([bidRequest], {...bidderRequest, ...{gdprConsent: false}}); + const requests = spec.buildRequests([bidRequest], { ...bidderRequest, ...{ gdprConsent: false } }); const data = JSON.parse(requests[0].data); expect(data.regs).to.be.an('object').that.is.empty; }); @@ -384,14 +395,12 @@ describe('InsticatorBidAdapter', function () { width: 300, height: 200, mediaType: 'banner', - meta: { - advertiserDomains: [ - 'test1.com' - ], - test: 1 - }, ad: 'adm1', adUnitCode: 'adunit-code-1', + meta: { + advertiserDomains: ['test1.com'], + test: 1 + } }, { requestId: 'bid2', From 68a3c4e4e4982de6039bdcaf5841be8e32632d5e Mon Sep 17 00:00:00 2001 From: Chris Pabst Date: Wed, 4 May 2022 06:49:58 -0600 Subject: [PATCH 20/44] Sovrn Bid Adapter: Enforce required video parameters (#8276) * fix: add mime validation * fix: remove unused identifier & add required fields for bid Co-authored-by: test-name Co-authored-by: mikhalovich --- modules/sovrnBidAdapter.js | 19 ++++++++++++++++--- test/spec/modules/sovrnBidAdapter_spec.js | 12 ++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 4ca8f03c6b4..23571373147 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -30,6 +30,14 @@ const ORTB_VIDEO_PARAMS = { 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) } +const REQUIRED_VIDEO_PARAMS = { + context: (value) => value !== ADPOD, + mimes: ORTB_VIDEO_PARAMS.mimes, + minduration: ORTB_VIDEO_PARAMS.minduration, + maxduration: ORTB_VIDEO_PARAMS.maxduration, + protocols: ORTB_VIDEO_PARAMS.protocols +} + export const spec = { code: 'sovrn', supportedMediaTypes: [BANNER, VIDEO], @@ -40,12 +48,17 @@ export const spec = { * @param {object} bid the Sovrn bid to validate * @return boolean for whether or not a bid is valid */ - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { + const video = bid?.mediaTypes?.video return !!( bid.params.tagid && !isNaN(parseFloat(bid.params.tagid)) && - isFinite(bid.params.tagid) && - deepAccess(bid, 'mediaTypes.video.context') !== ADPOD + isFinite(bid.params.tagid) && ( + !video || ( + Object.keys(REQUIRED_VIDEO_PARAMS) + .every(key => REQUIRED_VIDEO_PARAMS[key](video[key])) + ) + ) ) }, diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 09a61c82b6c..ee25f729a0a 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -51,6 +51,18 @@ describe('sovrnBidAdapter', function() { expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); + + it('should return false when require video params are not passed', function () { + const bidRequest = { + ...baseBidRequest, + 'mediaTypes': { + 'video': { + } + } + } + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); }); describe('buildRequests', function () { From e1169ec5cc1f7e960085e89b4c64b354964a6f3c Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Wed, 4 May 2022 15:03:30 +0200 Subject: [PATCH 21/44] Livewrapped adapter: Original ortb2 config was unintentionally modified (#8368) * Original ortb2 config was modified when both ortb2 config and eids were present * Handle empty ortb2 config --- modules/livewrappedBidAdapter.js | 2 +- test/spec/modules/livewrappedBidAdapter_spec.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 32e09e4b28e..8afeaf80652 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -67,7 +67,7 @@ export const spec = { var adRequests = bidRequests.map(bidToAdRequest); if (eids) { - ortb2 = mergeDeep(ortb2 || {}, eids); + ortb2 = mergeDeep(mergeDeep({}, ortb2 || {}), eids); } const payload = { diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index fac1ae1d6a0..6e4245dd82c 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -890,9 +890,10 @@ describe('Livewrapped adapter tests', function () { sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); let origGetConfig = config.getConfig; + let orgOrtb2 = {user: {ext: {prop: 'value'}}}; sandbox.stub(config, 'getConfig').callsFake(function (key) { if (key === 'ortb2') { - return {user: {ext: {prop: 'value'}}}; + return orgOrtb2; } return origGetConfig.apply(config, arguments); }); @@ -914,6 +915,7 @@ describe('Livewrapped adapter tests', function () { var expected = {user: {ext: {prop: 'value', eids: testbidRequest.bids[0].userIdAsEids}}} expect(data.rtbData).to.deep.equal(expected); + expect(orgOrtb2).to.deep.equal({user: {ext: {prop: 'value'}}}); }); it('should send schain object if available', function() { From d632468b8d025f8bc60baed9019c286e042d3358 Mon Sep 17 00:00:00 2001 From: Noam Tzuberi Date: Wed, 4 May 2022 17:24:26 +0300 Subject: [PATCH 22/44] MimuteMedia - Add support for Banner (#8292) --- modules/minutemediaBidAdapter.js | 341 +++++++++++------- modules/minutemediaBidAdapter.md | 59 ++- .../modules/minutemediaBidAdapter_spec.js | 318 +++++++++------- 3 files changed, 444 insertions(+), 274 deletions(-) diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index 1a9ccfdf824..604a5dd7ea8 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -1,17 +1,17 @@ -import { logWarn, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter } from '../src/utils.js'; +import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -const SUPPORTED_AD_TYPES = [VIDEO]; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'minutemedia'; -const ADAPTER_VERSION = '5.0.1'; +const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const CURRENCY = 'USD'; const SELLER_ENDPOINT = 'https://hb.minutemedia-prebid.com/'; const MODES = { - PRODUCTION: 'hb-mm', - TEST: 'hb-mm-test' + PRODUCTION: 'hb-mm-multi', + TEST: 'hb-multi-mm-test' } const SUPPORTED_SYNC_METHODS = { IFRAME: 'iframe', @@ -23,7 +23,7 @@ export const spec = { gvlid: 918, version: ADAPTER_VERSION, supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid: function(bidRequest) { + isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to MinuteMedia adapter'); return false; @@ -36,54 +36,70 @@ export const spec = { return true; }, - buildRequests: function (bidRequests, bidderRequest) { - if (bidRequests.length === 0) { - return []; - } + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; - const requests = []; + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; - bidRequests.forEach(bid => { - requests.push(buildVideoRequest(bid, bidderRequest)); - }); + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - return requests; + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject + } }, - interpretResponse: function({body}) { + interpretResponse: function ({body}) { const bidResponses = []; - const bidResponse = { - requestId: body.requestId, - cpm: body.cpm, - width: body.width, - height: body.height, - creativeId: body.requestId, - currency: body.currency, - netRevenue: body.netRevenue, - ttl: body.ttl || TTL, - vastXml: body.vastXml, - mediaType: VIDEO - }; - - if (body.adomain && body.adomain.length) { - bidResponse.meta = {}; - bidResponse.meta.advertiserDomains = body.adomain + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.requestId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType + } + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; + } + + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } + + bidResponses.push(bidResponse); + }); } - bidResponses.push(bidResponse); return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.userSyncURL) { + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { syncs.push({ type: 'iframe', - url: response.body.userSyncURL + url: response.body.params.userSyncURL }); } - if (syncOptions.pixelEnabled && isArray(response.body.userSyncPixels)) { - const pixels = response.body.userSyncPixels.map(pixel => { + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel @@ -93,6 +109,16 @@ export const spec = { } } return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } } }; @@ -103,46 +129,33 @@ registerBidder(spec); * @param bid {bid} * @returns {Number} */ -function getFloor(bid) { +function getFloor(bid, mediaType) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ currency: CURRENCY, - mediaType: VIDEO, + mediaType: mediaType, size: '*' }); return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; } /** - * Build the video request - * @param bid {bid} - * @param bidderRequest {bidderRequest} - * @returns {Object} - */ -function buildVideoRequest(bid, bidderRequest) { - const sellerParams = generateParameters(bid, bidderRequest); - const {params} = bid; - return { - method: 'GET', - url: getEndpoint(params.testMode), - data: sellerParams - }; -} - -/** - * Get the the ad size from the bid + * Get the the ad sizes array from the bid * @param bid {bid} * @returns {Array} */ -function getSizes(bid) { - if (deepAccess(bid, 'mediaTypes.video.sizes')) { - return bid.mediaTypes.video.sizes[0]; +function getSizesArray(bid, mediaType) { + let sizesArray = [] + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - return bid.sizes[0]; + sizesArray = bid.sizes; } - return []; + + return sizesArray; } /** @@ -239,122 +252,180 @@ function getDeviceType(ua) { return '1'; } +function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + /** - * Generate query parameters for the request - * @param bid {bid} - * @param bidderRequest {bidderRequest} - * @returns {Object} + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object */ -function generateParameters(bid, bidderRequest) { +function generateBidParameters(bid, bidderRequest) { const {params} = bid; - const timeout = config.getConfig('bidderTimeout'); - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const [width, height] = getSizes(bid); - const {bidderCode} = bidderRequest; - const domain = window.location.hostname; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); // fix floor price in case of NAN if (isNaN(params.floorPrice)) { params.floorPrice = 0; } - const requestParams = { + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: getBidIdParameter('transactionId', bid), + }; + + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; + } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + if (mediaType === VIDEO) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + // verify playbackMethod is of type integer array, or integer only. + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + } + + return bidObject; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +/** + * Generate params that are common between all bids + * @param {single bid object} generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object + */ +function generateGeneralParams(generalObject, bidderRequest) { + const domain = window.location.hostname; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const {bidderCode} = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = config.getConfig('bidderTimeout'); + + // these params are snake_case instead of camelCase to allow backwards compatability on the server. + // in the future, these will be converted to camelCase to match our convention. + const generalParams = { wrapper_type: 'prebidjs', wrapper_vendor: '$$PREBID_GLOBAL$$', wrapper_version: '$prebid.version$', adapter_version: ADAPTER_VERSION, auction_start: timestamp(), - ad_unit_code: getBidIdParameter('adUnitCode', bid), - tmax: timeout, - width: width, - height: height, - publisher_id: params.org, - floor_price: Math.max(getFloor(bid), params.floorPrice), - ua: navigator.userAgent, - bid_id: getBidIdParameter('bidId', bid), - bidder_request_id: getBidIdParameter('bidderRequestId', bid), - transaction_id: getBidIdParameter('transactionId', bid), - session_id: getBidIdParameter('auctionId', bid), + publisher_id: generalBidParams.org, publisher_name: domain, site_domain: domain, dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent) - }; + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + session_id: getBidIdParameter('auctionId', generalObject), + tmax: timeout + } - const userIdsParam = getBidIdParameter('userId', bid); + const userIdsParam = getBidIdParameter('userId', generalObject); if (userIdsParam) { - requestParams.userIds = JSON.stringify(userIdsParam); + generalParams.userIds = JSON.stringify(userIdsParam); } const ortb2Metadata = config.getConfig('ortb2') || {}; if (ortb2Metadata.site) { - requestParams.site_metadata = JSON.stringify(ortb2Metadata.site); + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); } if (ortb2Metadata.user) { - requestParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - const playbackMethod = deepAccess(bid, 'mediaTypes.video.playbackmethod'); - if (playbackMethod) { - requestParams.playback_method = playbackMethod; - } - const placement = deepAccess(bid, 'mediaTypes.video.placement'); - if (placement) { - requestParams.placement = placement; - } - const pos = deepAccess(bid, 'mediaTypes.video.pos'); - if (pos) { - requestParams.pos = pos; - } - const minduration = deepAccess(bid, 'mediaTypes.video.minduration'); - if (minduration) { - requestParams.min_duration = minduration; - } - const maxduration = deepAccess(bid, 'mediaTypes.video.maxduration'); - if (maxduration) { - requestParams.max_duration = maxduration; - } - const skip = deepAccess(bid, 'mediaTypes.video.skip'); - if (skip) { - requestParams.skip = skip; - } - const linearity = deepAccess(bid, 'mediaTypes.video.linearity'); - if (linearity) { - requestParams.linearity = linearity; - } - - if (params.placementId) { - requestParams.placement_id = params.placementId; + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); } if (syncEnabled) { const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); if (allowedSyncMethod) { - requestParams.cs_method = allowedSyncMethod; + generalParams.cs_method = allowedSyncMethod; } } if (bidderRequest.uspConsent) { - requestParams.us_privacy = bidderRequest.uspConsent; + generalParams.us_privacy = bidderRequest.uspConsent; } if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; } - if (params.ifa) { - requestParams.ifa = params.ifa; + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; } - if (bid.schain) { - requestParams.schain = getSupplyChain(bid.schain); + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); } if (bidderRequest && bidderRequest.refererInfo) { - requestParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); - requestParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); + generalParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); } - return requestParams; + return generalParams } diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md index 348cc586e08..70f106a745f 100644 --- a/modules/minutemediaBidAdapter.md +++ b/modules/minutemediaBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to MinuteMedia's demand sources. The MinuteMedia adapter requires setup and approval from the MinuteMedia. Please reach out to hb@minutemedia.com to create an MinuteMedia account. -The adapter supports Video(instream). +The adapter supports Video(instream) & Banner. # Bid Parameters ## Video @@ -27,25 +27,50 @@ The adapter supports Video(instream). # Test Parameters ```javascript -var adUnits = [ - { +var adUnits = [{ code: 'dfp-video-div', - sizes: [[640, 480]], + sizes: [ + [640, 480] + ], mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - } + video: { + playerSize: [ + [640, 480] + ], + context: 'instream' + } }, bids: [{ - bidder: 'minutemedia', - params: { - org: '56f91cd4d3e3660002000033', // Required - floorPrice: 2.00, // Optional - placementId: '12345678', // Optional - testMode: false // Optional - } + bidder: 'minutemedia', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + placementId: 'video-test', // Optional + testMode: false // Optional + } }] - } - ]; + }, + { + code: 'dfp-banner-div', + sizes: [ + [640, 480] + ], + mediaTypes: { + banner: { + sizes: [ + [640, 480] + ] + } + }, + bids: [{ + bidder: 'minutemedia', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + placementId: 'banner-test', // Optional + testMode: false // Optional + } + }] + } +]; ``` diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index b1ad7f96bc4..cce08e615a3 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -2,12 +2,13 @@ import { expect } from 'chai'; import { spec } from 'modules/minutemediaBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { VIDEO } from '../../../src/mediaTypes.js'; -import { deepClone } from 'src/utils.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from 'src/utils.js'; -const ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm'; -const TEST_ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm-test'; +const ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm-multi'; +const TEST_ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-multi-mm-test'; const TTL = 360; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ describe('minutemediaAdapter', function () { const adapter = newBidder(spec); @@ -54,6 +55,29 @@ describe('minutemediaAdapter', function () { 'bidId': '299ffc8cca0b87', 'bidderRequestId': '1144f487e563f9', 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream' + } + }, + 'vastXml': '"..."' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + } + }, + 'ad': '""' } ]; @@ -77,44 +101,41 @@ describe('minutemediaAdapter', function () { } const placementId = '12345678'; - it('sends the placementId as a query param', function () { + it('sends the placementId to ENDPOINT via POST', function () { bidRequests[0].params.placementId = placementId; - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data.placement_id).to.equal(placementId); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].placementId).to.equal(placementId); }); - it('sends bid request to ENDPOINT via GET', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('GET'); - } + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); }); - it('sends bid request to test ENDPOINT via GET', function () { - const requests = spec.buildRequests(testModeBidRequests, bidderRequest); - for (const request of requests) { - expect(request.url).to.equal(TEST_ENDPOINT); - expect(request.method).to.equal('GET'); - } + it('sends bid request to TEST ENDPOINT via POST', function () { + const request = spec.buildRequests(testModeBidRequests, bidderRequest); + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('POST'); }); it('should send the correct bid Id', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data.bid_id).to.equal('299ffc8cca0b87'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); }); - it('should send the correct width and height', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('width', 640); - expect(request.data).to.have.property('height', 480); - } + it('should send the correct sizes array', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sizes).to.be.an('array'); + expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) + expect(request.data.bids[1].sizes).to.be.an('array'); + expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + }); + + it('should send the correct media type', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].mediaType).to.equal(VIDEO) + expect(request.data.bids[1].mediaType).to.equal(BANNER) }); it('should respect syncEnabled option', function() { @@ -129,11 +150,9 @@ describe('minutemediaAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('cs_method'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); }); it('should respect "iframe" filter settings', function () { @@ -148,11 +167,9 @@ describe('minutemediaAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'iframe'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); }); it('should respect "all" filter settings', function () { @@ -167,24 +184,21 @@ describe('minutemediaAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'iframe'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); }); it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.resetConfig(); config.setConfig({ userSync: { - syncEnabled: true + syncEnabled: true, } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'pixel'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); }); it('should respect total exclusion', function() { @@ -203,48 +217,38 @@ describe('minutemediaAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('cs_method'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); }); it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('us_privacy', '1YNN'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('us_privacy', '1YNN'); }); it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('us_privacy'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('us_privacy'); }); it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('gdpr'); - expect(request.data).to.not.have.property('gdpr_consent'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gdpr'); + expect(request.data.params).to.not.have.property('gdpr_consent'); }); it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('gdpr', true); - expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gdpr', true); + expect(request.data.params).to.have.property('gdpr_consent', 'test-consent-string'); }); it('should have schain param if it is available in the bidRequest', () => { @@ -254,15 +258,13 @@ describe('minutemediaAdapter', function () { nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], }; bidRequests[0].schain = schain; - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); }); - it('should set floor_price to getFloor.floor value if it is greater than params.floorPrice', function() { - const bid = deepClone(bidRequests[0]); + it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = utils.deepClone(bidRequests[0]); bid.getFloor = () => { return { currency: 'USD', @@ -270,13 +272,13 @@ describe('minutemediaAdapter', function () { } } bid.params.floorPrice = 0.64; - const request = spec.buildRequests([bid], bidderRequest)[0]; - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('floor_price', 3.32); + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 3.32); }); - it('should set floor_price to params.floorPrice value if it is greater than getFloor.floor', function() { - const bid = deepClone(bidRequests[0]); + it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = utils.deepClone(bidRequests[0]); bid.getFloor = () => { return { currency: 'USD', @@ -284,61 +286,109 @@ describe('minutemediaAdapter', function () { } } bid.params.floorPrice = 1.5; - const request = spec.buildRequests([bid], bidderRequest)[0]; - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('floor_price', 1.5); + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); }); describe('interpretResponse', function () { const response = { + params: { + currency: 'USD', + netRevenue: true, + }, + bids: [{ + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: VIDEO + }, + { + cpm: 12.5, + ad: '""', + width: 300, + height: 250, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: BANNER + }] + }; + + const expectedVideoResponse = { + requestId: '21e12606d47ba7', cpm: 12.5, - vastXml: '', + currency: 'USD', width: 640, height: 480, - requestId: '21e12606d47ba7', + ttl: TTL, + creativeId: '21e12606d47ba7', netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: VIDEO, + meta: { + mediaType: VIDEO, + advertiserDomains: ['abc.com'] + }, + vastXml: '', + }; + + const expectedBannerResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, currency: 'USD', - adomain: ['abc.com'] + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: BANNER, + meta: { + mediaType: BANNER, + advertiserDomains: ['abc.com'] + }, + ad: '""' }; it('should get correct bid response', function () { - let expectedResponse = [ - { - requestId: '21e12606d47ba7', - cpm: 12.5, - width: 640, - height: 480, - creativeId: '21e12606d47ba7', - currency: 'USD', - netRevenue: true, - ttl: TTL, - vastXml: '', - mediaType: VIDEO, - meta: { - advertiserDomains: ['abc.com'] - } - } - ]; const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + }); + + it('video type should have vastXml key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[0].vastXml).to.equal(expectedVideoResponse.vastXml) + }); + + it('banner type should have ad key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); }) describe('getUserSyncs', function() { const imageSyncResponse = { body: { - userSyncPixels: [ - 'https://image-sync-url.test/1', - 'https://image-sync-url.test/2', - 'https://image-sync-url.test/3' - ] + params: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } } }; const iframeSyncResponse = { body: { - userSyncURL: 'https://iframe-sync-url.test' + params: { + userSyncURL: 'https://iframe-sync-url.test' + } } }; @@ -402,4 +452,28 @@ describe('minutemediaAdapter', function () { expect(syncs).to.deep.equal([]); }); }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should trigger pixel if bid nurl', function() { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'nurl': 'http://example.com/win/1234', + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + spec.onBidWon(bid); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) }); From 97ae929aee9c909587ca5fce2580c910db67c2cd Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 4 May 2022 07:34:48 -0700 Subject: [PATCH 23/44] UserID module: fix bug with userID init sometimes getting stuck in an infinite loop (#8362) In some situations userID submodules can throw exceptions (https://github.com/prebid/Prebid.js/issues/8360) which then, after https://github.com/prebid/Prebid.js/pull/8201, causes the ID system to get stuck in an infinite loop. --- modules/userId/index.js | 7 +++++-- test/spec/modules/userId_spec.js | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 44fbc871bb5..045786f59ec 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -520,6 +520,8 @@ function delayFor(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } +const INIT_CANCELED = {}; + function idSystemInitializer({delay = delayFor} = {}) { /** * @returns a {promise, resolve, reject} trio where `promise` is resolved by calling `resolve` or `reject`. @@ -562,7 +564,7 @@ function idSystemInitializer({delay = delayFor} = {}) { function cancelAndTry(promise) { if (cancel != null) { - cancel.reject(); + cancel.reject(INIT_CANCELED); } cancel = breakpoint(); return Promise.race([promise, cancel.promise]); @@ -803,8 +805,9 @@ function refreshUserIds({submoduleNames} = {}, callback) { * }); * ``` */ + function getUserIdsAsync() { - return initIdSystem().then(() => getUserIds(), () => getUserIdsAsync()); + return initIdSystem().then(() => getUserIds(), (e) => e === INIT_CANCELED ? getUserIdsAsync() : Promise.reject(e)); } /** diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 0829aa9927a..48d7ea6d53e 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -497,6 +497,14 @@ describe('User ID', function () { mockIdCallback.callArg(0, {id: {MOCKID: '1111'}}); }) }); + + it('should not get stuck when init fails', () => { + const err = new Error(); + mockIdCallback.callsFake(() => { throw err; }); + return getGlobal().getUserIdsAsync().catch((e) => + expect(e).to.equal(err) + ); + }); }); it('pbjs.refreshUserIds updates submodules', function(done) { From 645abee2c446eca70d22ebd37c1ef76995d999c7 Mon Sep 17 00:00:00 2001 From: Justas Pupelis Date: Thu, 5 May 2022 23:36:20 +0300 Subject: [PATCH 24/44] Adf adapter: add coppa signal support (#8375) * Coppa signal support in adf adapter * Update Co-authored-by: Justas Pupelis --- modules/adfBidAdapter.js | 5 +++++ test/spec/modules/adfBidAdapter_spec.js | 27 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index f0425a174ff..0b9c72a2cee 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -206,6 +206,11 @@ export const spec = { request.is_debug = !!test; request.test = 1; } + + if (config.getConfig('coppa')) { + deepSetValue(request, 'regs.coppa', 1); + } + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index ed096e7189d..25f7fda4419 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -178,6 +178,33 @@ describe('Adf adapter', function () { assert.equal(request.source.fd, 1); }); + it('should not set coppa when coppa is not provided or is set to false', function () { + config.setConfig({ + }); + let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; + let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.regs.coppa, undefined); + + config.setConfig({ + coppa: false + }); + request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.regs.coppa, undefined); + }); + + it('should set coppa to 1 when coppa is provided with value true', function () { + config.setConfig({ + coppa: true + }); + let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.regs.coppa, 1); + }); + it('should send info about device', function () { config.setConfig({ device: { w: 100, h: 100 } From 2697d17bb9340b7eb478bc8cabd903c2d4938677 Mon Sep 17 00:00:00 2001 From: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Thu, 5 May 2022 23:37:51 +0300 Subject: [PATCH 25/44] bizzclickAdapter refactiring, remove privacy settings duplication (#8377) --- modules/bizzclickBidAdapter.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 6223626834d..a798671cbaf 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -95,6 +95,7 @@ export const spec = { }, regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} }, user: { ext: {} @@ -106,25 +107,15 @@ export const spec = { imp: [impObject], }; - if (bidderRequest && bidderRequest.uspConsent) { - data.regs.ext.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - data.regs.ext.gdpr = gdprApplies ? 1 : 0; - data.user.ext.consent = consentString; - } - - if (bidRequest.schain) { - deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } - let connection = navigator.connection || navigator.webkitConnection; if (connection && connection.effectiveType) { data.device.connectiontype = connection.effectiveType; } if (bidRequest) { + if (bidRequest.schain) { + deepSetValue(data, 'source.ext.schain', bidRequest.schain); + } + if (bidRequest.gdprConsent && bidRequest.gdprConsent.gdprApplies) { deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); From ce156c7d3983ad3e48acd4425cab4cae7ecf6237 Mon Sep 17 00:00:00 2001 From: Yuki Tsujii Date: Fri, 6 May 2022 19:19:31 +0900 Subject: [PATCH 26/44] Bid Richemedia adapter : use a 1x1 creative (#8327) --- modules/big-richmediaBidAdapter.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js index cd8b2462eb8..2ee31e8cfd6 100644 --- a/modules/big-richmediaBidAdapter.js +++ b/modules/big-richmediaBidAdapter.js @@ -8,7 +8,7 @@ const BIDDER_CODE = 'big-richmedia'; const metadataByRequestId = {}; export const spec = { - version: '1.4.0', + version: '1.5.0', code: BIDDER_CODE, gvlid: baseAdapter.GVLID, // use base adapter gvlid supportedMediaTypes: [ BANNER, VIDEO ], @@ -78,6 +78,14 @@ export const spec = { customSelector, isReplayable }; + + // This is a workaround needed for the rendering step (so that the adserver iframe does not get resized to 1800x1000 + // when there is skin demand + if (format === 'skin') { + renderParams.width = 1 + renderParams.height = 1 + } + const encoded = window.btoa(JSON.stringify(renderParams)); bid.ad = ` `; From 2840e3e30b4e99de3e02d6a7f50d26117d05f268 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 6 May 2022 10:32:41 +0000 Subject: [PATCH 27/44] Prebid 6.23.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4e86036f99..34e98660ede 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.23.0-pre", + "version": "6.23.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9e5d68ac5e8..6be5102e3c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.23.0-pre", + "version": "6.23.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 8e903db12d15690fc727644ccfe53139de6d54ce Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 6 May 2022 10:32:41 +0000 Subject: [PATCH 28/44] Increment version to 6.24.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 34e98660ede..c5797787eb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.23.0", + "version": "6.24.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6be5102e3c2..f2d282c77aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.23.0", + "version": "6.24.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 1a2ed86b52b7cfc18787ea3f5a6469551378cf63 Mon Sep 17 00:00:00 2001 From: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Date: Fri, 6 May 2022 13:49:58 +0300 Subject: [PATCH 29/44] Adman Bid Adapter: add support for idx UserID(#8370) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Adman bid adapter * Add supportedMediaTypes property * Update ADman Media bidder adapter * Remove console.log * Fix typo * revert package-json.lock * Delete package-lock.json * back to original package-lock.json * catch pbjs error * catch pbjs error * catch pbjs error * log * remove eu url * remove eu url * remove eu url * remove eu url * remove eu url * Update admanBidAdapter.js add consnet to sync url * Update admanBidAdapter.js fix import * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js check consent object data availability * сompatible with prebid v5 * add Lotame Panorama ID * update getUserSyncs * fix * fix tests * remove package-lock.json * update sync url * update test * add idx (UserID Module) * update tests Co-authored-by: minoru katogi Co-authored-by: minoru katogi Co-authored-by: ADman Media Co-authored-by: SmartyAdman --- modules/admanBidAdapter.js | 1 + test/spec/modules/admanBidAdapter_spec.js | 227 ++++++++++++++++++---- 2 files changed, 190 insertions(+), 38 deletions(-) diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index 241864c50fc..21bcb6cee26 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -110,6 +110,7 @@ export const spec = { if (bid.userId) { getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); + getUserId(placement.eids, bid.userId.idx, 'idx.lat'); } if (traff === VIDEO) { placement.playerSize = bid.mediaTypes[VIDEO].playerSize; diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index 89d140a7f25..feee8e39b45 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -1,8 +1,9 @@ import {expect} from 'chai'; import {spec} from '../../../modules/admanBidAdapter.js'; +import {deepClone} from '../../../src/utils' describe('AdmanAdapter', function () { - let bid = { + let bidBanner = { bidId: '2dd581a2b6281d', bidder: 'adman', bidderRequestId: '145e1d6a7837c9', @@ -32,6 +33,20 @@ describe('AdmanAdapter', function () { ] } }; + + let bidVideo = deepClone({ + ...bidBanner, + params: { + placementId: 0, + traffic: 'video' + }, + mediaTypes: { + video: { + playerSize: [300, 250] + } + } + }); + let bidderRequest = { bidderCode: 'adman', auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', @@ -40,25 +55,27 @@ describe('AdmanAdapter', function () { auctionStart: 1472239426000, timeout: 5000, uspConsent: '1YN-', + gdprConsent: 'gdprConsent', refererInfo: { referer: 'http://www.example.com', reachedTop: true, }, - bids: [bid] + bids: [bidBanner, bidVideo] } describe('isBidRequestValid', function () { it('Should return true when placementId can be cast to a number', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bidBanner)).to.be.true; }); it('Should return false when placementId is not a number', function () { - bid.params.placementId = 'aaa'; - expect(spec.isBidRequestValid(bid)).to.be.false; + bidBanner.params.placementId = 'aaa'; + expect(spec.isBidRequestValid(bidBanner)).to.be.false; + bidBanner.params.placementId = 0; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests([bidBanner], bidderRequest); it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -75,10 +92,11 @@ describe('AdmanAdapter', function () { expect(serverRequest.data.ccpa).to.be.an('string') }) - it('Returns valid data if array of bids is valid', function () { + it('Returns valid BANNER data if array of bids is valid', function () { + serverRequest = spec.buildRequests([bidBanner], bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); @@ -97,6 +115,33 @@ describe('AdmanAdapter', function () { expect(placement.bidFloor).to.be.an('number'); } }); + + it('Returns valid VIDEO data if array of bids is valid', function () { + serverRequest = spec.buildRequests([bidVideo], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placements = data['placements']; + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor', + 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', + 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); + expect(placement.schain).to.be.an('object') + expect(placement.placementId).to.be.a('number'); + expect(placement.bidId).to.be.a('string'); + expect(placement.traffic).to.be.a('string'); + expect(placement.sizes).to.be.an('array'); + expect(placement.bidFloor).to.be.an('number'); + } + }); + it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); let data = serverRequest.data; @@ -105,9 +150,9 @@ describe('AdmanAdapter', function () { }); describe('buildRequests with user ids', function () { - bid.userId = {} - bid.userId.uid2 = { id: 'uid2id123' }; - let serverRequest = spec.buildRequests([bid], bidderRequest); + bidBanner.userId = {} + bidBanner.userId.uid2 = { id: 'uid2id123' }; + let serverRequest = spec.buildRequests([bidBanner], bidderRequest); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; let placements = data['placements']; @@ -130,31 +175,34 @@ describe('AdmanAdapter', function () { }); describe('interpretResponse', function () { - let resObject = { - body: [ { - requestId: '123', - mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['google.com'], - advertiserId: 1234 - } - } ] - }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { + it('(BANNER) Returns an array of valid server responses if response object is valid', function () { + const resBannerObject = { + body: [ { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + adomain: ['example.com'], + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + } ] + }; + + const serverResponses = spec.interpretResponse(resBannerObject); + expect(serverResponses).to.be.an('array').that.is.not.empty; for (let i = 0; i < serverResponses.length; i++) { let dataItem = serverResponses[i]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'meta'); + 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); expect(dataItem.requestId).to.be.a('string'); expect(dataItem.cpm).to.be.a('number'); expect(dataItem.width).to.be.a('number'); @@ -167,21 +215,124 @@ describe('AdmanAdapter', function () { expect(dataItem.mediaType).to.be.a('string'); expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); } - it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; - }); + }); + + it('(VIDEO) Returns an array of valid server responses if response object is valid', function () { + const resVideoObject = { + body: [ { + requestId: '123', + mediaType: 'video', + cpm: 0.3, + width: 320, + height: 50, + vastUrl: 'https://', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + adomain: ['example.com'], + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + } ] + }; + + const serverResponses = spec.interpretResponse(resVideoObject); + + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.vastUrl).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + } + }); + + it('(NATIVE) Returns an array of valid server responses if response object is valid', function () { + const resNativeObject = { + body: [ { + requestId: '123', + mediaType: 'native', + cpm: 0.3, + width: 320, + height: 50, + native: { + title: 'title', + image: 'image', + impressionTrackers: [ 'https://' ] + }, + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + adomain: ['example.com'], + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + } ] + }; + + const serverResponses = spec.interpretResponse(resNativeObject); + + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.native).to.be.an('object'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + } + }); + + it('Invalid mediaType in response', function () { + const resBadObject = { + body: [ { + mediaType: 'other', + requestId: '123', + cpm: 0.3, + ttl: 1000, + creativeId: '123asd', + currency: 'USD' + } ] + }; + + const serverResponses = spec.interpretResponse(resBadObject); + + expect(serverResponses).to.be.an('array').that.is.empty; }); }); describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs({}); + const gdprConsent = { consentString: 'consentString', gdprApplies: 1 }; + const consentString = { consentString: 'consentString' } + let userSync = spec.getUserSyncs({}, {}, gdprConsent, consentString); it('Returns valid URL and type', function () { expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://sync.admanmedia.com/image?pbjs=1&coppa=0'); + expect(userSync[0].url).to.be.equal('https://sync.admanmedia.com/image?pbjs=1&gdpr=0&gdpr_consent=consentString&ccpa_consent=consentString&coppa=0'); }); }); }); From 0f87f02c1b35a5f753f6adca79479e15dfe545c6 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 6 May 2022 10:00:50 -0700 Subject: [PATCH 30/44] Prebid core: do not enforce adapters' mediaType support for PBS bids (#8357) This fixes adUnit mediaType validation to skip bids that will get routed to PBS (https://github.com/prebid/Prebid.js/issues/8291), and also removes overzealous mediaTytpe validation from the PBS adapter (by the time it gets there, adUnits have already been validated) --- modules/prebidServerBidAdapter/index.js | 11 +++-------- src/prebid.js | 16 +++------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b1b54d9e313..8279da13054 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1238,18 +1238,13 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let { gdprConsent } = getConsentData(bidRequests); const adUnits = deepClone(s2sBidRequest.ad_units); - // at this point ad units should have a size array either directly or mapped so filter for that - const validAdUnits = adUnits.filter(unit => - unit.mediaTypes && (unit.mediaTypes.native || (unit.mediaTypes.banner && unit.mediaTypes.banner.sizes) || (unit.mediaTypes.video && unit.mediaTypes.video.playerSize)) - ); - // in case config.bidders contains invalid bidders, we only process those we sent requests for - const requestedBidders = validAdUnits + const requestedBidders = adUnits .map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(uniques)) - .reduce(flatten) + .reduce(flatten, []) .filter(uniques); - const ortb2 = new ORTB2(s2sBidRequest, bidRequests, validAdUnits, requestedBidders); + const ortb2 = new ORTB2(s2sBidRequest, bidRequests, adUnits, requestedBidders); const request = ortb2.buildRequest(); const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); diff --git a/src/prebid.js b/src/prebid.js index 98655825e89..4a7d01dfe76 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -20,7 +20,7 @@ import { executeRenderer, isRendererRequired } from './Renderer.js'; import { createBid } from './bidfactory.js'; import { storageCallbacks } from './storageManager.js'; import { emitAdRenderSucceeded, emitAdRenderFail } from './adRendering.js'; -import { gdprDataHandler, uspDataHandler } from './adapterManager.js' +import {gdprDataHandler, getS2SBidderSet, uspDataHandler} from './adapterManager.js'; const $$PREBID_GLOBAL$$ = getGlobal(); const CONSTANTS = require('./constants.json'); @@ -578,17 +578,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); - let _s2sConfigs = []; - const s2sBidders = []; - config.getConfig('s2sConfig', config => { - if (config && config.s2sConfig) { - _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; - } - }); - - _s2sConfigs.forEach(s2sConfig => { - s2sBidders.push(...s2sConfig.bidders); - }); + const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); adUnits = checkAdUnitSetup(adUnits); @@ -614,7 +604,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo const allBidders = adUnit.bids.map(bid => bid.bidder); const bidderRegistry = adapterManager.bidderRegistry; - const bidders = (s2sBidders) ? allBidders.filter(bidder => !includes(s2sBidders, bidder)) : allBidders; + const bidders = allBidders.filter(bidder => !s2sBidders.has(bidder)); adUnit.transactionId = generateUUID(); From a47650b680dbcb52b369b432bb5fd73cc87c48fc Mon Sep 17 00:00:00 2001 From: Chris Pabst Date: Mon, 9 May 2022 06:25:32 -0600 Subject: [PATCH 31/44] Sovrn Bid Adapter: handle multiple seatbids in response (#8378) * EX-1752 Add seatbid array support in sovrn adapter * Resolved conflicts. * Fix indentation Co-authored-by: mikhalovich --- modules/sovrnBidAdapter.js | 37 +++++++------ test/spec/modules/sovrnBidAdapter_spec.js | 64 +++++++++++++++++++---- 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 23571373147..eed9ccb7461 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -64,8 +64,9 @@ export const spec = { /** * Format the bid request object for our endpoint - * @param {BidRequest[]} bidRequests Array of Sovrn bidders * @return object of parameters for Prebid AJAX request + * @param bidReqs + * @param bidderRequest */ buildRequests: function(bidReqs, bidderRequest) { try { @@ -192,14 +193,12 @@ export const spec = { * @return {Bid[]} An array of formatted bids. */ interpretResponse: function({ body: {id, seatbid} }) { + if (!id || !seatbid || !Array.isArray(seatbid)) return [] + try { - let sovrnBidResponses = []; - if (id && - seatbid && - seatbid.length > 0 && - seatbid[0].bid && - seatbid[0].bid.length > 0) { - seatbid[0].bid.map(sovrnBid => { + return seatbid + .filter(seat => seat) + .map(seat => seat.bid.map(sovrnBid => { const bid = { requestId: sovrnBid.impid, cpm: parseFloat(sovrnBid.price), @@ -209,23 +208,23 @@ export const spec = { dealId: sovrnBid.dealid || null, currency: 'USD', netRevenue: true, - ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90, + mediaType: sovrnBid.nurl ? BANNER : VIDEO, + ttl: sovrnBid.ext?.ttl || 90, meta: { advertiserDomains: sovrnBid && sovrnBid.adomain ? sovrnBid.adomain : [] } } - if (!sovrnBid.nurl) { - bid.mediaType = VIDEO - bid.vastXml = decodeURIComponent(sovrnBid.adm) - } else { - bid.mediaType = BANNER + if (sovrnBid.nurl) { bid.ad = decodeURIComponent(`${sovrnBid.adm}`) + } else { + bid.vastXml = decodeURIComponent(sovrnBid.adm) } - sovrnBidResponses.push(bid); - }); - } - return sovrnBidResponses + + return bid + })) + .flat() } catch (e) { - logError('Could not intrepret bidresponse, error deatils:', e); + logError('Could not interpret bidresponse, error details:', e) + return e } }, diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index ee25f729a0a..83a13f0db7b 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -404,9 +404,29 @@ describe('sovrnBidAdapter', function() { 'currency': 'USD', 'netRevenue': true, 'mediaType': 'banner', - 'ad': decodeURIComponent(``), 'ttl': 90, - 'meta': { advertiserDomains: [] } + 'meta': { advertiserDomains: [] }, + 'ad': decodeURIComponent(``), + } + const videoBid = { + 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', + 'crid': 'creativelycreatedcreativecreative', + 'impid': '263c448586f5a1', + 'price': 0.45882675, + 'nurl': '', + 'adm': 'key%3Dvalue', + 'h': 480, + 'w': 640 + } + const bannerBid = { + 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', + 'crid': 'creativelycreatedcreativecreative', + 'impid': '263c448586f5a1', + 'price': 0.45882675, + 'nurl': '', + 'adm': '', + 'h': 90, + 'w': 728 } beforeEach(function () { response = { @@ -414,14 +434,7 @@ describe('sovrnBidAdapter', function() { 'id': '37386aade21a71', 'seatbid': [{ 'bid': [{ - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': '', - 'h': 90, - 'w': 728 + ...bannerBid }] }] } @@ -431,7 +444,6 @@ describe('sovrnBidAdapter', function() { it('should get the correct bid response', function () { const expectedResponse = { ...baseResponse, - 'ad': decodeURIComponent(`>`), 'ttl': 60000, }; @@ -491,6 +503,36 @@ describe('sovrnBidAdapter', function() { expect(result.length).to.equal(0); }); + + it('should get the correct bid response with 2 different bids', function () { + const expectedVideoResponse = { + ...baseResponse, + 'vastXml': decodeURIComponent(videoBid.adm) + } + delete expectedVideoResponse.ad + + const expectedBannerResponse = { + ...baseResponse + } + + response.body.seatbid = [{ bid: [bannerBid] }, { bid: [videoBid] }] + const result = spec.interpretResponse(response) + + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedBannerResponse)) + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedVideoResponse)) + }) + + it('should get the correct bid response with 2 seatbid items', function () { + const expectedResponse = { + ...baseResponse + } + response.body.seatbid = [response.body.seatbid[0], response.body.seatbid[0]] + + const result = spec.interpretResponse(response) + + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedResponse)) + }) }); describe('interpretResponse video', function () { From 028843dfe2a813d186464cfe44ef89f2870b5eb9 Mon Sep 17 00:00:00 2001 From: Alexander <32703851+pro-nsk@users.noreply.github.com> Date: Mon, 9 May 2022 21:05:14 +0700 Subject: [PATCH 32/44] Alkimi Bid Adapter: add new bid adapter (#8326) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes Co-authored-by: Alexander Bogdanov --- modules/alkimiBidAdapter.js | 119 +++++++++++++++ modules/alkimiBidAdapter.md | 29 ++++ test/spec/modules/alkimiBidAdapter_spec.js | 164 +++++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 modules/alkimiBidAdapter.js create mode 100644 modules/alkimiBidAdapter.md create mode 100644 test/spec/modules/alkimiBidAdapter_spec.js diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js new file mode 100644 index 00000000000..261fd9dee68 --- /dev/null +++ b/modules/alkimiBidAdapter.js @@ -0,0 +1,119 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepClone, deepAccess } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'alkimi'; +export const ENDPOINT = 'https://exchange.alkimi-onboarding.com/bid?prebid=true'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.bidFloor && bid.params.token); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let bids = []; + let bidIds = []; + validBidRequests.forEach(bidRequest => { + let sizes = prepareSizes(bidRequest.sizes) + + bids.push({ + token: bidRequest.params.token, + pos: bidRequest.params.pos, + bidFloor: bidRequest.params.bidFloor, + width: sizes[0].width, + height: sizes[0].height, + impMediaType: getFormatType(bidRequest) + }) + bidIds.push(bidRequest.bidId) + }) + + const alkimiConfig = config.getConfig('alkimi'); + + let payload = { + requestId: bidderRequest.auctionId, + signRequest: { bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID }, + bidIds, + referer: bidderRequest.refererInfo.referer, + signature: alkimiConfig && alkimiConfig.signature + } + + const options = { + contentType: 'application/json', + customHeaders: { + 'Rtb-Direct': true + } + } + + return { + method: 'POST', + url: ENDPOINT, + data: payload, + options + }; + }, + + interpretResponse: function (serverResponse, request) { + const serverBody = serverResponse.body; + if (!serverBody || typeof serverBody !== 'object') { + return []; + } + + const { prebidResponse } = serverBody; + if (!prebidResponse || typeof prebidResponse !== 'object') { + return []; + } + + let bids = []; + prebidResponse.forEach(bidResponse => { + let bid = deepClone(bidResponse); + bid.cpm = parseFloat(bidResponse.cpm); + + // banner or video + if (VIDEO === bid.mediaType) { + bid.vastXml = bid.ad; + } + + bid.meta = {}; + bid.meta.advertiserDomains = bid.adomain || []; + + bids.push(bid); + }) + + return bids; + }, + + onBidWon: function (bid) { + let winUrl; + if (bid.winUrl || bid.vastUrl) { + winUrl = bid.winUrl ? bid.winUrl : bid.vastUrl; + winUrl = winUrl.replace(/\$\{AUCTION_PRICE\}/, bid.cpm); + } else if (bid.ad) { + let trackImg = bid.ad.match(/(?!^)/); + bid.ad = bid.ad.replace(trackImg[0], ''); + winUrl = trackImg[0].split('"')[1]; + winUrl = winUrl.replace(/\$%7BAUCTION_PRICE%7D/, bid.cpm); + } else { + return false; + } + + ajax(winUrl, null); + return true; + } +} + +function prepareSizes(sizes) { + return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); +} + +const getFormatType = bidRequest => { + if (deepAccess(bidRequest, 'mediaTypes.banner')) return 'Banner' + if (deepAccess(bidRequest, 'mediaTypes.video')) return 'Video' + if (deepAccess(bidRequest, 'mediaTypes.audio')) return 'Audio' +} + +registerBidder(spec); diff --git a/modules/alkimiBidAdapter.md b/modules/alkimiBidAdapter.md new file mode 100644 index 00000000000..92a7c2aefe1 --- /dev/null +++ b/modules/alkimiBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: Alkimi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: abogdanov@asteriosoft.com +``` + +# Description + +Connects to Alkimi Bidder for bids. +Alkimi bid adapter supports Banner and Video ads. + +# Test Parameters +``` +const adUnits = [ + { + bids: [ + { + bidder: 'alkimi', + params: { + bidFloor: 0.1, + token: '?????????????????????', // Publisher Token provided by Alkimi + } + } + ] + } +]; +``` diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js new file mode 100644 index 00000000000..58a5a3b54ab --- /dev/null +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -0,0 +1,164 @@ +import { expect } from 'chai' +import { ENDPOINT, spec } from 'modules/alkimiBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' + +const REQUEST = { + 'bidId': '456', + 'bidder': 'alkimi', + 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'params': { + bidFloor: 0.1, + token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', + pos: 7 + } +} + +const BIDDER_BANNER_RESPONSE = { + 'prebidResponse': [{ + 'ad': '
test
', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d', + 'cpm': 900.5, + 'currency': 'USD', + 'width': 640, + 'height': 480, + 'ttl': 300, + 'creativeId': 1, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'mediaType': 'banner', + 'adomain': ['test.com'] + }] +} + +const BIDDER_VIDEO_RESPONSE = { + 'prebidResponse': [{ + 'ad': 'vast', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z', + 'cpm': 800.4, + 'currency': 'USD', + 'width': 1024, + 'height': 768, + 'ttl': 200, + 'creativeId': 2, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'mediaType': 'video', + 'adomain': ['test.com'] + }] +} + +const BIDDER_NO_BID_RESPONSE = '' + +describe('alkimiBidAdapter', function () { + const adapter = newBidder(spec) + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(REQUEST)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, REQUEST) + delete bid.params.token + expect(spec.isBidRequestValid(bid)).to.equal(false) + + bid = Object.assign({}, REQUEST) + delete bid.params.bidFloor + expect(spec.isBidRequestValid(bid)).to.equal(false) + + bid = Object.assign({}, REQUEST) + delete bid.params + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [REQUEST] + const bidderRequest = spec.buildRequests(bidRequests, { + auctionId: '123', + refererInfo: { + referer: 'http://test.com/path.html' + } + }) + + it('sends bid request to ENDPOINT via POST', function () { + expect(bidderRequest.method).to.equal('POST') + expect(bidderRequest.data.requestId).to.equal('123') + expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') + expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner' }) + expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) + expect(bidderRequest.data.bidIds).to.deep.contains('456') + expect(bidderRequest.data.signature).to.equal(undefined) + expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) + expect(bidderRequest.options.contentType).to.equal('application/json') + expect(bidderRequest.url).to.equal(ENDPOINT) + }) + }) + + describe('interpretResponse', function () { + it('handles banner request : should get correct bid response', function () { + const result = spec.interpretResponse({ body: BIDDER_BANNER_RESPONSE }, {}) + + expect(result[0]).to.have.property('ad').equal('
test
') + expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46d') + expect(result[0]).to.have.property('cpm').equal(900.5) + expect(result[0]).to.have.property('currency').equal('USD') + expect(result[0]).to.have.property('width').equal(640) + expect(result[0]).to.have.property('height').equal(480) + expect(result[0]).to.have.property('ttl').equal(300) + expect(result[0]).to.have.property('creativeId').equal(1) + expect(result[0]).to.have.property('netRevenue').equal(true) + expect(result[0]).to.have.property('winUrl').equal('http://test.com') + expect(result[0]).to.have.property('mediaType').equal('banner') + expect(result[0].meta).to.exist.property('advertiserDomains') + expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1) + }) + + it('handles video request : should get correct bid response', function () { + const result = spec.interpretResponse({ body: BIDDER_VIDEO_RESPONSE }, {}) + + expect(result[0]).to.have.property('ad').equal('vast') + expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46z') + expect(result[0]).to.have.property('cpm').equal(800.4) + expect(result[0]).to.have.property('currency').equal('USD') + expect(result[0]).to.have.property('width').equal(1024) + expect(result[0]).to.have.property('height').equal(768) + expect(result[0]).to.have.property('ttl').equal(200) + expect(result[0]).to.have.property('creativeId').equal(2) + expect(result[0]).to.have.property('netRevenue').equal(true) + expect(result[0]).to.have.property('winUrl').equal('http://test.com') + expect(result[0]).to.have.property('mediaType').equal('video') + expect(result[0]).to.have.property('vastXml').equal('vast') + expect(result[0].meta).to.exist.property('advertiserDomains') + expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1) + }) + + it('handles no bid response : should get empty array', function () { + let result = spec.interpretResponse({ body: undefined }, {}) + expect(result).to.deep.equal([]) + + result = spec.interpretResponse({ body: BIDDER_NO_BID_RESPONSE }, {}) + expect(result).to.deep.equal([]) + }) + }) + + describe('onBidWon', function () { + it('handles banner win: should get true', function () { + const win = BIDDER_BANNER_RESPONSE.prebidResponse[0] + const bidWonResult = spec.onBidWon(win) + + expect(bidWonResult).to.equal(true) + }) + }) +}) From 7f0a56ba06ebca94ec61973bc671fc246e15a4a1 Mon Sep 17 00:00:00 2001 From: natexo-technical-team <91968830+natexo-technical-team@users.noreply.github.com> Date: Mon, 9 May 2022 21:17:58 +0200 Subject: [PATCH 33/44] talkads Bid Adapter: update params access in case of different ad servers (#8390) * Update talkadsBidAdapter.js Update params access * Update talkadsBidAdapter.js update functions headers * Update talkadsBidAdapter.js Delete params attribute * Update talkadsBidAdapter_spec.js Delete params attribute --- modules/talkadsBidAdapter.js | 13 ++++++++----- test/spec/modules/talkadsBidAdapter_spec.js | 4 +++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/talkadsBidAdapter.js b/modules/talkadsBidAdapter.js index 068dce23b43..dae452b9a7d 100644 --- a/modules/talkadsBidAdapter.js +++ b/modules/talkadsBidAdapter.js @@ -5,11 +5,12 @@ import {ajax} from '../src/ajax.js'; const CURRENCY = 'EUR'; const BIDDER_CODE = 'talkads'; +const GVLID = 1074; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [ NATIVE, BANNER ], - params: null, /** * Determines whether or not the given bid request is valid. @@ -31,7 +32,7 @@ export const spec = { utils.logError('VALIDATION FAILED : the parameter "bidder_url" must be defined'); return false; } - this.params = poBid.params; + return !!(poBid.nativeParams || poBid.sizes); }, // isBidRequestValid @@ -54,6 +55,7 @@ export const spec = { } return loOne; }); + let laParams = paValidBidRequests[0].params; const loServerRequest = { cur: CURRENCY, timeout: poBidderRequest.timeout, @@ -71,7 +73,7 @@ export const spec = { loServerRequest.gdpr.consent = poBidderRequest.gdprConsent.consentString; } } - const lsUrl = this.params.bidder_url + '/' + this.params.tag_id; + const lsUrl = laParams.bidder_url + '/' + laParams.tag_id; return { method: 'POST', url: lsUrl, @@ -86,7 +88,7 @@ export const spec = { * @param poPidRequest Request original server request * @return An array of bids which were nested inside the server. */ - interpretResponse: (poServerResponse, poPidRequest) => { + interpretResponse: function (poServerResponse, poPidRequest) { utils.logInfo('interpretResponse : ', poServerResponse); if (!poServerResponse.body) { return []; @@ -120,8 +122,9 @@ export const spec = { */ onBidWon: function (poBid) { utils.logInfo('onBidWon : ', poBid); + let laParams = poBid.params[0]; if (poBid.pbid) { - ajax(this.params.bidder_url + 'won/' + poBid.pbid); + ajax(laParams.bidder_url + 'won/' + poBid.pbid); } }, // onBidWon }; diff --git a/test/spec/modules/talkadsBidAdapter_spec.js b/test/spec/modules/talkadsBidAdapter_spec.js index 00f52ba7b6a..c48808cbc15 100644 --- a/test/spec/modules/talkadsBidAdapter_spec.js +++ b/test/spec/modules/talkadsBidAdapter_spec.js @@ -207,6 +207,7 @@ describe('TalkAds adapter', function () { ttl: 60, creativeId: 'c123a456', netRevenue: false, + params: [Object.assign({}, commonBidRequest.params)], } spec.onBidWon(loBid) expect(server.requests.length).to.equals(0); @@ -222,7 +223,8 @@ describe('TalkAds adapter', function () { ttl: 60, creativeId: 'c123a456', netRevenue: false, - pbid: '6147833a65749742875ace47' + pbid: '6147833a65749742875ace47', + params: [Object.assign({}, commonBidRequest.params)], } spec.onBidWon(loBid) expect(server.requests[0].url).to.equals('https://test.natexo-programmatic.com/tad/tag/prebidwon/6147833a65749742875ace47'); From 6dbad9a1b28ac4a556efb92b350a3eae2888ae25 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Tue, 10 May 2022 14:35:36 +0200 Subject: [PATCH 34/44] BidWatch Analytics Adapter: add new analytics adapter (#8302) * New Analytics Adapter bidwatch * test for bidwatch Analytics Adapter * change maintainer address * Update bidwatchAnalyticsAdapter.js * Update bidwatchAnalyticsAdapter.js * Update bidwatchAnalyticsAdapter.md * Update bidwatchAnalyticsAdapter.md --- modules/bidwatchAnalyticsAdapter.js | 90 ++++++ modules/bidwatchAnalyticsAdapter.md | 21 ++ .../modules/bidwatchAnalyticsAdapter_spec.js | 288 ++++++++++++++++++ 3 files changed, 399 insertions(+) create mode 100644 modules/bidwatchAnalyticsAdapter.js create mode 100644 modules/bidwatchAnalyticsAdapter.md create mode 100644 test/spec/modules/bidwatchAnalyticsAdapter_spec.js diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js new file mode 100644 index 00000000000..26a8c370af3 --- /dev/null +++ b/modules/bidwatchAnalyticsAdapter.js @@ -0,0 +1,90 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; + +const analyticsType = 'endpoint'; +const url = 'URL_TO_SERVER_ENDPOINT'; + +const { + EVENTS: { + AUCTION_END, + BID_WON, + } +} = CONSTANTS; + +let allEvents = {} +let initOptions = {} +let endpoint = 'https://default' +let objectToSearchForBidderCode = ['bidderRequests', 'bidsReceived', 'noBids'] + +function getAdapterNameForAlias(aliasName) { + return adapterManager.aliasRegistry[aliasName] || aliasName; +} + +function setOriginalBidder(arg) { + Object.keys(arg).forEach(key => { + arg[key]['originalBidder'] = getAdapterNameForAlias(arg[key]['bidderCode']); + if (typeof arg[key]['creativeId'] == 'number') { arg[key]['creativeId'] = arg[key]['creativeId'].toString(); } + }); + return arg +} + +function checkBidderCode(args) { + if (typeof args == 'object') { + for (let i = 0; i < objectToSearchForBidderCode.length; i++) { + if (typeof args[objectToSearchForBidderCode[i]] == 'object') { args[objectToSearchForBidderCode[i]] = setOriginalBidder(args[objectToSearchForBidderCode[i]]) } + } + } + if (typeof args['bidderCode'] == 'string') { args['originalBidder'] = getAdapterNameForAlias(args['bidderCode']); } else if (typeof args['bidder'] == 'string') { args['originalBidder'] = getAdapterNameForAlias(args['bidder']); } + if (typeof args['creativeId'] == 'number') { args['creativeId'] = args['creativeId'].toString(); } + return args +} + +function addEvent(eventType, args) { + if (allEvents[eventType] == undefined) { allEvents[eventType] = [] } + if (eventType && args) { args = checkBidderCode(args); } + allEvents[eventType].push(args); +} + +function handleBidWon(args) { + if (typeof allEvents.bidRequested == 'object' && allEvents.bidRequested.length > 0 && allEvents.bidRequested[0].gdprConsent) { args.gdpr = allEvents.bidRequested[0].gdprConsent; } + ajax(endpoint + '.bidwatch.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true}); +} + +function handleAuctionEnd() { + ajax(endpoint + '.bidwatch.io/analytics/auctions', null, JSON.stringify(allEvents), {method: 'POST', withCredentials: true}); +} + +let bidwatchAnalytics = Object.assign(adapter({url, analyticsType}), { + track({ + eventType, + args + }) { + addEvent(eventType, args); + switch (eventType) { + case AUCTION_END: + handleAuctionEnd(); + break; + case BID_WON: + handleBidWon(args); + break; + } + }}); + +// save the base class function +bidwatchAnalytics.originEnableAnalytics = bidwatchAnalytics.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +bidwatchAnalytics.enableAnalytics = function (config) { + bidwatchAnalytics.originEnableAnalytics(config); // call the base class function + initOptions = config.options; + if (initOptions.domain) { endpoint = 'https://' + initOptions.domain; } +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: bidwatchAnalytics, + code: 'bidwatch' +}); + +export default bidwatchAnalytics; diff --git a/modules/bidwatchAnalyticsAdapter.md b/modules/bidwatchAnalyticsAdapter.md new file mode 100644 index 00000000000..bfa453640b8 --- /dev/null +++ b/modules/bidwatchAnalyticsAdapter.md @@ -0,0 +1,21 @@ +# Overview +Module Name: bidwatch Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: tech@bidwatch.io + +# Description + +Analytics adapter for bidwatch.io. + +# Test Parameters + +``` +{ + provider: 'bidwatch', + options : { + domain: 'test.endpoint' + } +} +``` diff --git a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..1a322d131a9 --- /dev/null +++ b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js @@ -0,0 +1,288 @@ +import bidwatchAnalytics from 'modules/bidwatchAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('BidWatch Analytics', function () { + let timestamp = new Date() - 256; + let auctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + let timeout = 1500; + + let bidTimeout = [ + { + 'bidId': '5fe418f2d70364', + 'bidder': 'appnexusAst', + 'adUnitCode': 'tag_200124_banner', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b' + } + ]; + + const auctionEnd = { + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'timestamp': 1647424261187, + 'auctionEnd': 1647424261714, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': 'tag_200124_banner', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 123456 + } + }, + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 234567 + } + } + ], + 'sizes': [ + [ + 300, + 600 + ] + ], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40' + } + ], + 'adUnitCodes': [ + 'tag_200124_banner' + ], + 'bidderRequests': [ + { + 'bidderCode': 'appnexus', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'bidderRequestId': '11dc6ff6378de7', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 123456 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '34a63e5d5378a3', + 'bidderRequestId': '11dc6ff6378de7', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1647424261187, + 'timeout': 1000, + 'gdprConsent': { + 'consentString': 'CONSENT', + 'gdprApplies': true, + 'apiVersion': 2 + }, + 'start': 1647424261189 + }, + ], + 'noBids': [ + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 10471298 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '5fe418f2d70364', + 'bidderRequestId': '4229a45ab8ea87', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'bidsReceived': [ + { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 600, + 'statusMessage': 'Bid available', + 'adId': '7a4ced80f33d33', + 'requestId': '34a63e5d5378a3', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158534630', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261559, + 'requestTimestamp': 1647424261189, + 'bidder': 'appnexus', + 'adUnitCode': 'tag_200124_banner', + 'timeToRespond': 370, + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '300x600', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '7a4ced80f33d33', + 'hb_pb': '20.000000', + 'hb_size': '300x600', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + } + } + ], + 'winningBids': [ + + ], + 'timeout': 1000 + }; + + let bidWon = { + 'bidderCode': 'appnexus', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158533702', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261558, + 'requestTimestamp': 1647424261189, + 'bidder': 'appnexus', + 'adUnitCode': 'tag_200123_banner', + 'timeToRespond': 369, + 'originalBidder': 'appnexus', + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '970x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '65d16ef039a97a', + 'hb_pb': '20.000000', + 'hb_size': '970x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 123456 + } + ] + }; + + after(function () { + bidwatchAnalytics.disableAnalytics(); + }); + + describe('main test flow', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + it('should catch events of interest', function () { + sinon.spy(bidwatchAnalytics, 'track'); + + adapterManager.registerAnalyticsAdapter({ + code: 'bidwatch', + adapter: bidwatchAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'bidwatch', + options: { + domain: 'test' + } + }); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + events.emit(constants.EVENTS.BID_WON, bidWon); + sinon.assert.callCount(bidwatchAnalytics.track, 3); + }); + }); +}); From c66840c4744ac7e2fdc718d6d8a197251d7b357b Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Tue, 10 May 2022 15:15:11 +0200 Subject: [PATCH 35/44] Native ads: change asset param (#8371) --- modules/improvedigitalBidAdapter.js | 14 +++- .../modules/improvedigitalBidAdapter_spec.js | 71 +++++++++++++------ 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index d1c35f63f9a..9de2e2b2d32 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -304,7 +304,10 @@ const ID_REQUEST = { } if (deepAccess(bidRequest, 'mediaTypes.native')) { - imp.native = this.buildNativeRequest(bidRequest); + const nativeImp = this.buildNativeRequest(bidRequest); + if (nativeImp) { + imp.native = nativeImp; + } } return imp; @@ -358,7 +361,10 @@ const ID_REQUEST = { }, buildNativeRequest(bidRequest) { - const nativeParams = bidRequest.mediaTypes.native; + const nativeParams = bidRequest.nativeParams; + if (!nativeParams) { + return null; + } const request = { assets: [], } @@ -392,6 +398,10 @@ const ID_REQUEST = { request.assets.push(asset); } } + if (!request.assets.length) { + logWarn('No native assets recognized. Ignoring native ad request'); + return null; + } return { ver: NATIVE_DATA.VERSION, request: JSON.stringify(request) }; }, diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index d721c2d20bc..8004c8d6f62 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/improvedigitalBidAdapter.js'; import { config } from 'src/config.js'; import { deepClone } from 'src/utils.js'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; describe('Improve Digital Adapter Tests', function () { const METHOD = 'POST'; @@ -50,17 +50,31 @@ describe('Improve Digital Adapter Tests', function () { } }; + const nativeBidRequest = deepClone(simpleBidRequest); + nativeBidRequest.mediaTypes = { native: {} }; + nativeBidRequest.nativeParams = { + title: {required: true}, + body: {required: true} + }; + const multiFormatBidRequest = deepClone(simpleBidRequest); multiFormatBidRequest.mediaTypes = { banner: { sizes: [[300, 250], [160, 600]] }, + native: {}, video: { context: 'outstream', playerSize: [640, 480] } }; + multiFormatBidRequest.nativeParams = { + body: { + required: true + } + }; + const simpleSmartTagBidRequest = { bidder: 'improvedigital', bidId: '1a2b3c', @@ -87,6 +101,10 @@ describe('Improve Digital Adapter Tests', function () { bids: [multiFormatBidRequest] }; + const nativeBidderRequest = { + bids: [nativeBidRequest] + }; + const bidderRequestGdpr = { bids: [simpleBidRequest], gdprConsent: { @@ -197,6 +215,10 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp).to.deep.equal([ { id: '33e9500b21129f', + native: { + request: '{"assets":[{"id":3,"required":1,"data":{"type":2}}]}', + ver: '1.2' + }, secure: 0, ext: { bidder: { @@ -220,6 +242,28 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub.restore(); }); + it('should make a well-formed native request', function () { + const payload = JSON.parse(spec.buildRequests([nativeBidRequest])[0].data); + expect(payload.imp[0].native).to.deep.equal({ + ver: '1.2', + request: '{\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":3,\"required\":1,\"data\":{\"type\":2}}]}' + }); + }); + + it('should not make native request when nativeParams is undefined', function () { + const request = deepClone(nativeBidRequest); + delete request.nativeParams; + const payload = JSON.parse(spec.buildRequests([request])[0].data); + expect(payload.imp[0].native).to.not.exist; + }); + + it('should not make native request when no assets', function () { + const request = deepClone(nativeBidRequest); + request.nativeParams = {}; + const payload = JSON.parse(spec.buildRequests([request])[0].data); + expect(payload.imp[0].native).to.not.exist; + }); + it('should set placementKey and publisherId for smart tags', function () { const payload = JSON.parse(spec.buildRequests([simpleSmartTagBidRequest], bidderRequest)[0].data); expect(payload.imp[0].ext.bidder.publisherId).to.equal(1032); @@ -238,20 +282,6 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].ext.bidder.keyValues).to.deep.equal(keyValues); }); - // it('should add single size filter', function () { - // const bidRequest = Object.assign({}, simpleBidRequest); - // const size = { - // w: 800, - // h: 600 - // }; - // bidRequest.params.size = size; - // const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data); - // expect(payload.imp[0].banner).to.deep.equal(size); - // // When single size filter is set, format shouldn't be populated. This - // // is to maintain backward compatibily - // expect(payload.imp[0].banner.format).to.not.exist; - // }); - it('should add currency', function () { const bidRequest = Object.assign({}, simpleBidRequest); const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); @@ -790,7 +820,7 @@ describe('Improve Digital Adapter Tests', function () { 'id': '52098fad-20c1-476b-a4fa-41e275e5a4a5', 'price': 1.8600000000000003, 'adm': "{\"ver\":\"1.1\",\"imptrackers\":[\"https://secure.adnxs.com/imptr?id=52311&t=2\",\"https://euw-ice.360yield.com/imp_pixel?ic=hcUBlCANx1FabHBf6FR2gC7UO4xEyXahdZAn0-B5qL-bb3A74BJ1smyWIyW7IWcC0SOjSXzVpevTHXxTqJ.sf.Qhahyy6tSo.0j1QWfXlH8sM4-8vKWjMjw-x.IrJJNlwkQ0s1CdwcwTefcLXm5l2E-W19VhACuV7f3mgrZMNjiSw.SjJAfyPC3SIyAMRjYfj53UmjriQ46T7lhmkqxK8wHmksYCdbZc3PZESk8NWl28sxdjNvnYYCFMcJbeav.LOLabyTXfwy-1cEPbQs.IKMRZIKaqccTDPV3wOtzbNv0jQzatd3Nnv-PGFQcjQ-GW3i27W04Fws4kodpFSn-B6VwZAjzLzoyd5gBncyRnAyCplEbgHU5sZ1IyKHWjgCl3ZtRIK5vqrRD5D-xqgSnOi7-phG.CqZWDZ4bMDSfQg2ZnbvUTyGKcEl0WR59dW5izTMV4Fjizcrvr5T-t.zMbGwz.hGnmLIyhTqh.IcwW.GiDLVExlDlix5S1LXIWVsSyrQ==\"],\"assets\":[{\"id\":1,\"data\":{\"value\":\"ImproveDigital\",\"type\":1}},{\"id\":3,\"data\":{\"value\":\"Test content.\",\"type\":2}},{\"id\":0,\"title\":{\"text\":\"Sample Prebid Test Title\"}}],\"link\":{\"url\":\"https://euw-ice.360yield.com/click/hcUBlHOV7YhVse8RyBa0ajjyPa9Vt17e4g-1m3cRj3E67vq-RYux.SiUeAmBfNBcoOqkUc6A15AWmi4yFu5K-BdkaYjildyyk7fNLyR6hWr411kv4vrFwm5jrIBceuHS6K8oN69f.uCo8zGTdR2TbSlldwcpahQPlufZU.6VaMsu4IC53uEiUT5vb7kAw6TTlxuGBNq6zaGryiWEV2.N3YYJDTyYPh8tv-ZFyeFZFm0Gnjv.xWbC.70JcRUVU9UelQaPsTpTWYTXBhJt84YJUw1-GNtaLNVLSjjZbVoA2fsMti5p6OBmF.7u39on2OPgvseIkSmge7Pqg63pRqdP75hp.DAEk6OkcN1jGnwP2DSbvpaSbin5lVqjfO0B-wnQgfQTCUtM5v4JmkNweLhUf9Q-x.nPKLW5SccEk9ZFXzY2-1wpT3PWm8Tix3NRscLPZub9wHzL..pl6ip8cQ9hp16UjwT4H6RMAxL0R7bl-h2pAicGAzYmuO7ntRESKUoIWA==//http%3A%2F%2Fquantum-advertising.com%2Ffr%2F\"},\"jstracker\":\"\"}", - 'impid': '2d7a7db325c6f', + 'impid': '33e9500b21129f', 'cid': '196108' } ], @@ -1043,10 +1073,6 @@ describe('Improve Digital Adapter Tests', function () { // // Native ads it('should return a well-formed native ad bid', function () { - const nativeBidderRequest = JSON.parse(JSON.stringify(bidderRequest)); - nativeBidderRequest.bids[0].bidId = '2d7a7db325c6f'; - delete nativeBidderRequest.bids[0].mediaTypes.banner; - nativeBidderRequest.bids[0].mediaTypes.native = {}; const bids = spec.interpretResponse(serverResponseNative, {bidderRequest: nativeBidderRequest}); // Verify Native Response expect(bids[0].native).to.exist; @@ -1063,6 +1089,11 @@ describe('Improve Digital Adapter Tests', function () { expect(nativeBid.body).to.exist.and.equal('Test content.'); }); + it('should return a well-formed native bid for multi-format ad unit', function () { + const bids = spec.interpretResponse(serverResponseNative, {bidderRequest: multiFormatBidderRequest}); + expect(bids[0].mediaType).to.equal(NATIVE); + }); + // Video it('should return a well-formed instream video bid', function () { const bids = spec.interpretResponse(serverResponseVideo, {bidderRequest: instreamBidderRequest}); From bcfb1270a317bd376e062602e1eeed9b0e3ef677 Mon Sep 17 00:00:00 2001 From: TPMN Admin Date: Tue, 10 May 2022 23:25:32 +0900 Subject: [PATCH 36/44] TPMN Bidder Adapter: write id in first party domain; force syncs with various parties (#8341) * add TPMN UserSync Bidder Adapter(Test Modify) Updating the source code that was forked in the past. make test case more. - add pb7 bidderSettings option - userSync fix. * fix indentation from CircleCI error report * fix indentation from CircleCI error report * fix indentation from CircleCI error report * fix user sync. static user sync without checking uuid. * fix user sync. static user sync without checking uuid. * fix user sync. static user sync without checking uuid. * fix user sync. static user sync without checking uuid. Co-authored-by: changjun --- modules/tpmnBidAdapter.js | 78 +++++++++++++---- test/spec/modules/tpmnBidAdapter_spec.js | 103 ++++++++++++++++++----- 2 files changed, 145 insertions(+), 36 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 006357cd4b9..88e89bcd64b 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -1,13 +1,16 @@ /* eslint-disable no-tabs */ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { parseUrl, deepAccess } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; export const ADAPTER_VERSION = '1'; const SUPPORTED_AD_TYPES = [BANNER]; - const BIDDER_CODE = 'tpmn'; const URL = 'https://ad.tpmn.co.kr/prebidhb.tpmn'; +const IFRAMESYNC = 'https://ad.tpmn.co.kr/sync.tpmn?type=iframe'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -18,20 +21,20 @@ export const spec = { * @param {object} bid The bid to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { return 'params' in bid && - 'inventoryId' in bid.params && - 'publisherId' in bid.params && - !isNaN(Number(bid.params.inventoryId)) && - bid.params.inventoryId > 0 && - (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes + 'inventoryId' in bid.params && + 'publisherId' in bid.params && + !isNaN(Number(bid.params.inventoryId)) && + bid.params.inventoryId > 0 && + (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes }, /** - * @param {BidRequest[]} bidRequests - * @param {*} bidderRequest - * @return {ServerRequest} - */ + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ buildRequests: (bidRequests, bidderRequest) => { if (bidRequests.length === 0) { return []; @@ -49,11 +52,11 @@ export const spec = { }]; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {serverResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {serverResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function (serverResponse, serverRequest) { if (!Array.isArray(serverResponse.body)) { return []; @@ -63,7 +66,48 @@ export const spec = { // our server directly returns the format needed by prebid.js so no more // transformation is needed here. return bidResults; - } + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncArr = []; + if (syncOptions.iframeEnabled) { + let policyParam = ''; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + policyParam += `&ccpa_consent=${uspConsent.consentString}`; + } + const coppa = config.getConfig('coppa') ? 1 : 0; + policyParam += `&coppa=${coppa}`; + syncArr.push({ + type: 'iframe', + url: IFRAMESYNC + policyParam + }) + } else { + syncArr.push({ + type: 'image', + url: 'https://x.bidswitch.net/sync?ssp=tpmn' + }); + syncArr.push({ + type: 'image', + url: 'https://gocm.c.appier.net/tpmn' + }); + syncArr.push({ + type: 'image', + url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D' + }); + syncArr.push({ + type: 'image', + url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId' + }); + } + return syncArr; + }, }; registerBidder(spec); diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index b4f6882dbe1..468769c2573 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -1,9 +1,39 @@ /* eslint-disable no-tabs */ -import { expect } from 'chai'; -import { spec } from 'modules/tpmnBidAdapter.js'; +import {expect} from 'chai'; +import {spec, storage} from 'modules/tpmnBidAdapter.js'; +import {generateUUID} from '../../../src/utils.js'; +import {newBidder} from '../../../src/adapters/bidderFactory'; +import * as sinon from 'sinon'; -describe('tpmnAdapterTests', function() { - describe('isBidRequestValid', function() { +describe('tpmnAdapterTests', function () { + const adapter = newBidder(spec); + const BIDDER_CODE = 'tpmn'; + let sandbox = sinon.sandbox.create(); + let getCookieStub; + + beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + tpmn: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + getCookieStub = sinon.stub(storage, 'getCookie'); + }); + + afterEach(function () { + sandbox.restore(); + getCookieStub.restore(); + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }); + + describe('isBidRequestValid', function () { let bid = { adUnitCode: 'temp-unitcode', bidder: 'tpmn', @@ -20,17 +50,18 @@ describe('tpmnAdapterTests', function() { } } }; - it('should return true if a bid is valid banner bid request', function() { + + it('should return true if a bid is valid banner bid request', function () { expect(spec.isBidRequestValid(bid)).to.be.equal(true); }); - it('should return false where requried param is missing', function() { + it('should return false where requried param is missing', function () { let bid = Object.assign({}, bid); bid.params = {}; expect(spec.isBidRequestValid(bid)).to.be.equal(false); }); - it('should return false when required param values have invalid type', function() { + it('should return false when required param values have invalid type', function () { let bid = Object.assign({}, bid); bid.params = { 'inventoryId': null, @@ -40,14 +71,14 @@ describe('tpmnAdapterTests', function() { }); }); - describe('buildRequests', function() { - it('should return an empty list if there are no bid requests', function() { + describe('buildRequests', function () { + it('should return an empty list if there are no bid requests', function () { const emptyBidRequests = []; const bidderRequest = {}; const request = spec.buildRequests(emptyBidRequests, bidderRequest); expect(request).to.be.an('array').that.is.empty; }); - it('should generate a POST server request with bidder API url, data', function() { + it('should generate a POST server request with bidder API url, data', function () { const bid = { adUnitCode: 'temp-unitcode', bidder: 'tpmn', @@ -65,13 +96,15 @@ describe('tpmnAdapterTests', function() { } }; const tempBidRequests = [bid]; - const tempBidderRequest = {refererInfo: { - referer: 'http://localhost/test', - site: { - domain: 'localhost', - page: 'http://localhost/test' + const tempBidderRequest = { + refererInfo: { + referer: 'http://localhost/test', + site: { + domain: 'localhost', + page: 'http://localhost/test' + } } - }}; + }; const builtRequest = spec.buildRequests(tempBidRequests, tempBidderRequest); expect(builtRequest).to.have.lengthOf(1); @@ -96,7 +129,7 @@ describe('tpmnAdapterTests', function() { }); }); - describe('interpretResponse', function() { + describe('interpretResponse', function () { const bid = { adUnitCode: 'temp-unitcode', bidder: 'tpmn', @@ -115,12 +148,12 @@ describe('tpmnAdapterTests', function() { }; const tempBidRequests = [bid]; - it('should return an empty aray to indicate no valid bids', function() { + it('should return an empty aray to indicate no valid bids', function () { const emptyServerResponse = {}; const bidResponses = spec.interpretResponse(emptyServerResponse, tempBidRequests); expect(bidResponses).is.an('array').that.is.empty; }); - it('should return an empty array to indicate no valid bids', function() { + it('should return an empty array to indicate no valid bids', function () { const mockBidResult = { requestId: '9cf19229-34f6-4d06-bc1d-0e44e8d616c8', cpm: 10.0, @@ -141,4 +174,36 @@ describe('tpmnAdapterTests', function() { expect(bidResponses).deep.equal([mockBidResult]); }); }); + + describe('getUserSync', function () { + const KEY_ID = 'uuid'; + const TMP_UUID = generateUUID().replace(/-/g, ''); + + it('getCookie mock Test', () => { + const uuid = storage.getCookie(KEY_ID); + expect(uuid).to.equal(undefined); + }); + + it('getCookie mock Test', () => { + expect(TMP_UUID.length).to.equal(32); + getCookieStub.withArgs(KEY_ID).returns(TMP_UUID); + const uuid = storage.getCookie(KEY_ID); + expect(uuid).to.equal(TMP_UUID); + }); + + it('case 1 -> allow iframe', () => { + const syncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + }); + + it('case 2 -> allow pixel with static sync', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }); + expect(syncs.length).to.be.equal(4); + expect(syncs[0].type).to.be.equal('image'); + expect(syncs[1].type).to.be.equal('image'); + expect(syncs[2].type).to.be.equal('image'); + expect(syncs[3].type).to.be.equal('image'); + }); + }); }); From a322c34ecb676dbfc8af63685f05d62eb3f80ff6 Mon Sep 17 00:00:00 2001 From: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Date: Wed, 11 May 2022 01:39:08 +0800 Subject: [PATCH 37/44] Jixie Bid Adapter: send device info (#8397) * Adapter does not seem capable of supporting advertiserDomains #6650 added response comment and some trivial code. * removed a blank line at the end of file added a space behind the // in comments * in response to comment from reviewer. add the aspect of advertiserdomain in unit tests * added the code to get the keywords from the meta tags if available. * to support sending object of device info to backend. yes the backend can handle it even though device was a string so far --- modules/jixieBidAdapter.js | 13 ++++++++++--- test/spec/modules/jixieBidAdapter_spec.js | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 700d3276e06..90ea17395f7 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn, parseUrl, deepAccess, isArray } from '../src/utils.js'; +import { logWarn, parseUrl, deepAccess, isArray, getDNT } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -60,9 +60,16 @@ function fetchIds_() { return ret; } +// device in the payload had been a simple string ('desktop', 'mobile') +// Now changed to an object. yes the backend is able to handle it. function getDevice_() { - return ((/(ios|ipod|ipad|iphone|android|blackberry|iemobile|opera mini|webos)/i).test(navigator.userAgent) - ? 'mobile' : 'desktop'); + const device = config.getConfig('device') || {}; + device.w = device.w || window.innerWidth; + device.h = device.h || window.innerHeight; + device.ua = device.ua || navigator.userAgent; + device.dnt = getDNT() ? 1 : 0; + device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + return device; } function pingTracking_(endpointOverride, qpobj) { diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index 7af0372c22c..0a6ba272970 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -268,6 +268,23 @@ describe('jixie Adapter', function () { expect(payload.pricegranularity).to.deep.include(content); }); + it('it should popular the device info when it is available', function () { + let getConfigStub = sinon.stub(config, 'getConfig'); + let content = {w: 500, h: 400}; + getConfigStub.callsFake(function fakeFn(prop) { + if (prop == 'device') { + return content; + } + return null; + }); + const oneSpecialBidReq = Object.assign({}, bidRequests_[0]); + const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + const payload = JSON.parse(request.data); + getConfigStub.restore(); + expect(payload.device).to.have.property('ua', navigator.userAgent); + expect(payload.device).to.deep.include(content); + }); + it('should populate eids when supported userIds are available', function () { const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { userId: { From 87b7dc8af973cadd1aaa1c9e21c07d98fad5b7dc Mon Sep 17 00:00:00 2001 From: David Carver <54326287+david-carver@users.noreply.github.com> Date: Tue, 10 May 2022 13:01:12 -0500 Subject: [PATCH 38/44] LKQD Bid Adapter: remove device ip bug (#8400) * LKQD: remove device ip from request * LKQD: remove device IP test --- modules/lkqdBidAdapter.js | 4 +--- test/spec/modules/lkqdBidAdapter_spec.js | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index 275ab38915d..e58c643f4f0 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -36,7 +36,6 @@ export const spec = { const serverRequestObjects = []; const UTC_OFFSET = new Date().getTimezoneOffset(); const UA = navigator.userAgent; - const IP = navigator.ip ? navigator.ip : 'prebid.js'; const USP = BIDDER_REQUEST.uspConsent || null; const REFERER = BIDDER_REQUEST.refererInfo ? new URL(BIDDER_REQUEST.refererInfo.referer).hostname : window.location.hostname; const BIDDER_GDPR = BIDDER_REQUEST.gdprConsent && BIDDER_REQUEST.gdprConsent.gdprApplies ? 1 : null; @@ -60,8 +59,7 @@ export const spec = { ua: UA, geo: { utcoffset: UTC_OFFSET - }, - ip: IP + } }, user: { ext: {} diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js index 6e2c7fe8e2c..7fee9bf6e41 100644 --- a/test/spec/modules/lkqdBidAdapter_spec.js +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -226,7 +226,6 @@ describe('lkqdBidAdapter', () => { 'utcoffset': -420, }, 'dnt': 0, - 'ip': '184.103.177.205', 'ifa': 'f4254ada-4174-6cfd-83c7-2f999c457c1d' }, 'user': { From 8a4fd447686e7f4bef04df9103b6d905f33e6d8e Mon Sep 17 00:00:00 2001 From: David Spohr Date: Tue, 10 May 2022 20:57:44 +0200 Subject: [PATCH 39/44] cpex Id System: initial release (#8364) * Adds cpexIdSystem * Fixes cpexIdSystem tests * Added markdown document * Remove unnecessary storage config --- modules/.submodules.json | 1 + modules/cpexIdSystem.js | 49 ++++++++++++++++++++++++++ modules/cpexIdSystem.md | 27 ++++++++++++++ modules/userId/userId.md | 2 ++ test/spec/modules/cpexIdSystem_spec.js | 38 ++++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 modules/cpexIdSystem.js create mode 100644 modules/cpexIdSystem.md create mode 100644 test/spec/modules/cpexIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index b0f19aa0bae..7e21fc0e33b 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -7,6 +7,7 @@ "amxIdSystem", "britepoolIdSystem", "connectIdSystem", + "cpexIdSystem", "criteoIdSystem", "dacIdSystem", "deepintentDpesIdSystem", diff --git a/modules/cpexIdSystem.js b/modules/cpexIdSystem.js new file mode 100644 index 00000000000..4600601cb11 --- /dev/null +++ b/modules/cpexIdSystem.js @@ -0,0 +1,49 @@ +/** + * This module adds 'caid' to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/cpexIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js' +import { getStorageManager } from '../src/storageManager.js' + +window.top.cpexIdVersion = '0.0.3' + +// Returns StorageManager +export const storage = getStorageManager({ gvlid: 570, moduleName: 'cpexId' }) + +// Returns the id string from either cookie or localstorage +const getId = () => { return storage.getCookie('caid') || storage.getDataFromLocalStorage('caid') } + +/** @type {Submodule} */ +export const cpexIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'cpexId', + /** + * Vendor ID of Czech Publisher Exchange + * @type {Number} + */ + gvlid: 570, + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode (value) { return { cpexId: getId() } }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @param {(Object|undefined)} cacheIdObj + * @returns {IdResponse|undefined} + */ + getId (config, consentData) { return { cpexId: getId() } } +} + +submodule('userId', cpexIdSubmodule) diff --git a/modules/cpexIdSystem.md b/modules/cpexIdSystem.md new file mode 100644 index 00000000000..8aceb7fe4ec --- /dev/null +++ b/modules/cpexIdSystem.md @@ -0,0 +1,27 @@ +## CPEx User ID Submodule + +CPExID is provided by [Czech Publisher Exchange](https://www.cpex.cz/), or CPEx. It is a user ID for ad targeting by using first party cookie, or localStorage mechanism. Please contact CPEx before using this ID. + +## Building Prebid with CPExID Support + +First, make sure to add the cpexId to your Prebid.js package with: + +``` +gulp build --modules=cpexIdSystem +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'cpexId' + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"cpexId"` | diff --git a/modules/userId/userId.md b/modules/userId/userId.md index bc0dad6462a..ed5b0adcd71 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -111,6 +111,8 @@ pbjs.setConfig({ name: '_criteoId', expires: 1 } + }, { + name: "cpexId" }, { name: 'mwOpenLinkId', params: { diff --git a/test/spec/modules/cpexIdSystem_spec.js b/test/spec/modules/cpexIdSystem_spec.js new file mode 100644 index 00000000000..ee203e6c83a --- /dev/null +++ b/test/spec/modules/cpexIdSystem_spec.js @@ -0,0 +1,38 @@ +import { cpexIdSubmodule, storage } from 'modules/cpexIdSystem.js'; + +describe('cpexId module', function () { + let getCookieStub; + + beforeEach(function (done) { + getCookieStub = sinon.stub(storage, 'getCookie'); + done(); + }); + + afterEach(function () { + getCookieStub.restore(); + }); + + const cookieTestCasesForEmpty = [undefined, null, ''] + + describe('getId()', function () { + it('should return the uid when it exists in cookie', function () { + getCookieStub.withArgs('caid').returns('cpexIdTest'); + const id = cpexIdSubmodule.getId(); + expect(id).to.be.deep.equal({ cpexId: 'cpexIdTest' }); + }); + + cookieTestCasesForEmpty.forEach(testCase => it('should not return the uid when it doesnt exist in cookie', function () { + getCookieStub.withArgs('caid').returns(testCase); + const id = cpexIdSubmodule.getId(); + expect(id).to.be.deep.equal({ cpexId: null }); + })); + }); + + describe('decode()', function () { + it('should return the uid when it exists in cookie', function () { + getCookieStub.withArgs('caid').returns('cpexIdTest'); + const decoded = cpexIdSubmodule.decode(); + expect(decoded).to.be.deep.equal({ cpexId: 'cpexIdTest' }); + }); + }); +}); From 04fefef1633a4b1d663f2f5e2af727874c54658a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 10 May 2022 12:01:31 -0700 Subject: [PATCH 40/44] UserID module: graceful handling of exceptions from ID submodules (#8401) * Do not error out when malformed JSON is set in cookies * UserID module: graceful handling of exceptions from ID submodules --- modules/id5IdSystem.js | 15 ++++++-- modules/userId/index.js | 19 +++++++--- src/utils.js | 11 ++++++ test/spec/modules/id5IdSystem_spec.js | 17 ++++++++- test/spec/modules/userId_spec.js | 51 +++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 9 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index f2143c1cced..b57be00d3ac 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -5,7 +5,16 @@ * @requires module:modules/userId */ -import { deepAccess, logInfo, deepSetValue, logError, isEmpty, isEmptyStr, logWarn } from '../src/utils.js'; +import { + deepAccess, + logInfo, + deepSetValue, + logError, + isEmpty, + isEmptyStr, + logWarn, + safeJSONParse +} from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -24,7 +33,7 @@ const LOG_PREFIX = 'User ID - ID5 submodule: '; // cookie in the array is the most preferred to use const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st', 'id5id' ]; -const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const id5IdSubmodule = { @@ -253,7 +262,7 @@ function getLegacyCookieSignature() { let legacyStoredValue; LEGACY_COOKIE_NAMES.forEach(function(cookie) { if (storage.getCookie(cookie)) { - legacyStoredValue = JSON.parse(storage.getCookie(cookie)) || legacyStoredValue; + legacyStoredValue = safeJSONParse(storage.getCookie(cookie)) || legacyStoredValue; } }); return (legacyStoredValue && legacyStoredValue.signature) || ''; diff --git a/modules/userId/index.js b/modules/userId/index.js index 045786f59ec..5aed9b808f6 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -423,7 +423,7 @@ function processSubmoduleCallbacks(submodules, cb) { }, submodules.length); } submodules.forEach(function (submodule) { - submodule.callback(function callbackCompleted(idObj) { + function callbackCompleted(idObj) { // if valid, id data should be saved to cookie/html storage if (idObj) { if (submodule.config.storage) { @@ -435,8 +435,13 @@ function processSubmoduleCallbacks(submodules, cb) { logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } done(); - }); - + } + try { + submodule.callback(callbackCompleted); + } catch (e) { + logError(`Error in userID module '${submodule.submodule.name}':`, e); + done(); + } // clear callback, this prop is used to test if all submodule callbacks are complete below submodule.callback = undefined; }); @@ -881,8 +886,12 @@ function initSubmodules(dest, submodules, consentData, forceRefresh = false) { setStoredConsentData(consentData); const initialized = userIdModules.reduce((carry, submodule) => { - populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh); - carry.push(submodule); + try { + populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh); + carry.push(submodule); + } catch (e) { + logError(`Error in userID module '${submodule.submodule.name}':`, e); + } return carry; }, []); if (initialized.length) { diff --git a/src/utils.js b/src/utils.js index 33755a4fb82..3109b52c4df 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1364,3 +1364,14 @@ export function cyrb53Hash(str, seed = 0) { export function getWindowFromDocument(doc) { return (doc) ? doc.defaultView : null; } + +/** + * returns the result of `JSON.parse(data)`, or undefined if that throws an error. + * @param data + * @returns {any} + */ +export function safeJSONParse(data) { + try { + return JSON.parse(data); + } catch (e) {} +} diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 74c2b053ce1..a54542f7278 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -5,7 +5,7 @@ import { ID5_PRIVACY_STORAGE_NAME, ID5_STORAGE_NAME, id5IdSubmodule, - nbCacheName, + nbCacheName, storage, storeInLocalStorage, storeNbInCache, } from 'modules/id5IdSystem.js'; @@ -310,6 +310,21 @@ describe('ID5 ID System', function() { request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).to.be.null; }); + + describe('when legacy cookies are set', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getCookie'); + }); + afterEach(() => { + sandbox.restore(); + }); + it('should not throw if malformed JSON is forced into cookies', () => { + storage.getCookie.callsFake(() => ' Not JSON '); + id5IdSubmodule.getId(getId5FetchConfig()); + }); + }) }); describe('Request Bids Hook', function() { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 48d7ea6d53e..03924e320c0 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -507,6 +507,57 @@ describe('User ID', function () { }); }); + describe('when ID systems throw errors', () => { + function mockIdSystem(name) { + return { + name, + decode: function(value) { + return { + [name]: value + }; + }, + getId: sinon.stub().callsFake(() => ({id: name})) + }; + } + let id1, id2; + beforeEach(() => { + id1 = mockIdSystem('mock1'); + id2 = mockIdSystem('mock2'); + init(config); + setSubmoduleRegistry([id1, id2]); + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'mock1', + storage: {name: 'mock1', type: 'cookie'} + }, { + name: 'mock2', + storage: {name: 'mock2', type: 'cookie'} + }] + } + }) + }); + afterEach(() => { + config.resetConfig(); + }) + Object.entries({ + 'in init': () => id1.getId.callsFake(() => { throw new Error() }), + 'in callback': () => { + const mockCallback = sinon.stub().callsFake(() => { throw new Error() }); + id1.getId.callsFake(() => ({callback: mockCallback})) + } + }).forEach(([t, setup]) => { + describe(`${t}`, () => { + beforeEach(setup); + it('should still retrieve IDs that do not throw', () => { + return getGlobal().getUserIdsAsync().then((uid) => { + expect(uid.mock2).to.not.be.undefined; + }) + }); + }) + }) + }); it('pbjs.refreshUserIds updates submodules', function(done) { let sandbox = sinon.createSandbox(); let mockIdCallback = sandbox.stub().returns({id: {'MOCKID': '1111'}}); From 2651c7b86201e355cc97a0a1140c781fe9e012aa Mon Sep 17 00:00:00 2001 From: Mehdi Bouallagui <45876988+mbouallagui@users.noreply.github.com> Date: Tue, 10 May 2022 22:53:58 +0200 Subject: [PATCH 41/44] removing floor field from bid request when not defined (#8398) --- modules/oguryBidAdapter.js | 5 +++-- test/spec/modules/oguryBidAdapter_spec.js | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 7d2989b2066..295a0042f4b 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -10,7 +10,7 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.2.10'; +const ADAPTER_VERSION = '1.2.11'; function isBidRequestValid(bid) { const adUnitSizes = getAdUnitSizes(bid); @@ -74,11 +74,12 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('banner')) { openRtbBidRequestBanner.site.id = bidRequest.params.assetKey; + const floor = getFloor(bidRequest); openRtbBidRequestBanner.imp.push({ id: bidRequest.bidId, tagid: bidRequest.params.adUnitId, - bidfloor: getFloor(bidRequest), + ...(floor && {bidfloor: floor}), banner: { format: sizes }, diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index acf62bf5a7b..0d1a530044f 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -245,7 +245,6 @@ describe('OguryBidAdapter', function () { }, { id: bidRequests[1].bidId, tagid: bidRequests[1].params.adUnitId, - bidfloor: 0, banner: { format: [{ w: 600, @@ -271,7 +270,7 @@ describe('OguryBidAdapter', function () { }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.2.10' + adapterversion: '1.2.11' } }; @@ -401,7 +400,7 @@ describe('OguryBidAdapter', function () { it('should handle bidFloor when currency is not USD', () => { const expectedRequestWithUnsupportedFloorCurrency = utils.deepClone(expectedRequestObject) - expectedRequestWithUnsupportedFloorCurrency.imp[0].bidfloor = 0; + delete expectedRequestWithUnsupportedFloorCurrency.imp[0].bidfloor; let validBidRequests = utils.deepClone(bidRequests); validBidRequests[0] = { ...validBidRequests[0], @@ -482,7 +481,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.2.10', + adapterVersion: '1.2.11', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -499,7 +498,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.2.10', + adapterVersion: '1.2.11', prebidVersion: '$prebid.version$' }] From b74cd109d38c3bad3dd74377683c55f4be038b9e Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 10 May 2022 14:39:17 -0700 Subject: [PATCH 42/44] passing floors signal to PBS (#8392) --- modules/prebidServerBidAdapter/index.js | 5 +++++ modules/rubiconBidAdapter.js | 6 ++++++ .../modules/prebidServerBidAdapter_spec.js | 21 +++++++++++++++++++ test/spec/modules/rubiconBidAdapter_spec.js | 16 ++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 8279da13054..0ffb16d23a4 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -849,6 +849,11 @@ Object.assign(ORTB2.prototype, { } }; + // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check + if (typeof deepAccess(firstBidRequest, 'bids.0.floorData') === 'object') { + request.ext.prebid.floors = { enabled: false }; + } + // This is no longer overwritten unless name and version explicitly overwritten by extPrebid (mergeDeep) request.ext.prebid = Object.assign(request.ext.prebid, {channel: {name: 'pbjs', version: $$PREBID_GLOBAL$$.version}}) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index afb95d56d69..48c5ddf813a 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -253,6 +253,12 @@ export const spec = { if (!isNaN(bidFloor)) { data.imp[0].bidfloor = bidFloor; } + + // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check + if (typeof bidRequest.floorData === 'object') { + data.ext.prebid.floors = { enabled: false }; + } + // if value is set, will overwrite with same value data.imp[0].ext[bidRequest.bidder].video.size_id = determineRubiconVideoSizeId(bidRequest) diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 3edb89dd573..434f668aad0 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3190,5 +3190,26 @@ describe('S2S Adapter', function () { expect(requestBid.ext.prebid.debug).is.equal(true); }); + + it('should correctly add floors flag', function () { + let bidRequest = utils.deepClone(BID_REQUESTS); + + // should not pass if floorData is undefined + adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.ext.prebid.floors).to.be.undefined; + + // should pass of floorData is object + bidRequest[0].bids[0].floorData = { + skipped: false, + location: 'fetch', + } + + adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + requestBid = JSON.parse(server.requests[1].requestBody); + + expect(requestBid.ext.prebid.floors).to.deep.equal({ enabled: false }); + }); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index c281c195dd2..46b1b519a81 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1714,6 +1714,22 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); }); + it('should add floors flag correctly to PBS Request', function () { + createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + // should not pass if undefined + expect(request.data.ext.prebid.floors).to.be.undefined; + + // should pass it as false + bidderRequest.bids[0].floorData = { + skipped: false, + location: 'fetch', + } + let [newRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(newRequest.data.ext.prebid.floors).to.deep.equal({ enabled: false }); + }); + it('should add multibid configuration to PBS Request', function () { createVideoBidderRequest(); From 9b22db3c5e541d6210259b8b1744ea775cec0cbb Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 10 May 2022 14:40:14 -0700 Subject: [PATCH 43/44] add adunit floor min price floors (#8396) --- modules/priceFloors.js | 4 +++ test/spec/modules/priceFloors_spec.js | 50 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index ff4213f1330..e548de768b4 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -142,6 +142,10 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) matchingData: allPossibleMatches[0], // the first possible match is an "exact" so contains all data relevant for anlaytics adapters matchingRule }; + // use adUnit floorMin as priority! + if (typeof deepAccess(bidObject, 'ortb2Imp.ext.prebid.floorMin') === 'number') { + matchingData.floorMin = bidObject.ortb2Imp.ext.prebid.floorMin; + } matchingData.matchingFloor = Math.max(matchingData.floorMin, matchingData.floorRuleValue); // save for later lookup if needed deepSetValue(floorData, `matchingInputs.${matchingInput}`, {...matchingData}); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 6ea58e8c47a..cbdd4d179e3 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -268,6 +268,56 @@ describe('the price floors module', function () { matchingRule: undefined }); }); + it('correctly applies floorMin if on adunit', function () { + let inputFloorData = { + floorMin: 2.6, + currency: 'USD', + schema: { + delimiter: '|', + fields: ['adUnitCode'] + }, + values: { + 'test_div_1': 1.0, + 'test_div_2': 2.0 + }, + default: 0.5 + }; + + let myBidRequest = { ...basicBidRequest }; + + // should take adunit floormin first even if lower + utils.deepSetValue(myBidRequest, 'ortb2Imp.ext.prebid.floorMin', 2.2); + expect(getFirstMatchingFloor(inputFloorData, myBidRequest, { mediaType: 'banner', size: '*' })).to.deep.equal({ + floorMin: 2.2, + floorRuleValue: 1.0, + matchingFloor: 2.2, + matchingData: 'test_div_1', + matchingRule: 'test_div_1' + }); + delete inputFloorData.matchingInputs; + + // should take adunit floormin if higher + utils.deepSetValue(myBidRequest, 'ortb2Imp.ext.prebid.floorMin', 3.0); + expect(getFirstMatchingFloor(inputFloorData, myBidRequest, { mediaType: 'banner', size: '*' })).to.deep.equal({ + floorMin: 3.0, + floorRuleValue: 1.0, + matchingFloor: 3.0, + matchingData: 'test_div_1', + matchingRule: 'test_div_1' + }); + delete inputFloorData.matchingInputs; + + // should take top floormin if no adunit floor min + delete myBidRequest.ortb2Imp; + expect(getFirstMatchingFloor(inputFloorData, myBidRequest, { mediaType: 'banner', size: '*' })).to.deep.equal({ + floorMin: 2.6, + floorRuleValue: 1.0, + matchingFloor: 2.6, + matchingData: 'test_div_1', + matchingRule: 'test_div_1' + }); + delete inputFloorData.matchingInputs; + }); it('selects the right floor for different mediaTypes', function () { // banner with * size (not in rule file so does not do anything) expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ From 14c9bc7e86baf4afdfa110f74306e468633af071 Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 10 May 2022 22:43:07 +0100 Subject: [PATCH 44/44] AirGrid RTD module: Support for xandr / appnexus auction level keywords. (#8388) * chore: update docs page to match official docs * feat: add support for appnexus / xandr auction level keywords --- modules/.submodules.json | 1 + modules/airgridRtdProvider.js | 56 ++++++--- modules/airgridRtdProvider.md | 39 +++--- test/spec/modules/airgridRtdProvider_spec.js | 122 +++++++++++++------ 4 files changed, 146 insertions(+), 72 deletions(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index 7e21fc0e33b..59bae2013d1 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -50,6 +50,7 @@ "dfpAdServerVideo" ], "rtdModule": [ + "airgridRtdProvider", "browsiRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index b2e78a7df78..e9011343a74 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -5,18 +5,26 @@ * @module modules/airgridRtdProvider * @requires module:modules/realTimeData */ -import {config} from '../src/config.js'; -import {submodule} from '../src/hook.js'; -import {mergeDeep, isPlainObject, deepSetValue, deepAccess} from '../src/utils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {getStorageManager} from '../src/storageManager.js'; +import { config } from '../src/config.js'; +import { submodule } from '../src/hook.js'; +import { + mergeDeep, + isPlainObject, + deepSetValue, + deepAccess, +} from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { getStorageManager } from '../src/storageManager.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'airgrid'; const AG_TCF_ID = 782; -export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids' +export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids'; -export const storage = getStorageManager({gvlid: AG_TCF_ID, moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({ + gvlid: AG_TCF_ID, + moduleName: SUBMODULE_NAME, +}); /** * Attach script tag to DOM @@ -24,13 +32,13 @@ export const storage = getStorageManager({gvlid: AG_TCF_ID, moduleName: SUBMODUL * @return {void} */ export function attachScriptTagToDOM(rtdConfig) { - var edktInitializor = window.edktInitializor = window.edktInitializor || {}; + var edktInitializor = (window.edktInitializor = window.edktInitializor || {}); if (!edktInitializor.invoked) { edktInitializor.invoked = true; edktInitializor.accountId = rtdConfig.params.accountId; edktInitializor.publisherId = rtdConfig.params.publisherId; edktInitializor.apiKey = rtdConfig.params.apiKey; - edktInitializor.load = function(e) { + edktInitializor.load = function (e) { var p = e || 'sdk'; var n = document.createElement('script'); n.type = 'module'; @@ -48,7 +56,7 @@ export function attachScriptTagToDOM(rtdConfig) { */ export function getMatchedAudiencesFromStorage() { const audiences = storage.getDataFromLocalStorage(AG_AUDIENCE_IDS_KEY); - if (!audiences) return [] + if (!audiences) return []; try { return JSON.parse(audiences); } catch (e) { @@ -68,8 +76,8 @@ function setAudiencesToAppNexusAdUnits(adUnits, audiences) { if (bid.bidder && bid.bidder === 'appnexus') { deepSetValue(bid, 'params.keywords.perid', audiences || []); } - }) - }) + }); + }); } /** @@ -82,7 +90,7 @@ export function setAudiencesUsingBidderOrtb2(rtdConfig, audiences) { const bidders = deepAccess(rtdConfig, 'params.bidders'); if (!bidders || bidders.length === 0) return; const allBiddersConfig = config.getBidderConfig(); - const agOrtb2 = {} + const agOrtb2 = {}; deepSetValue(agOrtb2, 'ortb2.user.ext.data.airgrid', audiences || []); bidders.forEach((bidder) => { @@ -92,11 +100,19 @@ export function setAudiencesUsingBidderOrtb2(rtdConfig, audiences) { } config.setBidderConfig({ bidders: [bidder], - config: mergeDeep(bidderConfig, agOrtb2) + config: mergeDeep(bidderConfig, agOrtb2), }); }); } +export function setAudiencesUsingAppNexusAuctionKeywords(audiences) { + config.setConfig({ + appnexusAuctionKeywords: { + perid: audiences, + }, + }); +} + /** * Module init * @param {Object} rtdConfig @@ -116,23 +132,29 @@ function init(rtdConfig, userConsent) { * @param {Object} userConsent * @return {void} */ -export function passAudiencesToBidders(bidConfig, onDone, rtdConfig, userConsent) { +export function passAudiencesToBidders( + bidConfig, + onDone, + rtdConfig, + userConsent +) { const adUnits = bidConfig.adUnits || getGlobal().adUnits; const audiences = getMatchedAudiencesFromStorage(); if (audiences.length > 0) { + setAudiencesUsingAppNexusAuctionKeywords(audiences); setAudiencesUsingBidderOrtb2(rtdConfig, audiences); if (adUnits) { setAudiencesToAppNexusAdUnits(adUnits, audiences); } } onDone(); -}; +} /** @type {RtdSubmodule} */ export const airgridSubmodule = { name: SUBMODULE_NAME, init: init, - getBidRequestData: passAudiencesToBidders + getBidRequestData: passAudiencesToBidders, }; submodule(MODULE_NAME, airgridSubmodule); diff --git a/modules/airgridRtdProvider.md b/modules/airgridRtdProvider.md index 7ee502b4c10..6251c63fce9 100644 --- a/modules/airgridRtdProvider.md +++ b/modules/airgridRtdProvider.md @@ -1,15 +1,17 @@ - --- - layout: page_v2 - title: AirGrid RTD SubModule - description: Client-side, cookieless and privacy-first audiences. - page_type: module - module_type: rtd - module_code : example - enable_download : true - sidebarType : 1 - --- - -# AirGrid +--- +layout: page_v2 +title: AirGrid RTD Provider +display_name: AirGrid RTD Provider +description: Client-side, cookieless and privacy-first audiences. +page_type: module +module_type: rtd +module_code : airgridRtdProvider +enable_download : true +vendor_specific: true +sidebarType : 1 +--- + +# AirGrid RTD Provider AirGrid is a privacy-first, cookie-less audience platform. Designed to help publishers increase inventory yield, whilst providing audience signal to buyers in the bid request, without exposing raw user level data to any party. @@ -17,13 +19,17 @@ whilst providing audience signal to buyers in the bid request, without exposing This real-time data module provides quality first-party data, contextual data, site-level data and more that is injected into bid request objects destined for different bidders in order to optimize targeting. +{:.no_toc} +* TOC +{:toc} + ## Usage -Compile the Halo RTD module into your Prebid build: +Compile the AirGrid RTD module (`airgridRtdProvider`) into your Prebid build, along with the parent RTD Module (`rtdModule`): `gulp build --modules=rtdModule,airgridRtdProvider,appnexusBidAdapter` -Add the AirGrid RTD provider to your Prebid config. In this example we will configure publisher 1234 to retrieve segments from Audigent. See the "Parameter Descriptions" below for more detailed information of the configuration parameters. +Next we configure the module, via `pbjs.setConfig`. See the **Parameter Descriptions** below for more detailed information of the configuration parameters. ```js pbjs.setConfig( @@ -50,6 +56,7 @@ pbjs.setConfig( ### Parameter Descriptions +{: .table .table-bordered .table-striped } | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | | name | `String` | RTD sub module name | Always 'airgrid' | @@ -61,7 +68,7 @@ pbjs.setConfig( _Note: Although the module supports passing segment data to any bidder using the ORTB2 spec, there is no way for this to be currently monetised. Please reach out to support, to discuss using bidders other than Xandr/AppNexus._ -If you do not have your own `apiKey`, `accountId` & `publisherId` please reach out to [support@airgrid.io](mailto:support@airgrid.io) +If you do not have your own `apiKey`, `accountId` & `publisherId` please reach out to [support@airgrid.io](mailto:support@airgrid.io) or you can sign up via the [AirGrid platform](https://app.airgrid.io). ## Testing @@ -89,7 +96,7 @@ If you require further assistance or are interested in discussing the module fun - [hello@airgrid.io](mailto:hello@airgrid.io) for general questions. - [support@airgrid.io](mailto:support@airgrid.io) for technical questions. -You are also able to find more examples and other integration routes on the [AirGrid docs site](docs.airgrid.io). +You are also able to find more examples and other integration routes on the [AirGrid docs site](https://docs.airgrid.io), or learn more on our [site](https://airgrid.io)! Happy Coding! 😊 The AirGrid Team. diff --git a/test/spec/modules/airgridRtdProvider_spec.js b/test/spec/modules/airgridRtdProvider_spec.js index cc10dda4ad1..a54f6f40e2f 100644 --- a/test/spec/modules/airgridRtdProvider_spec.js +++ b/test/spec/modules/airgridRtdProvider_spec.js @@ -1,79 +1,102 @@ -import {config} from 'src/config.js'; -import {deepAccess} from 'src/utils.js' -import {getAdUnits} from '../../fixtures/fixtures.js'; +import { config } from 'src/config.js'; +import { deepAccess } from 'src/utils.js'; +import { getAdUnits } from '../../fixtures/fixtures.js'; import * as agRTD from 'modules/airgridRtdProvider.js'; const MATCHED_AUDIENCES = ['travel', 'sport']; const RTD_CONFIG = { auctionDelay: 250, - dataProviders: [{ - name: 'airgrid', - waitForIt: true, - params: { - apiKey: 'key123', - accountId: 'sdk', - publisherId: 'pub123', - bidders: ['pubmatic'] - } - }] + dataProviders: [ + { + name: 'airgrid', + waitForIt: true, + params: { + apiKey: 'key123', + accountId: 'sdk', + publisherId: 'pub123', + bidders: ['pubmatic'], + }, + }, + ], }; -describe('airgrid RTD Submodule', function() { +describe('airgrid RTD Submodule', function () { let getDataFromLocalStorageStub; - beforeEach(function() { + beforeEach(function () { config.resetConfig(); - getDataFromLocalStorageStub = sinon.stub(agRTD.storage, 'getDataFromLocalStorage'); + getDataFromLocalStorageStub = sinon.stub( + agRTD.storage, + 'getDataFromLocalStorage' + ); }); afterEach(function () { getDataFromLocalStorageStub.restore(); }); - describe('Initialise module', function() { + describe('Initialise module', function () { it('should initalise and return true', function () { - expect(agRTD.airgridSubmodule.init(RTD_CONFIG.dataProviders[0])).to.equal(true); + expect(agRTD.airgridSubmodule.init(RTD_CONFIG.dataProviders[0])).to.equal( + true + ); }); - it('should attach script to DOM with correct config', function() { + + it('should attach script to DOM with correct config', function () { agRTD.attachScriptTagToDOM(RTD_CONFIG); expect(window.edktInitializor.invoked).to.be.true; - expect(window.edktInitializor.apiKey).to.equal(RTD_CONFIG.dataProviders[0].params.apiKey); - expect(window.edktInitializor.accountId).to.equal(RTD_CONFIG.dataProviders[0].params.accountId); - expect(window.edktInitializor.publisherId).to.equal(RTD_CONFIG.dataProviders[0].params.publisherId); + expect(window.edktInitializor.apiKey).to.equal( + RTD_CONFIG.dataProviders[0].params.apiKey + ); + expect(window.edktInitializor.accountId).to.equal( + RTD_CONFIG.dataProviders[0].params.accountId + ); + expect(window.edktInitializor.publisherId).to.equal( + RTD_CONFIG.dataProviders[0].params.publisherId + ); }); }); - describe('Get matched audiences', function() { - it('gets matched audiences from local storage', function() { - getDataFromLocalStorageStub.withArgs(agRTD.AG_AUDIENCE_IDS_KEY).returns(JSON.stringify(MATCHED_AUDIENCES)); + describe('Get matched audiences', function () { + it('gets matched audiences from local storage', function () { + getDataFromLocalStorageStub + .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) + .returns(JSON.stringify(MATCHED_AUDIENCES)); const audiences = agRTD.getMatchedAudiencesFromStorage(); expect(audiences).to.have.members(MATCHED_AUDIENCES); }); }); - describe('Add matched audiences', function() { - it('merges matched audiences on appnexus AdUnits', function() { + describe('Add matched audiences', function () { + it('merges matched audiences on appnexus AdUnits', function () { const adUnits = getAdUnits(); - getDataFromLocalStorageStub.withArgs(agRTD.AG_AUDIENCE_IDS_KEY).returns(JSON.stringify(MATCHED_AUDIENCES)); + getDataFromLocalStorageStub + .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) + .returns(JSON.stringify(MATCHED_AUDIENCES)); agRTD.passAudiencesToBidders({ adUnits }, () => {}, {}, {}); - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { + adUnits.forEach((adUnit) => { + adUnit.bids.forEach((bid) => { const { bidder, params } = bid; if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.perid')).to.eql(MATCHED_AUDIENCES); + expect(deepAccess(params, 'keywords.perid')).to.eql( + MATCHED_AUDIENCES + ); } }); }); }); - it('does not merge audiences on appnexus adunits, since none are matched', function() { + + it('does not merge audiences on appnexus adunits, since none are matched', function () { const adUnits = getAdUnits(); - getDataFromLocalStorageStub.withArgs(agRTD.AG_AUDIENCE_IDS_KEY).returns(undefined); + getDataFromLocalStorageStub + .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) + .returns(undefined); agRTD.passAudiencesToBidders({ adUnits }, () => {}, {}, {}); - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { + adUnits.forEach((adUnit) => { + adUnit.bids.forEach((bid) => { const { bidder, params } = bid; if (bidder === 'appnexus') { expect(deepAccess(params, 'keywords.perid')).to.be.undefined; @@ -81,17 +104,38 @@ describe('airgrid RTD Submodule', function() { }); }); }); - it('sets bidder specific ORTB2 config', function() { - getDataFromLocalStorageStub.withArgs(agRTD.AG_AUDIENCE_IDS_KEY).returns(JSON.stringify(MATCHED_AUDIENCES)); + + it('sets bidder specific ORTB2 config', function () { + getDataFromLocalStorageStub + .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) + .returns(JSON.stringify(MATCHED_AUDIENCES)); const audiences = agRTD.getMatchedAudiencesFromStorage(); - agRTD.setAudiencesUsingBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); + agRTD.setAudiencesUsingBidderOrtb2( + RTD_CONFIG.dataProviders[0], + audiences + ); const allBiddersConfig = config.getBidderConfig(); const bidders = RTD_CONFIG.dataProviders[0].params.bidders; Object.keys(allBiddersConfig).forEach((bidder) => { if (bidders.indexOf(bidder) === -1) return; - expect(deepAccess(allBiddersConfig[bidder], 'ortb2.user.ext.data.airgrid')).to.eql(MATCHED_AUDIENCES); + expect( + deepAccess(allBiddersConfig[bidder], 'ortb2.user.ext.data.airgrid') + ).to.eql(MATCHED_AUDIENCES); }); }); + + it('sets audiences using appnexus auction level keywords', function () { + getDataFromLocalStorageStub + .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) + .returns(JSON.stringify(MATCHED_AUDIENCES)); + const audiences = agRTD.getMatchedAudiencesFromStorage(); + agRTD.setAudiencesUsingAppNexusAuctionKeywords(audiences); + + const bidderConfig = config.getConfig(); + expect(deepAccess(bidderConfig, 'appnexusAuctionKeywords.perid')).to.eql( + MATCHED_AUDIENCES + ); + }); }); });