From b0c20eecbb576f5c29b616270fb24f1c0b8ce975 Mon Sep 17 00:00:00 2001 From: Hai Lee Date: Wed, 24 Mar 2021 15:33:23 +0700 Subject: [PATCH 01/25] AdTrue Bid Adapter: getUserSyncs logic update (#6338) * add AdTrue bid adapter * add AdTrue Bid Adapter unit tests * add AdTrue Bid Adapter unit tests * add AdTrue bid adapter * add AdTrue Bid Adapter unit tests * add AdTrue Bid Adapter unit tests * add AdTrue bid adapter * adtrue usersync udpate * adtrue usersync udpate * #adtrue bidder usersync udpate * #Refactoring code & add unit test * Update package.json * Update package-lock.json * 1. Merge branch 'master' of https://github.com/prebid/Prebid.js 2.AdTrue Bid Adapter Refactoring code * 1. Merge branch 'master' of https://github.com/prebid/Prebid.js 2.AdTrue Bid Adapter Refactoring code * removing a package-lock.json from PR * - removing a package-lock.json from PR - AdTrue bidder: refactoring code & add unit test * - removing a package-lock.json from PR - AdTrue bidder: refactoring code & add unit test * - removing a package-lock.json from PR - AdTrue bidder: refactoring code & add unit test * update package-lock.json from upstream --- modules/adtrueBidAdapter.js | 70 +++++++------ test/spec/modules/adtrueBidAdapter_spec.js | 115 ++++++++++++++++----- 2 files changed, 124 insertions(+), 61 deletions(-) diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index fa83f9f29f7..b4dc7f7ea89 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -14,9 +14,8 @@ const UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; const NET_REVENUE = false; -const USER_SYNC_URL_IFRAME = 'https://hb.adtrue.com/prebid/usersync?t=iframe&p='; -const USER_SYNC_URL_IMAGE = 'https://hb.adtrue.com/prebid/usersync?t=img&p='; let publisherId = 0; +let zoneId = 0; let NATIVE_ASSET_ID_TO_KEY_MAP = {}; const DATA_TYPES = { 'NUMBER': 'number', @@ -25,6 +24,11 @@ const DATA_TYPES = { 'ARRAY': 'array', 'OBJECT': 'object' }; +const SYNC_TYPES = Object.freeze({ + 1: 'iframe', + 2: 'image' +}); + const VIDEO_CUSTOM_PARAMS = { 'mimes': DATA_TYPES.ARRAY, 'minduration': DATA_TYPES.NUMBER, @@ -440,6 +444,14 @@ export const spec = { utils.logWarn(LOG_WARN_PREFIX + 'Error: missing publisherId'); return false; } + if (!utils.isStr(bid.params.publisherId)) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric'); + return false; + } + if (!utils.isStr(bid.params.zoneId)) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: zoneId is mandatory and cannot be numeric'); + return false; + } return true; } return false; @@ -478,6 +490,7 @@ export const spec = { return; } publisherId = conf.pubId.trim(); + zoneId = conf.zoneId.trim(); payload.site.publisher.id = conf.pubId.trim(); payload.ext.wrapper = {}; @@ -498,9 +511,7 @@ export const spec = { if (typeof config.getConfig('device') === 'object') { payload.device = Object.assign(payload.device, config.getConfig('device')); } - utils.deepSetValue(payload, 'source.tid', conf.transactionId); - // test bids if (window.location.href.indexOf('adtrueTest=true') !== -1) { payload.test = 1; @@ -509,7 +520,6 @@ export const spec = { if (validBidRequests[0].schain) { utils.deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); } - // Attaching GDPR Consent Params if (bidderRequest && bidderRequest.gdprConsent) { utils.deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); @@ -520,7 +530,6 @@ export const spec = { if (bidderRequest && bidderRequest.uspConsent) { utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - // coppa compliance if (config.getConfig('coppa') === true) { utils.deepSetValue(payload, 'regs.coppa', 1); @@ -576,7 +585,6 @@ export const spec = { } }); } - newBid.meta = {}; if (bid.ext && bid.ext.dspid) { newBid.meta.networkId = bid.ext.dspid; @@ -588,14 +596,12 @@ export const spec = { newBid.meta.advertiserDomains = bid.adomain; newBid.meta.clickUrl = bid.adomain[0]; } - // adserverTargeting if (seatbidder.ext && seatbidder.ext.buyid) { newBid.adserverTargeting = { 'hb_buyid_adtrue': seatbidder.ext.buyid }; } - bidResponses.push(newBid); }); }); @@ -606,32 +612,28 @@ export const spec = { return bidResponses; }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - let syncurl = '' + publisherId; - - if (gdprConsent) { - syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); - syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); - } - if (uspConsent) { - syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); - } - - // coppa compliance - if (config.getConfig('coppa') === true) { - syncurl += '&coppa=1'; - } - - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: USER_SYNC_URL_IFRAME + syncurl - }]; - } else { - return [{ - type: 'image', - url: USER_SYNC_URL_IMAGE + syncurl - }]; + if (!responses || responses.length === 0 || (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled)) { + return []; } + return responses.reduce((accum, rsp) => { + let cookieSyncs = utils.deepAccess(rsp, 'body.ext.cookie_sync'); + if (cookieSyncs) { + let cookieSyncObjects = cookieSyncs.map(cookieSync => { + return { + type: SYNC_TYPES[cookieSync.type], + url: cookieSync.url + + '&publisherId=' + publisherId + + '&zoneId=' + zoneId + + '&gdpr=' + (gdprConsent && gdprConsent.gdprApplies ? 1 : 0) + + '&gdpr_consent=' + encodeURIComponent((gdprConsent ? gdprConsent.consentString : '')) + + '&us_privacy=' + encodeURIComponent((uspConsent || '')) + + '&coppa=' + (config.getConfig('coppa') === true ? 1 : 0) + }; + }); + return accum.concat(cookieSyncObjects); + } + }, []); } }; + registerBidder(spec); diff --git a/test/spec/modules/adtrueBidAdapter_spec.js b/test/spec/modules/adtrueBidAdapter_spec.js index a7732d6aeb7..8e1c872d460 100644 --- a/test/spec/modules/adtrueBidAdapter_spec.js +++ b/test/spec/modules/adtrueBidAdapter_spec.js @@ -8,6 +8,7 @@ describe('AdTrueBidAdapter', function () { const adapter = newBidder(spec) let bidRequests; let bidResponses; + let bidResponses2; beforeEach(function () { bidRequests = [ { @@ -48,34 +49,85 @@ describe('AdTrueBidAdapter', function () { } ]; bidResponses = { - 'body': { - 'id': '1610681506302', - 'seatbid': [ + body: { + id: '1610681506302', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': '1', - 'impid': '201fb513ca24e9', - 'price': 2.880000114440918, - 'burl': 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', - 'adm': '', - 'adid': '1610681506302', - 'adomain': [ + id: '1', + impid: '201fb513ca24e9', + price: 2.880000114440918, + burl: 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + adm: '', + adid: '1610681506302', + adomain: [ 'adtrue.com' ], - 'cid': 'f6l0r6n', - 'crid': 'abc77au4', - 'attr': [], - 'w': 300, - 'h': 250 + cid: 'f6l0r6n', + crid: 'abc77au4', + attr: [], + w: 300, + h: 250 } ], - 'seat': 'adtrue', - 'group': 0 + seat: 'adtrue', + group: 0 } ], - 'bidid': '1610681506302', - 'cur': 'USD' + bidid: '1610681506302', + cur: 'USD', + ext: { + cookie_sync: [ + { + type: 1, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue' + } + ] + } + } + }; + bidResponses2 = { + body: { + id: '1610681506302', + seatbid: [ + { + bid: [ + { + id: '1', + impid: '201fb513ca24e9', + price: 2.880000114440918, + burl: 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + adm: '', + adid: '1610681506302', + adomain: [ + 'adtrue.com' + ], + cid: 'f6l0r6n', + crid: 'abc77au4', + attr: [], + w: 300, + h: 250 + } + ], + seat: 'adtrue', + group: 0 + } + ], + bidid: '1610681506302', + cur: 'USD', + ext: { + cookie_sync: [ + { + type: 2, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&type=image' + }, + { + type: 1, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=appnexus' + } + ] + } } }; }); @@ -349,8 +401,6 @@ describe('AdTrueBidAdapter', function () { }); }); describe('getUserSyncs', function () { - let USER_SYNC_URL_IFRAME = 'https://hb.adtrue.com/prebid/usersync?t=iframe&p=1212'; - let USER_SYNC_URL_IMAGE = 'https://hb.adtrue.com/prebid/usersync?t=img&p=1212'; let sandbox; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -359,12 +409,23 @@ describe('AdTrueBidAdapter', function () { sandbox.restore(); }); it('execute as per config', function () { - expect(spec.getUserSyncs({iframeEnabled: true}, {}, undefined, undefined)).to.deep.equal([{ - type: 'iframe', url: USER_SYNC_URL_IFRAME - }]); - expect(spec.getUserSyncs({iframeEnabled: false}, {}, undefined, undefined)).to.deep.equal([{ - type: 'image', url: USER_SYNC_URL_IMAGE + expect(spec.getUserSyncs({iframeEnabled: true}, [bidResponses], undefined, undefined)).to.deep.equal([{ + type: 'iframe', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); + // Multiple user sync output + it('execute as per config', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, [bidResponses2], undefined, undefined)).to.deep.equal([ + { + type: 'image', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&type=image&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }, + { + type: 'iframe', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=appnexus&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + } + ]); + }); }); }); From d2a036054571515c75d114a7766f8e5e192694c4 Mon Sep 17 00:00:00 2001 From: punkiller Date: Wed, 24 Mar 2021 01:40:53 -0700 Subject: [PATCH 02/25] IX Bid Adapter: add priceFloors support and an integration example (#6390) * Adding priceFloors Support to IX bid adapter and some refactoring * refactoring the getfloor logic, and removing dead code * newline at end of integration example * spacing issues lint fix * try catch around getFloor, in case the priceFloors module throws an exception * minor rename allU -> allu * falsey values fix, log prefix added --- integrationExamples/gpt/adUnitFloors.html | 112 ++++++++++++ modules/ixBidAdapter.js | 104 +++++++++-- test/spec/modules/ixBidAdapter_spec.js | 210 +++++++++++++++++++++- 3 files changed, 404 insertions(+), 22 deletions(-) create mode 100644 integrationExamples/gpt/adUnitFloors.html diff --git a/integrationExamples/gpt/adUnitFloors.html b/integrationExamples/gpt/adUnitFloors.html new file mode 100644 index 00000000000..bb48a20ef78 --- /dev/null +++ b/integrationExamples/gpt/adUnitFloors.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + +

Prebid.js Test

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