From c3813fd6acc030c1afe49a5c226eb125918af2ed Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Wed, 12 May 2021 18:45:43 +0700 Subject: [PATCH 01/48] Qwarry Bid Adapter: remove gdpr field from request (#6746) * qwarry bid adapter * formatting fixes * fix tests for qwarry * qwarry bid adapter * add header for qwarry bid adapter * bid requests fix * fix tests * response fix * fix tests for Qwarry bid adapter * add pos parameter to qwarry bid adapter * qwarryBidAdapter onBidWon hotfix * Change bidder endpoint url for Qwarry adapter * add referer JS detection * use bidderRequest.refererInfo * fix tests * GDPR consent string support * NPE fix * gdpr value added * merge master * gdpr value added * qwarry bid adapter: add tests * Qwarry bid adapter: remove gdpr field from request Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 3 +-- test/spec/modules/qwarryBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index e2782d82512..d19ad0b4fe4 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -33,8 +33,7 @@ export const spec = { if (bidderRequest && bidderRequest.gdprConsent) { payload.gdprConsent = { consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : false, - consentString: bidderRequest.gdprConsent.consentString, - gdpr: bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0 + consentString: bidderRequest.gdprConsent.consentString } } diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 5d56203131a..5d297d32014 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -86,7 +86,7 @@ describe('qwarryBidAdapter', function () { expect(bidderRequest.data.requestId).to.equal('123') expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 }) - expect(bidderRequest.data.gdprConsent).to.deep.contains({ consentRequired: true, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', gdpr: 1 }) + expect(bidderRequest.data.gdprConsent).to.deep.contains({ consentRequired: true, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') expect(bidderRequest.url).to.equal(ENDPOINT) From 9da0c01a15e3ab934292a021953cd6c15a3c563e Mon Sep 17 00:00:00 2001 From: Anthony Lauzon Date: Wed, 12 May 2021 08:12:20 -0400 Subject: [PATCH 02/48] Halo RTD Module: add publisher params to RTD request (#6742) * add pub provided halo params to rtd request * missing semicolon * halo id url arg update --- modules/haloRtdProvider.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/haloRtdProvider.js b/modules/haloRtdProvider.js index 055b40c9d09..b57787aab14 100644 --- a/modules/haloRtdProvider.js +++ b/modules/haloRtdProvider.js @@ -53,9 +53,9 @@ function mergeLazy(target, source) { * @param {String} param * @param {String} defaultVal */ -function paramOrDefault(param, defaultVal) { +function paramOrDefault(param, defaultVal, arg) { if (isFn(param)) { - return param(); + return param(arg); } else if (isStr(param)) { return param; } @@ -107,7 +107,7 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { userIds.haloId = haloId; getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); } else { - var script = document.createElement('script') + var script = document.createElement('script'); script.type = 'text/javascript'; window.pubHaloCb = (haloId) => { @@ -116,7 +116,7 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { } const haloIdUrl = rtdConfig.params && rtdConfig.params.haloIdUrl; - script.src = paramOrDefault(haloIdUrl, 'https://id.halo.ad.gt/api/v1/haloid'); + script.src = paramOrDefault(haloIdUrl, 'https://id.halo.ad.gt/api/v1/haloid', userIds); document.getElementsByTagName('head')[0].appendChild(script); } } @@ -136,6 +136,10 @@ export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, reqParams = rtdConfig.params.requestParams; } + if (isPlainObject(window.pubHaloPm)) { + reqParams.pubHaloPm = window.pubHaloPm; + } + const url = `https://seg.halo.ad.gt/api/v1/rtd`; ajax(url, { success: function (response, req) { From 8790da9c51c84b1a9e8c226347f153164be4b0c0 Mon Sep 17 00:00:00 2001 From: guiann Date: Wed, 12 May 2021 14:25:24 +0200 Subject: [PATCH 03/48] Prebid Core: Remove size check on native icons and image assets (#6678) * remove image size checking * fix unit tests --- src/native.js | 12 ------------ test/spec/native_spec.js | 18 +++++------------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/native.js b/src/native.js index b43e582327b..7596b38534d 100644 --- a/src/native.js +++ b/src/native.js @@ -77,18 +77,6 @@ export function nativeBidIsValid(bid, bidRequests) { return false; } - if (deepAccess(bid, 'native.image')) { - if (!deepAccess(bid, 'native.image.height') || !deepAccess(bid, 'native.image.width')) { - return false; - } - } - - if (deepAccess(bid, 'native.icon')) { - if (!deepAccess(bid, 'native.icon.height') || !deepAccess(bid, 'native.icon.width')) { - return false; - } - } - const requestedAssets = bidRequest.nativeParams; if (!requestedAssets) { return true; diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 7154a0fde37..0ffef30965b 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -433,11 +433,7 @@ describe('validate native', function () { native: { body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', clickTrackers: ['http://my.click.tracker/url'], - icon: { - url: 'http://my.image.file/ad_image.jpg', - height: 0, - width: 0 - }, + icon: 'http://my.image.file/ad_image.jpg', image: { url: 'http://my.icon.file/ad_icon.jpg', height: 2250, @@ -463,11 +459,7 @@ describe('validate native', function () { height: 75, width: 75 }, - image: { - url: 'http://my.icon.file/ad_icon.jpg', - height: 0, - width: 0 - }, + image: 'http://my.icon.file/ad_icon.jpg', clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', impressionTrackers: ['http://my.imp.tracker/url'], javascriptTrackers: '', @@ -479,12 +471,12 @@ describe('validate native', function () { afterEach(function () {}); - it('should reject bid if no image sizes are defined', function () { + it('should accept bid if no image sizes are defined', function () { let result = nativeBidIsValid(validBid, bidReq); expect(result).to.be.true; result = nativeBidIsValid(noIconDimBid, bidReq); - expect(result).to.be.false; + expect(result).to.be.true; result = nativeBidIsValid(noImgDimBid, bidReq); - expect(result).to.be.false; + expect(result).to.be.true; }); }); From f9272fc06c8f64bb542951be99499098a6b02e5a Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 12 May 2021 10:55:27 -0700 Subject: [PATCH 04/48] Prebid Server Bid Adapter: Bugfix for not taking defaultVendor enabled (#6740) * quick fix for enabled s2s bug * move enabled check outside of defaultVendor if --- modules/prebidServerBidAdapter/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 38a499c794e..4ca61cae452 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -72,7 +72,6 @@ let eidPermissions; * @type {S2SDefaultConfig} */ const s2sDefaultConfig = { - enabled: false, timeout: 1000, maxBids: 1, adapter: 'prebidServer', @@ -89,7 +88,7 @@ config.setDefaults({ * @return {boolean} */ function updateConfigDefaultVendor(option) { - if (option.defaultVendor && option.enabled !== false) { + if (option.defaultVendor) { let vendor = option.defaultVendor; let optionKeys = Object.keys(option); if (S2S_VENDORS[vendor]) { @@ -105,6 +104,8 @@ function updateConfigDefaultVendor(option) { return false; } } + // this is how we can know if user / defaultVendor has set it, or if we should default to false + return option.enabled = typeof option.enabled === 'boolean' ? option.enabled : false; } /** @@ -163,6 +164,7 @@ function setS2sConfig(options) { return true; } } + utils.logWarn('prebidServer: s2s config is disabled'); return false; }); From f999c0da11e286d9361bb09b8faedf8db30ce70a Mon Sep 17 00:00:00 2001 From: Jason Snellbaker Date: Wed, 12 May 2021 15:35:56 -0400 Subject: [PATCH 05/48] Prebid 4.39.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a04743eab39..4085ca3df08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.39.0-pre", + "version": "4.39.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 5215ade32747facd4fb4c4f932d44b9b49c9daa7 Mon Sep 17 00:00:00 2001 From: Jason Snellbaker Date: Wed, 12 May 2021 15:52:46 -0400 Subject: [PATCH 06/48] increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4085ca3df08..05f68f4a009 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.39.0", + "version": "4.40.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From b132a4ba9d10a78bdea1a04fc2088d0957126385 Mon Sep 17 00:00:00 2001 From: John Salis Date: Thu, 13 May 2021 08:46:35 -0400 Subject: [PATCH 07/48] Beachfront Bid Adapter: add schain support (#6751) * add schain support to beachfront adapter * remove unnecessary mock tests for outstream player Co-authored-by: John Salis --- modules/beachfrontBidAdapter.js | 11 ++ modules/beachfrontBidAdapter.md | 2 +- .../spec/modules/beachfrontBidAdapter_spec.js | 101 ++++++++---------- 3 files changed, 55 insertions(+), 59 deletions(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 44755a78864..8ddc0ca5ba9 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -351,6 +351,9 @@ function createVideoRequestData(bid, bidderRequest) { regs: { ext: {} }, + source: { + ext: {} + }, user: { ext: {} }, @@ -367,6 +370,10 @@ function createVideoRequestData(bid, bidderRequest) { payload.user.ext.consent = consentString; } + if (bid.schain) { + payload.source.ext.schain = bid.schain; + } + if (eids.length > 0) { payload.user.ext.eids = eids; } @@ -416,6 +423,10 @@ function createBannerRequestData(bids, bidderRequest) { payload.gdprConsent = consentString; } + if (bids[0] && bids[0].schain) { + payload.schain = bids[0].schain; + } + SUPPORTED_USER_IDS.forEach(({ key, queryParam }) => { let id = bids[0] && bids[0].userId && bids[0].userId[key]; if (id) { diff --git a/modules/beachfrontBidAdapter.md b/modules/beachfrontBidAdapter.md index 0a6b8b73da4..a2eb79ee331 100644 --- a/modules/beachfrontBidAdapter.md +++ b/modules/beachfrontBidAdapter.md @@ -4,7 +4,7 @@ Module Name: Beachfront Bid Adapter Module Type: Bidder Adapter -Maintainer: john@beachfront.com +Maintainer: prebid@beachfront.com # Description diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 43c71dd6349..c7ae5c799ac 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,5 +1,4 @@ import { expect } from 'chai'; -import sinon from 'sinon'; import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT, OUTSTREAM_SRC, DEFAULT_MIMES } from 'modules/beachfrontBidAdapter.js'; import { parseUrl } from 'src/utils.js'; @@ -278,6 +277,27 @@ describe('BeachfrontAdapter', function () { expect(data.user.ext.consent).to.equal(consentString); }); + it('must add schain data to the request', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1 + } + ] + }; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.schain = schain; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.source.ext.schain).to.deep.equal(schain); + }); + it('must add the Trade Desk User ID to the request', () => { const tdid = '4321'; const bidRequest = bidRequests[0]; @@ -446,6 +466,27 @@ describe('BeachfrontAdapter', function () { expect(data.gdprConsent).to.equal(consentString); }); + it('must add schain data to the request', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1 + } + ] + }; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.schain = schain; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.schain).to.deep.equal(schain); + }); + it('must add the Trade Desk User ID to the request', () => { const tdid = '4321'; const bidRequest = bidRequests[0]; @@ -654,63 +695,7 @@ describe('BeachfrontAdapter', function () { id: bidRequest.bidId, url: OUTSTREAM_SRC }); - }); - - it('should initialize a player for outstream bids', () => { - const width = 640; - const height = 480; - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { - video: { - context: 'outstream', - playerSize: [ width, height ] - } - }; - const serverResponse = { - bidPrice: 5.00, - url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - crid: '123abc' - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - window.Beachfront = { Player: sinon.spy() }; - bidResponse.adUnitCode = bidRequest.adUnitCode; - bidResponse.renderer.render(bidResponse); - sinon.assert.calledWith(window.Beachfront.Player, bidResponse.adUnitCode, sinon.match({ - adTagUrl: bidResponse.vastUrl, - width: bidResponse.width, - height: bidResponse.height, - expandInView: false, - collapseOnComplete: true - })); - delete window.Beachfront; - }); - - it('should configure outstream player settings from the bidder params', () => { - const width = 640; - const height = 480; - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { - video: { - context: 'outstream', - playerSize: [ width, height ] - } - }; - bidRequest.params.player = { - expandInView: true, - collapseOnComplete: false, - progressColor: 'green' - }; - const serverResponse = { - bidPrice: 5.00, - url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - crid: '123abc' - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - window.Beachfront = { Player: sinon.spy() }; - bidResponse.adUnitCode = bidRequest.adUnitCode; - bidResponse.renderer.render(bidResponse); - sinon.assert.calledWith(window.Beachfront.Player, bidResponse.adUnitCode, sinon.match(bidRequest.params.player)); - delete window.Beachfront; + expect(bidResponse.renderer.render).to.be.a('function'); }); }); From 73690781de088db65e8871a9a0437fbb95a2986a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mar=C3=ADn?= Date: Thu, 13 May 2021 15:03:40 +0200 Subject: [PATCH 08/48] fix: Webpack v5 complain about named export from JSON modules (#6755) --- modules/gdprEnforcement.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index adbccd8666d..02a2da3a7a4 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -12,7 +12,7 @@ import { registerSyncInner } from '../src/adapters/bidderFactory.js'; import { getHook } from '../src/hook.js'; import { validateStorageEnforcement } from '../src/storageManager.js'; import events from '../src/events.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; const TCF2 = { 'purpose1': { id: 1, name: 'storage' }, @@ -345,10 +345,10 @@ function emitTCF2FinalResults() { analyticsBlocked: formatArray(analyticsBlocked) }; - events.emit(EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); + events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); } -events.on(EVENTS.AUCTION_END, emitTCF2FinalResults); +events.on(CONSTANTS.EVENTS.AUCTION_END, emitTCF2FinalResults); /* Set of callback functions used to detect presence of a TCF rule, passed as the second argument to find(). From 752e0c2757227f50313554c4af467eacb44a5c76 Mon Sep 17 00:00:00 2001 From: Danny Khatib Date: Thu, 13 May 2021 07:58:50 -0700 Subject: [PATCH 09/48] Pbs bid adapter: constants import styling for webpack v5 upgrade (#6723) * mapping spotx dealid to bid object * using proper syntax to import default json module * kick off tests * es lint fix Co-authored-by: Danny Khatib Co-authored-by: Chris Huie <3444727+ChrisHuie@users.noreply.github.com> --- modules/prebidServerBidAdapter/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 4ca61cae452..d91858ed9b2 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1,7 +1,7 @@ import Adapter from '../../src/adapter.js'; import { createBid } from '../../src/bidfactory.js'; import * as utils from '../../src/utils.js'; -import { STATUS, S2S, EVENTS } from '../../src/constants.json'; +import CONSTANTS from '../../src/constants.json'; import adapterManager from '../../src/adapterManager.js'; import { config } from '../../src/config.js'; import { VIDEO, NATIVE } from '../../src/mediaTypes.js'; @@ -16,7 +16,7 @@ import { getPrebidInternal } from '../../src/utils.js'; const getConfig = config.getConfig; -const TYPE = S2S.SRC; +const TYPE = CONSTANTS.S2S.SRC; let _syncCount = 0; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; @@ -840,7 +840,7 @@ const OPEN_RTB_PROTOCOL = { } const cpm = bid.price; - const status = cpm !== 0 ? STATUS.GOOD : STATUS.NO_BID; + const status = cpm !== 0 ? CONSTANTS.STATUS.GOOD : CONSTANTS.STATUS.NO_BID; let bidObject = createBid(status, bidRequest || { bidder: seatbid.seat, src: TYPE @@ -1099,7 +1099,7 @@ export function PrebidServer() { } }); - bidderRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_DONE, bidderRequest)); + bidderRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); } catch (error) { utils.logError(error); } @@ -1113,7 +1113,7 @@ export function PrebidServer() { } // Listen for bid won to call wurl - events.on(EVENTS.BID_WON, bidWonHandler); + events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); return Object.assign(this, { callBids: baseAdapter.callBids, From 3f02a15968d16d1aa9cddc48ed73098cc5677006 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Thu, 13 May 2021 14:31:40 -0400 Subject: [PATCH 10/48] First Party Data module: Add new module and two submodules to populate defaults and validate ortb2 (#6452) * Creating fpd module * Continued work on FPD module. - Data validation - Pubcid optout check - Misc Fixes * Revert userId update. Committed in error * Added first party data unit tests and fixed bug * Added an unsubscribe for tests to run properly * Reworked logic to use bidderRequests hook to update global/bidder configs instead of subscribing - former method was preventing tests from completing properly * Merge master * Removing unused references. Fixing device data to point to device.h/device.w * Update to include opt out configuration for enrichments/validations * Modified logic to use ortb2 configuration mapping. This will allow for entries to be added/removed/modified from configuration as opposed to be specifically called out in the validation functions * Removed LGTM unneeded defensive code for check on object 'cur' * Remove unused conditional * Fix lint error * Updates to remove currency enrichment as well as optout for user object * Added optout flag to user.yob and user.gender fields * Added test for arbitrary values Added more comments * Broke module out into module and two submodules * Updated cur to validate as an array of strings not just a string Updated comments --- modules/.submodules.json | 4 + modules/enrichmentFpdModule.js | 107 ++++ modules/fpdModule/index.js | 58 +++ modules/fpdModule/index.md | 46 ++ modules/validationFpdModule/config.js | 125 +++++ modules/validationFpdModule/index.js | 232 +++++++++ test/spec/modules/enrichmentFpdModule_spec.js | 97 ++++ test/spec/modules/fpdModule_spec.js | 464 ++++++++++++++++++ test/spec/modules/validationFpdModule_spec.js | 313 ++++++++++++ 9 files changed, 1446 insertions(+) create mode 100644 modules/enrichmentFpdModule.js create mode 100644 modules/fpdModule/index.js create mode 100644 modules/fpdModule/index.md create mode 100644 modules/validationFpdModule/config.js create mode 100644 modules/validationFpdModule/index.js create mode 100644 test/spec/modules/enrichmentFpdModule_spec.js create mode 100644 test/spec/modules/fpdModule_spec.js create mode 100644 test/spec/modules/validationFpdModule_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 7760d31cfff..a8804321278 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -41,5 +41,9 @@ "reconciliationRtdProvider", "geoedgeRtdProvider", "sirdataRtdProvider" + ], + "fpdModule": [ + "enrichmentFpdModule", + "validationFpdModule" ] } diff --git a/modules/enrichmentFpdModule.js b/modules/enrichmentFpdModule.js new file mode 100644 index 00000000000..a1d815917e0 --- /dev/null +++ b/modules/enrichmentFpdModule.js @@ -0,0 +1,107 @@ +/** + * This module sets default values and validates ortb2 first part data + * @module modules/firstPartyData + */ +import * as utils from '../src/utils.js'; +import { submodule } from '../src/hook.js' +import { getRefererInfo } from '../src/refererDetection.js' + +let ortb2 = {}; +let win = (window === window.top) ? window : window.top; + +/** + * Checks for referer and if exists merges into ortb2 global data + */ +function setReferer() { + if (getRefererInfo().referer) utils.mergeDeep(ortb2, { site: { ref: getRefererInfo().referer } }); +} + +/** + * Checks for canonical url and if exists merges into ortb2 global data + */ +function setPage() { + if (getRefererInfo().canonicalUrl) utils.mergeDeep(ortb2, { site: { page: getRefererInfo().canonicalUrl } }); +} + +/** + * Checks for canonical url and if exists retrieves domain and merges into ortb2 global data + */ +function setDomain() { + let parseDomain = function(url) { + if (!url || typeof url !== 'string' || url.length === 0) return; + + var match = url.match(/^(?:https?:\/\/)?(?:www\.)?(.*?(?=(\?|\#|\/|$)))/i); + + return match && match[1]; + }; + + let domain = parseDomain(getRefererInfo().canonicalUrl) + + if (domain) utils.mergeDeep(ortb2, { site: { domain: domain } }); +} + +/** + * Checks for screen/device width and height and sets dimensions + */ +function setDimensions() { + let width; + let height; + + try { + width = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; + height = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; + } catch (e) { + width = window.innerWidth || window.document.documentElement.clientWidth || window.document.body.clientWidth; + height = window.innerHeight || window.document.documentElement.clientHeight || window.document.body.clientHeight; + } + + utils.mergeDeep(ortb2, { device: { w: width, h: height } }); +} + +/** + * Scans page for meta keywords, and if exists, merges into site.keywords + */ +function setKeywords() { + let keywords; + + try { + keywords = win.document.querySelector("meta[name='keywords']"); + } catch (e) { + keywords = window.document.querySelector("meta[name='keywords']"); + } + + if (keywords && keywords.content) utils.mergeDeep(ortb2, { site: { keywords: keywords.content.replace(/\s/g, '') } }); +} + +/** + * Resets modules global ortb2 data + */ +const resetOrtb2 = () => { ortb2 = {} }; + +function runEnrichments() { + setReferer(); + setPage(); + setDomain(); + setDimensions(); + setKeywords(); + + return ortb2; +} + +/** + * Sets default values to ortb2 if exists and adds currency and ortb2 setConfig callbacks on init + */ +export function initSubmodule(fpdConf, data) { + resetOrtb2(); + + return (!fpdConf.skipEnrichments) ? utils.mergeDeep(runEnrichments(), data) : data; +} + +/** @type {firstPartyDataSubmodule} */ +export const enrichmentsSubmodule = { + name: 'enrichments', + queue: 2, + init: initSubmodule +} + +submodule('firstPartyData', enrichmentsSubmodule) diff --git a/modules/fpdModule/index.js b/modules/fpdModule/index.js new file mode 100644 index 00000000000..427547a4e4d --- /dev/null +++ b/modules/fpdModule/index.js @@ -0,0 +1,58 @@ +/** + * This module sets default values and validates ortb2 first part data + * @module modules/firstPartyData + */ +import { config } from '../../src/config.js'; +import { module, getHook } from '../../src/hook.js'; +import { getGlobal } from '../../src/prebidGlobal.js'; +import { addBidderRequests } from '../../src/auction.js'; + +let submodules = []; + +/** + * enable submodule in User ID + * @param {RtdSubmodule} submodule + */ +export function registerSubmodules(submodule) { + submodules.push(submodule); +} + +export function init() { + let modConf = config.getConfig('firstPartyData') || {}; + let ortb2 = config.getConfig('ortb2') || {}; + + submodules.sort((a, b) => { + return ((a.queue || 1) - (b.queue || 1)); + }).forEach(submodule => { + ortb2 = submodule.init(modConf, ortb2); + }); + + config.setConfig({ortb2}); +} + +/** + * BidderRequests hook to intiate module and reset modules ortb2 data object + */ +function addBidderRequestHook(fn, bidderRequests) { + init(); + fn.call(this, bidderRequests); + // Removes hook after run + addBidderRequests.getHooks({ hook: addBidderRequestHook }).remove(); +} + +/** + * Sets bidderRequests hook + */ +function setupHook() { + getHook('addBidderRequests').before(addBidderRequestHook); +} + +module('firstPartyData', registerSubmodules); + +// Runs setupHook on initial load +setupHook(); + +/** + * Global function to reinitiate module + */ +(getGlobal()).refreshFpd = setupHook; diff --git a/modules/fpdModule/index.md b/modules/fpdModule/index.md new file mode 100644 index 00000000000..638c966883a --- /dev/null +++ b/modules/fpdModule/index.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: First Party Data Module +``` + +# Description + +Module to perform the following functions to allow for consistent set of first party data using the following submodules. + +Enrichment Submodule: +- populate available data into object: referer, meta-keywords, cur + +Validation Submodule: +- verify OpenRTB datatypes, remove/warn any that are likely to choke downstream readers +- verify that certain OpenRTB attributes are not specified +- optionally suppress user FPD based on the existence of _pubcid_optout + + +1. Module initializes on first load and set bidRequestHook +2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations dependant on submodule +3. After hook complete, it is disabled - meaning module only runs on first auction +4. To reinitiate the module, run pbjs.refreshFPD(), which allows module to rerun as if initial load + + +This module will automatically run first party data enrichments and validations dependant on which submodules are included. There is no configuration required. In order to load the module and submodule(s) and opt out of either enrichements or validations, use the below opt out configuration + +# Module Control Configuration + +``` + +pbjs.setConfig({ + firstPartyData: { + skipValidations: true, // default to false + skipEnrichments: true // default to false + } +}); + +``` + +# Requirements + +At least one of the submodules must be included in order to successfully run the corresponding above operations. + +enrichmentFpdModule +validationFpdModule \ No newline at end of file diff --git a/modules/validationFpdModule/config.js b/modules/validationFpdModule/config.js new file mode 100644 index 00000000000..f6adfea70eb --- /dev/null +++ b/modules/validationFpdModule/config.js @@ -0,0 +1,125 @@ +/** + * Data type map + */ +const TYPES = { + string: 'string', + object: 'object', + number: 'number', +}; + +/** + * Template to define what ortb2 attributes should be validated + * Accepted fields: + * -- invalid - {Boolean} if true, field is not valid + * -- type - {String} valid data type of field + * -- isArray - {Boolean} if true, field must be an array + * -- childType - {String} used in conjuction with isArray: true, defines valid type of array indices + * -- children - {Object} defines child properties needed to be validated (used only if type: object) + * -- required - {Array} array of strings defining any required properties for object (used only if type: object) + * -- optoutApplies - {Boolean} if true, optout logic will filter if applicable (currently only applies to user object) + */ +export const ORTB_MAP = { + imp: { + invalid: true + }, + cur: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + device: { + type: TYPES.object, + children: { + w: { type: TYPES.number }, + h: { type: TYPES.number } + } + }, + site: { + type: TYPES.object, + children: { + name: { type: TYPES.string }, + domain: { type: TYPES.string }, + page: { type: TYPES.string }, + ref: { type: TYPES.string }, + keywords: { type: TYPES.string }, + search: { type: TYPES.string }, + cat: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + sectioncat: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + pagecat: { + type: TYPES.object, + isArray: true, + childType: TYPES.string + }, + content: { + type: TYPES.object, + isArray: false, + children: { + data: { + type: TYPES.object, + isArray: true, + childType: TYPES.object, + required: ['name', 'segment'], + children: { + segment: { + type: TYPES.object, + isArray: true, + childType: TYPES.object, + required: ['id'], + children: { + id: { type: TYPES.string } + } + }, + name: { type: TYPES.string }, + ext: { type: TYPES.object }, + } + } + } + }, + publisher: { + type: TYPES.object, + isArray: false + }, + } + }, + user: { + type: TYPES.object, + children: { + yob: { + type: TYPES.number, + optoutApplies: true + }, + gender: { + type: TYPES.string, + optoutApplies: true + }, + keywords: { type: TYPES.string }, + data: { + type: TYPES.object, + isArray: true, + childType: TYPES.object, + required: ['name', 'segment'], + children: { + segment: { + type: TYPES.object, + isArray: true, + childType: TYPES.object, + required: ['id'], + children: { + id: { type: TYPES.string } + } + }, + name: { type: TYPES.string }, + ext: { type: TYPES.object }, + } + } + } + } +} diff --git a/modules/validationFpdModule/index.js b/modules/validationFpdModule/index.js new file mode 100644 index 00000000000..c23f7e09316 --- /dev/null +++ b/modules/validationFpdModule/index.js @@ -0,0 +1,232 @@ +/** + * This module sets default values and validates ortb2 first part data + * @module modules/firstPartyData + */ +import { config } from '../../src/config.js'; +import * as utils from '../../src/utils.js'; +import { ORTB_MAP } from './config.js'; +import { submodule } from '../../src/hook.js'; +import { getStorageManager } from '../../src/storageManager.js'; + +const STORAGE = getStorageManager(); +let optout; + +/** + * Check if data passed is empty + * @param {*} value to test against + * @returns {Boolean} is value empty + */ +function isEmptyData(data) { + let check = true; + + if (typeof data === 'object' && !utils.isEmpty(data)) { + check = false; + } else if (typeof data !== 'object' && (utils.isNumber(data) || data)) { + check = false; + } + + return check; +} + +/** + * Check if required keys exist in data object + * @param {Object} data object + * @param {Array} array of required keys + * @param {String} object path (for printing warning) + * @param {Number} index of object value in the data array (for printing warning) + * @returns {Boolean} is requirements fulfilled + */ +function getRequiredData(obj, required, parent, i) { + let check = true; + + required.forEach(key => { + if (!obj[key] || isEmptyData(obj[key])) { + check = false; + utils.logWarn(`Filtered ${parent}[] value at index ${i} in ortb2 data: missing required property ${key}`); + } + }); + + return check; +} + +/** + * Check if data type is valid + * @param {*} value to test against + * @param {Object} object containing type definition and if should be array bool + * @returns {Boolean} is type fulfilled + */ +function typeValidation(data, mapping) { + let check = false; + + switch (mapping.type) { + case 'string': + if (typeof data === 'string') check = true; + break; + case 'number': + if (typeof data === 'number' && isFinite(data)) check = true; + break; + case 'object': + if (typeof data === 'object') { + if ((Array.isArray(data) && mapping.isArray) || (!Array.isArray(data) && !mapping.isArray)) check = true; + } + break; + } + + return check; +} + +/** + * Validates ortb2 data arrays and filters out invalid data + * @param {Array} ortb2 data array + * @param {Object} object defining child type and if array + * @param {String} config path of data array + * @param {String} parent path for logging warnings + * @returns {Array} validated/filtered data + */ +export function filterArrayData(arr, child, path, parent) { + arr = arr.filter((index, i) => { + let check = typeValidation(index, {type: child.type, isArray: child.isArray}); + + if (check && Array.isArray(index) === Boolean(child.isArray)) { + return true; + } + + utils.logWarn(`Filtered ${parent}[] value at index ${i} in ortb2 data: expected type ${child.type}`); + }).filter((index, i) => { + let requiredCheck = true; + let mapping = utils.deepAccess(ORTB_MAP, path); + + if (mapping && mapping.required) requiredCheck = getRequiredData(index, mapping.required, parent, i); + + if (requiredCheck) return true; + }).reduce((result, value, i) => { + let typeBool = false; + let mapping = utils.deepAccess(ORTB_MAP, path); + + switch (child.type) { + case 'string': + result.push(value); + break; + case 'object': + if (mapping && mapping.children) { + let validObject = validateFpd(value, path + '.children.', parent + '.'); + if (Object.keys(validObject).length) { + let requiredCheck = getRequiredData(validObject, mapping.required, parent, i); + + if (requiredCheck) { + result.push(validObject); + typeBool = true; + } + } + } else { + result.push(value); + typeBool = true; + } + break; + } + + if (!typeBool) utils.logWarn(`Filtered ${parent}[] value at index ${i} in ortb2 data: expected type ${child.type}`); + + return result; + }, []); + + return arr; +} + +/** + * Validates ortb2 object and filters out invalid data + * @param {Object} ortb2 object + * @param {String} config path of data array + * @param {String} parent path for logging warnings + * @returns {Object} validated/filtered data + */ +export function validateFpd(fpd, path = '', parent = '') { + if (!fpd) return {}; + + // Filter out imp property if exists + let validObject = Object.assign({}, Object.keys(fpd).filter(key => { + let mapping = utils.deepAccess(ORTB_MAP, path + key); + + if (!mapping || !mapping.invalid) return key; + + utils.logWarn(`Filtered ${parent}${key} property in ortb2 data: invalid property`); + }).filter(key => { + let mapping = utils.deepAccess(ORTB_MAP, path + key); + // let typeBool = false; + let typeBool = (mapping) ? typeValidation(fpd[key], {type: mapping.type, isArray: mapping.isArray}) : true; + + if (typeBool || !mapping) return key; + + utils.logWarn(`Filtered ${parent}${key} property in ortb2 data: expected type ${(mapping.isArray) ? 'array' : mapping.type}`); + }).reduce((result, key) => { + let mapping = utils.deepAccess(ORTB_MAP, path + key); + let modified = {}; + + if (mapping) { + if (mapping.optoutApplies && optout) { + utils.logWarn(`Filtered ${parent}${key} data: pubcid optout found`); + return result; + } + + modified = (mapping.type === 'object' && !mapping.isArray) + ? validateFpd(fpd[key], path + key + '.children.', parent + key + '.') + : (mapping.isArray && mapping.childType) + ? filterArrayData(fpd[key], { type: mapping.childType, isArray: mapping.childisArray }, path + key, parent + key) : fpd[key]; + + // Check if modified data has data and return + (!isEmptyData(modified)) ? result[key] = modified + : utils.logWarn(`Filtered ${parent}${key} property in ortb2 data: empty data found`); + } else { + result[key] = fpd[key]; + } + + return result; + }, {})); + + // Return validated data + return validObject; +} + +/** + * Run validation on global and bidder config data for ortb2 + */ +function runValidations(data) { + let conf = validateFpd(data); + + let bidderDuplicate = { ...config.getBidderConfig() }; + + Object.keys(bidderDuplicate).forEach(bidder => { + let modConf = Object.keys(bidderDuplicate[bidder]).reduce((res, key) => { + let valid = (key !== 'ortb2') ? bidderDuplicate[bidder][key] : validateFpd(bidderDuplicate[bidder][key]); + + if (valid) res[key] = valid; + + return res; + }, {}); + + if (Object.keys(modConf).length) config.setBidderConfig({ bidders: [bidder], config: modConf }); + }); + + return conf; +} + +/** + * Sets default values to ortb2 if exists and adds currency and ortb2 setConfig callbacks on init + */ +export function initSubmodule(fpdConf, data) { + // Checks for existsnece of pubcid optout cookie/storage + // if exists, filters user data out + optout = (STORAGE.cookiesAreEnabled() && STORAGE.getCookie('_pubcid_optout')) || + (STORAGE.hasLocalStorage() && STORAGE.getDataFromLocalStorage('_pubcid_optout')); + + return (!fpdConf.skipValidations) ? runValidations(data) : data; +} + +/** @type {firstPartyDataSubmodule} */ +export const validationSubmodule = { + name: 'validation', + queue: 1, + init: initSubmodule +} + +submodule('firstPartyData', validationSubmodule) diff --git a/test/spec/modules/enrichmentFpdModule_spec.js b/test/spec/modules/enrichmentFpdModule_spec.js new file mode 100644 index 00000000000..e5271143f2c --- /dev/null +++ b/test/spec/modules/enrichmentFpdModule_spec.js @@ -0,0 +1,97 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils.js'; +import { getRefererInfo } from 'src/refererDetection.js'; +import { initSubmodule } from 'modules/enrichmentFpdModule.js'; + +describe('the first party data enrichment module', function() { + let width; + let widthStub; + let height; + let heightStub; + let querySelectorStub; + let canonical; + let keywords; + + before(function() { + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + keywords = document.createElement('meta'); + keywords.name = 'keywords'; + }); + + beforeEach(function() { + querySelectorStub = sinon.stub(window.top.document, 'querySelector'); + querySelectorStub.withArgs("link[rel='canonical']").returns(canonical); + querySelectorStub.withArgs("meta[name='keywords']").returns(keywords); + widthStub = sinon.stub(window.top, 'innerWidth').get(function() { + return width; + }); + heightStub = sinon.stub(window.top, 'innerHeight').get(function() { + return height; + }); + }); + + afterEach(function() { + widthStub.restore(); + heightStub.restore(); + querySelectorStub.restore(); + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + keywords = document.createElement('meta'); + keywords.name = 'keywords'; + }); + + it('adds ref and device values', function() { + width = 800; + height = 500; + + let validated = initSubmodule({}, {}); + + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.equal({ w: 800, h: 500 }); + expect(validated.site.keywords).to.be.undefined; + }); + + it('adds page and domain values if canonical url exists', function() { + width = 800; + height = 500; + canonical.href = 'https://www.domain.com/path?query=12345'; + + let validated = initSubmodule({}, {}); + + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); + expect(validated.site.domain).to.equal('domain.com'); + expect(validated.device).to.deep.equal({ w: 800, h: 500 }); + expect(validated.site.keywords).to.be.undefined; + }); + + it('adds keyword value if keyword meta content exists', function() { + width = 800; + height = 500; + keywords.content = 'value1,value2,value3'; + + let validated = initSubmodule({}, {}); + + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.equal({ w: 800, h: 500 }); + expect(validated.site.keywords).to.equal('value1,value2,value3'); + }); + + it('does not overwrite existing data from getConfig ortb2', function() { + width = 800; + height = 500; + + let validated = initSubmodule({}, {device: {w: 1200, h: 700}, site: {ref: 'https://someUrl.com', page: 'test.com'}}); + + expect(validated.site.ref).to.equal('https://someUrl.com'); + expect(validated.site.page).to.equal('test.com'); + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.equal({ w: 1200, h: 700 }); + expect(validated.site.keywords).to.be.undefined; + }); +}); diff --git a/test/spec/modules/fpdModule_spec.js b/test/spec/modules/fpdModule_spec.js new file mode 100644 index 00000000000..c2a6c41835e --- /dev/null +++ b/test/spec/modules/fpdModule_spec.js @@ -0,0 +1,464 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import {config} from 'src/config.js'; +import {getRefererInfo} from 'src/refererDetection.js'; +import {init, registerSubmodules} from 'modules/fpdModule/index.js'; +import * as enrichmentModule from 'modules/enrichmentFpdModule.js'; +import * as validationModule from 'modules/validationFpdModule/index.js'; + +let enrichments = { + name: 'enrichments', + queue: 2, + init: enrichmentModule.initSubmodule +}; +let validations = { + name: 'validations', + queue: 1, + init: validationModule.initSubmodule +}; + +describe('the first party data module', function () { + let ortb2 = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar', + ext: 'string' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + let conf = { + device: { + h: 500, + w: 750 + }, + user: { + keywords: 'test1, test2', + gender: 'f', + data: [{ + segment: [{ + id: 'test' + }], + name: 'alt' + }] + }, + site: { + ref: 'domain.com', + page: 'www.domain.com/test', + ext: { + data: { + inventory: ['first'] + } + } + } + }; + + afterEach(function () { + config.resetConfig(); + }); + + describe('first party data intitializing', function () { + let width; + let widthStub; + let height; + let heightStub; + let querySelectorStub; + let canonical; + let keywords; + + before(function() { + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + keywords = document.createElement('meta'); + keywords.name = 'keywords'; + }); + + beforeEach(function() { + querySelectorStub = sinon.stub(window.top.document, 'querySelector'); + querySelectorStub.withArgs("link[rel='canonical']").returns(canonical); + querySelectorStub.withArgs("meta[name='keywords']").returns(keywords); + widthStub = sinon.stub(window.top, 'innerWidth').get(function () { + return width; + }); + heightStub = sinon.stub(window.top, 'innerHeight').get(function () { + return height; + }); + }); + + afterEach(function() { + widthStub.restore(); + heightStub.restore(); + querySelectorStub.restore(); + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + keywords = document.createElement('meta'); + keywords.name = 'keywords'; + }); + + it('sets default referer and dimension values to ortb2 data', function () { + registerSubmodules(enrichments); + registerSubmodules(validations); + + let validated; + + width = 1120; + height = 750; + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.equal({w: 1120, h: 750}); + expect(validated.site.keywords).to.be.undefined; + }); + + it('sets page and domain values to ortb2 data if canonical link exists', function () { + let validated; + + canonical.href = 'https://www.domain.com/path?query=12345'; + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); + expect(validated.site.domain).to.equal('domain.com'); + expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); + expect(validated.site.keywords).to.be.undefined; + }); + + it('sets keyword values to ortb2 data if keywords meta exists', function () { + let validated; + + keywords.content = 'value1,value2,value3'; + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); + expect(validated.site.keywords).to.equal('value1,value2,value3'); + }); + + it('only sets values that do not exist in ortb2 config', function () { + let validated; + + config.setConfig({ortb2: {site: {ref: 'https://testpage.com', domain: 'newDomain.com'}}}); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal('https://testpage.com'); + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.equal('newDomain.com'); + expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); + expect(validated.site.keywords).to.be.undefined; + }); + + it('filters ortb2 data that is set', function () { + let validated; + let conf = { + ortb2: { + user: { + data: {}, + gender: 'f', + age: 45 + }, + site: { + content: { + data: [{ + segment: { + test: 1 + }, + name: 'foo' + }, { + segment: [{ + id: 'test' + }, { + id: 3 + }], + name: 'bar' + }] + } + }, + device: { + w: 1, + h: 1 + } + } + }; + + config.setConfig(conf); + canonical.href = 'https://www.domain.com/path?query=12345'; + width = 1120; + height = 750; + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); + expect(validated.site.domain).to.equal('domain.com'); + expect(validated.site.content.data).to.deep.equal([{segment: [{id: 'test'}], name: 'bar'}]); + expect(validated.user.data).to.be.undefined; + expect(validated.device).to.deep.to.equal({w: 1, h: 1}); + expect(validated.site.keywords).to.be.undefined; + }); + + it('should not overwrite existing data with default settings', function () { + let validated; + let conf = { + ortb2: { + site: { + ref: 'https://referer.com' + } + } + }; + + config.setConfig(conf); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal('https://referer.com'); + }); + + it('should allow overwrite default data with setConfig', function () { + let validated; + let conf = { + ortb2: { + site: { + ref: 'https://referer.com' + } + } + }; + + config.setConfig(conf); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal('https://referer.com'); + }); + + it('should filter all data', function () { + let validated; + let conf = { + imp: [], + site: { + name: 123, + domain: 456, + page: 789, + ref: 987, + keywords: ['keywords'], + search: 654, + cat: 'cat', + sectioncat: 'sectioncat', + pagecat: 'pagecat', + content: { + data: [{ + name: 1, + segment: [] + }] + } + }, + user: { + yob: 'twenty', + gender: 0, + keywords: ['foobar'], + data: ['test'] + }, + device: [800, 450], + cur: { + adServerCurrency: 'USD' + } + }; + + config.setConfig({'firstPartyData': {skipEnrichments: true}}); + + config.setConfig({ortb2: conf}); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated).to.deep.equal({}); + }); + + it('should add enrichments but not alter any arbitrary ortb2 data', function () { + let validated; + let conf = { + site: { + ext: { + data: { + inventory: ['value1'] + } + } + }, + user: { + ext: { + data: { + visitor: ['value2'] + } + } + }, + cur: ['USD'] + }; + + config.setConfig({ortb2: conf}); + + init(); + + validated = config.getConfig('ortb2'); + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.ext.data).to.deep.equal({inventory: ['value1']}); + expect(validated.user.ext.data).to.deep.equal({visitor: ['value2']}); + expect(validated.cur).to.deep.equal(['USD']); + }); + + it('should filter bidderConfig data', function () { + let validated; + let conf = { + bidders: ['bidderA', 'bidderB'], + config: { + ortb2: { + site: { + keywords: 'other', + ref: 'https://domain.com' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 4}], + name: 't' + }] + } + } + } + }; + + config.setBidderConfig(conf); + + init(); + + validated = config.getBidderConfig(); + expect(validated.bidderA.ortb2).to.not.be.undefined; + expect(validated.bidderA.ortb2.user.data).to.be.undefined; + expect(validated.bidderA.ortb2.user.keywords).to.equal('test'); + expect(validated.bidderA.ortb2.site.keywords).to.equal('other'); + expect(validated.bidderA.ortb2.site.ref).to.equal('https://domain.com'); + }); + + it('should not filter bidderConfig data as it is valid', function () { + let validated; + let conf = { + bidders: ['bidderA', 'bidderB'], + config: { + ortb2: { + site: { + keywords: 'other', + ref: 'https://domain.com' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 'data1_id'}], + name: 'data1' + }] + } + } + } + }; + + config.setBidderConfig(conf); + + init(); + + validated = config.getBidderConfig(); + expect(validated.bidderA.ortb2).to.not.be.undefined; + expect(validated.bidderA.ortb2.user.data).to.deep.equal([{segment: [{id: 'data1_id'}], name: 'data1'}]); + expect(validated.bidderA.ortb2.user.keywords).to.equal('test'); + expect(validated.bidderA.ortb2.site.keywords).to.equal('other'); + expect(validated.bidderA.ortb2.site.ref).to.equal('https://domain.com'); + }); + + it('should not set default values if skipEnrichments is turned on', function () { + let validated; + config.setConfig({'firstPartyData': {skipEnrichments: true}}); + + let conf = { + site: { + keywords: 'other' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 'data1_id'}], + name: 'data1' + }] + } + } + ; + + config.setConfig({ortb2: conf}); + + init(); + + validated = config.getConfig(); + expect(validated.ortb2).to.not.be.undefined; + expect(validated.ortb2.device).to.be.undefined; + expect(validated.ortb2.site.ref).to.be.undefined; + expect(validated.ortb2.site.page).to.be.undefined; + expect(validated.ortb2.site.domain).to.be.undefined; + }); + + it('should not validate ortb2 data if skipValidations is turned on', function () { + let validated; + config.setConfig({'firstPartyData': {skipValidations: true}}); + + let conf = { + site: { + keywords: 'other' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 'nonfiltered'}] + }] + } + } + ; + + config.setConfig({ortb2: conf}); + + init(); + + validated = config.getConfig(); + expect(validated.ortb2).to.not.be.undefined; + expect(validated.ortb2.user.data).to.deep.equal([{segment: [{id: 'nonfiltered'}]}]); + }); + }); +}); diff --git a/test/spec/modules/validationFpdModule_spec.js b/test/spec/modules/validationFpdModule_spec.js new file mode 100644 index 00000000000..9e8072cb9ed --- /dev/null +++ b/test/spec/modules/validationFpdModule_spec.js @@ -0,0 +1,313 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import { + filterArrayData, + validateFpd +} from 'modules/validationFpdModule/index.js'; + +describe('the first party data validation module', function () { + let ortb2 = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar', + ext: 'string' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + let conf = { + device: { + h: 500, + w: 750 + }, + user: { + keywords: 'test1, test2', + gender: 'f', + data: [{ + segment: [{ + id: 'test' + }], + name: 'alt' + }] + }, + site: { + ref: 'domain.com', + page: 'www.domain.com/test', + ext: { + data: { + inventory: ['first'] + } + } + } + }; + + describe('filtering first party array data', function () { + it('returns empty array if no valid data', function () { + let arr = [{}]; + let path = 'site.children.cat'; + let child = {type: 'string'}; + let parent = 'site'; + let key = 'cat'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal([]); + }); + + it('filters invalid type of array data', function () { + let arr = ['foo', {test: 1}]; + let path = 'site.children.cat'; + let child = {type: 'string'}; + let parent = 'site'; + let key = 'cat'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal(['foo']); + }); + + it('filters all data for missing required children', function () { + let arr = [{test: 1}]; + let path = 'site.children.content.children.data'; + let child = {type: 'object'}; + let parent = 'site'; + let key = 'data'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal([]); + }); + + it('filters all data for invalid required children types', function () { + let arr = [{name: 'foo', segment: 1}]; + let path = 'site.children.content.children.data'; + let child = {type: 'object'}; + let parent = 'site'; + let key = 'data'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal([]); + }); + + it('returns only data with valid required nested children types', function () { + let arr = [{name: 'foo', segment: [{id: '1'}, {id: 2}, 'foobar']}]; + let path = 'site.children.content.children.data'; + let child = {type: 'object'}; + let parent = 'site'; + let key = 'data'; + let validated = filterArrayData(arr, child, path, parent, key); + expect(validated).to.deep.equal([{name: 'foo', segment: [{id: '1'}]}]); + }); + }); + + describe('validating first party data', function () { + it('filters user.data[0].ext for incorrect type', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + let expected = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters user and site for empty data', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + let expected = { + device: { + h: 911, + w: 1733 + } + }; + + duplicate.user.data = []; + duplicate.site.content.data = []; + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters user for empty valid segment values', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + let expected = { + device: { + h: 911, + w: 1733 + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + duplicate.user.data[0].segment.push({test: 3}); + duplicate.user.data[0].segment[0] = {foo: 'bar'}; + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters user.data[0].ext and site.content.data[0].segement[1] for invalid data', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + let expected = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + duplicate.site.content.data[0].segment.push({test: 3}); + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters device for invalid data types', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + duplicate.device = { + h: '1', + w: '1' + } + + let expected = { + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + duplicate.site.content.data[0].segment.push({test: 3}); + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + + it('filters cur for invalid data type', function () { + let validated; + let duplicate = utils.deepClone(ortb2); + duplicate.cur = 8; + + let expected = { + device: { + h: 911, + w: 1733 + }, + user: { + data: [{ + segment: [{ + id: 'foo' + }], + name: 'bar' + }] + }, + site: { + content: { + data: [{ + segment: [{ + id: 'test' + }], + name: 'content', + ext: { + foo: 'bar' + } + }] + } + } + }; + + duplicate.site.content.data[0].segment.push({test: 3}); + + validated = validateFpd(duplicate); + expect(validated).to.deep.equal(expected); + }); + }); +}); From 2b8f888dc11d727f49867309f37e492d65eaee96 Mon Sep 17 00:00:00 2001 From: htang555 Date: Fri, 14 May 2021 07:26:42 -0400 Subject: [PATCH 11/48] Datablocks bid adapter: update adapter to conform to new bid server's format (#6696) * update datablocks bid adapter * remove TODO and fix linting errors * updated readme and changed insights to ortb2 * fixed ortb2 change Co-authored-by: John Mayor --- modules/datablocksAnalyticsAdapter.js | 2 +- modules/datablocksBidAdapter.js | 862 ++++++++++++------ modules/datablocksBidAdapter.md | 30 +- .../spec/modules/datablocksBidAdapter_spec.js | 601 +++++++----- 4 files changed, 988 insertions(+), 507 deletions(-) diff --git a/modules/datablocksAnalyticsAdapter.js b/modules/datablocksAnalyticsAdapter.js index 5e977155284..3e4e9e95a4f 100644 --- a/modules/datablocksAnalyticsAdapter.js +++ b/modules/datablocksAnalyticsAdapter.js @@ -16,4 +16,4 @@ adapterManager.registerAnalyticsAdapter({ code: 'datablocks' }); -export default datablocksAdapter; +export default datablocksAdapter; \ No newline at end of file diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index b00a3eae659..038a521308d 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,330 +1,634 @@ -import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -const NATIVE_MAP = { - 'body': 2, - 'body2': 10, - 'price': 6, - 'displayUrl': 11, - 'cta': 12 -}; -const NATIVE_IMAGE = [{ - id: 1, - required: 1, +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; +export const storage = getStorageManager(); + +const NATIVE_ID_MAP = {}; +const NATIVE_PARAMS = { title: { - len: 140 - } -}, { - id: 2, - required: 1, - img: { type: 3 } -}, { - id: 3, - required: 1, - data: { - type: 11 - } -}, { - id: 4, - required: 0, - data: { + id: 1, + name: 'title' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + body: { + id: 4, + name: 'data', type: 2 + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + cta: { + id: 6, + type: 12, + name: 'data' + }, + body2: { + id: 7, + name: 'data', + type: 10 + }, + rating: { + id: 8, + name: 'data', + type: 3 + }, + likes: { + id: 9, + name: 'data', + type: 4 + }, + downloads: { + id: 10, + name: 'data', + type: 5 + }, + displayUrl: { + id: 11, + name: 'data', + type: 11 + }, + price: { + id: 12, + name: 'data', + type: 6 + }, + salePrice: { + id: 13, + name: 'data', + type: 7 + }, + address: { + id: 14, + name: 'data', + type: 9 + }, + phone: { + id: 15, + name: 'data', + type: 8 } -}, { - id: 5, - required: 0, - img: { type: 1 } -}, { - id: 6, - required: 0, - data: { - type: 12 - } -}]; +}; -const VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', - 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', - 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', - 'pos', 'companionad', 'api', 'companiontype', 'ext']; +Object.keys(NATIVE_PARAMS).forEach((key) => { + NATIVE_ID_MAP[NATIVE_PARAMS[key].id] = key; +}); +// DEFINE THE PREBID BIDDER SPEC export const spec = { - supportedMediaTypes: [BANNER, NATIVE, VIDEO], + supportedMediaTypes: [BANNER, NATIVE], code: 'datablocks', + + // DATABLOCKS SCOPED OBJECT + db_obj: {metrics_host: 'prebid.datablocks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0}, + + // STORE THE DATABLOCKS BUYERID IN STORAGE + store_dbid: function(dbid) { + let stored = false; + + // CREATE 1 YEAR EXPIRY DATE + let d = new Date(); + d.setTime(Date.now() + (365 * 24 * 60 * 60 * 1000)); + + // TRY TO STORE IN COOKIE + if (storage.cookiesAreEnabled) { + storage.setCookie('_db_dbid', dbid, d.toUTCString(), 'None', null); + stored = true; + } + + // TRY TO STORE IN LOCAL STORAGE + if (storage.localStorageIsEnabled) { + storage.setDataInLocalStorage('_db_dbid', dbid); + stored = true; + } + + return stored; + }, + + // FETCH DATABLOCKS BUYERID FROM STORAGE + get_dbid: function() { + let dbId = ''; + if (storage.cookiesAreEnabled) { + dbId = storage.getCookie('_db_dbid') || ''; + } + + if (!dbId && storage.localStorageIsEnabled) { + dbId = storage.getDataFromLocalStorage('_db_dbid') || ''; + } + return dbId; + }, + + // STORE SYNCS IN STORAGE + store_syncs: function(syncs) { + if (storage.localStorageIsEnabled) { + let syncObj = {}; + syncs.forEach(sync => { + syncObj[sync.id] = sync.uid; + }); + + // FETCH EXISTING SYNCS AND MERGE NEW INTO STORAGE + let storedSyncs = this.get_syncs(); + storage.setDataInLocalStorage('_db_syncs', JSON.stringify(Object.assign(storedSyncs, syncObj))); + + return true; + } + }, + + // GET SYNCS FROM STORAGE + get_syncs: function() { + if (storage.localStorageIsEnabled) { + let syncData = storage.getDataFromLocalStorage('_db_syncs'); + if (syncData) { + return JSON.parse(syncData); + } else { + return {}; + } + } else { + return {}; + } + }, + + // ADD METRIC DATA TO THE METRICS RESPONSE QUEUE + queue_metric: function(metric) { + if (typeof metric === 'object') { + // PUT METRICS IN THE QUEUE + this.db_obj.metrics.push(metric); + + // RESET PREVIOUS TIMER + if (this.db_obj.metrics_timer) { + clearTimeout(this.db_obj.metrics_timer); + } + + // SETUP THE TIMER TO FIRE BACK THE DATA + let scope = this; + this.db_obj.metrics_timer = setTimeout(function() { + scope.send_metrics(); + }, this.db_obj.metrics_queue_time); + + return true; + } else { + return false; + } + }, + + // POST CONSOLIDATED METRICS BACK TO SERVER + send_metrics: function() { + // POST TO SERVER + ajax(`https://${this.db_obj.metrics_host}/a/pb/`, null, JSON.stringify(this.db_obj.metrics), {method: 'POST', withCredentials: true}); + + // RESET THE QUEUE OF METRIC DATA + this.db_obj.metrics = []; + + return true; + }, + + // GET BASIC CLIENT INFORMATION + get_client_info: function () { + let botTest = new BotClientTests(); + let win = utils.getWindowTop(); + return { + 'wiw': win.innerWidth, + 'wih': win.innerHeight, + 'saw': screen ? screen.availWidth : null, + 'sah': screen ? screen.availHeight : null, + 'scd': screen ? screen.colorDepth : null, + 'sw': screen ? screen.width : null, + 'sh': screen ? screen.height : null, + 'whl': win.history.length, + 'wxo': win.pageXOffset, + 'wyo': win.pageYOffset, + 'wpr': win.devicePixelRatio, + 'is_bot': botTest.doTests(), + 'is_hid': win.document.hidden, + 'vs': win.document.visibilityState + }; + }, + + // LISTEN FOR GPT VIEWABILITY EVENTS + get_viewability: function(bid) { + // ONLY RUN ONCE IF PUBLISHER HAS OPTED IN + if (!this.db_obj.vis_optout && !this.db_obj.vis_run) { + this.db_obj.vis_run = true; + + // ADD GPT EVENT LISTENERS + let scope = this; + if (utils.isGptPubadsDefined()) { + if (typeof window['googletag'].pubads().addEventListener == 'function') { + window['googletag'].pubads().addEventListener('impressionViewable', function(event) { + scope.queue_metric({type: 'slot_view', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()}); + }); + window['googletag'].pubads().addEventListener('slotRenderEnded', function(event) { + scope.queue_metric({type: 'slot_render', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()}); + }) + } + } + } + }, + + // VALIDATE THE BID REQUEST isBidRequestValid: function(bid) { - return !!(bid.params.host && bid.params.sourceId && - bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native || bid.mediaTypes.video)); + // SET GLOBAL VARS FROM BIDDER CONFIG + this.db_obj.source_id = bid.params.source_id; + if (bid.params.vis_optout) { + this.db_obj.vis_optout = true; + } + + return !!(bid.params.source_id && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native)); }, - buildRequests: function(validBidRequests, bidderRequest) { - if (!validBidRequests.length) { return []; } - let imps = {}; - let site = {}; - let device = {}; - let refurl = utils.parseUrl(bidderRequest.referrer); - let requests = []; + // GENERATE THE RTB REQUEST + buildRequests: function(validRequests, bidderRequest) { + // RETURN EMPTY IF THERE ARE NO VALID REQUESTS + if (!validRequests.length) { + return []; + } - validBidRequests.forEach(bidRequest => { + // CONVERT PREBID NATIVE REQUEST OBJ INTO RTB OBJ + function createNativeRequest(bid) { + const assets = []; + if (bid.nativeParams) { + Object.keys(bid.nativeParams).forEach((key) => { + if (NATIVE_PARAMS[key]) { + const {name, type, id} = NATIVE_PARAMS[key]; + const assetObj = type ? {type} : {}; + let {len, sizes, required, aspect_ratios: aRatios} = bid.nativeParams[key]; + if (len) { + assetObj.len = len; + } + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + let wmin = aRatios.min_width || 0; + let hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + assetObj.wmin = wmin; + assetObj.hmin = hmin; + } + if (sizes && sizes.length) { + sizes = [].concat(...sizes); + assetObj.w = sizes[0]; + assetObj.h = sizes[1]; + } + const asset = {required: required ? 1 : 0, id}; + asset[name] = assetObj; + assets.push(asset); + } + }); + } + return { + ver: '1.2', + request: { + assets: assets, + context: 1, + plcmttype: 1, + ver: '1.2' + } + } + } + let imps = []; + // ITERATE THE VALID REQUESTS AND GENERATE IMP OBJECT + validRequests.forEach(bidRequest => { + // BUILD THE IMP OBJECT let imp = { id: bidRequest.bidId, - tagid: bidRequest.adUnitCode, - secure: window.location.protocol == 'https:' + tagid: bidRequest.params.tagid || bidRequest.adUnitCode, + placement_id: bidRequest.params.placement_id || 0, + secure: window.location.protocol == 'https:', + ortb2: utils.deepAccess(bidRequest, `ortb2Imp`) || {}, + floor: {} } + // CHECK FOR FLOORS + if (typeof bidRequest.getFloor === 'function') { + imp.floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + } + + // BUILD THE SIZES if (utils.deepAccess(bidRequest, `mediaTypes.banner`)) { - let sizes = bidRequest.mediaTypes.banner.sizes; - if (sizes.length == 1) { + let sizes = utils.getAdUnitSizes(bidRequest); + if (sizes.length) { imp.banner = { w: sizes[0][0], - h: sizes[0][1] - } - } else if (sizes.length > 1) { - imp.banner = { + h: sizes[0][1], format: sizes.map(size => ({ w: size[0], h: size[1] })) }; - } else { - return; - } - } else if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { - let nativeImp = bidRequest.mediaTypes.native; - - if (nativeImp.type) { - let nativeAssets = []; - switch (nativeImp.type) { - case 'image': - nativeAssets = NATIVE_IMAGE; - break; - default: - return; - } - imp.native = JSON.stringify({ assets: nativeAssets }); - } else { - let nativeAssets = []; - let nativeKeys = Object.keys(nativeImp); - nativeKeys.forEach((nativeKey, index) => { - let required = !!nativeImp[nativeKey].required; - let assetId = index + 1; - switch (nativeKey) { - case 'title': - nativeAssets.push({ - id: assetId, - required: required, - title: { - len: nativeImp[nativeKey].len || 140 - } - }); - break; - case 'body': // desc - case 'body2': // desc2 - case 'price': - case 'display_url': - let data = { - id: assetId, - required: required, - data: { - type: NATIVE_MAP[nativeKey] - } - } - if (nativeImp[nativeKey].data && nativeImp[nativeKey].data.len) { data.data.len = nativeImp[nativeKey].data.len; } - - nativeAssets.push(data); - break; - case 'image': - if (nativeImp[nativeKey].sizes && nativeImp[nativeKey].sizes.length) { - nativeAssets.push({ - id: assetId, - required: required, - image: { - type: 3, - w: nativeImp[nativeKey].sizes[0], - h: nativeImp[nativeKey].sizes[1] - } - }) - } - } - }); - imp.native = { - request: JSON.stringify({native: {assets: nativeAssets}}) - }; - } - } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { - let video = bidRequest.mediaTypes.video; - let sizes = video.playerSize || bidRequest.sizes || []; - if (sizes.length && Array.isArray(sizes[0])) { - imp.video = { - w: sizes[0][0], - h: sizes[0][1] - }; - } else if (sizes.length == 2 && !Array.isArray(sizes[0])) { - imp.video = { - w: sizes[0], - h: sizes[1] - }; - } else { - return; - } - - if (video.durationRangeSec) { - if (Array.isArray(video.durationRangeSec)) { - if (video.durationRangeSec.length == 1) { - imp.video.maxduration = video.durationRangeSec[0]; - } else if (video.durationRangeSec.length == 2) { - imp.video.minduration = video.durationRangeSec[0]; - imp.video.maxduration = video.durationRangeSec[1]; - } - } else { - imp.video.maxduration = video.durationRangeSec; - } - } - if (bidRequest.params.video) { - Object.keys(bidRequest.params.video).forEach(k => { - if (VIDEO_PARAMS.indexOf(k) > -1) { - imp.video[k] = bidRequest.params.video[k]; - } - }) + // ADD TO THE LIST OF IMP REQUESTS + imps.push(imp); } + } else if (utils.deepAccess(bidRequest, `mediaTypes.native`)) { + // ADD TO THE LIST OF IMP REQUESTS + imp.native = createNativeRequest(bidRequest); + imps.push(imp); } - let host = bidRequest.params.host; - let sourceId = bidRequest.params.sourceId; - imps[host] = imps[host] || {}; - let hostImp = imps[host][sourceId] = imps[host][sourceId] || { imps: [] }; - hostImp.imps.push(imp); - hostImp.subid = hostImp.imps.subid || bidRequest.params.subid || 'blank'; - hostImp.path = 'search'; - hostImp.idParam = 'sid'; - hostImp.protocol = '//'; }); - // Generate Site obj - site.domain = refurl.hostname; - site.page = refurl.protocol + '://' + refurl.hostname + refurl.pathname; + // RETURN EMPTY IF THERE WERE NO PROPER ADUNIT REQUESTS TO BE MADE + if (!imps.length) { + return []; + } + + // GENERATE SITE OBJECT + let site = { + domain: window.location.host, + page: bidderRequest.refererInfo.referer, + schain: validRequests[0].schain || {}, + ext: { + p_domain: config.getConfig('publisherDomain'), + rt: bidderRequest.refererInfo.reachedTop, + frames: bidderRequest.refererInfo.numIframes, + stack: bidderRequest.refererInfo.stack, + timeout: config.getConfig('bidderTimeout') + }, + } + + // ADD REF URL IF FOUND if (self === top && document.referrer) { site.ref = document.referrer; } + + // ADD META KEYWORDS IF FOUND let keywords = document.getElementsByTagName('meta')['keywords']; if (keywords && keywords.content) { site.keywords = keywords.content; } - // Generate Device obj. - device.ip = 'peer'; - device.ua = window.navigator.userAgent; - device.js = 1; - device.language = ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en'; - - RtbRequest(device, site, imps).forEach(formatted => { - requests.push({ - method: 'POST', - url: formatted.url, - data: formatted.body, - options: { - withCredentials: false + // GENERATE DEVICE OBJECT + let device = { + ip: 'peer', + ua: window.navigator.userAgent, + js: 1, + language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', + buyerid: this.get_dbid() || 0, + ext: { + pb_eids: validRequests[0].userIdAsEids || {}, + syncs: this.get_syncs() || {}, + coppa: config.getConfig('coppa') || 0, + gdpr: bidderRequest.gdprConsent || {}, + usp: bidderRequest.uspConsent || {}, + client_info: this.get_client_info(), + ortb2: config.getConfig('ortb2') || {} + } + }; + + let sourceId = validRequests[0].params.source_id || 0; + let host = validRequests[0].params.host || 'prebid.datablocks.net'; + + // RETURN WITH THE REQUEST AND PAYLOAD + return { + method: 'POST', + url: `https://${sourceId}.${host}/openrtb/?sid=${sourceId}`, + data: { + id: bidderRequest.auctionId, + imp: imps, + site: site, + device: device + }, + options: { + withCredentials: true + } + }; + }, + + // INITIATE USER SYNCING + getUserSyncs: function(options, rtbResponse, gdprConsent) { + const syncs = []; + let bidResponse = rtbResponse[0].body; + let scope = this; + + // LISTEN FOR SYNC DATA FROM IFRAME TYPE SYNC + window.addEventListener('message', function (event) { + if (event.data.sentinel && event.data.sentinel === 'dblks_syncData') { + // STORE FOUND SYNCS + if (event.data.syncs) { + scope.store_syncs(event.data.syncs); } - }) + } }); - return requests; - - function RtbRequest(device, site, imps) { - let collection = []; - Object.keys(imps).forEach(host => { - let sourceIds = imps[host]; - Object.keys(sourceIds).forEach(sourceId => { - let impObj = sourceIds[sourceId]; - collection.push({ - url: `https://${host}/${impObj.path}/?${impObj.idParam}=${sourceId}`, - body: { - id: bidderRequest.auctionId, - imp: impObj.imps, - site: Object.assign({ id: impObj.subid || 'blank' }, site), - device: Object.assign({}, device) - } - }) - }) + + // POPULATE GDPR INFORMATION + let gdprData = { + gdpr: 0, + gdprConsent: '' + } + if (typeof gdprConsent === 'object') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprData.gdpr = Number(gdprConsent.gdprApplies); + gdprData.gdprConsent = gdprConsent.consentString; + } else { + gdprData.gdprConsent = gdprConsent.consentString; + } + } + + // EXTRACT BUYERID COOKIE VALUE FROM BID RESPONSE AND PUT INTO STORAGE + let dbBuyerId = this.get_dbid() || ''; + if (bidResponse.ext && bidResponse.ext.buyerid) { + dbBuyerId = bidResponse.ext.buyerid; + this.store_dbid(dbBuyerId); + } + + // EXTRACT USERSYNCS FROM BID RESPONSE + if (bidResponse.ext && bidResponse.ext.syncs) { + bidResponse.ext.syncs.forEach(sync => { + if (checkValid(sync)) { + syncs.push(addParams(sync)); + } }) + } + + // APPEND PARAMS TO SYNC URL + function addParams(sync) { + // PARSE THE URL + let url = new URL(sync.url); + let urlParams = Object.assign({}, Object.fromEntries(url.searchParams)); + + // APPLY EXTRA VARS + urlParams.gdpr = gdprData.gdpr; + urlParams.gdprConsent = gdprData.gdprConsent; + urlParams.bidid = bidResponse.bidid; + urlParams.id = bidResponse.id; + urlParams.uid = dbBuyerId; + + // REBUILD URL + sync.url = `${url.origin}${url.pathname}?${Object.keys(urlParams).map(key => key + '=' + encodeURIComponent(urlParams[key])).join('&')}`; - return collection; + // RETURN THE REBUILT URL + return sync; } + + // ENSURE THAT THE SYNC TYPE IS VALID AND HAS PERMISSION + function checkValid(sync) { + if (!sync.type || !sync.url) { + return false; + } + switch (sync.type) { + case 'iframe': + return options.iframeEnabled; + case 'image': + return options.pixelEnabled; + default: + return false; + } + } + return syncs; }, - interpretResponse: function(serverResponse, bidRequest) { - if (!serverResponse || !serverResponse.body || !serverResponse.body.seatbid) { - return []; + + // DATABLOCKS WON THE AUCTION - REPORT SUCCESS + onBidWon: function(bid) { + this.queue_metric({type: 'bid_won', source_id: bid.params[0].source_id, req_id: bid.requestId, slot_id: bid.adUnitCode, auction_id: bid.auctionId, size: bid.size, cpm: bid.cpm, pb: bid.adserverTargeting.hb_pb, rt: bid.timeToRespond, ttl: bid.ttl}); + }, + + // TARGETING HAS BEEN SET + onSetTargeting: function(bid) { + // LISTEN FOR VIEWABILITY EVENTS + this.get_viewability(bid); + }, + + // PARSE THE RTB RESPONSE AND RETURN FINAL RESULTS + interpretResponse: function(rtbResponse, bidRequest) { + // CONVERT NATIVE RTB RESPONSE INTO PREBID RESPONSE + function parseNative(native) { + const {assets, link, imptrackers, jstracker} = native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || [], + impressionTrackers: imptrackers || [], + javascriptTrackers: jstracker ? [jstracker] : [] + }; + + (assets || []).forEach((asset) => { + const {id, img, data, title} = asset; + const key = NATIVE_ID_MAP[id]; + if (key) { + if (!utils.isEmpty(title)) { + result.title = title.text + } else if (!utils.isEmpty(img)) { + result[key] = { + url: img.url, + height: img.h, + width: img.w + } + } else if (!utils.isEmpty(data)) { + result[key] = data.value; + } + } + }); + + return result; } - let body = serverResponse.body; - - let bids = body.seatbid - .map(seatbid => seatbid.bid) - .reduce((memo, bid) => memo.concat(bid), []); - let req = bidRequest.data; - let reqImps = req.imp; - - return bids.map(rtbBid => { - let imp; - for (let i in reqImps) { - let testImp = reqImps[i] - if (testImp.id == rtbBid.impid) { - imp = testImp; + + let bids = []; + let resBids = utils.deepAccess(rtbResponse, 'body.seatbid') || []; + resBids.forEach(bid => { + let resultItem = {requestId: bid.id, cpm: bid.price, creativeId: bid.crid, currency: bid.currency || 'USD', netRevenue: true, ttl: bid.ttl || 360}; + + let mediaType = utils.deepAccess(bid, 'ext.mtype') || ''; + switch (mediaType) { + case 'banner': + bids.push(Object.assign({}, resultItem, {mediaType: BANNER, width: bid.w, height: bid.h, ad: bid.adm})); + break; + + case 'native': + let nativeResult = JSON.parse(bid.adm); + bids.push(Object.assign({}, resultItem, {mediaType: NATIVE, native: parseNative(nativeResult.native)})); + break; + + default: break; - } } - let br = { - requestId: rtbBid.impid, - cpm: rtbBid.price, - creativeId: rtbBid.crid, - currency: rtbBid.currency || 'USD', - netRevenue: true, - ttl: 360 - }; - if (!imp) { - return br; - } else if (imp.banner) { - br.mediaType = BANNER; - br.width = rtbBid.w; - br.height = rtbBid.h; - br.ad = rtbBid.adm; - } else if (imp.native) { - br.mediaType = NATIVE; - - let reverseNativeMap = {}; - let nativeKeys = Object.keys(NATIVE_MAP); - nativeKeys.forEach(k => { - reverseNativeMap[NATIVE_MAP[k]] = k; - }); + }) - let idMap = {}; - let nativeReq = JSON.parse(imp.native.request); - if (nativeReq.native && nativeReq.native.assets) { - nativeReq.native.assets.forEach(asset => { - if (asset.data) { idMap[asset.id] = reverseNativeMap[asset.data.type]; } - }) + return bids; + } +}; + +// DETECT BOTS +export class BotClientTests { + constructor() { + this.tests = { + headless_chrome: function() { + if (self.navigator) { + if (self.navigator.webdriver) { + return true; + } } - const nativeResponse = JSON.parse(rtbBid.adm); - const { assets, link, imptrackers, jstrackers } = nativeResponse.native; - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || undefined, - javascriptTrackers: jstrackers ? [jstrackers] : undefined - }; - assets.forEach(asset => { - if (asset.title) { - result.title = asset.title.text; - } else if (asset.img) { - result.image = asset.img.url; - } else if (idMap[asset.id]) { - result[idMap[asset.id]] = asset.data.value; + return false; + }, + user_agent: function() { + try { + var re = new RegExp('(googlebot\/|bot|Googlebot-Mobile|Googlebot-Image|Google favicon|Mediapartners-Google|bingbot|slurp|java|wget|curl|Commons-HttpClient|Python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|jyxobot|FAST-WebCrawler|FAST Enterprise Crawler|biglotron|teoma|convera|seekbot|gigablast|exabot|ngbot|ia_archiver|GingerCrawler|webmon |httrack|webcrawler|grub.org|UsineNouvelleCrawler|antibot|netresearchserver|speedy|fluffy|bibnum.bnf|findlink|msrbot|panscient|yacybot|AISearchBot|IOI|ips-agent|tagoobot|MJ12bot|dotbot|woriobot|yanga|buzzbot|mlbot|yandexbot|purebot|Linguee Bot|Voyager|CyberPatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|Adidxbot|blekkobot|ezooms|dotbot|Mail.RU_Bot|discobot|heritrix|findthatfile|europarchive.org|NerdByNature.Bot|sistrix crawler|ahrefsbot|Aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|facebookexternalhit|yeti|RetrevoPageAnalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|DuckDuckBot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnam gnam spider|web-archive-net.com.bot|backlinkcrawler|coccoc|integromedb|content crawler spider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler.com|siteexplorer.info|elisabot|proximic|changedetection|blexbot|arabot|WeSEE:Search|niki-bot|CrystalSemanticsBot|rogerbot|360Spider|psbot|InterfaxScanBot|Lipperhey SEO Service|CC Metadata Scaper|g00g1e.net|GrapeshotCrawler|urlappendbot|brainobot|fr-crawler|binlar|SimpleCrawler|Livelapbot|Twitterbot|cXensebot|smtbot|bnf.fr_bot|A6-Indexer|ADmantX|Facebot|Twitterbot|OrangeBot|memorybot|AdvBot|MegaIndex|SemanticScholarBot|ltx71|nerdybot|xovibot|BUbiNG|Qwantify|archive.org_bot|Applebot|TweetmemeBot|crawler4j|findxbot|SemrushBot|yoozBot|lipperhey|y!j-asr|Domain Re-Animator Bot|AddThis)', 'i'); + if (re.test(navigator.userAgent)) { + return true; } - }) - br.native = result; - } else if (imp.video) { - br.mediaType = VIDEO; - br.width = rtbBid.w; - br.height = rtbBid.h; - if (rtbBid.adm) { br.vastXml = rtbBid.adm; } else if (rtbBid.nurl) { br.vastUrl = rtbBid.nurl; } + return false; + } catch (e) { + return false; + } + }, + + selenium: function () { + let response = false; + + if (window && document) { + let results = [ + 'webdriver' in window, + '_Selenium_IDE_Recorder' in window, + 'callSelenium' in window, + '_selenium' in window, + '__webdriver_script_fn' in document, + '__driver_evaluate' in document, + '__webdriver_evaluate' in document, + '__selenium_evaluate' in document, + '__fxdriver_evaluate' in document, + '__driver_unwrapped' in document, + '__webdriver_unwrapped' in document, + '__selenium_unwrapped' in document, + '__fxdriver_unwrapped' in document, + '__webdriver_script_func' in document, + document.documentElement.getAttribute('selenium') !== null, + document.documentElement.getAttribute('webdriver') !== null, + document.documentElement.getAttribute('driver') !== null + ]; + + results.forEach(result => { + if (result === true) { + response = true; + } + }) + } + + return response; + }, + } + } + + doTests() { + let response = false; + for (const [, t] of Object.entries(this.tests)) { + if (t() === true) { + response = true; } - return br; - }); + } + return response; } +} -}; +// INIT OUR BIDDER WITH PREBID registerBidder(spec); diff --git a/modules/datablocksBidAdapter.md b/modules/datablocksBidAdapter.md index e30cd361974..2730443d72d 100644 --- a/modules/datablocksBidAdapter.md +++ b/modules/datablocksBidAdapter.md @@ -8,8 +8,8 @@ Maintainer: support@datablocks.net # Description -Connects to Datablocks Version 5 Platform -Banner Native and Video +Connects to Datablocks Exchange +Banner and Native # Test Parameters @@ -27,12 +27,13 @@ Banner Native and Video { bidder: 'datablocks', params: { - sourceId: 12345, + source_id: 12345, host: 'prebid.datablocks.net' } } ] - }, { + }, + { code: 'native-div', mediaTypes : { native: { @@ -44,28 +45,9 @@ Banner Native and Video { bidder: 'datablocks', params: { - sourceId: 12345, + source_id: 12345, host: 'prebid.datablocks.net' } - }, { - code: 'video-div', - mediaTypes : { - video: { - playerSize:[500,400], - durationRangeSec:[15,30], - context: "linear" - } - }, - bids: [ - { - bidder: 'datablocks', - params: { - sourceId: 12345, - host: 'prebid.datablocks.net', - video: { - mimes:["video/flv"] - } - } } ] } diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 18b8aac7371..147000f2363 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -1,12 +1,15 @@ import { expect } from 'chai'; import { spec } from '../../../modules/datablocksBidAdapter.js'; +import { BotClientTests } from '../../../modules/datablocksBidAdapter.js'; +import { getStorageManager } from '../../../src/storageManager.js'; +export let storage = getStorageManager(); -let bid = { +const bid = { bidId: '2dd581a2b6281d', bidder: 'datablocks', bidderRequestId: '145e1d6a7837c9', params: { - sourceId: 7560, + source_id: 7560, host: 'v5demo.datablocks.net' }, adUnitCode: '/19968336/header-bid-tag-0', @@ -24,12 +27,12 @@ let bid = { transactionId: '1ccbee15-f6f6-46ce-8998-58fe5542e8e1' }; -let bid2 = { +const bid2 = { bidId: '2dd581a2b624324g', bidder: 'datablocks', bidderRequestId: '145e1d6a7837543', params: { - sourceId: 7560, + source_id: 7560, host: 'v5demo.datablocks.net' }, adUnitCode: '/19968336/header-bid-tag-0', @@ -43,7 +46,7 @@ let bid2 = { transactionId: '1ccbee15-f6f6-46ce-8998-58fe55425432' }; -let nativeBid = { +const nativeBid = { adUnitCode: '/19968336/header-bid-tag-0', auctionId: '160c78a4-f808-410f-b682-d8728f3a79ee', bidId: '332045ee374a99', @@ -78,41 +81,18 @@ let nativeBid = { } }, params: { - sourceId: 7560, + source_id: 7560, host: 'v5demo.datablocks.net' }, transactionId: '0a4e9788-4def-4b94-bc25-564d7cac99f6' } -let videoBid = { - adUnitCode: '/19968336/header-bid-tag-0', - auctionId: '160c78a4-f808-410f-b682-d8728f3a79e1', - bidId: '332045ee374b99', - bidder: 'datablocks', - bidderRequestId: '15d9012765e36d', - mediaTypes: { - video: { - context: 'instream', - playerSize: [501, 400], - durationRangeSec: [15, 60] - } - }, - params: { - sourceId: 7560, - host: 'v5demo.datablocks.net', - video: { - minduration: 14 - } - }, - transactionId: '0a4e9788-4def-4b94-bc25-564d7cac99f7' -} - const bidderRequest = { auctionId: '8bfef1be-d3ac-4d18-8859-754c7b4cf017', auctionStart: Date.now(), biddeCode: 'datablocks', bidderRequestId: '10c47a5fc3c41', - bids: [bid, bid2, nativeBid, videoBid], + bids: [bid, bid2, nativeBid], refererInfo: { numIframes: 0, reachedTop: true, @@ -123,208 +103,423 @@ const bidderRequest = { timeout: 10000 }; -let resObject = { +const res_object = { body: { - id: '10c47a5fc3c41', - bidid: '166895245-28-11347-1', - seatbid: [{ - seat: '7560', - bid: [{ - id: '1090738570', - impid: '2966b257c81d27', - price: 24.000000, - adm: 'RON', - cid: '55', - adid: '177654', - crid: '177656', - cat: [], - api: [], - w: 300, - h: 250 - }, { - id: '1090738571', - impid: '2966b257c81d28', - price: 24.000000, - adm: 'RON', - cid: '55', - adid: '177654', - crid: '177656', - cat: [], - api: [], - w: 728, - h: 90 - }, { - id: '1090738570', - impid: '15d9012765e36c', - price: 24.000000, - adm: '{"native":{"ver":"1.2","assets":[{"id":1,"required":1,"title":{"text":"Example Title"}},{"id":2,"required":1,"data":{"value":"Example Body"}},{"id":3,"required":1,"img":{"url":"https://example.image.com/"}}],"link":{"url":"https://click.example.com/c/264597/?fcid=29699699045816"},"imptrackers":["https://impression.example.com/i/264597/?fcid=29699699045816"]}}', - cid: '132145', - adid: '154321', - crid: '177432', - cat: [], - api: [] - }, { - id: '1090738575', - impid: '15d9012765e36f', - price: 25.000000, - cid: '12345', - adid: '12345', - crid: '123456', - nurl: 'https://click.v5demo.datablocks.net/m//?fcid=435235435432', - cat: [], - api: [], - w: 500, - h: 400 - }] - }], - cur: 'USD', - ext: {} + 'id': '10c47a5fc3c41', + 'bidid': '217868445-30021-19053-0', + 'seatbid': [ + { + 'id': '22621593137287', + 'impid': '1', + 'adm': 'John is great', + 'adomain': ['medianet.com'], + 'price': 0.430000, + 'cid': '2524568', + 'adid': '0', + 'crid': '0', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'type': 'CPM', + 'mtype': 'banner' + } + }, + { + 'id': '22645215457415', + 'impid': '2', + 'adm': 'john is the best', + 'adomain': ['td.com'], + 'price': 0.580000, + 'cid': '2524574', + 'adid': '0', + 'crid': '0', + 'cat': [], + 'w': 728, + 'h': 90, + 'ext': { + 'type': 'CPM', + 'mtype': 'banner' + } + }, + + { + 'id': '22645215457416', + 'impid': '3', + 'adm': '{"native":{"ver":"1.2","assets":[{"id":1,"required":1,"title":{"text":"John is amazing"}},{"id":5,"required":1,"data":{"value":"Sponsored by John"}},{"id":3,"required":1,"img":{"url":"https://example.image.com/", "h":"360", "w":"360"}}],"link":{"url":"https://click.example.com/c/264597/?fcid=29699699045816"},"imptrackers":["https://impression.example.com/i/264597/?fcid=29699699045816"]}}', + 'adomain': ['td.com'], + 'price': 10.00, + 'cid': '2524574', + 'adid': '0', + 'crid': '0', + 'cat': [], + 'ext': { + 'type': 'CPM', + 'mtype': 'native' + } + } + ], + 'cur': 'USD', + 'ext': { + 'version': '1.2.93', + 'buyerid': '1234567', + 'syncs': [ + { + 'type': 'iframe', + 'url': 'https://s.0cf.io' + }, + { + 'type': 'image', + 'url': 'https://us.dblks.net/set_uid/' + } + ] + } } -}; -let bidRequest = { +} + +let bid_request = { method: 'POST', - url: 'https://v5demo.datablocks.net/search/?sid=7560', + url: 'https://prebid.datablocks.net/openrtb/?sid=2523014', options: { - withCredentials: false + withCredentials: true }, data: { - device: { - ip: 'peer', - ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) Ap…ML, like Gecko) Chrome/73.0.3683.86 Safari/537.36', - js: 1, - language: 'en' - }, - id: '10c47a5fc3c41', - imp: [{ - banner: { w: 300, h: 250 }, - id: '2966b257c81d27', - secure: false, - tagid: '/19968336/header-bid-tag-0' - }, { - banner: { w: 728, h: 90 }, - id: '2966b257c81d28', - secure: false, - tagid: '/19968336/header-bid-tag-0' - }, { - id: '15d9012765e36c', - native: {request: '{"native":{"assets":[{"id":"1","required":true,"title":{"len":140}},{"id":"2","required":true,"data":{"type":2}},{"id":"3","img":{"w":728,"h":90,"type":3}}]}}'}, - secure: false, - tagid: '/19968336/header-bid-tag-0' - }, { - id: '15d9012765e36f', - video: {w: 500, h: 400, minduration: 15, maxduration: 60}, - secure: false, - tagid: '/19968336/header-bid-tag-0' - }], - site: { - domain: '', - id: 'blank', - page: 'https://v5demo.datablocks.net/test' - } + 'id': 'c09c6e47-8bdb-4884-a46d-93165322b368', + 'imp': [{ + 'id': '1', + 'tagid': '/19968336/header-bid-tag-0', + 'placement_id': 0, + 'secure': true, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{ + 'w': 300, + 'h': 250 + }, { + 'w': 300, + 'h': 600 + }] + } + }, { + 'id': '2', + 'tagid': '/19968336/header-bid-tag-1', + 'placement_id': 12345, + 'secure': true, + 'banner': { + 'w': 729, + 'h': 90, + 'format': [{ + 'w': 729, + 'h': 90 + }, { + 'w': 970, + 'h': 250 + }] + } + }, { + 'id': '3', + 'tagid': '/19968336/prebid_multiformat_test', + 'placement_id': 0, + 'secure': true, + 'native': { + 'ver': '1.2', + 'request': { + 'assets': [{ + 'required': 1, + 'id': 1, + 'title': {} + }, { + 'required': 1, + 'id': 3, + 'img': { + 'type': 3 + } + }, { + 'required': 1, + 'id': 5, + 'data': { + 'type': 1 + } + }], + 'context': 1, + 'plcmttype': 1, + 'ver': '1.2' + } + } + }], + 'site': { + 'domain': 'test.datablocks.net', + 'page': 'https://test.datablocks.net/index.html', + 'schain': {}, + 'ext': { + 'p_domain': 'https://test.datablocks.net', + 'rt': true, + 'frames': 0, + 'stack': ['https://test.datablocks.net/index.html'], + 'timeout': 3000 + }, + 'keywords': 'HTML, CSS, JavaScript' + }, + 'device': { + 'ip': 'peer', + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36', + 'js': 1, + 'language': 'en', + 'buyerid': '1234567', + 'ext': { + 'pb_eids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'test', + 'atype': 1 + }] + }], + 'syncs': { + '1000': 'db_4044853', + '1001': true + }, + 'coppa': 0, + 'gdpr': {}, + 'usp': {}, + 'client_info': { + 'wiw': 2560, + 'wih': 1281, + 'saw': 2560, + 'sah': 1417, + 'scd': 24, + 'sw': 2560, + 'sh': 1440, + 'whl': 4, + 'wxo': 0, + 'wyo': 0, + 'wpr': 2, + 'is_bot': false, + 'is_hid': false, + 'vs': 'hidden' + }, + 'fpd': {} + } + } } } describe('DatablocksAdapter', function() { + describe('All needed functions are available', function() { + it(`isBidRequestValid is present and type function`, function () { + expect(spec.isBidRequestValid).to.exist.and.to.be.a('function') + }); + + it(`buildRequests is present and type function`, function () { + expect(spec.buildRequests).to.exist.and.to.be.a('function') + }); + + it(`getUserSyncs is present and type function`, function () { + expect(spec.getUserSyncs).to.exist.and.to.be.a('function') + }); + + it(`onBidWon is present and type function`, function () { + expect(spec.onBidWon).to.exist.and.to.be.a('function') + }); + + it(`onSetTargeting is present and type function`, function () { + expect(spec.onSetTargeting).to.exist.and.to.be.a('function') + }); + + it(`interpretResponse is present and type function`, function () { + expect(spec.interpretResponse).to.exist.and.to.be.a('function') + }); + + it(`store_dbid is present and type function`, function () { + expect(spec.store_dbid).to.exist.and.to.be.a('function') + }); + + it(`get_dbid is present and type function`, function () { + expect(spec.get_dbid).to.exist.and.to.be.a('function') + }); + + it(`store_syncs is present and type function`, function () { + expect(spec.store_syncs).to.exist.and.to.be.a('function') + }); + + it(`get_syncs is present and type function`, function () { + expect(spec.get_syncs).to.exist.and.to.be.a('function') + }); + + it(`queue_metric is present and type function`, function () { + expect(spec.queue_metric).to.exist.and.to.be.a('function') + }); + + it(`send_metrics is present and type function`, function () { + expect(spec.send_metrics).to.exist.and.to.be.a('function') + }); + + it(`get_client_info is present and type function`, function () { + expect(spec.get_client_info).to.exist.and.to.be.a('function') + }); + + it(`get_viewability is present and type function`, function () { + expect(spec.get_viewability).to.exist.and.to.be.a('function') + }); + }); + + describe('get / store dbid', function() { + it('Should return true / undefined', function() { + expect(spec.store_dbid('12345')).to.be.true; + expect(spec.get_dbid()).to.be.a('string'); + }); + }) + + describe('get / store syncs', function() { + it('Should return true / array', function() { + expect(spec.store_syncs([{id: 1, uid: 'test'}])).to.be.true; + expect(spec.get_syncs()).to.be.a('object'); + }); + }) + + describe('queue / send metrics', function() { + it('Should return true', function() { + expect(spec.queue_metric({type: 'test'})).to.be.true; + expect(spec.queue_metric('string')).to.be.false; + expect(spec.send_metrics()).to.be.true; + }); + }) + + describe('get_viewability', function() { + it('Should return undefined', function() { + expect(spec.get_viewability()).to.equal(undefined); + }); + }) + + describe('get client info', function() { + it('Should return object', function() { + let client_info = spec.get_client_info() + expect(client_info).to.be.a('object'); + expect(client_info).to.have.all.keys('wiw', 'wih', 'saw', 'sah', 'scd', 'sw', 'sh', 'whl', 'wxo', 'wyo', 'wpr', 'is_bot', 'is_hid', 'vs'); + }); + + it('bot test should return boolean', function() { + let bot_test = new BotClientTests(); + expect(bot_test.doTests()).to.be.a('boolean'); + }); + }) + describe('isBidRequestValid', function() { - it('Should return true when sourceId and Host are set', function() { + it('Should return true when source_id and Host are set', function() { expect(spec.isBidRequestValid(bid)).to.be.true; }); - it('Should return false when host/sourceId is not set', function() { + it('Should return false when host/source_id is not set', function() { let moddedBid = Object.assign({}, bid); - delete moddedBid.params.sourceId; - delete moddedBid.params.host; - expect(spec.isBidRequestValid(bid)).to.be.false; + delete moddedBid.params.source_id; + expect(spec.isBidRequestValid(moddedBid)).to.be.false; + }); + + it('Should return true when viewability reporting is opted out', function() { + let moddedBid = Object.assign({}, bid); + moddedBid.params.vis_optout = true; + spec.isBidRequestValid(moddedBid); + expect(spec.db_obj.vis_optout).to.be.true; + }); + }) + + describe('getUserSyncs', function() { + it('Should return array of syncs', function() { + expect(spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [res_object], {gdprApplies: true, gdpr: 1, gdpr_consent: 'consent_string'}, {})).to.an('array'); + }); + }); + + describe('onSetTargeting', function() { + it('Should return undefined', function() { + expect(spec.onSetTargeting()).to.equal(undefined); + }); + }); + + describe('onBidWon', function() { + it('Should return undefined', function() { + let won_bid = {params: [{source_id: 1}], requestId: 1, adUnitCode: 'unit', auctionId: 1, size: '300x250', cpm: 10, adserverTargeting: {hb_pb: 10}, timeToRespond: 10, ttl: 10}; + expect(spec.onBidWon(won_bid)).to.equal(undefined); }); }); describe('buildRequests', function() { - let requests = spec.buildRequests([bid, bid2, nativeBid, videoBid], bidderRequest); + let request = spec.buildRequests([bid, bid2, nativeBid], bidderRequest); + + expect(request).to.exist; + it('Returns POST method', function() { + expect(request.method).to.exist; + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function() { + expect(request.url).to.exist; + expect(request.url).to.equal('https://7560.v5demo.datablocks.net/openrtb/?sid=7560'); + }); + it('Creates an array of request objects', function() { - expect(requests).to.be.an('array').that.is.not.empty; + expect(request.data.imp).to.be.an('array').that.is.not.empty; }); - requests.forEach(request => { - expect(request).to.exist; - it('Returns POST method', function() { - expect(request.method).to.exist; - expect(request.method).to.equal('POST'); - }); - it('Returns valid URL', function() { - expect(request.url).to.exist; - expect(request.url).to.equal('https://v5demo.datablocks.net/search/?sid=7560'); - }); + it('Should be a valid openRTB request', function() { + let data = request.data; - it('Should be a valid openRTB request', function() { - let data = request.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('device', 'imp', 'site', 'id'); - expect(data.id).to.be.a('string'); - - let imps = data['imp']; - imps.forEach((imp, index) => { - let curBid = bidderRequest.bids[index]; - if (imp.banner) { - expect(imp).to.have.all.keys('banner', 'id', 'secure', 'tagid'); - expect(imp.banner).to.be.a('object'); - } else if (imp.native) { - expect(imp).to.have.all.keys('native', 'id', 'secure', 'tagid'); - expect(imp.native).to.have.all.keys('request'); - expect(imp.native.request).to.be.a('string'); - let native = JSON.parse(imp.native.request); - expect(native).to.be.a('object'); - } else if (imp.video) { - expect(imp).to.have.all.keys('video', 'id', 'secure', 'tagid'); - expect(imp.video).to.have.all.keys('w', 'h', 'minduration', 'maxduration') - } else { - expect(true).to.equal(false); - } - - expect(imp.id).to.be.a('string'); - expect(imp.id).to.equal(curBid.bidId); - expect(imp.tagid).to.be.a('string'); - expect(imp.tagid).to.equal(curBid.adUnitCode); - expect(imp.secure).to.equal(false); - }) - - expect(data.device.ip).to.equal('peer'); - }); - }) + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('device', 'imp', 'site', 'id'); + expect(data.id).to.be.a('string'); + expect(data.imp).to.be.a('array'); + expect(data.device.ip).to.equal('peer'); + + let imps = data['imp']; + imps.forEach((imp, index) => { + let curBid = bidderRequest.bids[index]; + if (imp.banner) { + expect(imp.banner).to.be.a('object'); + expect(imp).to.have.all.keys('banner', 'id', 'secure', 'tagid', 'placement_id', 'ortb2', 'floor'); + } else if (imp.native) { + expect(imp).to.have.all.keys('native', 'id', 'secure', 'tagid', 'placement_id', 'ortb2', 'floor'); + expect(imp.native).to.have.all.keys('request', 'ver'); + expect(imp.native.request).to.be.a('object'); + } else { + expect(true).to.equal(false); + } + + expect(imp.id).to.be.a('string'); + expect(imp.id).to.equal(curBid.bidId); + expect(imp.tagid).to.be.a('string'); + expect(imp.tagid).to.equal(curBid.adUnitCode); + expect(imp.secure).to.equal(false); + }) + }); it('Returns empty data if no valid requests are passed', function() { - let request = spec.buildRequests([]); - expect(request).to.be.an('array').that.is.empty; + let test_request = spec.buildRequests([]); + expect(test_request).to.be.an('array').that.is.empty; }); }); + describe('interpretResponse', function() { - let serverResponses = spec.interpretResponse(resObject, bidRequest); + let response = spec.interpretResponse(res_object, bid_request); + it('Returns an array of valid server responses if response object is valid', function() { - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(Object.keys(dataItem)).to.include('cpm', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'requestId'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - - if (dataItem.mediaType == 'banner') { - expect(dataItem.ad).to.be.a('string'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - } else if (dataItem.mediaType == 'native') { - expect(dataItem.native.title).to.be.a('string'); - expect(dataItem.native.body).to.be.a('string'); - expect(dataItem.native.clickUrl).to.be.a('string'); - } else if (dataItem.mediaType == 'video') { - expect(dataItem.vastUrl).to.be.a('string'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); + expect(response).to.be.an('array').that.is.not.empty; + + response.forEach(bid => { + expect(parseInt(bid.requestId)).to.be.a('number').greaterThan(0); + expect(bid.cpm).to.be.a('number'); + expect(bid.creativeId).to.be.a('string'); + expect(bid.currency).to.be.a('string'); + expect(bid.netRevenue).to.be.a('boolean'); + expect(bid.ttl).to.be.a('number'); + expect(bid.mediaType).to.be.a('string'); + + if (bid.mediaType == 'banner') { + expect(bid.width).to.be.a('number'); + expect(bid.height).to.be.a('number'); + expect(bid.ad).to.be.a('string'); + } else if (bid.mediaType == 'native') { + expect(bid.native).to.be.a('object'); } - } + }) + it('Returns an empty array if invalid response is passed', function() { serverResponses = spec.interpretResponse('invalid_response'); expect(serverResponses).to.be.an('array').that.is.empty; From 78a00fd21c5f29e37f02d2d30fa28ca8ffbaa9e8 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Fri, 14 May 2021 08:33:09 -0400 Subject: [PATCH 12/48] Update to videoCache to include auction Id in vasttrack payload (#6757) --- src/videoCache.js | 1 + test/spec/videoCache_spec.js | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/videoCache.js b/src/videoCache.js index 9f1fd7e4117..9e378d90574 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -71,6 +71,7 @@ function toStorageRequest(bid) { if (config.getConfig('cache.vasttrack')) { payload.bidder = bid.bidder; payload.bidid = bid.requestId; + payload.aid = bid.auctionId; // function has a thisArg set to bidderRequest for accessing the auctionStart if (utils.isPlainObject(this) && this.hasOwnProperty('auctionStart')) { payload.timestamp = this.auctionStart; diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 6bb214af8a0..554db3ebe4e 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -201,13 +201,15 @@ describe('The video cache', function () { ttl: 25, customCacheKey: customKey1, requestId: '12345abc', - bidder: 'appnexus' + bidder: 'appnexus', + auctionId: '1234-56789-abcde' }, { vastXml: vastXml2, ttl: 25, customCacheKey: customKey2, requestId: 'cba54321', - bidder: 'rubicon' + bidder: 'rubicon', + auctionId: '1234-56789-abcde' }]; store(bids, function () { }); @@ -222,6 +224,7 @@ describe('The video cache', function () { ttlseconds: 25, key: customKey1, bidid: '12345abc', + aid: '1234-56789-abcde', bidder: 'appnexus' }, { type: 'xml', @@ -229,6 +232,7 @@ describe('The video cache', function () { ttlseconds: 25, key: customKey2, bidid: 'cba54321', + aid: '1234-56789-abcde', bidder: 'rubicon' }] }; @@ -254,13 +258,15 @@ describe('The video cache', function () { ttl: 25, customCacheKey: customKey1, requestId: '12345abc', - bidder: 'appnexus' + bidder: 'appnexus', + auctionId: '1234-56789-abcde' }, { vastXml: vastXml2, ttl: 25, customCacheKey: customKey2, requestId: 'cba54321', - bidder: 'rubicon' + bidder: 'rubicon', + auctionId: '1234-56789-abcde' }]; store(bids, function () { }, getMockBidRequest()); @@ -276,6 +282,7 @@ describe('The video cache', function () { key: customKey1, bidid: '12345abc', bidder: 'appnexus', + aid: '1234-56789-abcde', timestamp: 1510852447530 }, { type: 'xml', @@ -284,6 +291,7 @@ describe('The video cache', function () { key: customKey2, bidid: 'cba54321', bidder: 'rubicon', + aid: '1234-56789-abcde', timestamp: 1510852447530 }] }; From 3c5ce3e8841a40ed311a404b46af3ddc36a866b4 Mon Sep 17 00:00:00 2001 From: Chris Huie <3444727+ChrisHuie@users.noreply.github.com> Date: Fri, 14 May 2021 06:37:26 -0700 Subject: [PATCH 13/48] Revert "Datablocks bid adapter: update adapter to conform to new bid server's format (#6696)" (#6763) This reverts commit 2b8f888dc11d727f49867309f37e492d65eaee96. --- modules/datablocksAnalyticsAdapter.js | 2 +- modules/datablocksBidAdapter.js | 862 ++++++------------ modules/datablocksBidAdapter.md | 30 +- .../spec/modules/datablocksBidAdapter_spec.js | 601 +++++------- 4 files changed, 507 insertions(+), 988 deletions(-) diff --git a/modules/datablocksAnalyticsAdapter.js b/modules/datablocksAnalyticsAdapter.js index 3e4e9e95a4f..5e977155284 100644 --- a/modules/datablocksAnalyticsAdapter.js +++ b/modules/datablocksAnalyticsAdapter.js @@ -16,4 +16,4 @@ adapterManager.registerAnalyticsAdapter({ code: 'datablocks' }); -export default datablocksAdapter; \ No newline at end of file +export default datablocksAdapter; diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 038a521308d..b00a3eae659 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,634 +1,330 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; import * as utils from '../src/utils.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { ajax } from '../src/ajax.js'; -export const storage = getStorageManager(); - -const NATIVE_ID_MAP = {}; -const NATIVE_PARAMS = { +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +const NATIVE_MAP = { + 'body': 2, + 'body2': 10, + 'price': 6, + 'displayUrl': 11, + 'cta': 12 +}; +const NATIVE_IMAGE = [{ + id: 1, + required: 1, title: { - id: 1, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 - }, - cta: { - id: 6, - type: 12, - name: 'data' - }, - body2: { - id: 7, - name: 'data', - type: 10 - }, - rating: { - id: 8, - name: 'data', - type: 3 - }, - likes: { - id: 9, - name: 'data', - type: 4 - }, - downloads: { - id: 10, - name: 'data', - type: 5 - }, - displayUrl: { - id: 11, - name: 'data', + len: 140 + } +}, { + id: 2, + required: 1, + img: { type: 3 } +}, { + id: 3, + required: 1, + data: { type: 11 - }, - price: { - id: 12, - name: 'data', - type: 6 - }, - salePrice: { - id: 13, - name: 'data', - type: 7 - }, - address: { - id: 14, - name: 'data', - type: 9 - }, - phone: { - id: 15, - name: 'data', - type: 8 } -}; +}, { + id: 4, + required: 0, + data: { + type: 2 + } +}, { + id: 5, + required: 0, + img: { type: 1 } +}, { + id: 6, + required: 0, + data: { + type: 12 + } +}]; -Object.keys(NATIVE_PARAMS).forEach((key) => { - NATIVE_ID_MAP[NATIVE_PARAMS[key].id] = key; -}); +const VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', + 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', + 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', + 'pos', 'companionad', 'api', 'companiontype', 'ext']; -// DEFINE THE PREBID BIDDER SPEC export const spec = { - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], code: 'datablocks', - - // DATABLOCKS SCOPED OBJECT - db_obj: {metrics_host: 'prebid.datablocks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0}, - - // STORE THE DATABLOCKS BUYERID IN STORAGE - store_dbid: function(dbid) { - let stored = false; - - // CREATE 1 YEAR EXPIRY DATE - let d = new Date(); - d.setTime(Date.now() + (365 * 24 * 60 * 60 * 1000)); - - // TRY TO STORE IN COOKIE - if (storage.cookiesAreEnabled) { - storage.setCookie('_db_dbid', dbid, d.toUTCString(), 'None', null); - stored = true; - } - - // TRY TO STORE IN LOCAL STORAGE - if (storage.localStorageIsEnabled) { - storage.setDataInLocalStorage('_db_dbid', dbid); - stored = true; - } - - return stored; - }, - - // FETCH DATABLOCKS BUYERID FROM STORAGE - get_dbid: function() { - let dbId = ''; - if (storage.cookiesAreEnabled) { - dbId = storage.getCookie('_db_dbid') || ''; - } - - if (!dbId && storage.localStorageIsEnabled) { - dbId = storage.getDataFromLocalStorage('_db_dbid') || ''; - } - return dbId; - }, - - // STORE SYNCS IN STORAGE - store_syncs: function(syncs) { - if (storage.localStorageIsEnabled) { - let syncObj = {}; - syncs.forEach(sync => { - syncObj[sync.id] = sync.uid; - }); - - // FETCH EXISTING SYNCS AND MERGE NEW INTO STORAGE - let storedSyncs = this.get_syncs(); - storage.setDataInLocalStorage('_db_syncs', JSON.stringify(Object.assign(storedSyncs, syncObj))); - - return true; - } - }, - - // GET SYNCS FROM STORAGE - get_syncs: function() { - if (storage.localStorageIsEnabled) { - let syncData = storage.getDataFromLocalStorage('_db_syncs'); - if (syncData) { - return JSON.parse(syncData); - } else { - return {}; - } - } else { - return {}; - } - }, - - // ADD METRIC DATA TO THE METRICS RESPONSE QUEUE - queue_metric: function(metric) { - if (typeof metric === 'object') { - // PUT METRICS IN THE QUEUE - this.db_obj.metrics.push(metric); - - // RESET PREVIOUS TIMER - if (this.db_obj.metrics_timer) { - clearTimeout(this.db_obj.metrics_timer); - } - - // SETUP THE TIMER TO FIRE BACK THE DATA - let scope = this; - this.db_obj.metrics_timer = setTimeout(function() { - scope.send_metrics(); - }, this.db_obj.metrics_queue_time); - - return true; - } else { - return false; - } - }, - - // POST CONSOLIDATED METRICS BACK TO SERVER - send_metrics: function() { - // POST TO SERVER - ajax(`https://${this.db_obj.metrics_host}/a/pb/`, null, JSON.stringify(this.db_obj.metrics), {method: 'POST', withCredentials: true}); - - // RESET THE QUEUE OF METRIC DATA - this.db_obj.metrics = []; - - return true; - }, - - // GET BASIC CLIENT INFORMATION - get_client_info: function () { - let botTest = new BotClientTests(); - let win = utils.getWindowTop(); - return { - 'wiw': win.innerWidth, - 'wih': win.innerHeight, - 'saw': screen ? screen.availWidth : null, - 'sah': screen ? screen.availHeight : null, - 'scd': screen ? screen.colorDepth : null, - 'sw': screen ? screen.width : null, - 'sh': screen ? screen.height : null, - 'whl': win.history.length, - 'wxo': win.pageXOffset, - 'wyo': win.pageYOffset, - 'wpr': win.devicePixelRatio, - 'is_bot': botTest.doTests(), - 'is_hid': win.document.hidden, - 'vs': win.document.visibilityState - }; - }, - - // LISTEN FOR GPT VIEWABILITY EVENTS - get_viewability: function(bid) { - // ONLY RUN ONCE IF PUBLISHER HAS OPTED IN - if (!this.db_obj.vis_optout && !this.db_obj.vis_run) { - this.db_obj.vis_run = true; - - // ADD GPT EVENT LISTENERS - let scope = this; - if (utils.isGptPubadsDefined()) { - if (typeof window['googletag'].pubads().addEventListener == 'function') { - window['googletag'].pubads().addEventListener('impressionViewable', function(event) { - scope.queue_metric({type: 'slot_view', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()}); - }); - window['googletag'].pubads().addEventListener('slotRenderEnded', function(event) { - scope.queue_metric({type: 'slot_render', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()}); - }) - } - } - } - }, - - // VALIDATE THE BID REQUEST isBidRequestValid: function(bid) { - // SET GLOBAL VARS FROM BIDDER CONFIG - this.db_obj.source_id = bid.params.source_id; - if (bid.params.vis_optout) { - this.db_obj.vis_optout = true; - } - - return !!(bid.params.source_id && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native)); + return !!(bid.params.host && bid.params.sourceId && + bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native || bid.mediaTypes.video)); }, + buildRequests: function(validBidRequests, bidderRequest) { + if (!validBidRequests.length) { return []; } - // GENERATE THE RTB REQUEST - buildRequests: function(validRequests, bidderRequest) { - // RETURN EMPTY IF THERE ARE NO VALID REQUESTS - if (!validRequests.length) { - return []; - } + let imps = {}; + let site = {}; + let device = {}; + let refurl = utils.parseUrl(bidderRequest.referrer); + let requests = []; - // CONVERT PREBID NATIVE REQUEST OBJ INTO RTB OBJ - function createNativeRequest(bid) { - const assets = []; - if (bid.nativeParams) { - Object.keys(bid.nativeParams).forEach((key) => { - if (NATIVE_PARAMS[key]) { - const {name, type, id} = NATIVE_PARAMS[key]; - const assetObj = type ? {type} : {}; - let {len, sizes, required, aspect_ratios: aRatios} = bid.nativeParams[key]; - if (len) { - assetObj.len = len; - } - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - let wmin = aRatios.min_width || 0; - let hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - assetObj.wmin = wmin; - assetObj.hmin = hmin; - } - if (sizes && sizes.length) { - sizes = [].concat(...sizes); - assetObj.w = sizes[0]; - assetObj.h = sizes[1]; - } - const asset = {required: required ? 1 : 0, id}; - asset[name] = assetObj; - assets.push(asset); - } - }); - } - return { - ver: '1.2', - request: { - assets: assets, - context: 1, - plcmttype: 1, - ver: '1.2' - } - } - } - let imps = []; - // ITERATE THE VALID REQUESTS AND GENERATE IMP OBJECT - validRequests.forEach(bidRequest => { - // BUILD THE IMP OBJECT + validBidRequests.forEach(bidRequest => { let imp = { id: bidRequest.bidId, - tagid: bidRequest.params.tagid || bidRequest.adUnitCode, - placement_id: bidRequest.params.placement_id || 0, - secure: window.location.protocol == 'https:', - ortb2: utils.deepAccess(bidRequest, `ortb2Imp`) || {}, - floor: {} + tagid: bidRequest.adUnitCode, + secure: window.location.protocol == 'https:' } - // CHECK FOR FLOORS - if (typeof bidRequest.getFloor === 'function') { - imp.floor = bidRequest.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - } - - // BUILD THE SIZES if (utils.deepAccess(bidRequest, `mediaTypes.banner`)) { - let sizes = utils.getAdUnitSizes(bidRequest); - if (sizes.length) { + let sizes = bidRequest.mediaTypes.banner.sizes; + if (sizes.length == 1) { imp.banner = { w: sizes[0][0], - h: sizes[0][1], + h: sizes[0][1] + } + } else if (sizes.length > 1) { + imp.banner = { format: sizes.map(size => ({ w: size[0], h: size[1] })) }; + } else { + return; + } + } else if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { + let nativeImp = bidRequest.mediaTypes.native; + + if (nativeImp.type) { + let nativeAssets = []; + switch (nativeImp.type) { + case 'image': + nativeAssets = NATIVE_IMAGE; + break; + default: + return; + } + imp.native = JSON.stringify({ assets: nativeAssets }); + } else { + let nativeAssets = []; + let nativeKeys = Object.keys(nativeImp); + nativeKeys.forEach((nativeKey, index) => { + let required = !!nativeImp[nativeKey].required; + let assetId = index + 1; + switch (nativeKey) { + case 'title': + nativeAssets.push({ + id: assetId, + required: required, + title: { + len: nativeImp[nativeKey].len || 140 + } + }); + break; + case 'body': // desc + case 'body2': // desc2 + case 'price': + case 'display_url': + let data = { + id: assetId, + required: required, + data: { + type: NATIVE_MAP[nativeKey] + } + } + if (nativeImp[nativeKey].data && nativeImp[nativeKey].data.len) { data.data.len = nativeImp[nativeKey].data.len; } + + nativeAssets.push(data); + break; + case 'image': + if (nativeImp[nativeKey].sizes && nativeImp[nativeKey].sizes.length) { + nativeAssets.push({ + id: assetId, + required: required, + image: { + type: 3, + w: nativeImp[nativeKey].sizes[0], + h: nativeImp[nativeKey].sizes[1] + } + }) + } + } + }); + imp.native = { + request: JSON.stringify({native: {assets: nativeAssets}}) + }; + } + } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + let video = bidRequest.mediaTypes.video; + let sizes = video.playerSize || bidRequest.sizes || []; + if (sizes.length && Array.isArray(sizes[0])) { + imp.video = { + w: sizes[0][0], + h: sizes[0][1] + }; + } else if (sizes.length == 2 && !Array.isArray(sizes[0])) { + imp.video = { + w: sizes[0], + h: sizes[1] + }; + } else { + return; + } + + if (video.durationRangeSec) { + if (Array.isArray(video.durationRangeSec)) { + if (video.durationRangeSec.length == 1) { + imp.video.maxduration = video.durationRangeSec[0]; + } else if (video.durationRangeSec.length == 2) { + imp.video.minduration = video.durationRangeSec[0]; + imp.video.maxduration = video.durationRangeSec[1]; + } + } else { + imp.video.maxduration = video.durationRangeSec; + } + } - // ADD TO THE LIST OF IMP REQUESTS - imps.push(imp); + if (bidRequest.params.video) { + Object.keys(bidRequest.params.video).forEach(k => { + if (VIDEO_PARAMS.indexOf(k) > -1) { + imp.video[k] = bidRequest.params.video[k]; + } + }) } - } else if (utils.deepAccess(bidRequest, `mediaTypes.native`)) { - // ADD TO THE LIST OF IMP REQUESTS - imp.native = createNativeRequest(bidRequest); - imps.push(imp); } + let host = bidRequest.params.host; + let sourceId = bidRequest.params.sourceId; + imps[host] = imps[host] || {}; + let hostImp = imps[host][sourceId] = imps[host][sourceId] || { imps: [] }; + hostImp.imps.push(imp); + hostImp.subid = hostImp.imps.subid || bidRequest.params.subid || 'blank'; + hostImp.path = 'search'; + hostImp.idParam = 'sid'; + hostImp.protocol = '//'; }); - // RETURN EMPTY IF THERE WERE NO PROPER ADUNIT REQUESTS TO BE MADE - if (!imps.length) { - return []; - } - - // GENERATE SITE OBJECT - let site = { - domain: window.location.host, - page: bidderRequest.refererInfo.referer, - schain: validRequests[0].schain || {}, - ext: { - p_domain: config.getConfig('publisherDomain'), - rt: bidderRequest.refererInfo.reachedTop, - frames: bidderRequest.refererInfo.numIframes, - stack: bidderRequest.refererInfo.stack, - timeout: config.getConfig('bidderTimeout') - }, - } - - // ADD REF URL IF FOUND + // Generate Site obj + site.domain = refurl.hostname; + site.page = refurl.protocol + '://' + refurl.hostname + refurl.pathname; if (self === top && document.referrer) { site.ref = document.referrer; } - - // ADD META KEYWORDS IF FOUND let keywords = document.getElementsByTagName('meta')['keywords']; if (keywords && keywords.content) { site.keywords = keywords.content; } - // GENERATE DEVICE OBJECT - let device = { - ip: 'peer', - ua: window.navigator.userAgent, - js: 1, - language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', - buyerid: this.get_dbid() || 0, - ext: { - pb_eids: validRequests[0].userIdAsEids || {}, - syncs: this.get_syncs() || {}, - coppa: config.getConfig('coppa') || 0, - gdpr: bidderRequest.gdprConsent || {}, - usp: bidderRequest.uspConsent || {}, - client_info: this.get_client_info(), - ortb2: config.getConfig('ortb2') || {} - } - }; - - let sourceId = validRequests[0].params.source_id || 0; - let host = validRequests[0].params.host || 'prebid.datablocks.net'; - - // RETURN WITH THE REQUEST AND PAYLOAD - return { - method: 'POST', - url: `https://${sourceId}.${host}/openrtb/?sid=${sourceId}`, - data: { - id: bidderRequest.auctionId, - imp: imps, - site: site, - device: device - }, - options: { - withCredentials: true - } - }; - }, - - // INITIATE USER SYNCING - getUserSyncs: function(options, rtbResponse, gdprConsent) { - const syncs = []; - let bidResponse = rtbResponse[0].body; - let scope = this; - - // LISTEN FOR SYNC DATA FROM IFRAME TYPE SYNC - window.addEventListener('message', function (event) { - if (event.data.sentinel && event.data.sentinel === 'dblks_syncData') { - // STORE FOUND SYNCS - if (event.data.syncs) { - scope.store_syncs(event.data.syncs); + // Generate Device obj. + device.ip = 'peer'; + device.ua = window.navigator.userAgent; + device.js = 1; + device.language = ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en'; + + RtbRequest(device, site, imps).forEach(formatted => { + requests.push({ + method: 'POST', + url: formatted.url, + data: formatted.body, + options: { + withCredentials: false } - } + }) }); - - // POPULATE GDPR INFORMATION - let gdprData = { - gdpr: 0, - gdprConsent: '' - } - if (typeof gdprConsent === 'object') { - if (typeof gdprConsent.gdprApplies === 'boolean') { - gdprData.gdpr = Number(gdprConsent.gdprApplies); - gdprData.gdprConsent = gdprConsent.consentString; - } else { - gdprData.gdprConsent = gdprConsent.consentString; - } - } - - // EXTRACT BUYERID COOKIE VALUE FROM BID RESPONSE AND PUT INTO STORAGE - let dbBuyerId = this.get_dbid() || ''; - if (bidResponse.ext && bidResponse.ext.buyerid) { - dbBuyerId = bidResponse.ext.buyerid; - this.store_dbid(dbBuyerId); - } - - // EXTRACT USERSYNCS FROM BID RESPONSE - if (bidResponse.ext && bidResponse.ext.syncs) { - bidResponse.ext.syncs.forEach(sync => { - if (checkValid(sync)) { - syncs.push(addParams(sync)); - } + return requests; + + function RtbRequest(device, site, imps) { + let collection = []; + Object.keys(imps).forEach(host => { + let sourceIds = imps[host]; + Object.keys(sourceIds).forEach(sourceId => { + let impObj = sourceIds[sourceId]; + collection.push({ + url: `https://${host}/${impObj.path}/?${impObj.idParam}=${sourceId}`, + body: { + id: bidderRequest.auctionId, + imp: impObj.imps, + site: Object.assign({ id: impObj.subid || 'blank' }, site), + device: Object.assign({}, device) + } + }) + }) }) - } - - // APPEND PARAMS TO SYNC URL - function addParams(sync) { - // PARSE THE URL - let url = new URL(sync.url); - let urlParams = Object.assign({}, Object.fromEntries(url.searchParams)); - - // APPLY EXTRA VARS - urlParams.gdpr = gdprData.gdpr; - urlParams.gdprConsent = gdprData.gdprConsent; - urlParams.bidid = bidResponse.bidid; - urlParams.id = bidResponse.id; - urlParams.uid = dbBuyerId; - - // REBUILD URL - sync.url = `${url.origin}${url.pathname}?${Object.keys(urlParams).map(key => key + '=' + encodeURIComponent(urlParams[key])).join('&')}`; - // RETURN THE REBUILT URL - return sync; + return collection; } - - // ENSURE THAT THE SYNC TYPE IS VALID AND HAS PERMISSION - function checkValid(sync) { - if (!sync.type || !sync.url) { - return false; - } - switch (sync.type) { - case 'iframe': - return options.iframeEnabled; - case 'image': - return options.pixelEnabled; - default: - return false; - } - } - return syncs; - }, - - // DATABLOCKS WON THE AUCTION - REPORT SUCCESS - onBidWon: function(bid) { - this.queue_metric({type: 'bid_won', source_id: bid.params[0].source_id, req_id: bid.requestId, slot_id: bid.adUnitCode, auction_id: bid.auctionId, size: bid.size, cpm: bid.cpm, pb: bid.adserverTargeting.hb_pb, rt: bid.timeToRespond, ttl: bid.ttl}); }, - - // TARGETING HAS BEEN SET - onSetTargeting: function(bid) { - // LISTEN FOR VIEWABILITY EVENTS - this.get_viewability(bid); - }, - - // PARSE THE RTB RESPONSE AND RETURN FINAL RESULTS - interpretResponse: function(rtbResponse, bidRequest) { - // CONVERT NATIVE RTB RESPONSE INTO PREBID RESPONSE - function parseNative(native) { - const {assets, link, imptrackers, jstracker} = native; - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || [], - impressionTrackers: imptrackers || [], - javascriptTrackers: jstracker ? [jstracker] : [] - }; - - (assets || []).forEach((asset) => { - const {id, img, data, title} = asset; - const key = NATIVE_ID_MAP[id]; - if (key) { - if (!utils.isEmpty(title)) { - result.title = title.text - } else if (!utils.isEmpty(img)) { - result[key] = { - url: img.url, - height: img.h, - width: img.w - } - } else if (!utils.isEmpty(data)) { - result[key] = data.value; - } - } - }); - - return result; + interpretResponse: function(serverResponse, bidRequest) { + if (!serverResponse || !serverResponse.body || !serverResponse.body.seatbid) { + return []; } - - let bids = []; - let resBids = utils.deepAccess(rtbResponse, 'body.seatbid') || []; - resBids.forEach(bid => { - let resultItem = {requestId: bid.id, cpm: bid.price, creativeId: bid.crid, currency: bid.currency || 'USD', netRevenue: true, ttl: bid.ttl || 360}; - - let mediaType = utils.deepAccess(bid, 'ext.mtype') || ''; - switch (mediaType) { - case 'banner': - bids.push(Object.assign({}, resultItem, {mediaType: BANNER, width: bid.w, height: bid.h, ad: bid.adm})); - break; - - case 'native': - let nativeResult = JSON.parse(bid.adm); - bids.push(Object.assign({}, resultItem, {mediaType: NATIVE, native: parseNative(nativeResult.native)})); - break; - - default: + let body = serverResponse.body; + + let bids = body.seatbid + .map(seatbid => seatbid.bid) + .reduce((memo, bid) => memo.concat(bid), []); + let req = bidRequest.data; + let reqImps = req.imp; + + return bids.map(rtbBid => { + let imp; + for (let i in reqImps) { + let testImp = reqImps[i] + if (testImp.id == rtbBid.impid) { + imp = testImp; break; - } - }) - - return bids; - } -}; - -// DETECT BOTS -export class BotClientTests { - constructor() { - this.tests = { - headless_chrome: function() { - if (self.navigator) { - if (self.navigator.webdriver) { - return true; - } } + } + let br = { + requestId: rtbBid.impid, + cpm: rtbBid.price, + creativeId: rtbBid.crid, + currency: rtbBid.currency || 'USD', + netRevenue: true, + ttl: 360 + }; + if (!imp) { + return br; + } else if (imp.banner) { + br.mediaType = BANNER; + br.width = rtbBid.w; + br.height = rtbBid.h; + br.ad = rtbBid.adm; + } else if (imp.native) { + br.mediaType = NATIVE; + + let reverseNativeMap = {}; + let nativeKeys = Object.keys(NATIVE_MAP); + nativeKeys.forEach(k => { + reverseNativeMap[NATIVE_MAP[k]] = k; + }); - return false; - }, - user_agent: function() { - try { - var re = new RegExp('(googlebot\/|bot|Googlebot-Mobile|Googlebot-Image|Google favicon|Mediapartners-Google|bingbot|slurp|java|wget|curl|Commons-HttpClient|Python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|jyxobot|FAST-WebCrawler|FAST Enterprise Crawler|biglotron|teoma|convera|seekbot|gigablast|exabot|ngbot|ia_archiver|GingerCrawler|webmon |httrack|webcrawler|grub.org|UsineNouvelleCrawler|antibot|netresearchserver|speedy|fluffy|bibnum.bnf|findlink|msrbot|panscient|yacybot|AISearchBot|IOI|ips-agent|tagoobot|MJ12bot|dotbot|woriobot|yanga|buzzbot|mlbot|yandexbot|purebot|Linguee Bot|Voyager|CyberPatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|Adidxbot|blekkobot|ezooms|dotbot|Mail.RU_Bot|discobot|heritrix|findthatfile|europarchive.org|NerdByNature.Bot|sistrix crawler|ahrefsbot|Aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|facebookexternalhit|yeti|RetrevoPageAnalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|DuckDuckBot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnam gnam spider|web-archive-net.com.bot|backlinkcrawler|coccoc|integromedb|content crawler spider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler.com|siteexplorer.info|elisabot|proximic|changedetection|blexbot|arabot|WeSEE:Search|niki-bot|CrystalSemanticsBot|rogerbot|360Spider|psbot|InterfaxScanBot|Lipperhey SEO Service|CC Metadata Scaper|g00g1e.net|GrapeshotCrawler|urlappendbot|brainobot|fr-crawler|binlar|SimpleCrawler|Livelapbot|Twitterbot|cXensebot|smtbot|bnf.fr_bot|A6-Indexer|ADmantX|Facebot|Twitterbot|OrangeBot|memorybot|AdvBot|MegaIndex|SemanticScholarBot|ltx71|nerdybot|xovibot|BUbiNG|Qwantify|archive.org_bot|Applebot|TweetmemeBot|crawler4j|findxbot|SemrushBot|yoozBot|lipperhey|y!j-asr|Domain Re-Animator Bot|AddThis)', 'i'); - if (re.test(navigator.userAgent)) { - return true; - } - return false; - } catch (e) { - return false; - } - }, - - selenium: function () { - let response = false; - - if (window && document) { - let results = [ - 'webdriver' in window, - '_Selenium_IDE_Recorder' in window, - 'callSelenium' in window, - '_selenium' in window, - '__webdriver_script_fn' in document, - '__driver_evaluate' in document, - '__webdriver_evaluate' in document, - '__selenium_evaluate' in document, - '__fxdriver_evaluate' in document, - '__driver_unwrapped' in document, - '__webdriver_unwrapped' in document, - '__selenium_unwrapped' in document, - '__fxdriver_unwrapped' in document, - '__webdriver_script_func' in document, - document.documentElement.getAttribute('selenium') !== null, - document.documentElement.getAttribute('webdriver') !== null, - document.documentElement.getAttribute('driver') !== null - ]; - - results.forEach(result => { - if (result === true) { - response = true; - } + let idMap = {}; + let nativeReq = JSON.parse(imp.native.request); + if (nativeReq.native && nativeReq.native.assets) { + nativeReq.native.assets.forEach(asset => { + if (asset.data) { idMap[asset.id] = reverseNativeMap[asset.data.type]; } }) } - return response; - }, - } - } - - doTests() { - let response = false; - for (const [, t] of Object.entries(this.tests)) { - if (t() === true) { - response = true; + const nativeResponse = JSON.parse(rtbBid.adm); + const { assets, link, imptrackers, jstrackers } = nativeResponse.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstrackers ? [jstrackers] : undefined + }; + assets.forEach(asset => { + if (asset.title) { + result.title = asset.title.text; + } else if (asset.img) { + result.image = asset.img.url; + } else if (idMap[asset.id]) { + result[idMap[asset.id]] = asset.data.value; + } + }) + br.native = result; + } else if (imp.video) { + br.mediaType = VIDEO; + br.width = rtbBid.w; + br.height = rtbBid.h; + if (rtbBid.adm) { br.vastXml = rtbBid.adm; } else if (rtbBid.nurl) { br.vastUrl = rtbBid.nurl; } } - } - return response; + return br; + }); } -} -// INIT OUR BIDDER WITH PREBID +}; registerBidder(spec); diff --git a/modules/datablocksBidAdapter.md b/modules/datablocksBidAdapter.md index 2730443d72d..e30cd361974 100644 --- a/modules/datablocksBidAdapter.md +++ b/modules/datablocksBidAdapter.md @@ -8,8 +8,8 @@ Maintainer: support@datablocks.net # Description -Connects to Datablocks Exchange -Banner and Native +Connects to Datablocks Version 5 Platform +Banner Native and Video # Test Parameters @@ -27,13 +27,12 @@ Banner and Native { bidder: 'datablocks', params: { - source_id: 12345, + sourceId: 12345, host: 'prebid.datablocks.net' } } ] - }, - { + }, { code: 'native-div', mediaTypes : { native: { @@ -45,9 +44,28 @@ Banner and Native { bidder: 'datablocks', params: { - source_id: 12345, + sourceId: 12345, host: 'prebid.datablocks.net' } + }, { + code: 'video-div', + mediaTypes : { + video: { + playerSize:[500,400], + durationRangeSec:[15,30], + context: "linear" + } + }, + bids: [ + { + bidder: 'datablocks', + params: { + sourceId: 12345, + host: 'prebid.datablocks.net', + video: { + mimes:["video/flv"] + } + } } ] } diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 147000f2363..18b8aac7371 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -1,15 +1,12 @@ import { expect } from 'chai'; import { spec } from '../../../modules/datablocksBidAdapter.js'; -import { BotClientTests } from '../../../modules/datablocksBidAdapter.js'; -import { getStorageManager } from '../../../src/storageManager.js'; -export let storage = getStorageManager(); -const bid = { +let bid = { bidId: '2dd581a2b6281d', bidder: 'datablocks', bidderRequestId: '145e1d6a7837c9', params: { - source_id: 7560, + sourceId: 7560, host: 'v5demo.datablocks.net' }, adUnitCode: '/19968336/header-bid-tag-0', @@ -27,12 +24,12 @@ const bid = { transactionId: '1ccbee15-f6f6-46ce-8998-58fe5542e8e1' }; -const bid2 = { +let bid2 = { bidId: '2dd581a2b624324g', bidder: 'datablocks', bidderRequestId: '145e1d6a7837543', params: { - source_id: 7560, + sourceId: 7560, host: 'v5demo.datablocks.net' }, adUnitCode: '/19968336/header-bid-tag-0', @@ -46,7 +43,7 @@ const bid2 = { transactionId: '1ccbee15-f6f6-46ce-8998-58fe55425432' }; -const nativeBid = { +let nativeBid = { adUnitCode: '/19968336/header-bid-tag-0', auctionId: '160c78a4-f808-410f-b682-d8728f3a79ee', bidId: '332045ee374a99', @@ -81,18 +78,41 @@ const nativeBid = { } }, params: { - source_id: 7560, + sourceId: 7560, host: 'v5demo.datablocks.net' }, transactionId: '0a4e9788-4def-4b94-bc25-564d7cac99f6' } +let videoBid = { + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '160c78a4-f808-410f-b682-d8728f3a79e1', + bidId: '332045ee374b99', + bidder: 'datablocks', + bidderRequestId: '15d9012765e36d', + mediaTypes: { + video: { + context: 'instream', + playerSize: [501, 400], + durationRangeSec: [15, 60] + } + }, + params: { + sourceId: 7560, + host: 'v5demo.datablocks.net', + video: { + minduration: 14 + } + }, + transactionId: '0a4e9788-4def-4b94-bc25-564d7cac99f7' +} + const bidderRequest = { auctionId: '8bfef1be-d3ac-4d18-8859-754c7b4cf017', auctionStart: Date.now(), biddeCode: 'datablocks', bidderRequestId: '10c47a5fc3c41', - bids: [bid, bid2, nativeBid], + bids: [bid, bid2, nativeBid, videoBid], refererInfo: { numIframes: 0, reachedTop: true, @@ -103,423 +123,208 @@ const bidderRequest = { timeout: 10000 }; -const res_object = { +let resObject = { body: { - 'id': '10c47a5fc3c41', - 'bidid': '217868445-30021-19053-0', - 'seatbid': [ - { - 'id': '22621593137287', - 'impid': '1', - 'adm': 'John is great', - 'adomain': ['medianet.com'], - 'price': 0.430000, - 'cid': '2524568', - 'adid': '0', - 'crid': '0', - 'cat': [], - 'w': 300, - 'h': 250, - 'ext': { - 'type': 'CPM', - 'mtype': 'banner' - } - }, - { - 'id': '22645215457415', - 'impid': '2', - 'adm': 'john is the best', - 'adomain': ['td.com'], - 'price': 0.580000, - 'cid': '2524574', - 'adid': '0', - 'crid': '0', - 'cat': [], - 'w': 728, - 'h': 90, - 'ext': { - 'type': 'CPM', - 'mtype': 'banner' - } - }, - - { - 'id': '22645215457416', - 'impid': '3', - 'adm': '{"native":{"ver":"1.2","assets":[{"id":1,"required":1,"title":{"text":"John is amazing"}},{"id":5,"required":1,"data":{"value":"Sponsored by John"}},{"id":3,"required":1,"img":{"url":"https://example.image.com/", "h":"360", "w":"360"}}],"link":{"url":"https://click.example.com/c/264597/?fcid=29699699045816"},"imptrackers":["https://impression.example.com/i/264597/?fcid=29699699045816"]}}', - 'adomain': ['td.com'], - 'price': 10.00, - 'cid': '2524574', - 'adid': '0', - 'crid': '0', - 'cat': [], - 'ext': { - 'type': 'CPM', - 'mtype': 'native' - } - } - ], - 'cur': 'USD', - 'ext': { - 'version': '1.2.93', - 'buyerid': '1234567', - 'syncs': [ - { - 'type': 'iframe', - 'url': 'https://s.0cf.io' - }, - { - 'type': 'image', - 'url': 'https://us.dblks.net/set_uid/' - } - ] - } + id: '10c47a5fc3c41', + bidid: '166895245-28-11347-1', + seatbid: [{ + seat: '7560', + bid: [{ + id: '1090738570', + impid: '2966b257c81d27', + price: 24.000000, + adm: 'RON', + cid: '55', + adid: '177654', + crid: '177656', + cat: [], + api: [], + w: 300, + h: 250 + }, { + id: '1090738571', + impid: '2966b257c81d28', + price: 24.000000, + adm: 'RON', + cid: '55', + adid: '177654', + crid: '177656', + cat: [], + api: [], + w: 728, + h: 90 + }, { + id: '1090738570', + impid: '15d9012765e36c', + price: 24.000000, + adm: '{"native":{"ver":"1.2","assets":[{"id":1,"required":1,"title":{"text":"Example Title"}},{"id":2,"required":1,"data":{"value":"Example Body"}},{"id":3,"required":1,"img":{"url":"https://example.image.com/"}}],"link":{"url":"https://click.example.com/c/264597/?fcid=29699699045816"},"imptrackers":["https://impression.example.com/i/264597/?fcid=29699699045816"]}}', + cid: '132145', + adid: '154321', + crid: '177432', + cat: [], + api: [] + }, { + id: '1090738575', + impid: '15d9012765e36f', + price: 25.000000, + cid: '12345', + adid: '12345', + crid: '123456', + nurl: 'https://click.v5demo.datablocks.net/m//?fcid=435235435432', + cat: [], + api: [], + w: 500, + h: 400 + }] + }], + cur: 'USD', + ext: {} } -} - -let bid_request = { +}; +let bidRequest = { method: 'POST', - url: 'https://prebid.datablocks.net/openrtb/?sid=2523014', + url: 'https://v5demo.datablocks.net/search/?sid=7560', options: { - withCredentials: true + withCredentials: false }, data: { - 'id': 'c09c6e47-8bdb-4884-a46d-93165322b368', - 'imp': [{ - 'id': '1', - 'tagid': '/19968336/header-bid-tag-0', - 'placement_id': 0, - 'secure': true, - 'banner': { - 'w': 300, - 'h': 250, - 'format': [{ - 'w': 300, - 'h': 250 - }, { - 'w': 300, - 'h': 600 - }] - } - }, { - 'id': '2', - 'tagid': '/19968336/header-bid-tag-1', - 'placement_id': 12345, - 'secure': true, - 'banner': { - 'w': 729, - 'h': 90, - 'format': [{ - 'w': 729, - 'h': 90 - }, { - 'w': 970, - 'h': 250 - }] - } - }, { - 'id': '3', - 'tagid': '/19968336/prebid_multiformat_test', - 'placement_id': 0, - 'secure': true, - 'native': { - 'ver': '1.2', - 'request': { - 'assets': [{ - 'required': 1, - 'id': 1, - 'title': {} - }, { - 'required': 1, - 'id': 3, - 'img': { - 'type': 3 - } - }, { - 'required': 1, - 'id': 5, - 'data': { - 'type': 1 - } - }], - 'context': 1, - 'plcmttype': 1, - 'ver': '1.2' - } - } - }], - 'site': { - 'domain': 'test.datablocks.net', - 'page': 'https://test.datablocks.net/index.html', - 'schain': {}, - 'ext': { - 'p_domain': 'https://test.datablocks.net', - 'rt': true, - 'frames': 0, - 'stack': ['https://test.datablocks.net/index.html'], - 'timeout': 3000 - }, - 'keywords': 'HTML, CSS, JavaScript' - }, - 'device': { - 'ip': 'peer', - 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36', - 'js': 1, - 'language': 'en', - 'buyerid': '1234567', - 'ext': { - 'pb_eids': [{ - 'source': 'criteo.com', - 'uids': [{ - 'id': 'test', - 'atype': 1 - }] - }], - 'syncs': { - '1000': 'db_4044853', - '1001': true - }, - 'coppa': 0, - 'gdpr': {}, - 'usp': {}, - 'client_info': { - 'wiw': 2560, - 'wih': 1281, - 'saw': 2560, - 'sah': 1417, - 'scd': 24, - 'sw': 2560, - 'sh': 1440, - 'whl': 4, - 'wxo': 0, - 'wyo': 0, - 'wpr': 2, - 'is_bot': false, - 'is_hid': false, - 'vs': 'hidden' - }, - 'fpd': {} - } - } + device: { + ip: 'peer', + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) Ap…ML, like Gecko) Chrome/73.0.3683.86 Safari/537.36', + js: 1, + language: 'en' + }, + id: '10c47a5fc3c41', + imp: [{ + banner: { w: 300, h: 250 }, + id: '2966b257c81d27', + secure: false, + tagid: '/19968336/header-bid-tag-0' + }, { + banner: { w: 728, h: 90 }, + id: '2966b257c81d28', + secure: false, + tagid: '/19968336/header-bid-tag-0' + }, { + id: '15d9012765e36c', + native: {request: '{"native":{"assets":[{"id":"1","required":true,"title":{"len":140}},{"id":"2","required":true,"data":{"type":2}},{"id":"3","img":{"w":728,"h":90,"type":3}}]}}'}, + secure: false, + tagid: '/19968336/header-bid-tag-0' + }, { + id: '15d9012765e36f', + video: {w: 500, h: 400, minduration: 15, maxduration: 60}, + secure: false, + tagid: '/19968336/header-bid-tag-0' + }], + site: { + domain: '', + id: 'blank', + page: 'https://v5demo.datablocks.net/test' + } } } describe('DatablocksAdapter', function() { - describe('All needed functions are available', function() { - it(`isBidRequestValid is present and type function`, function () { - expect(spec.isBidRequestValid).to.exist.and.to.be.a('function') - }); - - it(`buildRequests is present and type function`, function () { - expect(spec.buildRequests).to.exist.and.to.be.a('function') - }); - - it(`getUserSyncs is present and type function`, function () { - expect(spec.getUserSyncs).to.exist.and.to.be.a('function') - }); - - it(`onBidWon is present and type function`, function () { - expect(spec.onBidWon).to.exist.and.to.be.a('function') - }); - - it(`onSetTargeting is present and type function`, function () { - expect(spec.onSetTargeting).to.exist.and.to.be.a('function') - }); - - it(`interpretResponse is present and type function`, function () { - expect(spec.interpretResponse).to.exist.and.to.be.a('function') - }); - - it(`store_dbid is present and type function`, function () { - expect(spec.store_dbid).to.exist.and.to.be.a('function') - }); - - it(`get_dbid is present and type function`, function () { - expect(spec.get_dbid).to.exist.and.to.be.a('function') - }); - - it(`store_syncs is present and type function`, function () { - expect(spec.store_syncs).to.exist.and.to.be.a('function') - }); - - it(`get_syncs is present and type function`, function () { - expect(spec.get_syncs).to.exist.and.to.be.a('function') - }); - - it(`queue_metric is present and type function`, function () { - expect(spec.queue_metric).to.exist.and.to.be.a('function') - }); - - it(`send_metrics is present and type function`, function () { - expect(spec.send_metrics).to.exist.and.to.be.a('function') - }); - - it(`get_client_info is present and type function`, function () { - expect(spec.get_client_info).to.exist.and.to.be.a('function') - }); - - it(`get_viewability is present and type function`, function () { - expect(spec.get_viewability).to.exist.and.to.be.a('function') - }); - }); - - describe('get / store dbid', function() { - it('Should return true / undefined', function() { - expect(spec.store_dbid('12345')).to.be.true; - expect(spec.get_dbid()).to.be.a('string'); - }); - }) - - describe('get / store syncs', function() { - it('Should return true / array', function() { - expect(spec.store_syncs([{id: 1, uid: 'test'}])).to.be.true; - expect(spec.get_syncs()).to.be.a('object'); - }); - }) - - describe('queue / send metrics', function() { - it('Should return true', function() { - expect(spec.queue_metric({type: 'test'})).to.be.true; - expect(spec.queue_metric('string')).to.be.false; - expect(spec.send_metrics()).to.be.true; - }); - }) - - describe('get_viewability', function() { - it('Should return undefined', function() { - expect(spec.get_viewability()).to.equal(undefined); - }); - }) - - describe('get client info', function() { - it('Should return object', function() { - let client_info = spec.get_client_info() - expect(client_info).to.be.a('object'); - expect(client_info).to.have.all.keys('wiw', 'wih', 'saw', 'sah', 'scd', 'sw', 'sh', 'whl', 'wxo', 'wyo', 'wpr', 'is_bot', 'is_hid', 'vs'); - }); - - it('bot test should return boolean', function() { - let bot_test = new BotClientTests(); - expect(bot_test.doTests()).to.be.a('boolean'); - }); - }) - describe('isBidRequestValid', function() { - it('Should return true when source_id and Host are set', function() { + it('Should return true when sourceId and Host are set', function() { expect(spec.isBidRequestValid(bid)).to.be.true; }); - it('Should return false when host/source_id is not set', function() { + it('Should return false when host/sourceId is not set', function() { let moddedBid = Object.assign({}, bid); - delete moddedBid.params.source_id; - expect(spec.isBidRequestValid(moddedBid)).to.be.false; - }); - - it('Should return true when viewability reporting is opted out', function() { - let moddedBid = Object.assign({}, bid); - moddedBid.params.vis_optout = true; - spec.isBidRequestValid(moddedBid); - expect(spec.db_obj.vis_optout).to.be.true; - }); - }) - - describe('getUserSyncs', function() { - it('Should return array of syncs', function() { - expect(spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [res_object], {gdprApplies: true, gdpr: 1, gdpr_consent: 'consent_string'}, {})).to.an('array'); - }); - }); - - describe('onSetTargeting', function() { - it('Should return undefined', function() { - expect(spec.onSetTargeting()).to.equal(undefined); - }); - }); - - describe('onBidWon', function() { - it('Should return undefined', function() { - let won_bid = {params: [{source_id: 1}], requestId: 1, adUnitCode: 'unit', auctionId: 1, size: '300x250', cpm: 10, adserverTargeting: {hb_pb: 10}, timeToRespond: 10, ttl: 10}; - expect(spec.onBidWon(won_bid)).to.equal(undefined); + delete moddedBid.params.sourceId; + delete moddedBid.params.host; + expect(spec.isBidRequestValid(bid)).to.be.false; }); }); describe('buildRequests', function() { - let request = spec.buildRequests([bid, bid2, nativeBid], bidderRequest); - - expect(request).to.exist; - it('Returns POST method', function() { - expect(request.method).to.exist; - expect(request.method).to.equal('POST'); - }); - - it('Returns valid URL', function() { - expect(request.url).to.exist; - expect(request.url).to.equal('https://7560.v5demo.datablocks.net/openrtb/?sid=7560'); - }); - + let requests = spec.buildRequests([bid, bid2, nativeBid, videoBid], bidderRequest); it('Creates an array of request objects', function() { - expect(request.data.imp).to.be.an('array').that.is.not.empty; + expect(requests).to.be.an('array').that.is.not.empty; }); - it('Should be a valid openRTB request', function() { - let data = request.data; - - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('device', 'imp', 'site', 'id'); - expect(data.id).to.be.a('string'); - expect(data.imp).to.be.a('array'); - expect(data.device.ip).to.equal('peer'); - - let imps = data['imp']; - imps.forEach((imp, index) => { - let curBid = bidderRequest.bids[index]; - if (imp.banner) { - expect(imp.banner).to.be.a('object'); - expect(imp).to.have.all.keys('banner', 'id', 'secure', 'tagid', 'placement_id', 'ortb2', 'floor'); - } else if (imp.native) { - expect(imp).to.have.all.keys('native', 'id', 'secure', 'tagid', 'placement_id', 'ortb2', 'floor'); - expect(imp.native).to.have.all.keys('request', 'ver'); - expect(imp.native.request).to.be.a('object'); - } else { - expect(true).to.equal(false); - } + requests.forEach(request => { + expect(request).to.exist; + it('Returns POST method', function() { + expect(request.method).to.exist; + expect(request.method).to.equal('POST'); + }); + it('Returns valid URL', function() { + expect(request.url).to.exist; + expect(request.url).to.equal('https://v5demo.datablocks.net/search/?sid=7560'); + }); - expect(imp.id).to.be.a('string'); - expect(imp.id).to.equal(curBid.bidId); - expect(imp.tagid).to.be.a('string'); - expect(imp.tagid).to.equal(curBid.adUnitCode); - expect(imp.secure).to.equal(false); - }) - }); + it('Should be a valid openRTB request', function() { + let data = request.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('device', 'imp', 'site', 'id'); + expect(data.id).to.be.a('string'); + + let imps = data['imp']; + imps.forEach((imp, index) => { + let curBid = bidderRequest.bids[index]; + if (imp.banner) { + expect(imp).to.have.all.keys('banner', 'id', 'secure', 'tagid'); + expect(imp.banner).to.be.a('object'); + } else if (imp.native) { + expect(imp).to.have.all.keys('native', 'id', 'secure', 'tagid'); + expect(imp.native).to.have.all.keys('request'); + expect(imp.native.request).to.be.a('string'); + let native = JSON.parse(imp.native.request); + expect(native).to.be.a('object'); + } else if (imp.video) { + expect(imp).to.have.all.keys('video', 'id', 'secure', 'tagid'); + expect(imp.video).to.have.all.keys('w', 'h', 'minduration', 'maxduration') + } else { + expect(true).to.equal(false); + } + + expect(imp.id).to.be.a('string'); + expect(imp.id).to.equal(curBid.bidId); + expect(imp.tagid).to.be.a('string'); + expect(imp.tagid).to.equal(curBid.adUnitCode); + expect(imp.secure).to.equal(false); + }) + + expect(data.device.ip).to.equal('peer'); + }); + }) it('Returns empty data if no valid requests are passed', function() { - let test_request = spec.buildRequests([]); - expect(test_request).to.be.an('array').that.is.empty; + let request = spec.buildRequests([]); + expect(request).to.be.an('array').that.is.empty; }); }); - describe('interpretResponse', function() { - let response = spec.interpretResponse(res_object, bid_request); - + let serverResponses = spec.interpretResponse(resObject, bidRequest); it('Returns an array of valid server responses if response object is valid', function() { - expect(response).to.be.an('array').that.is.not.empty; - - response.forEach(bid => { - expect(parseInt(bid.requestId)).to.be.a('number').greaterThan(0); - expect(bid.cpm).to.be.a('number'); - expect(bid.creativeId).to.be.a('string'); - expect(bid.currency).to.be.a('string'); - expect(bid.netRevenue).to.be.a('boolean'); - expect(bid.ttl).to.be.a('number'); - expect(bid.mediaType).to.be.a('string'); - - if (bid.mediaType == 'banner') { - expect(bid.width).to.be.a('number'); - expect(bid.height).to.be.a('number'); - expect(bid.ad).to.be.a('string'); - } else if (bid.mediaType == 'native') { - expect(bid.native).to.be.a('object'); + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(Object.keys(dataItem)).to.include('cpm', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType', 'requestId'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + + if (dataItem.mediaType == 'banner') { + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + } else if (dataItem.mediaType == 'native') { + expect(dataItem.native.title).to.be.a('string'); + expect(dataItem.native.body).to.be.a('string'); + expect(dataItem.native.clickUrl).to.be.a('string'); + } else if (dataItem.mediaType == 'video') { + expect(dataItem.vastUrl).to.be.a('string'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); } - }) - + } it('Returns an empty array if invalid response is passed', function() { serverResponses = spec.interpretResponse('invalid_response'); expect(serverResponses).to.be.an('array').that.is.empty; From 4e61f9d6f15a2af7577498b65ef074e7ef21da11 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Fri, 14 May 2021 15:26:13 +0100 Subject: [PATCH 14/48] clarify dependency on global rtdModule (#6759) As discussed with David from Permutive --- modules/permutiveRtdProvider.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index fe8c34c1b5c..3738c6e8be7 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -4,8 +4,11 @@ This submodule reads segments from Permutive and attaches them as targeting keys ## Usage Compile the Permutive RTD module into your Prebid build: ``` -gulp build --modules=permutiveRtdProvider +gulp build --modules=rtdModule,permutiveRtdProvider ``` + +> Note that the global RTD module, `rtdModule`, is a prerequisite of the Permutive RTD module. + You then need to enable the Permutive RTD in your Prebid configuration, using the below format: ```javascript From e533e1bbc8fa5c7fbc051b67c35c8aa488d89506 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Fri, 14 May 2021 11:06:40 -0400 Subject: [PATCH 15/48] Update to PBS bid adapter for video bids, pass along w and h respectively and filter out contxt and playerSize params as PBS does not use them (#6682) --- modules/prebidServerBidAdapter/index.js | 12 ++++++++++- .../modules/prebidServerBidAdapter_spec.js | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index d91858ed9b2..616cec47ff1 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -601,7 +601,17 @@ const OPEN_RTB_PROTOCOL = { if (videoParams.context === 'instream' && !videoParams.hasOwnProperty('placement')) { videoParams.placement = 1; } - mediaTypes['video'] = videoParams; + + mediaTypes['video'] = Object.keys(videoParams).filter(param => param !== 'context') + .reduce((result, param) => { + if (param === 'playerSize') { + result.w = utils.deepAccess(videoParams, `${param}.0.0`); + result.h = utils.deepAccess(videoParams, `${param}.0.1`); + } else { + result[param] = videoParams[param]; + } + return result; + }, {}); } } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index a6afed8ba3e..147ade6c77b 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -530,6 +530,26 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].video.placement).to.equal(1); }); + it('converts video mediaType properties into openRTB format', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + + config.setConfig({ s2sConfig: ortb2Config }); + + let videoBid = utils.deepClone(VIDEO_REQUEST); + videoBid.ad_units[0].mediaTypes.video.context = 'instream'; + adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.not.exist; + expect(requestBid.imp[0].video).to.exist; + expect(requestBid.imp[0].video.placement).to.equal(1); + expect(requestBid.imp[0].video.w).to.equal(640); + expect(requestBid.imp[0].video.h).to.equal(480); + expect(requestBid.imp[0].video.playerSize).to.be.undefined; + expect(requestBid.imp[0].video.context).to.be.undefined; + }); + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); From ea6d7d6c68e4c2906fb780ce0327fcdd3f2a93c0 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 14 May 2021 13:28:32 -0400 Subject: [PATCH 16/48] Sovrn and Sharethrough Bid Adapters: support for advertiserDomains (#6764) * Update eids.js * Update eids_spec.js * Update eids.js * Update pubmaticBidAdapter_spec.js * Update eids.js * Update eids_spec.js * Update conversantBidAdapter_spec.js * Update rubiconBidAdapter_spec.js * Update conversantBidAdapter_spec.js * Delete test/spec/adapters directory * Update userId_spec.js * Delete iasBidAdapter.js * Add files via upload * Update openxBidAdapter.js * Update openxBidAdapter.js * Update sovrnBidAdapter.js * Update sharethroughBidAdapter.js * Update sharethroughBidAdapter_spec.js * Update sovrnBidAdapter_spec.js * Update sovrnBidAdapter_spec.js * Update sharethroughBidAdapter_spec.js * Update sharethroughBidAdapter_spec.js * Update sharethroughBidAdapter_spec.js --- modules/sharethroughBidAdapter.js | 1 + modules/sovrnBidAdapter.js | 3 ++- test/spec/modules/sharethroughBidAdapter_spec.js | 5 +++-- test/spec/modules/sovrnBidAdapter_spec.js | 12 ++++++++---- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index eef18288b17..68ccde0da46 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -109,6 +109,7 @@ export const sharethroughAdapterSpec = { currency: 'USD', netRevenue: true, ttl: 360, + meta: { advertiserDomains: creative.creative && creative.creative.adomain ? creative.creative.adomain : [] }, ad: generateAd(body, req) }]; }, diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 708cb110dbd..cba90e7d434 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -160,7 +160,8 @@ export const spec = { netRevenue: true, mediaType: BANNER, ad: decodeURIComponent(`${sovrnBid.adm}`), - ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90 + ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90, + meta: { advertiserDomains: sovrnBid && sovrnBid.adomain ? sovrnBid.adomain : [] } }); }); } diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index f741f985f9f..5c8e01536dd 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -473,7 +473,7 @@ describe('sharethrough adapter spec', function() { describe('.interpretResponse', function() { it('returns a correctly parsed out response', function() { - expect(spec.interpretResponse(bidderResponse, prebidRequests[0])[0]).to.include( + expect(spec.interpretResponse(bidderResponse, prebidRequests[0])[0]).to.deep.include( { width: 1, height: 1, @@ -482,7 +482,8 @@ describe('sharethrough adapter spec', function() { dealId: 'aDealId', currency: 'USD', netRevenue: true, - ttl: 360 + ttl: 360, + meta: { advertiserDomains: [] } }); }); diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 1050fdf4856..729c48c28f4 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -387,7 +387,8 @@ describe('sovrnBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': decodeURIComponent(`>`), - 'ttl': 60000 + 'ttl': 60000, + 'meta': { advertiserDomains: [] } }]; let result = spec.interpretResponse(response); @@ -407,7 +408,8 @@ describe('sovrnBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': decodeURIComponent(``), - 'ttl': 90 + 'ttl': 90, + 'meta': { advertiserDomains: [] } }]; let result = spec.interpretResponse(response); @@ -428,7 +430,8 @@ describe('sovrnBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': decodeURIComponent(``), - 'ttl': 90 + 'ttl': 90, + 'meta': { advertiserDomains: [] } }]; let result = spec.interpretResponse(response); @@ -449,7 +452,8 @@ describe('sovrnBidAdapter', function() { 'netRevenue': true, 'mediaType': 'banner', 'ad': decodeURIComponent(``), - 'ttl': 480 + 'ttl': 480, + 'meta': { advertiserDomains: [] } }]; let result = spec.interpretResponse(response); From 3d376682fefe470da71c80f9822026b4a7d7111e Mon Sep 17 00:00:00 2001 From: jackhsiehucf <77815341+jackhsiehucf@users.noreply.github.com> Date: Mon, 17 May 2021 19:04:42 +0800 Subject: [PATCH 17/48] ucfunnel Bid Adapter: add support for FLoC and Verizon Media ConnectID (#6744) * Add a new ucfunnel Adapter and test page * Add a new ucfunnel Adapter and test page * 1. Use prebid lib in the repo to keep updated 2. Replace var with let 3. Put JSON.parse(JSON.stringify()) into try catch block * utils.getTopWindowLocation is a function * Change to modules from adapters * Migrate to module design * [Dev Fix] Remove width and height which can be got from ad unit id * Update ucfunnelBidAdapter to fit into new spec * Correct the endpoint. Fix the error of query string * Add test case for ucfunnelBidAdapter * Fix lint error * Update version number * Combine all checks on bid request * Add GDPR support for ucfunnel adapter * Add in-stream video and native support for ucfunnel adapter * Remove demo page. Add more test cases. * Change request method from POST to GET * Remove unnecessary comment * Support vastXml and vastUrl for video request * update TTL to 30 mins * Avoid using arrow function which is not discuraged in mocha * ucfunnel tdid support * ucfunnel fix error message in debug mode * ucfunnel adapter add bidfloor parameter * ucfunnel adapter support CCPA * ucfunnel adapter native support clicktrackers * ucfunnel adapter change cookie sync setting * ucfunnel adapter update request parameter * Update ucfunnelBidAdapter * ucfunnel adapter add currency in ad response * ucfunnel adapter support uid2 * ucfunnel Bid Adapter: add support for FLoC and Verizon Media ConnectID Co-authored-by: root Co-authored-by: Ryan Chou Co-authored-by: ucfunnel Co-authored-by: jack.hsieh --- modules/ucfunnelBidAdapter.js | 17 ++++++++++++++++- test/spec/modules/ucfunnelBidAdapter_spec.js | 7 +++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 9b9134a8ef0..734aba97789 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -300,6 +300,7 @@ function getRequestData(bid, bidderRequest) { } function addUserId(bidData, userId) { + bidData['eids'] = ''; utils._each(userId, (userIdObjectOrValue, userIdProviderKey) => { switch (userIdProviderKey) { case 'sharedid': @@ -333,7 +334,21 @@ function addUserId(bidData, userId) { break; case 'uid2': if (userIdObjectOrValue.id) { - bidData['eids'] = userIdProviderKey + ',' + userIdObjectOrValue.id + bidData['eids'] = (bidData['eids'].length > 0) + ? (bidData['eids'] + '!' + userIdProviderKey + ',' + userIdObjectOrValue.id) + : (userIdProviderKey + ',' + userIdObjectOrValue.id); + } + break; + case 'connectid': + if (userIdObjectOrValue) { + bidData['eids'] = (bidData['eids'].length > 0) + ? (bidData['eids'] + '!verizonMediaId,' + userIdObjectOrValue) + : ('verizonMediaId,' + userIdObjectOrValue); + } + break; + case 'flocId': + if (userIdObjectOrValue.id) { + bidData['cid'] = userIdObjectOrValue.id; } break; default: diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js index d7e82338ff3..bee420f40d4 100644 --- a/test/spec/modules/ucfunnelBidAdapter_spec.js +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -17,7 +17,9 @@ const userId = { 'sharedid': {'id': '01ESHXW4HD29KMF387T63JQ9H5', 'third': '01ESHXW4HD29KMF387T63JQ9H5'}, 'tdid': 'D6885E90-2A7A-4E0F-87CB-7734ED1B99A3', 'haloId': {}, - 'uid2': {'id': 'eb33b0cb-8d35-4722-b9c0-1a31d4064888'} + 'uid2': {'id': 'eb33b0cb-8d35-4722-b9c0-1a31d4064888'}, + 'flocId': {'id': '12144', 'version': 'chrome.1.1'}, + 'connectid': '4567' } const validBannerBidReq = { @@ -159,7 +161,8 @@ describe('ucfunnel Adapter', function () { expect(data.adid).to.equal('ad-34BBD2AA24B678BBFD4E7B9EE3B872D'); expect(data.w).to.equal(width); expect(data.h).to.equal(height); - expect(data.eids).to.equal('uid2,eb33b0cb-8d35-4722-b9c0-1a31d4064888'); + expect(data.eids).to.equal('uid2,eb33b0cb-8d35-4722-b9c0-1a31d4064888!verizonMediaId,4567'); + expect(data.cid).to.equal('12144'); expect(data.schain).to.equal('1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com'); }); From 9c0a4bf4f72512542423799afe028470548d3c06 Mon Sep 17 00:00:00 2001 From: Max Duval Date: Mon, 17 May 2021 16:31:56 +0100 Subject: [PATCH 18/48] Permutive Rtd Submodule: register submodule in submodules list (#6768) --- modules/.submodules.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index a8804321278..0a10044a78e 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -36,10 +36,11 @@ ], "rtdModule": [ "browsiRtdProvider", + "geoedgeRtdProvider", "haloRtdProvider", "jwplayerRtdProvider", + "permutiveRtdProvider", "reconciliationRtdProvider", - "geoedgeRtdProvider", "sirdataRtdProvider" ], "fpdModule": [ From eaad22d72fe6cea5301e253c25aa7a5ee1046acf Mon Sep 17 00:00:00 2001 From: John Salis Date: Mon, 17 May 2021 14:04:48 -0400 Subject: [PATCH 19/48] Beachfront Bid Adapter: update beachfront example docs (#6774) Co-authored-by: John Salis --- modules/beachfrontBidAdapter.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/modules/beachfrontBidAdapter.md b/modules/beachfrontBidAdapter.md index a2eb79ee331..9de415f8fc5 100644 --- a/modules/beachfrontBidAdapter.md +++ b/modules/beachfrontBidAdapter.md @@ -18,7 +18,8 @@ Module that connects to Beachfront's demand sources mediaTypes: { video: { context: 'instream', - playerSize: [ 640, 360 ] + playerSize: [640, 360], + mimes: ['video/mp4', 'application/javascript'] } }, bids: [ @@ -26,10 +27,7 @@ Module that connects to Beachfront's demand sources bidder: 'beachfront', params: { bidfloor: 0.01, - appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - video: { - mimes: [ 'video/mp4', 'application/javascript' ] - } + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' } } ] @@ -37,7 +35,7 @@ Module that connects to Beachfront's demand sources code: 'test-banner', mediaTypes: { banner: { - sizes: [ 300, 250 ] + sizes: [300, 250] } }, bids: [ @@ -61,10 +59,11 @@ Module that connects to Beachfront's demand sources mediaTypes: { video: { context: 'outstream', - playerSize: [ 640, 360 ] + playerSize: [640, 360], + mimes: ['video/mp4', 'application/javascript'] }, banner: { - sizes: [ 300, 250 ] + sizes: [300, 250] } }, bids: [ @@ -74,7 +73,6 @@ Module that connects to Beachfront's demand sources video: { bidfloor: 0.01, appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - mimes: [ 'video/mp4', 'application/javascript' ] }, banner: { bidfloor: 0.01, @@ -95,7 +93,8 @@ Module that connects to Beachfront's demand sources mediaTypes: { video: { context: 'outstream', - playerSize: [ 640, 360 ] + playerSize: [ 640, 360 ], + mimes: ['video/mp4', 'application/javascript'] } }, bids: [ @@ -104,8 +103,7 @@ Module that connects to Beachfront's demand sources params: { video: { bidfloor: 0.01, - appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - mimes: [ 'video/mp4', 'application/javascript' ] + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76' }, player: { progressColor: '#50A8FA', From 59313368e39276193963f19f702e2d79761ee1d2 Mon Sep 17 00:00:00 2001 From: hdwmsconfig Date: Mon, 17 May 2021 15:29:54 -0300 Subject: [PATCH 20/48] Eplanning Bid Adapter: Adjust endpoint parameter formatting (#6772) --- modules/eplanningBidAdapter.js | 3 +-- test/spec/modules/eplanningBidAdapter_spec.js | 11 +++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index d62d1ee46e8..98a0e290575 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -58,7 +58,6 @@ export const spec = { rnd: rnd, e: spaces.str, ur: pageUrl || FILE, - r: 'pbjs', pbv: '$prebid.version$', ncb: '1', vs: spaces.vs @@ -85,7 +84,7 @@ export const spec = { } const userIds = (getGlobal()).getUserIds(); for (var id in userIds) { - params[id] = (typeof userIds[id] === 'object') ? encodeURIComponent(JSON.stringify(userIds[id])) : encodeURIComponent(userIds[id]); + params['e_' + id] = (typeof userIds[id] === 'object') ? encodeURIComponent(JSON.stringify(userIds[id])) : encodeURIComponent(userIds[id]); } } diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index 2ab26cfe57d..1d3a8344170 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -324,11 +324,6 @@ describe('E-Planning Adapter', function () { expect(method).to.equal('GET'); }); - it('should return r parameter with value pbjs', function () { - const r = spec.buildRequests(bidRequests, bidderRequest).data.r; - expect(r).to.equal('pbjs'); - }); - it('should return pbv parameter with value prebid version', function () { const pbv = spec.buildRequests(bidRequests, bidderRequest).data.pbv; expect(pbv).to.equal('$prebid.version$'); @@ -922,9 +917,9 @@ describe('E-Planning Adapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const dataRequest = request.data; - expect('D6885E90-2A7A-4E0F-87CB-7734ED1B99A3').to.equal(dataRequest.tdid); - expect('c29cb2ae-769d-42f6-891a-f53cadee823d').to.equal(dataRequest.pubcid); - expect(expected_id5id).to.equal(dataRequest.id5id); + expect('D6885E90-2A7A-4E0F-87CB-7734ED1B99A3').to.equal(dataRequest.e_tdid); + expect('c29cb2ae-769d-42f6-891a-f53cadee823d').to.equal(dataRequest.e_pubcid); + expect(expected_id5id).to.equal(dataRequest.e_id5id); }); }); }); From a9232fdb8530d2480fec22a900e300755d486d4f Mon Sep 17 00:00:00 2001 From: John Salis Date: Mon, 17 May 2021 14:35:27 -0400 Subject: [PATCH 21/48] Beachfront Bid Adapter: add Unified ID 2.0 support (#6770) --- modules/beachfrontBidAdapter.js | 7 +++-- .../spec/modules/beachfrontBidAdapter_spec.js | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 8ddc0ca5ba9..7466b3d6a68 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -19,7 +19,8 @@ export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; export const SUPPORTED_USER_IDS = [ { key: 'tdid', source: 'adserver.org', rtiPartner: 'TDID', queryParam: 'tdid' }, - { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' } + { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' }, + { key: 'uid2.id', source: 'uidapi.com', rtiPartner: 'UID2', queryParam: 'uid2' } ]; let appId = ''; @@ -279,7 +280,7 @@ function getEids(bid) { function getUserId(bid) { return ({ key, source, rtiPartner }) => { - let id = bid.userId && bid.userId[key]; + let id = utils.deepAccess(bid, `userId.${key}`); return id ? formatEid(id, source, rtiPartner) : null; }; } @@ -428,7 +429,7 @@ function createBannerRequestData(bids, bidderRequest) { } SUPPORTED_USER_IDS.forEach(({ key, queryParam }) => { - let id = bids[0] && bids[0].userId && bids[0].userId[key]; + let id = utils.deepAccess(bids, `0.userId.${key}`) if (id) { payload[queryParam] = id; } diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index c7ae5c799ac..605ccc464cb 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -333,6 +333,24 @@ describe('BeachfrontAdapter', function () { }] }); }); + + it('must add the Unified ID 2.0 to the request', () => { + const uid2 = { id: '4321' }; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.userId = { uid2 }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.user.ext.eids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: uid2.id, + ext: { + rtiPartner: 'UID2' + } + }] + }); + }); }); describe('for banner bids', function () { @@ -506,6 +524,16 @@ describe('BeachfrontAdapter', function () { const data = requests[0].data; expect(data.idl).to.equal(idl_env); }); + + it('must add the Unified ID 2.0 to the request', () => { + const uid2 = { id: '4321' }; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.userId = { uid2 }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.uid2).to.equal(uid2.id); + }); }); describe('for multi-format bids', function () { From 5aec9205e7995774a15938c41e6523d2b65cb426 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Mon, 17 May 2021 14:51:06 -0400 Subject: [PATCH 22/48] appnexus bid adapter - create stub for meta.advertiserDomains (#6753) --- modules/appnexusBidAdapter.js | 5 +++++ test/spec/modules/appnexusBidAdapter_spec.js | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index f3c53e6fcb3..d28bf391aa5 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -578,6 +578,11 @@ function newBid(serverBid, rtbBid, bidderRequest) { } }; + // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance + if (rtbBid.adomain) { + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); + } + if (rtbBid.advertiser_id) { bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); } diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 919b36ff71a..c875cba12bc 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1280,6 +1280,21 @@ describe('AppNexusAdapter', function () { } let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); - }) + }); + + it('should add advertiserDomains', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); + expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); + }); }); }); From 4d90d7ad6813dbb3981323455f9e01c994679e09 Mon Sep 17 00:00:00 2001 From: Paul Yang Date: Mon, 17 May 2021 13:49:06 -0700 Subject: [PATCH 23/48] Conversant adapter - picks up additional params from mediaTypes.video (#6775) --- modules/conversantBidAdapter.js | 7 ++++--- modules/conversantBidAdapter.md | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 74cd97ad019..806f276fb72 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -33,10 +33,11 @@ export const spec = { } if (isVideoRequest(bid)) { - if (!bid.params.mimes) { + const mimes = bid.params.mimes || utils.deepAccess(bid, 'mediaTypes.video.mimes'); + if (!mimes) { // Give a warning but let it pass utils.logWarn(BIDDER_CODE + ': mimes should be specified for videos'); - } else if (!utils.isArray(bid.params.mimes) || !bid.params.mimes.every(s => utils.isStr(s))) { + } else if (!utils.isArray(mimes) || !mimes.every(s => utils.isStr(s))) { utils.logWarn(BIDDER_CODE + ': mimes must be an array of strings'); return false; } @@ -90,7 +91,7 @@ export const spec = { copyOptProperty(bid.params.position, video, 'pos'); copyOptProperty(bid.params.mimes || videoData.mimes, video, 'mimes'); - copyOptProperty(bid.params.maxduration, video, 'maxduration'); + copyOptProperty(bid.params.maxduration || videoData.maxduration, video, 'maxduration'); copyOptProperty(bid.params.protocols || videoData.protocols, video, 'protocols'); copyOptProperty(bid.params.api || videoData.api, video, 'api'); diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md index fba793adad2..07d9abf918b 100644 --- a/modules/conversantBidAdapter.md +++ b/modules/conversantBidAdapter.md @@ -29,17 +29,17 @@ var adUnits = [ mediaTypes: { video: { context: 'instream', - playerSize: [640, 480] + playerSize: [640, 480], + api: [2], + protocols: [1, 2], + mimes: ['video/mp4'] } }, bids: [{ bidder: "conversant", params: { site_id: '108060', - api: [2], - protocols: [1, 2], - white_label_url: 'https://web.hb.ad.cpe.dotomi.com/s2s/header/24', - mimes: ['video/mp4'] + white_label_url: 'https://web.hb.ad.cpe.dotomi.com/s2s/header/24' } }] }]; From d2195cad1fbef8bea600ea02f2975c4df1072105 Mon Sep 17 00:00:00 2001 From: relaido <63339139+relaido@users.noreply.github.com> Date: Tue, 18 May 2021 17:40:12 +0900 Subject: [PATCH 24/48] Relaido Adapter : size of the banner is checked and the banner can be bid on. (#6776) * add relaido adapter * remove event listener * fixed UserSyncs and e.data * fix conflicts * take size each mediaType Co-authored-by: ishigami_shingo Co-authored-by: cmertv-sishigami Co-authored-by: t_bun --- modules/relaidoBidAdapter.js | 70 ++++++++++----------- test/spec/modules/relaidoBidAdapter_spec.js | 35 ++++++++++- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index c77afbe6ec5..92709b7c047 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -6,7 +6,7 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.0.2'; +const ADAPTER_VERSION = '1.0.3'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; @@ -17,21 +17,17 @@ function isBidRequestValid(bid) { utils.logWarn('placementId param is reqeuired.'); return false; } - if (hasVideoMediaType(bid)) { - if (!isVideoValid(bid)) { - utils.logWarn('Invalid mediaType video.'); - return false; - } - } else if (hasBannerMediaType(bid)) { - if (!isBannerValid(bid)) { - utils.logWarn('Invalid mediaType banner.'); - return false; - } + if (hasVideoMediaType(bid) && isVideoValid(bid)) { + return true; } else { - utils.logWarn('Invalid mediaTypes input banner or video.'); - return false; + utils.logWarn('Invalid mediaType video.'); } - return true; + if (hasBannerMediaType(bid) && isBannerValid(bid)) { + return true; + } else { + utils.logWarn('Invalid mediaType banner.'); + } + return false; } function buildRequests(validBidRequests, bidderRequest) { @@ -43,7 +39,21 @@ function buildRequests(validBidRequests, bidderRequest) { const bidDomain = bidRequest.params.domain || BIDDER_DOMAIN; const bidUrl = `https://${bidDomain}/bid/v1/prebid/${placementId}`; const uuid = getUuid(); - const mediaType = getMediaType(bidRequest); + let mediaType = ''; + let width = 0; + let height = 0; + + if (hasVideoMediaType(bidRequest) && isVideoValid(bidRequest)) { + const playerSize = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize')); + width = playerSize[0][0]; + height = playerSize[0][1]; + mediaType = VIDEO; + } else if (hasBannerMediaType(bidRequest) && isBannerValid(bidRequest)) { + const sizes = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes')); + width = sizes[0][0]; + height = sizes[0][1]; + mediaType = BANNER; + } let payload = { version: ADAPTER_VERSION, @@ -57,18 +67,10 @@ function buildRequests(validBidRequests, bidderRequest) { transaction_id: bidRequest.transactionId, media_type: mediaType, uuid: uuid, + width: width, + height: height }; - if (hasVideoMediaType(bidRequest)) { - const playerSize = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize')); - payload.width = playerSize[0][0]; - payload.height = playerSize[0][1]; - } else if (hasBannerMediaType(bidRequest)) { - const sizes = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes')); - payload.width = sizes[0][0]; - payload.height = sizes[0][1]; - } - // It may not be encoded, so add it at the end of the payload payload.ref = bidderRequest.refererInfo.referer; @@ -83,10 +85,9 @@ function buildRequests(validBidRequests, bidderRequest) { player: bidRequest.params.player, width: payload.width, height: payload.height, - mediaType: mediaType, + mediaType: payload.media_type }); } - return bidRequests; } @@ -162,6 +163,7 @@ function onTimeout(data) { auction_id: utils.deepAccess(data, '0.auctionId'), bid_id: utils.deepAccess(data, '0.bidId'), ad_unit_code: utils.deepAccess(data, '0.adUnitCode'), + version: ADAPTER_VERSION, ref: window.location.href, }).replace(/\&$/, ''); const bidDomain = utils.deepAccess(data, '0.params.0.domain') || BIDDER_DOMAIN; @@ -248,15 +250,6 @@ export function isMobile() { return false; } -function getMediaType(bid) { - if (hasVideoMediaType(bid)) { - return VIDEO; - } else if (hasBannerMediaType(bid)) { - return BANNER; - } - return ''; -} - function hasBannerMediaType(bid) { return !!utils.deepAccess(bid, 'mediaTypes.banner'); } @@ -272,7 +265,10 @@ function getValidSizes(sizes) { if (utils.isArray(sizes[i]) && sizes[i].length == 2) { const width = sizes[i][0]; const height = sizes[i][1]; - if ((width >= 300 && height >= 250) || (width == 1 && height == 1)) { + if (width == 1 && height == 1) { + return [[1, 1]]; + } + if ((width >= 300 && height >= 250)) { result.push([width, height]); } } diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index ebc62752f16..91aa6b05e6e 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -208,10 +208,18 @@ describe('RelaidoAdapter', function () { }); it('should build bid requests by banner', function () { + setUAMobile(); bidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [ + [320, 180] + ] + }, banner: { sizes: [ - [640, 360] + [640, 360], + [1, 1] ] } }; @@ -221,6 +229,31 @@ describe('RelaidoAdapter', function () { expect(request.mediaType).to.equal('banner'); }); + it('should take 1x1 size', function () { + setUAMobile(); + bidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [ + [320, 180] + ] + }, + banner: { + sizes: [ + [640, 360], + [1, 1] + ] + } + }; + const bidRequests = spec.buildRequests([bidRequest], bidderRequest); + expect(bidRequests).to.have.lengthOf(1); + const request = bidRequests[0]; + + // eslint-disable-next-line no-console + console.log(bidRequests); + expect(request.width).to.equal(1); + }); + it('The referrer should be the last', function () { const bidRequests = spec.buildRequests([bidRequest], bidderRequest); expect(bidRequests).to.have.lengthOf(1); From 6c6affed808e75191c6c6717bb133d8075d76cce Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 18 May 2021 14:54:15 +0600 Subject: [PATCH 25/48] ZetaSspBidAdapter fix typo (#6777) Co-authored-by: Surovenko Alexey --- modules/zetaSspBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/zetaSspBidAdapter.js b/modules/zetaSspBidAdapter.js index 8f4d8995800..e267942862b 100644 --- a/modules/zetaSspBidAdapter.js +++ b/modules/zetaSspBidAdapter.js @@ -4,8 +4,8 @@ import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; const BIDDER_CODE = 'zeta_global_ssp'; -const ENDPOINT_URL = 'https:/ssp.disqus.com/bid'; -const USER_SYNC_URL = 'https:/ssp.disqus.com/match'; +const ENDPOINT_URL = 'https://ssp.disqus.com/bid'; +const USER_SYNC_URL = 'https://ssp.disqus.com/match'; const DEFAULT_CUR = 'USD'; const TTL = 200; const NET_REV = true; From e92009dd749979ed5af00db2d2be2d8a28acd0b4 Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 18 May 2021 12:35:11 +0200 Subject: [PATCH 26/48] Adagio Bid Adapter: handle meta.advertiserDomains (and more) (#6781) Related to #6650 --- modules/adagioBidAdapter.js | 6 ++- test/spec/modules/adagioBidAdapter_spec.js | 51 +++++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 134303bf400..66653567dab 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -14,7 +14,7 @@ import { OUTSTREAM } from '../src/video.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.8.0'; +export const VERSION = '2.10.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; export const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; @@ -1019,6 +1019,10 @@ export const spec = { const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); if (bidReq) { + bidObj.meta = utils.deepAccess(bidObj, 'meta', {}); + bidObj.meta.mediaType = bidObj.mediaType; + bidObj.meta.advertiserDomains = (Array.isArray(bidObj.aDomain) && bidObj.aDomain.length) ? bidObj.aDomain : []; + if (bidObj.mediaType === VIDEO) { const mediaTypeContext = utils.deepAccess(bidReq, 'mediaTypes.video.context'); // Adagio SSP returns a `vastXml` only. No `vastUrl` nor `videoCacheKey`. diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 3707c19e471..6ee42e47950 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -755,7 +755,14 @@ describe('Adagio bid adapter', () => { netRevenue: true, requestId: 'c180kg4267tyqz', ttl: 360, - width: 300 + width: 300, + aDomain: ['advertiser.com'], + mediaType: 'banner', + meta: { + advertiserId: '80', + advertiserName: 'An Advertiser', + networkId: '110' + } }] } }; @@ -821,13 +828,53 @@ describe('Adagio bid adapter', () => { pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', - environment: 'desktop' + environment: 'desktop', + aDomain: ['advertiser.com'], + mediaType: 'banner', + meta: { + advertiserId: '80', + advertiserName: 'An Advertiser', + advertiserDomains: ['advertiser.com'], + networkId: '110', + mediaType: 'banner' + } }]; expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.deep.equal(expectedResponse); }); + it('Meta props should be limited if no bid.meta is provided', function() { + const altServerResponse = utils.deepClone(serverResponse); + delete altServerResponse.body.bids[0].meta; + + let expectedResponse = [{ + ad: '
', + cpm: 1, + creativeId: 'creativeId', + currency: 'EUR', + height: 250, + netRevenue: true, + requestId: 'c180kg4267tyqz', + ttl: 360, + width: 300, + placement: 'PAVE_ATF', + site: 'SITE-NAME', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'desktop', + aDomain: ['advertiser.com'], + mediaType: 'banner', + meta: { + advertiserDomains: ['advertiser.com'], + mediaType: 'banner' + } + }]; + + expect(spec.interpretResponse(altServerResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + it('should populate ADAGIO queue with ssp-data', function() { sandbox.stub(Date, 'now').returns(12345); From 3b6442e8889fdb7e58748af7d8e809a112f7c7ab Mon Sep 17 00:00:00 2001 From: pm-shashank-jain <40654031+pm-shashank-jain@users.noreply.github.com> Date: Tue, 18 May 2021 17:59:23 +0530 Subject: [PATCH 27/48] PubMatic Adapter: add support for FloCID (#6749) * changes to support native in pubmaticbid adapter * Removed port from endpoint * Removed protocol from endpoint * Formatting * Fix request payload * Updated test case * Changed request and response as per ortb spec * Change in request and response * Removed comments and extra code * Code Review comments * Code Review Comments and Test cases for request and response * Removed data type as all data asset types are handled * Code Review Changes * Code Review Comments * Supporting both banner and native and sending 0x0 in case of native * Bug Fixes * Bug response not processed by prebid * Change warning message * Fixed typo * Do not send request in case of invalid native bid * Do not send request in case of invalid native requests * objects converted to strings in log for debug purposes * Fixed logic to check for required parmas * Fixed typo for stringify * documentation for native * Review comments from Prebid * Typo * Typo * Updated pub id for native * Code Review * Support for pubid * Test Cases for PubCommonId in PubMatic adapter * Delete yarn.lock * Rename adaptermanager.js to adapterManager.js * Rename yieldNexusBidAdapter.js to yieldnexusBidAdapter.js * Rename yieldNexusBidAdapter.md to yieldnexusBidAdapter.md * Rename yieldNexusBidAdapter_spec.js to yieldnexusBidAdapter_spec.yieldnexusBidAdaptera * Rename yieldnexusBidAdapter_spec.yieldnexusBidAdaptera to yieldnexusBidAdapter_spec.js * bluebillywig outstream player support in pubmatic adapter * removed pubcommon id test cases * BBW Renderer * Pubmatic should work with provided renderer * Review Comments * changes for flocId * code review comments * fixed for eids * code review comments * unit test cases for floc * updated md file * code review comment --- modules/pubmaticBidAdapter.js | 68 +++++++++++++++++++- test/spec/modules/pubmaticBidAdapter_spec.js | 62 ++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index c6ecea48abf..ff934204b43 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -107,6 +107,11 @@ const dealChannelValues = { 5: 'PREF', 6: 'PMPG' }; + +const FLOC_FORMAT = { + 'EID': 1, + 'SEGMENT': 2 +} // BB stands for Blue BillyWig const BB_RENDERER = { bootstrapPlayer: function(bid) { @@ -708,8 +713,67 @@ function _addFloorFromFloorModule(impObj, bid) { impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : UNDEFINED); } +function _getFlocId(validBidRequests, flocFormat) { + var flocIdObject = null; + var flocId = utils.deepAccess(validBidRequests, '0.userId.flocId'); + if (flocId && flocId.id) { + switch (flocFormat) { + case FLOC_FORMAT.SEGMENT: + flocIdObject = { + id: 'FLOC', + name: 'FLOC', + ext: { + ver: flocId.version + }, + segment: [{ + id: flocId.id, + name: 'chrome.com', + value: flocId.id.toString() + }] + } + break; + case FLOC_FORMAT.EID: + default: + flocIdObject = { + source: 'chrome.com', + uids: [ + { + atype: 1, + id: flocId.id, + ext: { + ver: flocId.version + } + }, + ] + } + break; + } + } + return flocIdObject; +} + +function _handleFlocId(payload, validBidRequests) { + var flocObject = _getFlocId(validBidRequests, FLOC_FORMAT.SEGMENT); + if (flocObject) { + if (!payload.user) { + payload.user = {}; + } + if (!payload.user.data) { + payload.user.data = []; + } + payload.user.data.push(flocObject); + } +} + function _handleEids(payload, validBidRequests) { - const bidUserIdAsEids = utils.deepAccess(validBidRequests, '0.userIdAsEids'); + let bidUserIdAsEids = utils.deepAccess(validBidRequests, '0.userIdAsEids'); + let flocObject = _getFlocId(validBidRequests, FLOC_FORMAT.EID); + if (flocObject) { + if (!bidUserIdAsEids) { + bidUserIdAsEids = []; + } + bidUserIdAsEids.push(flocObject); + } if (utils.isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { utils.deepSetValue(payload, 'user.eids', bidUserIdAsEids); } @@ -1029,7 +1093,7 @@ export const spec = { _handleDealCustomTargetings(payload, dctrArr, validBidRequests); _handleEids(payload, validBidRequests); _blockedIabCategoriesValidation(payload, blockedIabCategories); - + _handleFlocId(payload, validBidRequests); // First Party Data const commonFpd = config.getConfig('ortb2') || {}; if (commonFpd.site) { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 28e79a69e55..e8948fdc2d3 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2096,6 +2096,68 @@ describe('PubMatic adapter', function () { expect(data.user.eids).to.equal(undefined); }); }); + + describe('FlocId', function() { + it('send the FlocId if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.flocId = { + id: '1234', + version: 'chrome1.1' + } + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'chrome.com', + 'uids': [{ + 'id': '1234', + 'atype': 1, + 'ext': { + 'ver': 'chrome1.1' + } + }] + }]); + expect(data.user.data).to.deep.equal([{ + id: 'FLOC', + name: 'FLOC', + ext: { + ver: 'chrome1.1' + }, + segment: [{ + id: '1234', + name: 'chrome.com', + value: '1234' + }] + }]); + }); + + it('appnend the flocId if userIds are present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.netId = 'netid-user-id'; + bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); + bidRequests[0].userId.flocId = { + id: '1234', + version: 'chrome1.1' + } + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'netid.de', + 'uids': [{ + 'id': 'netid-user-id', + 'atype': 1 + }] + }, { + 'source': 'chrome.com', + 'uids': [{ + 'id': '1234', + 'atype': 1, + 'ext': { + 'ver': 'chrome1.1' + } + }] + }]); + }); + }); }); it('Request params check for video ad', function () { From 4319b59cc1287901d16dd42b8b8191beb37d8947 Mon Sep 17 00:00:00 2001 From: PWyrembak Date: Tue, 18 May 2021 18:17:32 +0300 Subject: [PATCH 28/48] TrustX Bid Adapter: added additional sync url (#6771) --- modules/trustxBidAdapter.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 3116400edf7..9f8de30a0d4 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -7,6 +7,7 @@ import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = 'https://sofia.trustx.org/hb'; const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; +const ADDITIONAL_SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -93,12 +94,32 @@ export const spec = { if (errorMessage) utils.logError(errorMessage); return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.pixelEnabled) { - return [{ + const syncsPerBidder = config.getConfig('userSync.syncsPerBidder'); + let params = []; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); + } else { + params.push(`gdpr_consent=${gdprConsent.consentString}`); + } + } + if (uspConsent) { + params.push(`us_privacy=${uspConsent}`); + } + const stringParams = params.join('&'); + const syncs = [{ type: 'image', - url: ADAPTER_SYNC_URL + url: ADAPTER_SYNC_URL + (stringParams ? `?${stringParams}` : '') }]; + if (syncsPerBidder > 1) { + syncs.push({ + type: 'image', + url: ADDITIONAL_SYNC_URL + (stringParams ? `&${stringParams}` : '') + }); + } + return syncs; } } } From c198fa7212bee455ee3c3b19d7add1037e40b1c0 Mon Sep 17 00:00:00 2001 From: Stefano <50023896+bkse-stefanodechicchis@users.noreply.github.com> Date: Tue, 18 May 2021 18:42:38 +0200 Subject: [PATCH 29/48] Bucksense Bid Adapter: add adomain to adapter (#6779) * Update bucksenseBidAdapter.js Added support for advertiserDomains * Update bucksenseBidAdapter_spec.js Co-authored-by: Patrick McCann --- modules/bucksenseBidAdapter.js | 6 +++++- test/spec/modules/bucksenseBidAdapter_spec.js | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js index 3f327e62121..46d0dfe3590 100644 --- a/modules/bucksenseBidAdapter.js +++ b/modules/bucksenseBidAdapter.js @@ -90,6 +90,7 @@ export const spec = { var sCurrency = oResponse.currency || 'USD'; var bNetRevenue = oResponse.netRevenue || true; var sAd = oResponse.ad || ''; + var sAdomains = oResponse.adomains || []; if (request && sRequestID.length == 0) { utils.logInfo(WHO + ' interpretResponse() - use RequestID from Placments'); @@ -110,7 +111,10 @@ export const spec = { creativeId: sCreativeID, currency: sCurrency, netRevenue: bNetRevenue, - ad: sAd + ad: sAd, + meta: { + advertiserDomains: sAdomains + } }; bidResponses.push(bidResponse); } else { diff --git a/test/spec/modules/bucksenseBidAdapter_spec.js b/test/spec/modules/bucksenseBidAdapter_spec.js index f49a63d2003..b977c3a9dd1 100644 --- a/test/spec/modules/bucksenseBidAdapter_spec.js +++ b/test/spec/modules/bucksenseBidAdapter_spec.js @@ -128,7 +128,10 @@ describe('Bucksense Adapter', function() { 'creativeId': 'creative002', 'currency': 'USD', 'netRevenue': false, - 'ad': '
' + 'ad': '
', + 'meta': { + 'advertiserDomains': ['http://www.bucksense.com/'] + } } }; }); From 0c8a30820f25d06a400e75fcff1de676dd6c42f1 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Tue, 18 May 2021 13:33:54 -0400 Subject: [PATCH 30/48] Update to PBS Bid Adapter and RP Bid Adapter to pass PBJS version in auction requests (#6767) --- modules/prebidServerBidAdapter/index.js | 3 ++ modules/rubiconBidAdapter.js | 4 +++ .../modules/prebidServerBidAdapter_spec.js | 30 +++++++++++++++++++ test/spec/modules/rubiconBidAdapter_spec.js | 2 ++ 4 files changed, 39 insertions(+) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 616cec47ff1..ec5d05f0fe0 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -732,6 +732,9 @@ const OPEN_RTB_PROTOCOL = { } }; + // Sets pbjs version, can be overwritten below if channel exists in s2sConfig.extPrebid + request.ext.prebid = Object.assign(request.ext.prebid, {channel: {name: 'pbjs', version: $$PREBID_GLOBAL$$.version}}) + // s2sConfig video.ext.prebid is passed through openrtb to PBS if (s2sConfig.extPrebid && typeof s2sConfig.extPrebid === 'object') { request.ext.prebid = Object.assign(request.ext.prebid, s2sConfig.extPrebid); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 9fb112eb765..078b5404baf 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -174,6 +174,10 @@ export const spec = { }], ext: { prebid: { + channel: { + name: 'pbjs', + version: $$PREBID_GLOBAL$$.version + }, cache: { vastxml: { returnCreative: rubiConf.returnVast === true diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 147ade6c77b..6833daf49cf 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1143,6 +1143,10 @@ describe('S2S Adapter', function () { targeting: { includebidderkeys: false, includewinners: true + }, + channel: { + name: 'pbjs', + version: 'v$prebid.version$' } } }); @@ -1177,6 +1181,10 @@ describe('S2S Adapter', function () { targeting: { includebidderkeys: false, includewinners: true + }, + channel: { + name: 'pbjs', + version: 'v$prebid.version$' } } }); @@ -1630,6 +1638,28 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.ext.prebid.multibid).to.deep.equal(expected); }); + it('sets and passes pbjs version in request if channel does not exist in s2sConfig', () => { + const s2sBidRequest = utils.deepClone(REQUEST); + const bidRequests = utils.deepClone(BID_REQUESTS); + + adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); + + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); + }); + + it('does not set pbjs version in request if channel does exist in s2sConfig', () => { + const s2sBidRequest = utils.deepClone(REQUEST); + const bidRequests = utils.deepClone(BID_REQUESTS); + + utils.deepSetValue(s2sBidRequest, 's2sConfig.extPrebid.channel', {test: 1}); + + adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); + + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({test: 1}); + }); + it('passes first party data in request', () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 5669e03759a..1964d0d6ecc 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1520,6 +1520,8 @@ describe('the rubicon adapter', function () { expect(imp.ext.rubicon.video.skip).to.equal(1); expect(imp.ext.rubicon.video.skipafter).to.equal(15); expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); + // should contain version + expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); // EIDs should exist expect(post.user.ext).to.have.property('eids').that.is.an('array'); From ff72a3df486801ce24eb927d654d299220d9e1ca Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 18 May 2021 17:11:50 -0400 Subject: [PATCH 31/48] AOL Bid Adapter: Remove client side support for already deprecated endpoint floor support (#6743) --- modules/aolBidAdapter.js | 4 ---- test/spec/modules/aolBidAdapter_spec.js | 19 ------------------- 2 files changed, 23 deletions(-) diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 5a5d5e6f417..14b529f4973 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -271,10 +271,6 @@ export const spec = { formatMarketplaceDynamicParams(params = {}, consentData = {}) { let queryParams = {}; - if (params.bidFloor) { - queryParams.bidfloor = params.bidFloor; - } - Object.assign(queryParams, this.formatKeyValues(params.keyValues)); Object.assign(queryParams, this.formatConsentData(consentData)); diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 78be0618594..50622180ad0 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -369,18 +369,6 @@ describe('AolAdapter', function () { expect(request.url).not.to.contain('bidfloor='); }); - it('should return url with bidFloor option if it is present', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - bidFloor: 0.80 - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('bidfloor=0.8'); - }); - it('should return url with key values if keyValues param is present', function () { let bidRequest = createCustomBidRequest({ params: { @@ -781,13 +769,6 @@ describe('AolAdapter', function () { }); expect(spec.formatMarketplaceDynamicParams()).to.be.equal('param1=val1;param2=val2;param3=val3;'); }); - - it('should return formatted bid floor param when it is present', function () { - let params = { - bidFloor: 0.45 - }; - expect(spec.formatMarketplaceDynamicParams(params)).to.be.equal('bidfloor=0.45;'); - }); }); describe('formatOneMobileDynamicParams()', function () { From 5fb4041298c6f42d69b9f92d30d9c5cfccf1d235 Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Wed, 19 May 2021 11:53:12 +0200 Subject: [PATCH 32/48] tappx Bid Adapter: update isBidRequestValid and fix request url (#6761) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * UPDATE: add initial UID * UPDATE: UID change user ext * UPDATE: UID clean logs * UPDATE: add host info * UPDATE: tappx bid adapter universal id * UPDATE: fix bidder param * UPDATE: tappxBidAdapter tests * tappxBidAdapter - fix spacing * tappxBidAdapter: add test user eids array * tappxBidAdapter: update eids array * FIX: package-lock.json * Conversant adapter: add adomain, remove digitrust (#6495) * Update eids.js * Update eids_spec.js * Update eids.js * Update pubmaticBidAdapter_spec.js * Update eids.js * Update eids_spec.js * Update conversantBidAdapter_spec.js * Update rubiconBidAdapter_spec.js * Update conversantBidAdapter_spec.js * Delete test/spec/adapters directory * Update userId_spec.js * Update conversantBidAdapter.js * Update conversantBidAdapter_spec.js * Update conversantBidAdapter_spec.js * Update conversantBidAdapter_spec.js * Rads Bid Adapter: add GDPR support & user sync support (#6455) * Proxistore Bid Adapter: add cookieless url endpoint & use floor module (#6427) * use floor module * call cookieless endpoint when necessary * test endpoint url * change url endpoint * delete console log * fix tests * add language to url * use ortb interface * unit test * update test unit * create proxistore module * add unit tests and documentation * delete modules * delete module * add proxistore rtd submodule * delete proxistore module * spacing * change url * AdYoulike Bid Adapter: Add an "Insertion" tracking for Native mediatype (#6481) * add insertion event * add missing campaign ID parameter * update unit test with new tracking checked * Dspx Bid Adapter : add user sync support (#6456) * Add sync support for dspx adapter * Dspx Bid Adapter : add user sync support Co-authored-by: Alexander * Multibid Module: add new module to handle multiple bids from single bidder & update rubicon adapter (#6404) * Multibid module - create new module - Expands the number of key value pairs going to the ad server in the normal Prebid way by establishing the concept of a "dynamic alias" First commit * Continued updates from 1st commit * Adding logWarn for filtered bids * Update to include passing multibid configuration to PBS requests * Update to rubicon bid adapter to pass query param rp_maxbids value taken from bidderRequest.bidLimit * Update to config to look for camelcase property names according to spec. These convert to all lowercase when passed to PBS endpoint * Adjust RP adapter to always include maxbids value - default is 1 * Added support for bidders array in multibid config * Fixed floor comparison to be <= bid cpm as oppossed to just < bid cpm. Updated md file to fix camelCase tpyo * Update to include originalBidderRequest in video call to prebid cache * Update to ignore adpod bids from multibid and allow them to return as normal bids * Adding uid2 to submodules.json (#6508) * NextRoll ID System: add new ID module (#6396) * Add Nextroll ID Module * Add nextroll to eids * Make configuration value names consistent with Adapter Module * Use parnerId instead of sellerId * Add nextroll to userId and eids md files * Remove storage configuration * Rename nextroll -> nextrollId * Add nextrollId to common ID specs * Qwarry Bid Adapter: add GDPR and consent string handling (#6489) * qwarry bid adapter * formatting fixes * fix tests for qwarry * qwarry bid adapter * add header for qwarry bid adapter * bid requests fix * fix tests * response fix * fix tests for Qwarry bid adapter * add pos parameter to qwarry bid adapter * qwarryBidAdapter onBidWon hotfix * Change bidder endpoint url for Qwarry adapter * add referer JS detection * use bidderRequest.refererInfo * fix tests * GDPR consent string support * NPE fix Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev Co-authored-by: pro-nsk <32703851+pro-nsk@users.noreply.github.com> * Zemanta Bid Adapter: add support for new params & consent strings to usersync URL (#6468) * add gvl id to spec * add support for bcat and badv params * add consent strings to usersync url * add bcat and badv params to doc * Automatad Bid Adapter: Add meta.advertiserDomains to bid response (#6509) * added bid meta with advertiserDomains * Adhese Bid Adapter: add support for caching video content (#6501) * adpod category support test * Revert "adpod category support test" This reverts commit 70a3cf2ad5db94757addd9e08c3a083caca282d0. * adpod category support test * Revert "adpod category support test" This reverts commit 70a3cf2ad5db94757addd9e08c3a083caca282d0. * Adhese Bid Adapter: cache video content Co-authored-by: Tim Sturtewagen Co-authored-by: Mateusz * update apacdex unit test to disable debug mode (#6511) * Telaria: not setting adid (#6507) * Prebid 4.33.0 Release * increment pre version * rubicon: removing maxduration as a required bidder parameter (#6513) * Zemanta adapter: add advertiserDomains (#6517) * Lemma Bid Adapter: accepting the floor to use the getFloor function (#6497) * lemmaBidAdapter.js Added lemma bid adapter file * lemmaBidAdapter.md Added lemma bid adapter md file * lemmaBidAdapter_spec.js Added lemma bid adapter test spec file * Update lemmaBidAdapter.js Fixed automated code review alert comparison between inconvertible types * Update lemmaBidAdapter.js Fixed review changes * Update lemmaBidAdapter.md Correct parameter value. * Update lemmaBidAdapter.js Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter_spec.js Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter.md Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter.js Added user sync support into bid adapter. * updated include modules file extension. updated include modules js file extension. * Update lemmaBidAdapter_spec.js Added unit test for user sync feature. * Update lemmaBidAdapter.js Fixed format error. * Update lemmaBidAdapter_spec.js Fixed format error and typo error. * Set mediaType key value into bid object Set mediaType key value into the bid object. * Update lemmaBidAdapter.js remove duplicate function * Update lemmaBidAdapter.js Remove non supported code. * Update lemmaBidAdapter_spec.js Remove GDPR test cases. * Update lemmaBidAdapter.js Made changes for accepting the floor to use the getFloor function * Update lemmaBidAdapter.js correct undefined keyword name. * Update lemmaBidAdapter_spec.js Added test coverage floor value * Update lemmaBidAdapter.js Remove trailing spaces on lines 379 and 381. * Update lemmaBidAdapter_spec.js Added getFloor function test case changes, Please review it. * Update lemmaBidAdapter_spec.js * Update lemmaBidAdapter.js * Update lemmaBidAdapter.js Fixed lint issue. * Update lemmaBidAdapter_spec.js Fixed test cases. * Update lemmaBidAdapter_spec.js Made suggested changes. Please review it. Co-authored-by: Abhijit Mane * Mediasquare Bid Adapter: fix getUserSyncs issue with empty bids + add metrics to onBidWon Event (#6480) * Mediasquare bidder: fix getUserSyncs issue with empty bids + add metrics to onBidWon Event * Mediasquare bidder: fix getUserSyncs issue with empty bids + add metrics to onBidWon Event * removing status as it does not seem populated when called * add tests * Update nextroll ID variable name to match published ID module (#6519) * Merkle User ID Module: updates to user id submodule (#6503) * AdKernel Bid/Analytics Adapters: user privacy related changes (#6488) * SynacorMedia: remove adId from the bid response (#6520) * Rubicon: making doc data types consistent (#6526) * Synacormedia Bid Adapter: add meta.advertiserDomains (#6527) * Adloox Analytics Adapter: add new analytics adapter (#6308) * gulp: fix supplying list of browsers to test against The following now works: gulp test --browserstack --nolint --nolintfix --browsers=bs_ie_11_windows_10 --file 'test/spec/modules/adloox{AnalyticsAdapter,AdServerVideo,RtdProvider}_spec.js' * instreamTracking: unit test tidy From @robertrmartinez in https://github.com/prebid/Prebid.js/pull/6308#issuecomment-810537538 * adloaderStub: expose stub for other unit tests to use From @robertrmartinez in https://github.com/prebid/Prebid.js/pull/6308#issuecomment-810537538 * Adloox analytic module * Seedtag adapter: Fixing bug preventing to receive the right params onTimeout. (#6525) * adot bid adapter: add publisher path from bidder config to endpoint url (#6476) * Admixer ID System: add userId submodule (#6238) * Migrating to Prebid 1.0 * Migrating to Prebid 1.0 * Fix spec * add gdpr and usp * remove changes in gdpr_hello_world.html * Update gdpr_hello_world.html add spaces * add user syncs * remove comments * tests * admixer id system * admixer id system * admixer id system eids.md userId.md * admixer id system .submodules.json * admixer id system Co-authored-by: atkachov * PBJS Core: call custom render func after loadscript if provided (#6422) * Pubxai Analytics Adapter: bug fixes and code revamp (#6474) * Updated PubxAiAnalytics adapter - Bug fixes and Code restructuring * Updated endpoint URLs * Changed array.includes to array.indexOf to fix IE issue * Code cleanup and changes as suggested. * Updated browser testing order and edge browser token * PBJS Core: canBidderRegisterSync ignoring iframe sync disabled by default (#6535) * Update eids.js * Update eids_spec.js * Update eids.js * Update pubmaticBidAdapter_spec.js * Update eids.js * Update eids_spec.js * Update conversantBidAdapter_spec.js * Update rubiconBidAdapter_spec.js * Update conversantBidAdapter_spec.js * Delete test/spec/adapters directory * Update userId_spec.js * Update userSync.js * Update userSync_spec.js * Added automatic tzo and targetId to adserver request. (#6534) * Impactify Bid Adapter: add new bid adapter (#6518) * Add impactify adapter with MD file * Add impactify adapter * Prebid 4.34.0 Release * Increment pre version * Prebid server adapter: add config for openx hosting (#6530) * Yieldmo adapter: add meta data to bids (#6550) * Smartx Bid Adapter: Add meta.advertiserDomains support (#6547) * Onevideo / Adap.tv Adapter: updated example configuration (#6546) * Mass Deal Rendering Module: support multiple custom configs for dealId and rendering (#6500) * ZetaSsp Bid Adapter: add new bid adapter (#6432) * Adnuntius Bid Adapter: Fix for bid too low. (#6557) * Added automatic tzo and targetId to adserver request. * Fixing issues with bid price being too low. * Fixing issues with bid price being too low. * ReadPeak Bid Adapter: fix api issues, add gdpr consent, & getfloor module support (#6548) * BetweenBidAdatper: added sharedid support (#6531) * adWMG Bid Adapter: update endpoints for cookie sync (#6544) * Support floorCPM parameter, fix some minor bugs * fix space-in-parens circleci error * example fix * clean usersync URL * spaces * spaces * add new unit tests, compatibility with IE11 * remove logInfo * Check for floorCPM value * Check params before sending * New endpoints * code format * new endpoint for cookie sync * update tests Co-authored-by: Mikhail Dykun * Yieldmo Bid Adapter: add support for the floors module (#6541) * Sortable Bid Adapter: add eids support (#6565) * Add Sortable adapter for Prebid 3.x Update tests to reflect changes. * Add .js in imports * hostname not host: don't include port * Trivial change to trigger build: failure wasn't our adapter * More failures in other adapters * PR Feedback - use https for URL - fix examples in markdown - request to endpoint should work now * Feedback: add native and video examples * Update unit tests Co-authored-by: Shannon Broekhoven * Outbrain Bid Adapter: replacing Zemanta (#6558) * Sirdata Real-time Data Module: add new RTD module (#6515) * Logicad Bid Adapter: add support for userid modules (#6529) * ATS-identityLinkIdSystem - add use3P config property to control firing of 3P envelope endpoint (#6568) * Proxistore Bid Adapter: add support for tcf v2 consent (#6543) * use tcf v2 consent * set cosentGiven to false and test Gdpr api v2 * BlueBillyWig Bid Adapter: add renderer customization options (#6540) * add Blue Billywig adapter * Blue Billywig Adapter - update according to review feedback * Blue Billywig Adapter - update to try and pass CircleCI * Remove the last for .. of in bluebillywigBidAdapter.js, hopefully... * Update bluebillywigBidAdapter test parameters to match renderer to rendererCode rename * Blue Billywig - Also pass through site config with OpenRTB request * add Blue Billywig adapter * Blue Billywig Adapter - update according to review feedback * Blue Billywig Adapter - update to try and pass CircleCI * Remove the last for .. of in bluebillywigBidAdapter.js, hopefully... * Code quality update, always hit user syncs, improved video params * Remove unnecessary export * Add rendererSettings param to bluebillywig adapter * Kick off CircleCi tests manually Co-authored-by: Klaas-Jan Boon Co-authored-by: Chris Huie * OpenX Bid Adapter: Set Deal ID for video requests (#6573) * 33Across Bid Adapter: add support for User ID modules (#6554) * pubGENIUS bid adapter: support floor module (#6555) * Welect Bid Adapter: update url of API (#6570) * update api url * update api url in tests * Bright Mountain Media Bid Adapter: change bidder code to bmtm; alias old name (#6574) * Adtelligent Bid Adapter: add adUrl support & new alias (#6559) * add adUrl support * add adUrl test * Bright Mountain Media Bid Adapter: Change Endpoint URL (#6576) * tappxBidAdapter: update * tasppxBidAdapter: add video * tappxBidAdapter: update video * tappxBidAdapter: update name interpret banner * tappxBidAdapter: add tests for video * tappxBidAdapter: add adomain * tappxBidAdapter: update adapter version * tappxBidAdapter: update interpretBid adomain and dealid * tappxBidAdapter: update isBidRequestValid * tappxBidAdapter: update tests. Adding video to isBidRequestValid * tappxBidAdapter: update doc .md file * tappxBidAdapter: update isBidRequestValid * tappxBidAdapter: update ads sizes available * tappxBidAdapter: update isBidRequestValid * tappxBidAdapter: update host depending tappx endpoint * tappxBidAdapter: update tappx adapter version * tappxBidAdapter: add EOL * revert outbrain cahnges to untrack in this pr * tappxBidAdapter: update isBidRequestValid tests * tappxBidAdapter: fix circleci error Co-authored-by: marc_tappx Co-authored-by: Patrick McCann Co-authored-by: onlsol <48312668+onlsol@users.noreply.github.com> Co-authored-by: vincentproxistore <56686565+vincentproxistore@users.noreply.github.com> Co-authored-by: guiann Co-authored-by: Alexander Co-authored-by: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Co-authored-by: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Co-authored-by: Abimael Martinez Co-authored-by: artemiokost Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev Co-authored-by: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Rok Sušnik Co-authored-by: Kanchika - Automatad Co-authored-by: Paweł L Co-authored-by: Tim Sturtewagen Co-authored-by: Mateusz Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: bretg Co-authored-by: Jason Snellbaker Co-authored-by: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Co-authored-by: Abhijit Mane Co-authored-by: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Co-authored-by: Denis Logachov Co-authored-by: RAJKUMAR NATARAJAN Co-authored-by: Alexander Clouter Co-authored-by: Laura Morillo-Velarde Co-authored-by: Giudici-a <34242194+Giudici-a@users.noreply.github.com> Co-authored-by: Galphimbl Co-authored-by: atkachov Co-authored-by: Jérémie Girault Co-authored-by: Phaneendra Hegde Co-authored-by: Mikael Lundin Co-authored-by: Thomas Co-authored-by: Mike Chowla Co-authored-by: Deivydas Šabaras Co-authored-by: ym-atsymuk <81176595+ym-atsymuk@users.noreply.github.com> Co-authored-by: Skylinar <53079123+Skylinar@users.noreply.github.com> Co-authored-by: Adam Browning <19834421+adam-browning@users.noreply.github.com> Co-authored-by: Catalin Ciocov Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: readpeaktuomo <66239046+readpeaktuomo@users.noreply.github.com> Co-authored-by: Ignat Khaylov Co-authored-by: nyakove <43004249+nyakove@users.noreply.github.com> Co-authored-by: Mikhail Dykun Co-authored-by: ym-dlabuzov <81709888+ym-dlabuzov@users.noreply.github.com> Co-authored-by: karentnarvaez <61426156+karentnarvaez@users.noreply.github.com> Co-authored-by: Shannon Broekhoven Co-authored-by: nouchy <33549554+nouchy@users.noreply.github.com> Co-authored-by: logicad Co-authored-by: mamatic <52153441+mamatic@users.noreply.github.com> Co-authored-by: Klaas-Jan Boon Co-authored-by: Klaas-Jan Boon Co-authored-by: Chris Huie Co-authored-by: Kenan Gillet <1706856+kenan-gillet@users.noreply.github.com> Co-authored-by: Aparna Rao Co-authored-by: Meng <5110935+edmonl@users.noreply.github.com> Co-authored-by: Nick Duitz <42961155+nduitz@users.noreply.github.com> Co-authored-by: BrightMountainMedia <69471268+BrightMountainMediaInc@users.noreply.github.com> Co-authored-by: Gena Co-authored-by: Chris Huie <3444727+ChrisHuie@users.noreply.github.com> --- modules/tappxBidAdapter.js | 57 +++++++++++++++-------- modules/tappxBidAdapter.md | 2 +- test/spec/modules/tappxBidAdapter_spec.js | 34 ++++++++++++-- 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index d8f6654a567..566795a204b 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -8,11 +8,10 @@ import { config } from '../src/config.js'; const BIDDER_CODE = 'tappx'; const TTL = 360; const CUR = 'USD'; -const TAPPX_BIDDER_VERSION = '0.1.10420'; +const TAPPX_BIDDER_VERSION = '0.1.10514'; const TYPE_CNN = 'prebidjs'; const VIDEO_SUPPORT = ['instream']; -var HOST; var hostDomain; export const spec = { @@ -105,13 +104,31 @@ export const spec = { } function validBasic(bid) { - if ( - (bid.params == null) || - (bid.params.endpoint == null) || - (bid.params.tappxkey == null)) { + if (bid.params == null) { utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters.`); return false; } + + if (bid.params.tappxkey == null) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappxkey parameter.`); + return false; + } + + if (bid.params.host == null) { + utils.logWarn(`[TAPPX]: Please review the mandatory Host parameter.`); + return false; + } + + let classicEndpoint = true + if ((new RegExp(`^(vz.*|zz.*)\.*$`, 'i')).test(bid.params.host)) { + classicEndpoint = false + } + + if (classicEndpoint && bid.params.endpoint == null) { + utils.logWarn(`[TAPPX]: Please review the mandatory endpoint Tappx parameters.`); + return false; + } + return true; } @@ -174,11 +191,10 @@ function interpretBid(serverBid, request) { * @return response ad */ function buildOneRequest(validBidRequests, bidderRequest) { - HOST = utils.deepAccess(validBidRequests, 'params.host'); - let hostInfo = getHostInfo(HOST); + let hostInfo = getHostInfo(validBidRequests); + const ENDPOINT = hostInfo.endpoint; hostDomain = hostInfo.domain; - const ENDPOINT = utils.deepAccess(validBidRequests, 'params.endpoint'); const TAPPXKEY = utils.deepAccess(validBidRequests, 'params.tappxkey'); const BIDFLOOR = utils.deepAccess(validBidRequests, 'params.bidfloor'); const BIDEXTRA = utils.deepAccess(validBidRequests, 'params.ext'); @@ -359,7 +375,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { return { method: 'POST', - url: `https://${HOST}/${ENDPOINT}?type_cnn=${TYPE_CNN}&v=${TAPPX_BIDDER_VERSION}`, + url: `${hostInfo.url}?type_cnn=${TYPE_CNN}&v=${TAPPX_BIDDER_VERSION}`, data: JSON.stringify(payload), bids: validBidRequests }; @@ -375,23 +391,24 @@ function getOs() { if (ua == null) { return 'unknown'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'ios'; } else if (ua.match(/Android/)) { return 'android'; } else if (ua.match(/Window/)) { return 'windows'; } else { return 'unknown'; } } -function getHostInfo(hostParam) { +function getHostInfo(validBidRequests) { let domainInfo = {}; + let endpoint = utils.deepAccess(validBidRequests, 'params.endpoint'); + let hostParam = utils.deepAccess(validBidRequests, 'params.host'); domainInfo.domain = hostParam.split('/', 1)[0]; - domainInfo.url = hostParam; - let regexNewEndpoints = new RegExp(`^(vz.*|zz.*|testing)\.ssp\.tappx\.com$`, 'i'); - let regexClassicEndpoints = new RegExp(`^[a-z]{3}\.[a-z]{3}\.tappx\.com$`, 'i'); + let regexNewEndpoints = new RegExp(`^(vz.*|zz.*)\.pub\.tappx\.com$`, 'i'); + let regexClassicEndpoints = new RegExp(`^([a-z]{3}|testing)\.[a-z]{3}\.tappx\.com$`, 'i'); if (regexNewEndpoints.test(domainInfo.domain)) { - let endpoint = domainInfo.domain.split('.', 1)[0] - if (endpoint.toUpperCase().indexOf('TESTING') === -1) { - domainInfo.endpoint = endpoint - domainInfo.new_endpoint = true; - } + domainInfo.newEndpoint = true; + domainInfo.endpoint = domainInfo.domain.split('.', 1)[0] + domainInfo.url = `https://${hostParam}` } else if (regexClassicEndpoints.test(domainInfo.domain)) { - domainInfo.new_endpoint = false; + domainInfo.newEndpoint = false; + domainInfo.endpoint = endpoint + domainInfo.url = `https://${hostParam}${endpoint}` } return domainInfo; diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md index a07bb2d88d1..776b24bb07c 100644 --- a/modules/tappxBidAdapter.md +++ b/modules/tappxBidAdapter.md @@ -9,7 +9,7 @@ Maintainer: prebid@tappx.com Module that connects to :tappx demand sources. Suppots Banner and Instream Video. Please use ```tappx``` as the bidder code. -Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] +Ads sizes available: [300,250], [320,50], [320,480], [480,320], [728,90], [768,1024], [1024,768] # Banner Test Parameters ``` diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index 54c4f8c9dca..9fab7d858e1 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -126,11 +126,35 @@ describe('Tappx bid adapter', function () { assert.isTrue(spec.isBidRequestValid(c_BIDREQUEST.bids[0]), JSON.stringify(c_BIDREQUEST)); }); - it('should return false when required params are missing', function () { - let badBidRequest = c_BIDREQUEST; - delete badBidRequest.bids[0].params.tappxkey; - delete badBidRequest.bids[0].params.endpoint; - assert.isFalse(spec.isBidRequestValid(badBidRequest.bids[0])); + it('should return false when params are missing', function () { + let badBidRequestParam = JSON.parse(JSON.stringify(c_BIDREQUEST)); + delete badBidRequestParam.bids[0].params; + assert.isFalse(spec.isBidRequestValid(badBidRequestParam.bids[0])); + }); + + it('should return false when tappxkey is missing', function () { + let badBidRequestTpxkey = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; + delete badBidRequestTpxkey.bids[0].params.tappxkey; + assert.isFalse(spec.isBidRequestValid(badBidRequestTpxkey.bids[0])); + }); + + it('should return false when host is missing', function () { + let badBidRequestHost = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; + delete badBidRequestHost.bids[0].params.host; + assert.isFalse(spec.isBidRequestValid(badBidRequestHost.bids[0])); + }); + + it('should return false when classic endpoint is missing', function () { + let badBidRequestClEp = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; + delete badBidRequestClEp.bids[0].params.endpoint; + assert.isFalse(spec.isBidRequestValid(badBidRequestClEp.bids[0])); + }); + + it('should return true when endpoint is not set for new endpoints', function () { + let badBidRequestNwEp = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; + delete badBidRequestNwEp.bids[0].params.endpoint; + badBidRequestNwEp.bids[0].params.host = 'zztesting.ssp.tappx.com/rtb/v2/'; + assert.isTrue(spec.isBidRequestValid(badBidRequestNwEp.bids[0])); }); it('should return false for not instream requests', function () { From a5b50837afb87b7076752f621970c3d39e7d6e21 Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Wed, 19 May 2021 17:45:51 +0700 Subject: [PATCH 33/48] Qwarry Bid Adapter: add sizes (#6787) * qwarry bid adapter * formatting fixes * fix tests for qwarry * qwarry bid adapter * add header for qwarry bid adapter * bid requests fix * fix tests * response fix * fix tests for Qwarry bid adapter * add pos parameter to qwarry bid adapter * qwarryBidAdapter onBidWon hotfix * Change bidder endpoint url for Qwarry adapter * add referer JS detection * use bidderRequest.refererInfo * fix tests * GDPR consent string support * NPE fix * gdpr value added * merge master * gdpr value added * qwarry bid adapter: add tests * Qwarry bid adapter: remove gdpr field from request * qwarry bid adapter: add sizes * qwarry bid adapter: add sizes Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 7 ++++++- test/spec/modules/qwarryBidAdapter_spec.js | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index d19ad0b4fe4..7ed5e5c984c 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -20,7 +20,8 @@ export const spec = { bids.push({ bidId: bidRequest.bidId, zoneToken: bidRequest.params.zoneToken, - pos: bidRequest.params.pos + pos: bidRequest.params.pos, + sizes: prepareSizes(bidRequest.sizes) }) }) @@ -90,4 +91,8 @@ export const spec = { } } +function prepareSizes(sizes) { + return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); +} + registerBidder(spec); diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 5d297d32014..06d4af0756c 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -5,6 +5,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js' const REQUEST = { 'bidId': '456', 'bidder': 'qwarry', + 'sizes': [[100, 200], [300, 400]], 'params': { zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 @@ -85,7 +86,7 @@ describe('qwarryBidAdapter', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') - expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 }) + expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, sizes: [{ width: 100, height: 200 }, { width: 300, height: 400 }] }) expect(bidderRequest.data.gdprConsent).to.deep.contains({ consentRequired: true, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') From c3463221febad83379da612a3088599e4d8e595c Mon Sep 17 00:00:00 2001 From: Rahul Shandilya <67756716+c3p-0@users.noreply.github.com> Date: Wed, 19 May 2021 18:53:34 +0530 Subject: [PATCH 34/48] Medianet bid adapter: floor module support (#6713) * Medianet floor module support * removing backslash from wildcard --- modules/medianetBidAdapter.js | 29 ++++++++++++++ test/spec/modules/medianetBidAdapter_spec.js | 41 ++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index a2dc8bdfd03..1a4269a8a1d 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -221,10 +221,39 @@ function slotParams(bidRequest) { } else { params.ext.visibility = SLOT_VISIBILITY.NOT_DETERMINED; } + const floorInfo = getBidFloorByType(bidRequest); + if (floorInfo && floorInfo.length > 0) { + params.bidfloors = floorInfo; + } return params; } +function getBidFloorByType(bidRequest) { + let floorInfo = []; + if (typeof bidRequest.getFloor === 'function') { + [BANNER, VIDEO, NATIVE].forEach(mediaType => { + if (bidRequest.mediaTypes.hasOwnProperty(mediaType)) { + if (mediaType == BANNER) { + bidRequest.mediaTypes.banner.sizes.forEach( + size => { + setFloorInfo(bidRequest, mediaType, size, floorInfo) + } + ) + } else { + setFloorInfo(bidRequest, mediaType, '*', floorInfo) + } + } + }); + } + return floorInfo; +} +function setFloorInfo(bidRequest, mediaType, size, floorInfo) { + let floor = bidRequest.getFloor({currency: 'USD', mediaType: mediaType, size: size}); + if (size.length > 1) floor.size = size; + floor.mediaType = mediaType; + floorInfo.push(floor); +} function getMinSize(sizes) { return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); } diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 1eeb167601e..adb9663ba7c 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1446,4 +1446,45 @@ describe('Media.net bid adapter', function () { let bids = spec.interpretResponse(SERVER_VIDEO_OUTSTREAM_RESPONSE_VALID_BID, []); expect(bids[0].context).to.equal('outstream'); }); + describe('buildRequests floor tests', function () { + let floor; + let getFloor = function(req) { + return floor[req.mediaType]; + }; + beforeEach(function () { + floor = { + 'banner': { + 'currency': 'USD', + 'floor': 1 + } + }; + $$PREBID_GLOBAL$$.medianetGlobals = {}; + + let documentStub = sandbox.stub(document, 'getElementById'); + let boundingRect = { + top: 50, + left: 50, + bottom: 100, + right: 100 + }; + documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ + getBoundingClientRect: () => boundingRect + }); + documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({ + getBoundingClientRect: () => boundingRect + }); + let windowSizeStub = sandbox.stub(spec, 'getWindowSize'); + windowSizeStub.returns({ + w: 1000, + h: 1000 + }); + VALID_BID_REQUEST[0].getFloor = getFloor; + }); + + it('should build valid payload with floor', function () { + let requestObj = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); + requestObj = JSON.parse(requestObj.data); + expect(requestObj.imp[0].hasOwnProperty('bidfloors')).to.equal(true); + }); + }); }); From 566c8ae9db52b0d15f266937d182c12065686244 Mon Sep 17 00:00:00 2001 From: Ignat Khaylov Date: Wed, 19 May 2021 17:23:15 +0300 Subject: [PATCH 35/48] add adomain support (#6791) Co-authored-by: Ignat Khaylov --- modules/betweenBidAdapter.js | 5 +++- test/spec/modules/betweenBidAdapter_spec.js | 33 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index f6360332368..5a351def958 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -104,7 +104,10 @@ export const spec = { creativeId: serverResponse.body[i].creativeid, currency: serverResponse.body[i].currency || 'RUB', netRevenue: serverResponse.body[i].netRevenue || true, - ad: serverResponse.body[i].ad + ad: serverResponse.body[i].ad, + meta: { + advertiserDomains: serverResponse.body[i].adomain ? serverResponse.body[i].adomain : [] + } }; bidResponses.push(bidResponse); } diff --git a/test/spec/modules/betweenBidAdapter_spec.js b/test/spec/modules/betweenBidAdapter_spec.js index 1b1ccd9efd2..0e772e7be02 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -267,4 +267,37 @@ describe('betweenBidAdapterTests', function () { const shid3 = JSON.parse(spec.buildRequests(bidRequestData).data)[0].data.shid3; expect(shid).to.equal('01EXQE7JKNDRDDVATB0S2GX1NT') && expect(shid3).to.equal(''); }); + it('check adomain', function() { + const serverResponse = { + body: [{ + bidid: 'bid1234', + cpm: 1.12, + w: 240, + h: 400, + currency: 'USD', + ad: 'Ad html', + adomain: ['domain1.com', 'domain2.com'] + }] + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.meta.advertiserDomains).to.deep.equal(['domain1.com', 'domain2.com']); + }); + it('check server response without adomain', function() { + const serverResponse = { + body: [{ + bidid: 'bid1234', + cpm: 1.12, + w: 240, + h: 400, + currency: 'USD', + ad: 'Ad html', + }] + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.meta.advertiserDomains).to.deep.equal([]); + }); }); From cb3ae12f5a8e79386afaea94277b5bb278532fff Mon Sep 17 00:00:00 2001 From: John Salis Date: Wed, 19 May 2021 12:04:41 -0400 Subject: [PATCH 36/48] Beachfront Bid Adapter: add floors module support (#6752) * add price floors support to beachfront adapter * revert doc changes to create separate pull request Co-authored-by: John Salis --- modules/beachfrontBidAdapter.js | 23 ++++++++---- .../spec/modules/beachfrontBidAdapter_spec.js | 36 +++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 7466b3d6a68..da5f385b0da 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -6,9 +6,10 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; -const ADAPTER_VERSION = '1.15'; +const ADAPTER_VERSION = '1.16'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; +const CURRENCY = 'USD'; export const VIDEO_ENDPOINT = 'https://reachms.bfmio.com/bid.json?exchange_id='; export const BANNER_ENDPOINT = 'https://display.bfmio.com/prebid_display'; @@ -79,7 +80,7 @@ export const spec = { creativeId: response.crid || response.cmpId, renderer: context === OUTSTREAM ? createRenderer(bidRequest) : null, mediaType: VIDEO, - currency: 'USD', + currency: CURRENCY, netRevenue: true, ttl: 300 }; @@ -111,7 +112,7 @@ export const spec = { width: bid.w, height: bid.h, mediaType: BANNER, - currency: 'USD', + currency: CURRENCY, netRevenue: true, ttl: 300 }; @@ -251,6 +252,16 @@ function getPlayerBidParam(bid, key, defaultValue) { return param === undefined ? defaultValue : param; } +function getBannerBidFloor(bid) { + let floorInfo = utils.isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'banner', size: '*' }) : {}; + return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); +} + +function getVideoBidFloor(bid) { + let floorInfo = utils.isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'video', size: '*' }) : {}; + return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); +} + function isVideoBidValid(bid) { return isVideoBid(bid) && getVideoBidParam(bid, 'appId') && getVideoBidParam(bid, 'bidfloor'); } @@ -316,7 +327,7 @@ function createVideoRequestData(bid, bidderRequest) { let firstSize = getFirstSize(sizes); let video = getVideoTargetingParams(bid); let appId = getVideoBidParam(bid, 'appId'); - let bidfloor = getVideoBidParam(bid, 'bidfloor'); + let bidfloor = getVideoBidFloor(bid); let tagid = getVideoBidParam(bid, 'tagid'); let topLocation = getTopWindowLocation(bidderRequest); let eids = getEids(bid); @@ -358,7 +369,7 @@ function createVideoRequestData(bid, bidderRequest) { user: { ext: {} }, - cur: ['USD'] + cur: [CURRENCY] }; if (bidderRequest && bidderRequest.uspConsent) { @@ -394,7 +405,7 @@ function createBannerRequestData(bids, bidderRequest) { return { slot: bid.adUnitCode, id: getBannerBidParam(bid, 'appId'), - bidfloor: getBannerBidParam(bid, 'bidfloor'), + bidfloor: getBannerBidFloor(bid), tagid: getBannerBidParam(bid, 'tagid'), sizes: getBannerSizes(bid) }; diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 605ccc464cb..fc74ec8a2aa 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -171,6 +171,24 @@ describe('BeachfrontAdapter', function () { expect(data.cur).to.deep.equal(['USD']); }); + it('must read from the floors module if available', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.getFloor = () => ({ currency: 'USD', floor: 1.16 }); + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].bidfloor).to.equal(1.16); + }); + + it('must use the bid floor param if no value is returned from the floors module', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.getFloor = () => ({}); + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + }); + it('must parse bid size from a nested array', function () { const width = 640; const height = 480; @@ -403,6 +421,24 @@ describe('BeachfrontAdapter', function () { expect(data.ua).to.equal(navigator.userAgent); }); + it('must read from the floors module if available', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.getFloor = () => ({ currency: 'USD', floor: 1.16 }); + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].bidfloor).to.equal(1.16); + }); + + it('must use the bid floor param if no value is returned from the floors module', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.getFloor = () => ({}); + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.slots[0].bidfloor).to.equal(bidRequest.params.bidfloor); + }); + it('must parse bid size from a nested array', function () { const width = 300; const height = 250; From d662340c08b529dcdef71ca28068ffa376418eb1 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Wed, 19 May 2021 22:43:24 +0600 Subject: [PATCH 37/48] Zeta Ssp Bid Adapter: merge fpd.device and params.device (#6786) --- modules/zetaSspBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/zetaSspBidAdapter.js b/modules/zetaSspBidAdapter.js index e267942862b..450608a82f4 100644 --- a/modules/zetaSspBidAdapter.js +++ b/modules/zetaSspBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { cur: [DEFAULT_CUR], imp: [impData], site: params.site ? params.site : {}, - device: fpd.device ? fpd.device : {}, + device: {...fpd.device, ...params.device}, user: params.user ? params.user : {}, app: params.app ? params.app : {}, ext: { From 03ad46db7e4b02e8835c53a4bb2a01a8290f1606 Mon Sep 17 00:00:00 2001 From: Meng <5110935+edmonl@users.noreply.github.com> Date: Wed, 19 May 2021 13:05:32 -0400 Subject: [PATCH 38/48] pubGENIUS bid adapter: read more video params from mediaTypes.video (#6793) --- modules/pubgeniusBidAdapter.js | 17 ++++++++++++++++- modules/pubgeniusBidAdapter.md | 12 ++++++------ test/spec/modules/pubgeniusBidAdapter_spec.js | 3 +++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 2df2d25f627..89dea545434 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -149,7 +149,22 @@ export const spec = { function buildVideoParams(videoMediaType, videoParams) { videoMediaType = videoMediaType || {}; - const params = pick(videoMediaType, ['api', 'mimes', 'protocols', 'playbackmethod']); + const params = pick(videoMediaType, [ + 'mimes', + 'minduration', + 'maxduration', + 'protocols', + 'startdelay', + 'placement', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity', + ]); switch (videoMediaType.context) { case 'instream': diff --git a/modules/pubgeniusBidAdapter.md b/modules/pubgeniusBidAdapter.md index 66e0c382285..2a10b421de2 100644 --- a/modules/pubgeniusBidAdapter.md +++ b/modules/pubgeniusBidAdapter.md @@ -65,16 +65,16 @@ var adUnits = [ adUnitId: '1001', test: true, - // other video parameters as in OpenRTB v2.5 spec + // Other video parameters can be put here as in OpenRTB v2.5 spec. + // This overrides the same parameters in mediaTypes.video. video: { - skip: 1 - - // the following overrides mediaTypes.video of the ad unit placement: 1, + + // w and h overrides mediaTypes.video.playerSize. w: 640, h: 360, - mimes: ['video/mp4'], - protocols: [3], + + skip: 1 } } } diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 57b83fced06..6568f7aa782 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -370,6 +370,8 @@ describe('pubGENIUS adapter', () => { protocols: [2, 3], api: [1, 2], playbackmethod: [3, 4], + maxduration: 10, + linearity: 1, }, }; bidRequest.params.video = { @@ -394,6 +396,7 @@ describe('pubGENIUS adapter', () => { skipafter: 1, playbackmethod: [3, 4], api: [1, 2], + linearity: 1, }; expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); From 3f4922f72fc572e2da70c25fdb8551d18339b81c Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 19 May 2021 15:56:13 -0400 Subject: [PATCH 39/48] Remove camel case for adserver.adslot value in RP Analytics Adapter (#6795) --- modules/rubiconAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 3237facb2e7..5a2e02b8f89 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -664,7 +664,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { }, 'gam', () => { if (utils.deepAccess(bid, 'ortb2Imp.ext.data.adserver.name') === 'gam') { - return {adSlot: bid.ortb2Imp.ext.data.adserver.adSlot} + return {adSlot: bid.ortb2Imp.ext.data.adserver.adslot} } }, 'pbAdSlot', () => utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), From f0af380141b95fee70a9e87b2aa483e75a9d5f6a Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 19 May 2021 13:12:32 -0700 Subject: [PATCH 40/48] Prebid 4.40.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 05f68f4a009..ecf293403b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.40.0-pre", + "version": "4.40.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 507c437f9ae74ef8c6bfcd8f1171dbeafac2c2e4 Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 19 May 2021 13:48:34 -0700 Subject: [PATCH 41/48] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ecf293403b7..f7176162f8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.40.0", + "version": "4.41.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a031f8d39496414e211dd40d019a4adb05d59031 Mon Sep 17 00:00:00 2001 From: mjaworskiccx <50406214+mjaworskiccx@users.noreply.github.com> Date: Thu, 20 May 2021 10:14:33 +0200 Subject: [PATCH 42/48] CCX Bid Adapter: add support for mediatypes video parameters (#6736) * adomain support * adomain support * adomain support * adomain support * adomain support * video params * docs changes --- modules/ccxBidAdapter.js | 12 ++--- modules/ccxBidAdapter.md | 58 +++---------------------- test/spec/modules/ccxBidAdapter_spec.js | 54 +++++++++++++++++++++++ 3 files changed, 66 insertions(+), 58 deletions(-) diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 37b6fdc3e98..2160e539040 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -94,12 +94,12 @@ function _buildBid (bid) { } } - placement.video.protocols = utils.deepAccess(bid, 'params.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS - placement.video.mimes = utils.deepAccess(bid, 'params.video.mimes') || SUPPORTED_VIDEO_MIMES - placement.video.playbackmethod = utils.deepAccess(bid, 'params.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS - placement.video.skip = utils.deepAccess(bid, 'params.video.skip') || 0 - if (placement.video.skip === 1 && utils.deepAccess(bid, 'params.video.skipafter')) { - placement.video.skipafter = utils.deepAccess(bid, 'params.video.skipafter') + placement.video.protocols = utils.deepAccess(bid, 'mediaTypes.video.protocols') || utils.deepAccess(bid, 'params.video.protocols') || SUPPORTED_VIDEO_PROTOCOLS + placement.video.mimes = utils.deepAccess(bid, 'mediaTypes.video.mimes') || utils.deepAccess(bid, 'params.video.mimes') || SUPPORTED_VIDEO_MIMES + placement.video.playbackmethod = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod') || utils.deepAccess(bid, 'params.video.playbackmethod') || SUPPORTED_VIDEO_PLAYBACK_METHODS + placement.video.skip = utils.deepAccess(bid, 'mediaTypes.video.skip') || utils.deepAccess(bid, 'params.video.skip') || 0 + if (placement.video.skip === 1 && (utils.deepAccess(bid, 'mediaTypes.video.skipafter') || utils.deepAccess(bid, 'params.video.skipafter'))) { + placement.video.skipafter = utils.deepAccess(bid, 'mediaTypes.video.skipafter') || utils.deepAccess(bid, 'params.video.skipafter') } } diff --git a/modules/ccxBidAdapter.md b/modules/ccxBidAdapter.md index 740457dd099..7d86507bccb 100644 --- a/modules/ccxBidAdapter.md +++ b/modules/ccxBidAdapter.md @@ -32,67 +32,21 @@ Module that connects to Clickonometrics's demand sources mediaTypes: { video: { playerSize: [1920, 1080] - + protocols: [2, 3, 5, 6], //default + mimes: ["video/mp4", "video/x-flv"], //default + playbackmethod: [1, 2, 3, 4], //default + skip: 1, //default 0 + skipafter: 5 //delete this key if skip = 0 } }, bids: [ { bidder: "ccx", params: { - placementId: 3287742, - //following options are not required, default values will be used. Uncomment if you want to use custom values - /*video: { - //check OpenRTB documentation for following options description - protocols: [2, 3, 5, 6], //default - mimes: ["video/mp4", "video/x-flv"], //default - playbackmethod: [1, 2, 3, 4], //default - skip: 1, //default 0 - skipafter: 5 //delete this key if skip = 0 - }*/ + placementId: 3287742 } } ] } ]; - -# Pre 1.0 Support - - var adUnits = [ - { - code: 'test-banner', - mediaType: 'banner', - sizes: [300, 250], - bids: [ - { - bidder: "ccx", - params: { - placementId: 3286844 - } - } - ] - }, - { - code: 'test-video', - mediaType: 'video', - sizes: [1920, 1080] - bids: [ - { - bidder: "ccx", - params: { - placementId: 3287742, - //following options are not required, default values will be used. Uncomment if you want to use custom values - /*video: { - //check OpenRTB documentation for following options description - protocols: [2, 3, 5, 6], //default - mimes: ["video/mp4", "video/x-flv"], //default - playbackmethod: [1, 2, 3, 4], //default - skip: 1, //default 0 - skipafter: 5 //delete this key if skip = 0 - }*/ - } - } - ] - } - - ]; \ No newline at end of file diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index ef86b391e39..d346a14d38a 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -434,4 +434,58 @@ describe('ccxAdapter', function () { expect(spec.getUserSyncs(syncOptions, [{body: response}])).to.be.empty; }); }); + describe('mediaTypesVideoParams', function () { + it('Valid video mediaTypes', function () { + let bids = [ + { + adUnitCode: 'video', + auctionId: '0b9de793-8eda-481e-a548-c187d58b28d9', + bidId: '3u94t90ut39tt3t', + bidder: 'ccx', + bidderRequestId: '23ur20r239r2r', + mediaTypes: { + video: { + playerSize: [[640, 480]], + protocols: [2, 3, 5, 6], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [1, 2, 3, 4], + skip: 1, + skipafter: 5 + } + }, + params: { + placementId: 608 + }, + sizes: [[640, 480]], + transactionId: 'aefddd38-cfa0-48ab-8bdd-325de4bab5f9' + } + ]; + + let imps = [ + { + video: { + w: 640, + h: 480, + protocols: [2, 3, 5, 6], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [1, 2, 3, 4], + skip: 1, + skipafter: 5 + }, + id: '3u94t90ut39tt3t', + secure: 1, + ext: { + pid: 608 + } + } + ]; + + let bidsClone = utils.deepClone(bids); + + let response = spec.buildRequests(bidsClone, {'bids': bidsClone}); + let data = JSON.parse(response.data); + + expect(data.imp).to.deep.have.same.members(imps); + }); + }); }); From a2401ae1aa6b1158cfc376765962ccc2e3e4e046 Mon Sep 17 00:00:00 2001 From: Kajan Umakanthan Date: Thu, 20 May 2021 05:39:08 -0700 Subject: [PATCH 43/48] Index Exchange Bid Adapter: adds support for floc (#6758) --- modules/ixBidAdapter.js | 92 +++++++++++---------- test/spec/modules/ixBidAdapter_spec.js | 106 ++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 41 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 64a3237e02a..3be919453a3 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -23,6 +23,33 @@ const PRICE_TO_DOLLAR_FACTOR = { const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; +// determines which eids we send and the rtiPartner field in ext +const SOURCE_RTI_MAPPING = { + 'liveramp.com': 'idl', + 'netid.de': 'NETID', + 'neustar.biz': 'fabrickId', + 'zeotap.com': 'zeotapIdPlus', + 'uidapi.com': 'UID2', + 'adserver.org': 'TDID' +}; + +const PROVIDERS = [ + 'britepoolid', + 'id5id', + 'lipbid', + 'haloId', + 'criteoId', + 'lotamePanoramaId', + 'merkleId', + 'parrableId', + 'connectid', + 'sharedid', + 'tapadId', + 'quantcastId', + 'pubcid', + 'TDID', + 'flocId' +] /** * Transform valid bid request config object to banner impression object that will be sent to ad server. @@ -285,35 +312,37 @@ function getBidRequest(id, impressions) { * From the userIdAsEids array, filter for the ones our adserver can use, and modify them * for our purposes, e.g. add rtiPartner * @param {array} allEids userIdAsEids passed in by prebid + * @param {object} flocId flocId passed in by prebid * @return {object} contains toSend (eids to send to the adserver) and seenSources (used to filter * identity info from IX Library) */ -function getEidInfo(allEids) { - // determines which eids we send and the rtiPartner field in ext - var sourceRTIMapping = { - 'liveramp.com': 'idl', - 'netid.de': 'NETID', - 'neustar.biz': 'fabrickId', - 'zeotap.com': 'zeotapIdPlus', - 'uidapi.com': 'UID2' - }; - var toSend = []; - var seenSources = {}; +function getEidInfo(allEids, flocData) { + let toSend = []; + let seenSources = {}; if (utils.isArray(allEids)) { - for (var i = 0; i < allEids.length; i++) { - if (sourceRTIMapping[allEids[i].source] && utils.deepAccess(allEids[i], 'uids.0')) { - seenSources[allEids[i].source] = 1; - allEids[i].uids[0].ext = { - rtiPartner: sourceRTIMapping[allEids[i].source] + for (const eid of allEids) { + if (SOURCE_RTI_MAPPING[eid.source] && utils.deepAccess(eid, 'uids.0')) { + seenSources[eid.source] = true; + eid.uids[0].ext = { + rtiPartner: SOURCE_RTI_MAPPING[eid.source] }; - delete allEids[i].uids[0].atype; - toSend.push(allEids[i]); + delete eid.uids[0].atype; + toSend.push(eid); } } } - return { toSend: toSend, seenSources: seenSources }; -} + const isValidFlocId = flocData && flocData.id && flocData.version; + if (isValidFlocId) { + const flocEid = { + 'source': 'chrome.com', + 'uids': [{ 'id': flocData.id, 'ext': { 'rtiPartner': 'flocId', 'ver': flocData.version } }] + }; + toSend.push(flocEid); + seenSources['chrome.com'] = true; + } + return { toSend, seenSources }; +} /** * Builds a request object to be sent to the ad server based on bid requests. * @@ -327,10 +356,9 @@ function getEidInfo(allEids) { function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; - // Get ids from Prebid User ID Modules - var eidInfo = getEidInfo(utils.deepAccess(validBidRequests, '0.userIdAsEids')); - var userEids = eidInfo.toSend; + let eidInfo = getEidInfo(utils.deepAccess(validBidRequests, '0.userIdAsEids'), utils.deepAccess(validBidRequests, '0.userId.flocId')); + let userEids = eidInfo.toSend; // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist @@ -570,23 +598,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { function _getUserIds(bidRequest) { const userIds = bidRequest.userId || {}; - const PROVIDERS = [ - 'britepoolid', - 'id5id', - 'lipbid', - 'haloId', - 'criteoId', - 'lotamePanoramaId', - 'merkleId', - 'parrableId', - 'connectid', - 'sharedid', - 'tapadId', - 'quantcastId', - 'pubcid' - ] - - return PROVIDERS.filter(provider => utils.deepAccess(userIds, provider)) + return PROVIDERS.filter(provider => userIds[provider]); } /** diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index e0d45913fd9..50920965b60 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -462,9 +462,23 @@ describe('IndexexchangeAdapter', function () { ]; const DEFAULT_USERID_BID_DATA = { - lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4' + lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4', + flocId: {id: '1234', version: 'chrome.1.2'} }; + const DEFAULT_FLOC_USERID_PAYLOAD = [ + { + source: 'chrome.com', + uids: [{ + id: DEFAULT_USERID_BID_DATA.flocId.id, + ext: { + rtiPartner: 'flocId', + ver: DEFAULT_USERID_BID_DATA.flocId.version + } + }] + } + ]; + describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -903,6 +917,96 @@ describe('IndexexchangeAdapter', function () { expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); }); + it('IX adapter reads floc id from prebid userId and adds it to eids when there is not other eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(1); + expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc id from prebid userId and appends it to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(6); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads empty floc obj from prebid userId it, floc is not added to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = {'flocId': {}} + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc obj from prebid userId it version is missing, floc is not added to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = {'flocId': {'id': 'abcd'}} + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc obj from prebid userId it ID is missing, floc is not added to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = {'flocId': {'version': 'chrome.a.b.c'}} + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + + it('IX adapter reads floc id with empty id from prebid userId and it does not added to eids', function() { + const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + cloneValidBid[0].userId = {flocID: {id: '', ver: 'chrome.1.2.3'}}; + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = JSON.parse(request.data.r); + + expect(payload.user.eids).to.have.lengthOf(5); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); + expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); + }); + it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { validIdentityResponse = { AdserverOrgIp: { From 1adc2e70ef6df386ee6abee95c1615bb652f9038 Mon Sep 17 00:00:00 2001 From: rcheptanariu <35690143+rcheptanariu@users.noreply.github.com> Date: Thu, 20 May 2021 15:53:59 +0300 Subject: [PATCH 44/48] InvibesBidAdapter - multiposition support & support for multiple id modules (#6506) --- modules/invibesBidAdapter.js | 119 ++++++++++---------- test/spec/modules/invibesBidAdapter_spec.js | 41 +++++-- 2 files changed, 95 insertions(+), 65 deletions(-) diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index d4d26b1e017..7d2942eea55 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -8,10 +8,10 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 5, + PREBID_VERSION: 6, METHOD: 'GET', INVIBES_VENDOR_ID: 436, - USERID_PROVIDERS: ['pubcid', 'pubProvidedId'] + USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'] }; const storage = getStorageManager(CONSTANTS.INVIBES_VENDOR_ID); @@ -168,12 +168,6 @@ function handleResponse(responseObj, bidRequests) { responseObj = responseObj.body || responseObj; responseObj = responseObj.videoAdContentResult || responseObj; - let bidModel = responseObj.BidModel; - if (typeof bidModel !== 'object') { - utils.logInfo('Invibes Adapter - Bidding is not configured'); - return []; - } - if (typeof invibes.bidResponse === 'object') { utils.logInfo('Invibes Adapter - Bid response already received. Invibes only responds to one bid request per user visit'); return []; @@ -181,51 +175,69 @@ function handleResponse(responseObj, bidRequests) { invibes.bidResponse = responseObj; - let ads = responseObj.Ads; + const bidResponses = []; + for (let i = 0; i < bidRequests.length; i++) { + let bidRequest = bidRequests[i]; - if (!Array.isArray(ads) || ads.length < 1) { - if (responseObj.AdReason != null) { - utils.logInfo('Invibes Adapter - ' + responseObj.AdReason); + let requestPlacement = null; + if (responseObj.AdPlacements != null) { + for (let j = 0; j < responseObj.AdPlacements.length; j++) { + let bidModel = responseObj.AdPlacements[j].BidModel; + if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + requestPlacement = responseObj.AdPlacements[j]; + break; + } + } + } else { + let bidModel = responseObj.BidModel; + if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + requestPlacement = responseObj; + } } - utils.logInfo('Invibes Adapter - No ads available'); - return []; + let bid = createBid(bidRequest, requestPlacement); + if (bid !== null) { + bidResponses.push(bid); + } } - let ad = ads[0]; + return bidResponses; +} - if (bidModel.PlacementId == null) { - utils.logInfo('Invibes Adapter - No Placement Id in response'); - return []; +function createBid(bidRequest, requestPlacement) { + if (requestPlacement === null || requestPlacement.BidModel === null) { + utils.logInfo('Invibes Adapter - Placement not configured for bidding ' + bidRequest.params.placementId); + return null; } - const bidResponses = []; - for (let i = 0; i < bidRequests.length; i++) { - let bidRequest = bidRequests[i]; - - if (bidModel.PlacementId == bidRequest.params.placementId) { - let size = getBiggerSize(bidRequest.sizes); - - bidResponses.push({ - requestId: bidRequest.bidId, - cpm: ad.BidPrice, - width: bidModel.Width || size[0], - height: bidModel.Height || size[1], - creativeId: ad.VideoExposedId, - currency: bidModel.Currency || CONSTANTS.DEFAULT_CURRENCY, - netRevenue: true, - ttl: CONSTANTS.TIME_TO_LIVE, - ad: renderCreative(bidModel) - }); - - const now = Date.now(); - ivLogger.info('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.'); - } else { - utils.logInfo('Invibes Adapter - Incorrect Placement Id: ' + bidRequest.params.placementId); + let bidModel = requestPlacement.BidModel; + let ads = requestPlacement.Ads; + if (!Array.isArray(ads) || ads.length < 1) { + if (requestPlacement.AdReason != null) { + utils.logInfo('Invibes Adapter - No ads ' + requestPlacement.AdReason); } + + utils.logInfo('Invibes Adapter - No ads available'); + return null; } - return bidResponses; + let ad = ads[0]; + let size = getBiggerSize(bidRequest.sizes); + + const now = Date.now(); + utils.logInfo('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.'); + + return { + requestId: bidRequest.bidId, + cpm: ad.BidPrice, + width: bidModel.Width || size[0], + height: bidModel.Height || size[1], + creativeId: ad.VideoExposedId, + currency: bidModel.Currency || CONSTANTS.DEFAULT_CURRENCY, + netRevenue: true, + ttl: CONSTANTS.TIME_TO_LIVE, + ad: renderCreative(bidModel) + }; } function generateRandomId() { @@ -357,17 +369,6 @@ function getCappedCampaignsAsString() { .join(','); } -const noop = function () { -}; - -function initLogger() { - if (storage.hasLocalStorage() && localStorage.InvibesDEBUG) { - return window.console; - } - - return {info: noop, error: noop, log: noop, warn: noop, debug: noop}; -} - function buildSyncUrl() { let syncUrl = _customUserSync || CONSTANTS.SYNC_ENDPOINT; syncUrl += '?visitId=' + invibes.visitId; @@ -392,7 +393,7 @@ function readGdprConsent(gdprConsent) { if (!gdprConsent.vendorData.gdprApplies || gdprConsent.vendorData.hasGlobalConsent) { var index; - for (index = 0; index < invibes.purposes; ++index) { + for (index = 0; index < invibes.purposes.length; ++index) { invibes.purposes[index] = true; } @@ -448,7 +449,13 @@ function tryCopyValueToArray(value, target, length) { } if (value.hasOwnProperty(prop)) { - target[i] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + let parsedProp = parseInt(prop); + if (isNaN(parsedProp)) { + target[i] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + } else { + target[parsedProp - 1] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + } + i++; } } @@ -515,8 +522,6 @@ function getVendorLegitimateInterest(vendorData) { return {}; } -const ivLogger = initLogger(); - /// Local domain cookie management ===================== invibes.Uid = { generate: function () { diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index ee3624b5b16..6a59bf98dad 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -736,6 +736,30 @@ describe('invibesBidAdapter:', function () { ` }]; + let multiResponse = { + AdPlacements: [{ + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }], + BidModel: { + BidVersion: 1, + PlacementId: '12345', + AuctionStartTime: Date.now(), + CreativeHtml: '' + } + }] + }; + + let invalidResponse = { + AdPlacements: [{ + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }] + }] + }; + context('when the response is not valid', function () { it('handles response with no bids requested', function () { let emptyResult = spec.interpretResponse({body: response}); @@ -781,24 +805,25 @@ describe('invibesBidAdapter:', function () { let emptyResult = spec.interpretResponse({BidModel: {}, Ads: [{BidPrice: 1}]}, {bidRequests}); expect(emptyResult).to.be.empty; }); + + it('handles response when bid model is missing', function () { + let emptyResult = spec.interpretResponse(invalidResponse); + expect(emptyResult).to.be.empty; + }); }); - context('when the response is valid', function () { - it('responds with a valid bid', function () { - // top.window.invibes.setCookie('a', 'b', 370); - // top.window.invibes.setCookie('c', 'd', 0); - let result = spec.interpretResponse({body: response}, {bidRequests}); + context('when the multiresponse is valid', function () { + it('responds with a valid multiresponse bid', function () { + let result = spec.interpretResponse({body: multiResponse}, {bidRequests}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('responds with a valid bid and uses logger', function () { - localStorage.InvibesDEBUG = true; + it('responds with a valid singleresponse bid', function () { let result = spec.interpretResponse({body: response}, {bidRequests}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); it('does not make multiple bids', function () { - localStorage.InvibesDEBUG = false; let result = spec.interpretResponse({body: response}, {bidRequests}); let secondResult = spec.interpretResponse({body: response}, {bidRequests}); expect(secondResult).to.be.empty; From 8388dd3e00c4112c6a5d21cc0930fc1c57c491a7 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Thu, 20 May 2021 14:34:36 -0400 Subject: [PATCH 45/48] appnexus bid adapter: add support for flocid (#6801) --- modules/appnexusBidAdapter.js | 1 + test/spec/modules/appnexusBidAdapter_spec.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index d28bf391aa5..df686f15885 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -244,6 +244,7 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index c875cba12bc..2c8703e648d 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -875,7 +875,11 @@ describe('AppNexusAdapter', function () { tdid: 'sample-userid', criteoId: 'sample-criteo-userid', netId: 'sample-netId-userid', - idl_env: 'sample-idl-userid' + idl_env: 'sample-idl-userid', + flocId: { + id: 'sample-flocid-value', + version: 'chrome.1.0' + } } }); @@ -892,6 +896,11 @@ describe('AppNexusAdapter', function () { id: 'sample-criteo-userid', }); + expect(payload.eids).to.deep.include({ + source: 'chrome.com', + id: 'sample-flocid-value' + }); + expect(payload.eids).to.deep.include({ source: 'netid.de', id: 'sample-netId-userid', From 2e2b4258a5cf12e2d744b7d1f344081db2aeb9f7 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Thu, 20 May 2021 15:04:21 -0400 Subject: [PATCH 46/48] appnexus bid adapter: add support for uid2 (#6802) --- modules/appnexusBidAdapter.js | 1 + test/spec/modules/appnexusBidAdapter_spec.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index df686f15885..404a8c04d14 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -249,6 +249,7 @@ export const spec = { addUserId(eids, utils.deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); addUserId(eids, utils.deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); + addUserId(eids, utils.deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); if (eids.length) { payload.eids = eids; diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 2c8703e648d..4baecdd1ba8 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -873,6 +873,7 @@ describe('AppNexusAdapter', function () { const bidRequest = Object.assign({}, bidRequests[0], { userId: { tdid: 'sample-userid', + uid2: { id: 'sample-uid2-value' }, criteoId: 'sample-criteo-userid', netId: 'sample-netId-userid', idl_env: 'sample-idl-userid', @@ -909,7 +910,13 @@ describe('AppNexusAdapter', function () { expect(payload.eids).to.deep.include({ source: 'liveramp.com', id: 'sample-idl-userid' - }) + }); + + expect(payload.eids).to.deep.include({ + source: 'uidapi.com', + id: 'sample-uid2-value', + rti_partner: 'UID2' + }); }); it('should populate iab_support object at the root level if omid support is detected', function () { From 889ff70bdd69f5058a903d124c8506e1f3658bb8 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 20 May 2021 15:18:59 -0400 Subject: [PATCH 47/48] Delete xhbBidAdapter.js --- modules/xhbBidAdapter.js | 457 --------------------------------------- 1 file changed, 457 deletions(-) delete mode 100644 modules/xhbBidAdapter.js diff --git a/modules/xhbBidAdapter.js b/modules/xhbBidAdapter.js deleted file mode 100644 index 9363eb97ddc..00000000000 --- a/modules/xhbBidAdapter.js +++ /dev/null @@ -1,457 +0,0 @@ -import { Renderer } from '../src/Renderer.js'; -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; - -const BIDDER_CODE = 'xhb'; -const URL = 'https://ib.adnxs.com/ut/v3/prebid'; -const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', - 'startdelay', 'skippable', 'playback_method', 'frameworks']; -const USER_PARAMS = ['age', 'external_uid', 'segments', 'gender', 'dnt', 'language']; -const NATIVE_MAPPING = { - body: 'description', - cta: 'ctatext', - image: { - serverName: 'main_image', - requiredParams: { required: true }, - minimumParams: { sizes: [{}] }, - }, - icon: { - serverName: 'icon', - requiredParams: { required: true }, - minimumParams: { sizes: [{}] }, - }, - sponsoredBy: 'sponsored_by', -}; -const SOURCE = 'pbjs'; - -export const spec = { - code: BIDDER_CODE, - aliases: [], - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - return !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - const tags = bidRequests.map(bidToTag); - const userObjBid = find(bidRequests, hasUserInfo); - let userObj; - if (userObjBid) { - userObj = {}; - Object.keys(userObjBid.params.user) - .filter(param => includes(USER_PARAMS, param)) - .forEach(param => userObj[param] = userObjBid.params.user[param]); - } - - const memberIdBid = find(bidRequests, hasMemberId); - const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; - - const payload = { - tags: [...tags], - user: userObj, - sdk: { - source: SOURCE, - version: '$prebid.version$' - } - }; - if (member > 0) { - payload.member_id = member; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - // note - objects for impbus use underscore instead of camelCase - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - } - - const payloadString = JSON.stringify(payload); - return { - method: 'POST', - url: URL, - data: payloadString, - bidderRequest - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, {bidderRequest}) { - serverResponse = serverResponse.body; - const bids = []; - if (!serverResponse || serverResponse.error) { - let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; - if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); - return bids; - } - - if (serverResponse.tags) { - serverResponse.tags.forEach(serverBid => { - const rtbBid = getRtbBid(serverBid); - if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid, bidderRequest); - bid.mediaType = parseMediaType(rtbBid); - bids.push(bid); - } - } - }); - } - return bids; - }, - - getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://acdn.adnxs.com/dmp/async_usersync.html' - }]; - } - } -}; - -function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { - const renderer = Renderer.install({ - id: rtbBid.renderer_id, - url: rtbBid.renderer_url, - config: rendererOptions, - loaded: false, - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - utils.logWarn('Prebid Error calling setRender on renderer', err); - } - - renderer.setEventHandlers({ - impression: () => utils.logMessage('xhb outstream video impression event'), - loaded: () => utils.logMessage('xhb outstream video loaded event'), - ended: () => { - utils.logMessage('xhb outstream renderer video event'); - document.querySelector(`#${adUnitCode}`).style.display = 'none'; - } - }); - return renderer; -} - -/* Turn keywords parameter into ut-compatible format */ -function getKeywords(keywords) { - let arrs = []; - - utils._each(keywords, (v, k) => { - if (utils.isArray(v)) { - let values = []; - utils._each(v, (val) => { - val = utils.getValueString('keywords.' + k, val); - if (val) { values.push(val); } - }); - v = values; - } else { - v = utils.getValueString('keywords.' + k, v); - if (utils.isStr(v)) { - v = [v]; - } else { - return; - } // unsuported types - don't send a key - } - arrs.push({key: k, value: v}); - }); - - return arrs; -} - -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, rtbBid, bidderRequest) { - const bid = { - requestId: serverBid.uuid, - cpm: 0.00, - creativeId: rtbBid.creative_id, - dealId: 99999999, - currency: 'USD', - netRevenue: true, - ttl: 300, - appnexus: { - buyerMemberId: rtbBid.buyer_member_id - } - }; - - if (rtbBid.rtb.video) { - Object.assign(bid, { - width: rtbBid.rtb.video.player_width, - height: rtbBid.rtb.video.player_height, - vastUrl: rtbBid.rtb.video.asset_url, - vastImpUrl: rtbBid.notify_url, - ttl: 3600 - }); - // This supports Outstream Video - if (rtbBid.renderer_url) { - const rendererOptions = utils.deepAccess( - bidderRequest.bids[0], - 'renderer.options' - ); - - Object.assign(bid, { - adResponse: serverBid, - renderer: newRenderer(bid.adUnitCode, rtbBid, rendererOptions) - }); - bid.adResponse.ad = bid.adResponse.ads[0]; - bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; - } - } else if (rtbBid.rtb[NATIVE]) { - const nativeAd = rtbBid.rtb[NATIVE]; - bid[NATIVE] = { - title: nativeAd.title, - body: nativeAd.desc, - cta: nativeAd.ctatext, - sponsoredBy: nativeAd.sponsored, - clickUrl: nativeAd.link.url, - clickTrackers: nativeAd.link.click_trackers, - impressionTrackers: nativeAd.impression_trackers, - javascriptTrackers: nativeAd.javascript_trackers, - }; - if (nativeAd.main_img) { - bid['native'].image = { - url: nativeAd.main_img.url, - height: nativeAd.main_img.height, - width: nativeAd.main_img.width, - }; - } - if (nativeAd.icon) { - bid['native'].icon = { - url: nativeAd.icon.url, - height: nativeAd.icon.height, - width: nativeAd.icon.width, - }; - } - } else { - Object.assign(bid, { - width: rtbBid.rtb.banner.width, - height: rtbBid.rtb.banner.height, - ad: rtbBid.rtb.banner.content - }); - try { - const url = rtbBid.rtb.trackers[0].impression_urls[0]; - const tracker = utils.createTrackPixelHtml(url); - bid.ad += tracker; - } catch (error) { - utils.logError('Error appending tracking pixel', error); - } - } - - return bid; -} - -function bidToTag(bid) { - const tag = {}; - tag.sizes = transformSizes(bid.sizes); - tag.primary_size = tag.sizes[0]; - tag.ad_types = []; - tag.uuid = bid.bidId; - if (bid.params.placementId) { - tag.id = parseInt(bid.params.placementId, 10); - } else { - tag.code = bid.params.invCode; - } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false; - tag.prebid = true; - tag.disable_psa = true; - if (bid.params.reserve) { - tag.reserve = bid.params.reserve; - } - if (bid.params.position) { - tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; - } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; - } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); - } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; - } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; - } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; - } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; - } - if (!utils.isEmpty(bid.params.keywords)) { - tag.keywords = getKeywords(bid.params.keywords); - } - - if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { - tag.ad_types.push(NATIVE); - - if (bid.nativeParams) { - const nativeRequest = buildNativeRequest(bid.nativeParams); - tag[NATIVE] = {layouts: [nativeRequest]}; - } - } - - const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } - - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => tag.video[param] = bid.params.video[param]); - } - - if ( - (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || - (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) - ) { - tag.ad_types.push(BANNER); - } - - return tag; -} - -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - let sizes = []; - let sizeObj = {}; - - if (utils.isArray(requestSizes) && requestSizes.length === 2 && - !utils.isArray(requestSizes[0])) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - sizes.push(sizeObj); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - sizes.push(sizeObj); - } - } - - return sizes; -} - -function hasUserInfo(bid) { - return !!bid.params.user; -} - -function hasMemberId(bid) { - return !!parseInt(bid.params.member, 10); -} - -function getRtbBid(tag) { - return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); -} - -function buildNativeRequest(params) { - const request = {}; - - // map standard prebid native asset identifier to /ut parameters - // e.g., tag specifies `body` but /ut only knows `description`. - // mapping may be in form {tag: ''} or - // {tag: {serverName: '', requiredParams: {...}}} - Object.keys(params).forEach(key => { - // check if one of the forms is used, otherwise - // a mapping wasn't specified so pass the key straight through - const requestKey = - (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || - NATIVE_MAPPING[key] || - key; - - // required params are always passed on request - const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; - request[requestKey] = Object.assign({}, requiredParams, params[key]); - - // minimum params are passed if no non-required params given on adunit - const minimumParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].minimumParams; - - if (requiredParams && minimumParams) { - // subtract required keys from adunit keys - const adunitKeys = Object.keys(params[key]); - const requiredKeys = Object.keys(requiredParams); - const remaining = adunitKeys.filter(key => !includes(requiredKeys, key)); - - // if none are left over, the minimum params needs to be sent - if (remaining.length === 0) { - request[requestKey] = Object.assign({}, request[requestKey], minimumParams); - } - } - }); - - return request; -} - -function outstreamRender(bid) { - // push to render queue because ANOutstreamVideo may not be loaded yet - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - tagId: bid.adResponse.tag_id, - sizes: [bid.getSize().split('x')], - targetId: bid.adUnitCode, // target div id to render video - uuid: bid.adResponse.uuid, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(null, bid)); - }); -} - -function handleOutstreamRendererEvents(bid, id, eventName) { - bid.renderer.handleVideoEvent({ id, eventName }); -} - -function parseMediaType(rtbBid) { - const adType = rtbBid.ad_type; - if (adType === VIDEO) { - return VIDEO; - } else if (adType === NATIVE) { - return NATIVE; - } else { - return BANNER; - } -} - -registerBidder(spec); From bb02ee1dea06ed8ecc6f779ebcd541092b06e39c Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 20 May 2021 16:02:31 -0400 Subject: [PATCH 48/48] Restore xhb (#6803) --- modules/xhbBidAdapter.js | 457 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 457 insertions(+) create mode 100644 modules/xhbBidAdapter.js diff --git a/modules/xhbBidAdapter.js b/modules/xhbBidAdapter.js new file mode 100644 index 00000000000..9363eb97ddc --- /dev/null +++ b/modules/xhbBidAdapter.js @@ -0,0 +1,457 @@ +import { Renderer } from '../src/Renderer.js'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const BIDDER_CODE = 'xhb'; +const URL = 'https://ib.adnxs.com/ut/v3/prebid'; +const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skippable', 'playback_method', 'frameworks']; +const USER_PARAMS = ['age', 'external_uid', 'segments', 'gender', 'dnt', 'language']; +const NATIVE_MAPPING = { + body: 'description', + cta: 'ctatext', + image: { + serverName: 'main_image', + requiredParams: { required: true }, + minimumParams: { sizes: [{}] }, + }, + icon: { + serverName: 'icon', + requiredParams: { required: true }, + minimumParams: { sizes: [{}] }, + }, + sponsoredBy: 'sponsored_by', +}; +const SOURCE = 'pbjs'; + +export const spec = { + code: BIDDER_CODE, + aliases: [], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const tags = bidRequests.map(bidToTag); + const userObjBid = find(bidRequests, hasUserInfo); + let userObj; + if (userObjBid) { + userObj = {}; + Object.keys(userObjBid.params.user) + .filter(param => includes(USER_PARAMS, param)) + .forEach(param => userObj[param] = userObjBid.params.user[param]); + } + + const memberIdBid = find(bidRequests, hasMemberId); + const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + } + }; + if (member > 0) { + payload.member_id = member; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + // note - objects for impbus use underscore instead of camelCase + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, {bidderRequest}) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + utils.logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } + } + }); + } + return bids; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' + }]; + } + } +}; + +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rtbBid.renderer_id, + url: rtbBid.renderer_url, + config: rendererOptions, + loaded: false, + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => utils.logMessage('xhb outstream video impression event'), + loaded: () => utils.logMessage('xhb outstream video loaded event'), + ended: () => { + utils.logMessage('xhb outstream renderer video event'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + return renderer; +} + +/* Turn keywords parameter into ut-compatible format */ +function getKeywords(keywords) { + let arrs = []; + + utils._each(keywords, (v, k) => { + if (utils.isArray(v)) { + let values = []; + utils._each(v, (val) => { + val = utils.getValueString('keywords.' + k, val); + if (val) { values.push(val); } + }); + v = values; + } else { + v = utils.getValueString('keywords.' + k, v); + if (utils.isStr(v)) { + v = [v]; + } else { + return; + } // unsuported types - don't send a key + } + arrs.push({key: k, value: v}); + }); + + return arrs; +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bid = { + requestId: serverBid.uuid, + cpm: 0.00, + creativeId: rtbBid.creative_id, + dealId: 99999999, + currency: 'USD', + netRevenue: true, + ttl: 300, + appnexus: { + buyerMemberId: rtbBid.buyer_member_id + } + }; + + if (rtbBid.rtb.video) { + Object.assign(bid, { + width: rtbBid.rtb.video.player_width, + height: rtbBid.rtb.video.player_height, + vastUrl: rtbBid.rtb.video.asset_url, + vastImpUrl: rtbBid.notify_url, + ttl: 3600 + }); + // This supports Outstream Video + if (rtbBid.renderer_url) { + const rendererOptions = utils.deepAccess( + bidderRequest.bids[0], + 'renderer.options' + ); + + Object.assign(bid, { + adResponse: serverBid, + renderer: newRenderer(bid.adUnitCode, rtbBid, rendererOptions) + }); + bid.adResponse.ad = bid.adResponse.ads[0]; + bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; + } + } else if (rtbBid.rtb[NATIVE]) { + const nativeAd = rtbBid.rtb[NATIVE]; + bid[NATIVE] = { + title: nativeAd.title, + body: nativeAd.desc, + cta: nativeAd.ctatext, + sponsoredBy: nativeAd.sponsored, + clickUrl: nativeAd.link.url, + clickTrackers: nativeAd.link.click_trackers, + impressionTrackers: nativeAd.impression_trackers, + javascriptTrackers: nativeAd.javascript_trackers, + }; + if (nativeAd.main_img) { + bid['native'].image = { + url: nativeAd.main_img.url, + height: nativeAd.main_img.height, + width: nativeAd.main_img.width, + }; + } + if (nativeAd.icon) { + bid['native'].icon = { + url: nativeAd.icon.url, + height: nativeAd.icon.height, + width: nativeAd.icon.width, + }; + } + } else { + Object.assign(bid, { + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + ad: rtbBid.rtb.banner.content + }); + try { + const url = rtbBid.rtb.trackers[0].impression_urls[0]; + const tracker = utils.createTrackPixelHtml(url); + bid.ad += tracker; + } catch (error) { + utils.logError('Error appending tracking pixel', error); + } + } + + return bid; +} + +function bidToTag(bid) { + const tag = {}; + tag.sizes = transformSizes(bid.sizes); + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } else { + tag.code = bid.params.invCode; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false; + tag.prebid = true; + tag.disable_psa = true; + if (bid.params.reserve) { + tag.reserve = bid.params.reserve; + } + if (bid.params.position) { + tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!utils.isEmpty(bid.params.keywords)) { + tag.keywords = getKeywords(bid.params.keywords); + } + + if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { + tag.ad_types.push(NATIVE); + + if (bid.nativeParams) { + const nativeRequest = buildNativeRequest(bid.nativeParams); + tag[NATIVE] = {layouts: [nativeRequest]}; + } + } + + const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } + + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; + } + + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => tag.video[param] = bid.params.video[param]); + } + + if ( + (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || + (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) + ) { + tag.ad_types.push(BANNER); + } + + return tag; +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if (utils.isArray(requestSizes) && requestSizes.length === 2 && + !utils.isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function hasMemberId(bid) { + return !!parseInt(bid.params.member, 10); +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function buildNativeRequest(params) { + const request = {}; + + // map standard prebid native asset identifier to /ut parameters + // e.g., tag specifies `body` but /ut only knows `description`. + // mapping may be in form {tag: ''} or + // {tag: {serverName: '', requiredParams: {...}}} + Object.keys(params).forEach(key => { + // check if one of the forms is used, otherwise + // a mapping wasn't specified so pass the key straight through + const requestKey = + (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || + NATIVE_MAPPING[key] || + key; + + // required params are always passed on request + const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; + request[requestKey] = Object.assign({}, requiredParams, params[key]); + + // minimum params are passed if no non-required params given on adunit + const minimumParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].minimumParams; + + if (requiredParams && minimumParams) { + // subtract required keys from adunit keys + const adunitKeys = Object.keys(params[key]); + const requiredKeys = Object.keys(requiredParams); + const remaining = adunitKeys.filter(key => !includes(requiredKeys, key)); + + // if none are left over, the minimum params needs to be sent + if (remaining.length === 0) { + request[requestKey] = Object.assign({}, request[requestKey], minimumParams); + } + } + }); + + return request; +} + +function outstreamRender(bid) { + // push to render queue because ANOutstreamVideo may not be loaded yet + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: [bid.getSize().split('x')], + targetId: bid.adUnitCode, // target div id to render video + uuid: bid.adResponse.uuid, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, handleOutstreamRendererEvents.bind(null, bid)); + }); +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else if (adType === NATIVE) { + return NATIVE; + } else { + return BANNER; + } +} + +registerBidder(spec);