diff --git a/integrationExamples/gpt/akamaidap_segments_example.html b/integrationExamples/gpt/akamaidap_segments_example.html index e85ac8e1337..f44c60292ce 100644 --- a/integrationExamples/gpt/akamaidap_segments_example.html +++ b/integrationExamples/gpt/akamaidap_segments_example.html @@ -68,6 +68,7 @@ } }, realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -76,9 +77,10 @@ apiHostname: "prebid.dap.akadns.net", apiVersion: "x1", domain: "prebid.org", - identityType: "dap-signature:1.0.0", - segtax: 503, - tokenTtl: 5, + identityType: "dap-signature:1.3.0", + segtax: 504, + dapEntropyUrl: 'https://dap-dist.akamaized.net/dapentropy.js', + dapEntropyTimeout: 1500 } } ] diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 52578ebcada..4bba558c4db 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -267,6 +267,9 @@ }, { "name": "dacId" + }, + { + "name": "gravitompId" } ], "syncDelay": 5000, diff --git a/modules/.submodules.json b/modules/.submodules.json index 59bae2013d1..8d62f30d7c4 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -43,7 +43,8 @@ "unifiedIdSystem", "verizonMediaIdSystem", "zeotapIdPlusIdSystem", - "adqueryIdSystem" + "adqueryIdSystem", + "gravitoIdSystem" ], "adpod": [ "freeWheelAdserverVideo", @@ -51,6 +52,7 @@ ], "rtdModule": [ "airgridRtdProvider", + "akamaiDapRtdProvider", "browsiRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 9fad2a6b8e9..b98567878a8 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -978,6 +978,8 @@ export const spec = { // remove useless props delete adUnitCopy.floorData; delete adUnitCopy.params.siteId; + delete adUnitCopy.userId + delete adUnitCopy.userIdAsEids groupedAdUnits[adUnitCopy.params.organizationId] = groupedAdUnits[adUnitCopy.params.organizationId] || []; groupedAdUnits[adUnitCopy.params.organizationId].push(adUnitCopy); diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 1091b87a22d..095fb917597 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -63,15 +63,15 @@ MACRO['pageurl'] = function(b, c) { const refererInfo = getRefererInfo(); return (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0]; }; -MACRO['pbadslot'] = function(b, c) { +MACRO['gpid'] = function(b, c) { const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); - return deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; + return deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; }; -MACRO['pbAdSlot'] = MACRO['pbadslot']; // legacy +MACRO['pbAdSlot'] = MACRO['pbadslot'] = MACRO['gpid']; // legacy const PARAMS_DEFAULT = { 'id1': function(b) { return b.adUnitCode }, - 'id2': '%%pbadslot%%', + 'id2': '%%gpid%%', 'id3': function(b) { return b.bidder }, 'id4': function(b) { return b.adId }, 'id5': function(b) { return b.dealId }, @@ -138,7 +138,11 @@ analyticsAdapter.enableAnalytics = function(config) { toselector: config.options.toselector || function(bid) { let code = getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; // https://mathiasbynens.be/notes/css-escapes - code = code.replace(/^\d/, '\\3$& '); + try { + code = CSS.escape(code); + } catch (_) { + code = code.replace(/^\d/, '\\3$& '); + } return `#${code}` }, client: config.options.client, diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md index e21261d0b8d..203b118652e 100644 --- a/modules/adlooxAnalyticsAdapter.md +++ b/modules/adlooxAnalyticsAdapter.md @@ -106,7 +106,7 @@ For example, you have a number of reporting breakdown slots available in the for tagid: 0, params: { id1: function(b) { return b.adUnitCode }, // do not change when using the Adloox RTD Provider - id2: '%%pbadslot%%', // do not change when using the Adloox RTD Provider + id2: '%%gpid%%', // do not change when using the Adloox RTD Provider id3: function(b) { return b.bidder }, id4: function(b) { return b.adId }, id5: function(b) { return b.dealId }, @@ -125,9 +125,9 @@ For example, you have a number of reporting breakdown slots available in the for The following macros are available - * `%%pbadslot%%`: [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) + * **`%%gpid%%` (alias `%%pbadslot%%`**): [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) * it is recommended you read the [Prebid Ad Slot section in the Adloox RTD Provider documentation](./adlooxRtdProvider.md#prebid-ad-slot) - * `%%pageurl%%`: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` + * **`%%pageurl%%`**: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` ### Functions diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index bb8334ec8fe..489fadf91f4 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -216,13 +216,11 @@ function init(config, userConsent) { function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { // gptPreAuction runs *after* RTD so pbadslot may not be populated... (╯°□°)╯ ┻━┻ const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits).map(adUnit => { - let path = deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot'); - if (!path) path = getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot; return { - path: path, + gpid: deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot || adUnit.code, unit: adUnit }; - }).filter(adUnit => !!adUnit.path); + }).filter(adUnit => !!adUnit.gpid); let response = {}; function setSegments() { @@ -315,7 +313,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { } const atfQueue = []; adUnits.map((adUnit, i) => { - const ref = [ adUnit.path ]; + const ref = [ adUnit.gpid ]; if (!config.params.slotinpath) ref.push(adUnit.unit.code); args.push(['s', ref.join('\t')]); diff --git a/modules/adlooxRtdProvider.md b/modules/adlooxRtdProvider.md index 6c75fbc2d8b..466f8ed1ba2 100644 --- a/modules/adlooxRtdProvider.md +++ b/modules/adlooxRtdProvider.md @@ -81,7 +81,7 @@ To use this, you *must* also integrate the [Adloox Analytics Adapter](./adlooxAn You may optionally pass a subsection `params` in the `params` block to the Adloox RTD Provider, these will be passed through to the segment handler as is and as described by the integration guidelines. -**N.B.** If you pass `params` to the Adloox Analytics Adapter, `id1` (`AdUnit.code`) and `id2` (`%%pbadslot%%`) *must* describe a stable identifier otherwise no usable segments will be served and so they *must not* be changed; if `id1` for your inventory could contain a non-stable random number please consult with us before continuing +**N.B.** If you pass `params` to the Adloox Analytics Adapter, `id1` (`AdUnit.code`) and `id2` (`%%gpid%%`) *must* describe a stable identifier otherwise no usable segments will be served and so they *must not* be changed; if `id1` for your inventory could contain a non-stable random number please consult with us before continuing Though our segment technology is fast (less than 10ms) the time it takes for the users device to connect to our service and fetch the segments may not be. For this reason we recommend setting `auctionDelay` no lower than 100ms and if possible you should explore using user-agent sourced information such as [NetworkInformation.{rtt,downlink,...}](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation) to dynamically tune this for each user. @@ -94,7 +94,7 @@ You may use one of two ways to do achieve this: * for display inventory [using GPT](https://developers.google.com/publisher-tag/guides/get-started) you may configure Prebid.js to automatically use the [full ad unit path](https://developers.google.com/publisher-tag/reference#googletag.Slot_getAdUnitPath) 1. include the [`gptPreAuction` module](https://docs.prebid.org/dev-docs/modules/gpt-pre-auction.html) 1. wrap both `pbjs.setConfig({...})` and `pbjs.enableAnalytics({...})` with `googletag.cmd.push(function() { ... })` - * set `pbadslot` in the [first party data](https://docs.prebid.org/dev-docs/adunit-reference.html#first-party-data) variable `AdUnit.ortb2Imp.ext.data.pbadslot` for all your ad units + * set `gpid` (or `pbadslot`) in the [first party data](https://docs.prebid.org/dev-docs/adunit-reference.html#first-party-data) variable `AdUnit.ortb2Imp.ext.gpid` (or `AdUnit.ortb2Imp.ext.data.pbadslot`) for all your ad units ## Timeouts diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index d8638c4da47..13174ff337c 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -21,6 +21,7 @@ const HOST_GETTERS = { bidsxchange: () => 'ghb.hbd.bidsxchange.com', streamkey: () => 'ghb.hb.streamkey.net', janet: () => 'ghb.bidder.jmgads.com', + pgam: () => 'ghb.pgamssp.com', } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -37,7 +38,8 @@ export const spec = { code: BIDDER_CODE, gvlid: 410, aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet', - { code: 'navelix', gvlid: 380 } + { code: 'navelix', gvlid: 380 }, + 'pgam' ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 6fcce753596..784afd6cfe1 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -77,6 +77,9 @@ export const spec = { if (typeof bidReq.getFloor === 'function') { accumulator[bidReq.bidId].Pricing = getFloor(bidReq, size, mediatype); } + if (bidReq.schain) { + accumulator[bidReq.bidId].SChain = bidReq.schain; + } if (mediatype === NATIVE) { let nativeReq = bidReq.mediaTypes.native; if (nativeReq.type === 'image') { diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index aca984d39c8..845c0f2e574 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -10,12 +10,22 @@ import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; +const MODULE_CODE = 'akamaidap'; + +export const DAP_TOKEN = 'async_dap_token'; +export const DAP_MEMBERSHIP = 'async_dap_membership'; +export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership'; +export const DAP_SS_ID = 'dap_ss_id'; +export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds +export const DAP_MAX_RETRY_TOKENIZE = 1; +export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' -export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); +let dapRetryTokenize = 0; /** * Lazy merge objects. @@ -53,61 +63,68 @@ export function addRealTimeData(rtd) { * Real-time data retrieval from Audigent * @param {Object} reqBidsConfigObj * @param {function} onDone - * @param {Object} rtdConfi + * @param {Object} rtdConfig * @param {Object} userConsent */ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - logInfo('DEBUG(getRealTimeData) - ENTER'); + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + let loadScriptPromise = new Promise((resolve, reject) => { + if (rtdConfig && rtdConfig.params && rtdConfig.params.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) { + setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy script could not be loaded')); + } + if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { + logMessage('Using cached entropy'); + resolve(); + } else { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { + loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + } else { + reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); + } + } + } + }); + loadScriptPromise + .catch((error) => { + logError('Entropy could not be calculated due to: ', error.message); + }) + .finally(() => { + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + }); +} + +export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + logInfo('DEBUG(generateRealTimeData) - ENTER'); logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); - let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY); - if (jsonData) { - let data = JSON.parse(jsonData); - if (data.rtd) { - addRealTimeData(data.rtd); - onDone(); - logInfo('DEBUG(getRealTimeData) - 1'); - // Don't return - ensure the data is always fresh. + dapRetryTokenize = 0; + var jsonData = null; + if (rtdConfig && isPlainObject(rtdConfig.params)) { + if (rtdConfig.params.segtax == 504) { + let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); + if (encMembership) { + jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) + } + } else { + let membership = dapUtils.dapGetMembershipFromLocalStorage(); + if (membership) { + jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) + } } } - - if (rtdConfig && isPlainObject(rtdConfig.params)) { - let config = { - api_hostname: rtdConfig.params.apiHostname, - api_version: rtdConfig.params.apiVersion, - domain: rtdConfig.params.domain, - segtax: rtdConfig.params.segtax - }; - let identity = { - type: rtdConfig.params.identityType - }; - let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl); - if (token !== null) { - let membership = dapUtils.dapGetMembership(config, token); - let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config); - logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment); - let data = { - rtd: { - ortb2: { - user: { - data: [ - udSegment - ] - }, - site: { - ext: { - data: { - dapSAID: membership.said - } - } - } - } - } - }; - storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data)); + if (jsonData) { + if (jsonData.rtd) { + addRealTimeData(jsonData.rtd); onDone(); + logInfo('DEBUG(generateRealTimeData) - 1'); + // Don't return - ensure the data is always fresh. } } + // Calling setTimeout to release the main thread so that the bid request could be sent. + setTimeout(dapUtils.callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); } /** @@ -117,6 +134,9 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { * @return {boolean} */ function init(provider, userConsent) { + if (dapUtils.checkConsent(userConsent) === false) { + return false; + } return true; } @@ -128,104 +148,215 @@ export const akamaiDapRtdSubmodule = { }; submodule(MODULE_NAME, akamaiDapRtdSubmodule); - export const dapUtils = { - dapGetToken: function(config, identity, ttl) { + callDapAPIs: function(bidConfig, onDone, rtdConfig, userConsent) { + if (rtdConfig && isPlainObject(rtdConfig.params)) { + let config = { + api_hostname: rtdConfig.params.apiHostname, + api_version: rtdConfig.params.apiVersion, + domain: rtdConfig.params.domain, + segtax: rtdConfig.params.segtax, + identity: {type: rtdConfig.params.identityType} + }; + let refreshMembership = true; + let token = dapUtils.dapGetTokenFromLocalStorage(); + logMessage('token is: ', token); + if (token !== null) { // If token is not null then check the membership in storage and add the RTD object + if (config.segtax == 504) { // Follow the encrypted membership path + dapUtils.dapRefreshEncryptedMembership(config, token, onDone) // Get the encrypted membership from server + refreshMembership = false; + } else { + dapUtils.dapRefreshMembership(config, token, onDone) // Get the membership from server + refreshMembership = false; + } + } + dapUtils.dapRefreshToken(config, refreshMembership, onDone) // Refresh Token and membership in all the cases + } + }, + dapGetEntropy: function(resolve, reject) { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + reject(Error('window.dapCalculateEntropy function is not defined')) + } + }, + + dapGetTokenFromLocalStorage: function(ttl) { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_token'; let token = null; - - if (ttl == 0) { - localStorage.removeItem(storageName); + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)); + if (item) { + if (now < item.expires_at) { + token = item.token; + } } + return token; + }, - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null) { - item = { - expires_at: now - 1, - token: null - }; - } else { - token = item.token; - } - - if (now > item.expires_at) { - dapUtils.dapLog('Token missing or expired, fetching a new one...'); - // Trigger a refresh - let configAsync = {...config}; - dapUtils.dapTokenize(configAsync, identity, - function(token, status, xhr) { - item.expires_at = now + ttl; - item.token = token; - localStorage.setItem(storageName, JSON.stringify(item)); - dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds'); - let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); - if (deviceId100 != null) { - localStorage.setItem('dap_deviceId100', deviceId100); - dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + dapRefreshToken: function(config, refreshMembership, onDone) { + dapUtils.dapLog('Token missing or expired, fetching a new one...'); + // Trigger a refresh + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} + let configAsync = {...config}; + dapUtils.dapTokenize(configAsync, config.identity, onDone, + function(token, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(token) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.token = token; + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at); + let dapSSID = xhr.getResponseHeader('Akamai-DAP-SS-ID'); + if (dapSSID) { + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID)); + } + let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); + if (deviceId100 != null) { + storage.setDataInLocalStorage('dap_deviceId100', deviceId100); + dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + } + if (refreshMembership) { + if (config.segtax == 504) { + dapUtils.dapRefreshEncryptedMembership(config, token, onDone); + } else { + dapUtils.dapRefreshMembership(config, token, onDone); } - }, - function(xhr, status, error) { - logError('ERROR(' + error + '): failed to retrieve token! ' + status); } - ); - } - - return token; + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve token! ' + status); + onDone() + } + ); }, - dapGetMembership: function(config, token) { + dapGetMembershipFromLocalStorage: function() { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_membership'; - let maxTtl = 3600; // if the cached membership is older than this, return null let membership = null; - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null || (now - item.expires_at) > maxTtl) { - item = { - expires_at: now - 1, - said: null, - cohorts: null, - attributes: null - }; - } else { - membership = { - said: item.said, - cohorts: item.cohorts, - attributes: null - }; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + membership = { + said: item.said, + cohorts: item.cohorts, + attributes: null + }; + } } + return membership; + }, - // Always refresh the cached membership. + dapRefreshMembership: function(config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} let configAsync = {...config}; - dapUtils.dapMembership(configAsync, token, - function(membership, status, xhr) { - item.expires_at = now + maxTtl; + dapUtils.dapMembership(configAsync, token, onDone, + function(membership, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(membership.said) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } item.said = membership.said; item.cohorts = membership.cohorts; - localStorage.setItem(storageName, JSON.stringify(item)); + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item)); dapUtils.dapLog('Successfully updated and stored membership:'); dapUtils.dapLog(item); + + let data = dapUtils.dapGetRtdObj(item, config.segtax) + dapUtils.checkAndAddRealtimeData(data, config.segtax); + onDone(); }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { logError('ERROR(' + error + '): failed to retrieve membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(config, true, onDone); + } else { + onDone(); + } } ); + }, - return membership; + dapGetEncryptedMembershipFromLocalStorage: function() { + let now = Math.round(Date.now() / 1000.0); // in seconds + let encMembership = null; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + encMembership = { + encryptedSegments: item.encryptedSegments + }; + } + } + return encMembership; + }, + + dapRefreshEncryptedMembership: function(config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {}; + let configAsync = {...config}; + dapUtils.dapEncryptedMembership(configAsync, token, onDone, + function(encToken, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(encToken) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.encryptedSegments = encToken; + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored encrypted membership:'); + dapUtils.dapLog(item); + + let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax); + dapUtils.checkAndAddRealtimeData(encData, config.segtax); + onDone(); + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(config, true, onDone); + } else { + onDone(); + } + } + ); + }, + + /** + * DESCRIPTION + * Extract expiry value from a token + */ + dapExtractExpiryFromToken: function(token) { + let exp = null; + if (token) { + const tokenArray = token.split('..'); + if (tokenArray && tokenArray.length > 0) { + let decode = atob(tokenArray[0]) + let header = JSON.parse(decode.replace(/"/g, '"')); + exp = header.exp; + } + } + return exp }, /** * DESCRIPTION * * Convert a DAP membership response to an OpenRTB2 segment object suitable - * for insertion into user.data.segment or site.data.segment. + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. */ - dapMembershipToRtbSegment: function(membership, config) { + dapGetRtdObj: function(membership, segtax) { let segment = { name: 'dap.akamai.com', ext: { - 'segtax': config.segtax + 'segtax': segtax }, segment: [] }; @@ -234,7 +365,83 @@ export const dapUtils = { segment.segment.push({ id: i }); } } - return segment; + let data = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + }, + site: { + ext: { + data: { + dapSAID: membership.said + } + } + } + } + } + }; + return data; + }, + + /** + * DESCRIPTION + * + * Convert a DAP membership response to an OpenRTB2 segment object suitable + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. + */ + dapGetEncryptedRtdObj: function(encToken, segtax) { + let segment = { + name: 'dap.akamai.com', + ext: { + 'segtax': segtax + }, + segment: [] + }; + if (encToken != null) { + segment.segment.push({ id: encToken.encryptedSegments }); + } + let encData = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + } + } + } + }; + return encData; + }, + + checkAndAddRealtimeData: function(data, segtax) { + if (data.rtd) { + if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(data.rtd, 504)) { + logMessage('DEBUG(handleInit): rtb Object already added'); + } else { + addRealTimeData(data.rtd); + } + logInfo('DEBUG(checkAndAddRealtimeData) - 1'); + } + }, + + checkIfSegmentsAlreadyExist: function(rtd, segtax) { + let segmentsExist = false + let ortb2 = config.getConfig('ortb2') || {}; + if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) { + for (let i = 0; i < ortb2.user.data.length; i++) { + let element = ortb2.user.data[i] + if (element.ext && element.ext.segtax == segtax) { + segmentsExist = true + logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data); + break; + } + } + } + return segmentsExist }, dapLog: function(args) { @@ -248,6 +455,35 @@ export const dapUtils = { logInfo('%cDAP Client', css, args); }, + isValidHttpsUrl: function(urlString) { + let url; + try { + url = new URL(urlString); + } catch (_) { + return false; + } + return url.protocol === 'https:'; + }, + + checkConsent: function(userConsent) { + let consent = true; + + if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { + const gdpr = userConsent.gdpr; + const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? gdpr.consentString : ''; + if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { + logError('akamaiDapRtd submodule requires consent string to call API'); + consent = false; + } + } else if (userConsent && userConsent.usp) { + const usp = userConsent.usp; + consent = usp[1] !== 'N' && usp[2] !== 'Y'; + } + + return consent; + }, + /******************************************************************************* * * V2 (And Beyond) API @@ -293,23 +529,23 @@ export const dapUtils = { * function( response, status, xhr } { token = response; }, * function( xhr, status, error ) { ; } // handle error */ - dapTokenize: function(config, identity, onSuccess = null, onError = null) { + dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } if (typeof (config.domain) != 'string') { - onError(null, 'Invalid config.domain: must be a string', 'ClientError'); + onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone); return; } if (config.domain.length <= 0) { - onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError'); + onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone); return; } @@ -318,22 +554,22 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (identity == null || typeof (identity) == typeof (undefined)) { - onError(null, 'Invalid identity object', 'ClientError'); + onError(null, 'Invalid identity object', 'ClientError', onDone); return; } if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) { - onError(null, "Identity must contain a valid 'type' field", 'ClientError'); + onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone); return; } @@ -348,6 +584,11 @@ export const dapUtils = { apiParams.attributes = identity.attributes; } + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + if (entropyDict && entropyDict.entropy) { + apiParams.entropy = entropyDict.entropy; + } + let method; let body; let path; @@ -359,10 +600,16 @@ export const dapUtils = { body = JSON.stringify(apiParams); break; default: - onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError'); + onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone); return; } + let customHeaders = {'Content-Type': 'application/json'}; + let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); + if (dapSSID) { + customHeaders['Akamai-DAP-SS-ID'] = dapSSID; + } + let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { @@ -373,19 +620,16 @@ export const dapUtils = { token = request.getResponseHeader('Akamai-DAP-Token'); break; } - onSuccess(token, request.status, request); + onSuccess(token, request.status, request, onDone); }, error: (request, error) => { - onError(request, request.statusText, error); + onError(request, request.statusText, error, onDone); } }; ajax(url, cb, body, { method: method, - customHeaders: { - 'Content-Type': 'application/json', - 'Pragma': 'akamai-x-cache-on' - } + customHeaders: customHeaders }); }, @@ -411,7 +655,7 @@ export const dapUtils = { * api_hostname: 'api.dap.akadns.net', * }; * - * // token from dap_x1_tokenize + * // token from dap_tokenize * * dapMembership( config, token, * function( membership, status, xhr ) { @@ -422,13 +666,13 @@ export const dapUtils = { * } ); * */ - dapMembership: function(config, token, onSuccess = null, onError = null) { + dapMembership: function(config, token, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } @@ -437,32 +681,32 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError'); + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); return; } let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership'; + config.api_version + + '/token/' + token + + '/membership'; let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { - onSuccess(JSON.parse(response), request.status, request); + onSuccess(JSON.parse(response), request.status, request, onDone); }, error: (error, request) => { - onError(request, request.status, error); + onError(request, request.status, error, onDone); } }; @@ -470,5 +714,91 @@ export const dapUtils = { method: 'GET', customHeaders: {} }); + }, + + /** + * SYNOPSIS + * + * dapEncryptedMembership( config, token, onSuccess, onError ); + * + * DESCRIPTION + * + * Return the audience segment membership along with a new Secure Advertising + * ID for this token in encrypted format. + * + * PARAMETERS + * + * config: an array of system configuration parameters + * + * token: the token previously returned from the tokenize API + * + * EXAMPLE + * + * config = { + * api_hostname: 'api.dap.akadns.net', + * }; + * + * // token from dap_tokenize + * + * dapEncryptedMembership( config, token, + * function( membership, status, xhr ) { + * // Run auction with membership.segments and membership.said after decryption + * }, + * function( xhr, status, error ) { + * // error + * } ); + * + */ + dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) { + if (onError == null) { + onError = function(xhr, status, error, onDone) {}; + } + + if (config == null || typeof (config) == typeof (undefined)) { + onError(null, 'Invalid config object', 'ClientError', onDone); + return; + } + + if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { + config.api_version = 'x1'; + } + + if (typeof (config.api_version) != 'string') { + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); + return; + } + + if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); + return; + } + + if (token == null || typeof (token) != 'string') { + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); + return; + } + let path = '/data-activation/' + + config.api_version + + '/token/' + token + + '/membership/encrypt'; + + let url = 'https://' + config.api_hostname + path; + + let cb = { + success: (response, request) => { + let encToken = request.getResponseHeader('Akamai-DAP-Token'); + onSuccess(encToken, request.status, request, onDone); + }, + error: (error, request) => { + onError(request, request.status, error, onDone); + } + }; + ajax(url, cb, undefined, { + method: 'GET', + customHeaders: { + 'Content-Type': 'application/json', + 'Pragma': 'akamai-x-get-extracted-values' + } + }); } } diff --git a/modules/akamaiDapRtdProvider.md b/modules/akamaiDapRtdProvider.md index ade11b88602..efd93db3a51 100644 --- a/modules/akamaiDapRtdProvider.md +++ b/modules/akamaiDapRtdProvider.md @@ -17,6 +17,7 @@ ``` pbjs.setConfig({ realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -25,9 +26,10 @@ apiHostname: '', apiVersion: "x1", domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', - segtax: , - tokenTtl: 5, + identityType: 'email' | 'mobile' | ... | 'dap-signature:1.3.0', + segtax: 504, + dapEntropyUrl: 'https://dap-dist.akamaized.net/dapentropy.js', + dapEntropyTimeout: 1500 // Maximum time for dapentropy to run } } ] diff --git a/modules/alkimiBidAdapter.md b/modules/alkimiBidAdapter.md index 0d76f7e63ab..2d1fd42c70f 100644 --- a/modules/alkimiBidAdapter.md +++ b/modules/alkimiBidAdapter.md @@ -15,15 +15,21 @@ Alkimi bid adapter supports Banner and Video ads. ``` const adUnits = [ { - bids: [ + code: 'banner1', + mediaTypes: { + banner: { // Media Type can be banner or video or ... + sizes: [[300, 250]], + } + }, + bids: [ { bidder: 'alkimi', params: { - bidFloor: 0.1, - token: '?????????????????????', // Publisher Token provided by Alkimi + bidFloor: 0.5, + token: 'a6b042a5-2d68-4170-a051-77fbaf00203a', // Publisher Token(Id) provided by Alkimi } } ] } -]; +] ``` diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index 2e74170fcaf..ba960838395 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -1,4 +1,5 @@ import { deepAccess, isArray, logWarn, triggerPixel, buildUrl, logInfo, getValue, getBidIdParameter } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'beop'; @@ -36,11 +37,11 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { const slots = validBidRequests.map(beOpRequestSlotsMaker); - let pageUrl = deepAccess(window, 'location.href') || deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl'); - let fpd = config.getLegacyFpd(config.getConfig('ortb2')); - let gdpr = bidderRequest.gdprConsent; - let firstSlot = slots[0]; - let payloadObject = { + const pageUrl = getPageUrl(bidderRequest.refererInfo, window); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')); + const gdpr = bidderRequest.gdprConsent; + const firstSlot = slots[0]; + const payloadObject = { at: new Date().toString(), nid: firstSlot.nid, nptnid: firstSlot.nptnid, @@ -100,6 +101,7 @@ export const spec = { function buildTrackingParams(data, info, value) { const accountId = data.params.accountId; + const pageUrl = getPageUrl(null, window); return { pid: accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : accountId, nid: data.params.networkId, @@ -110,7 +112,7 @@ function buildTrackingParams(data, info, value) { se_ca: 'bid', se_ac: info, se_va: value, - url: window.location.href + url: pageUrl }; } @@ -141,4 +143,47 @@ function beOpRequestSlotsMaker(bid) { } } +const protocolRelativeRegExp = /^\/\// +function isProtocolRelativeUrl(url) { + return url && url.match(protocolRelativeRegExp) != null; +} + +const withProtocolRegExp = /[a-z]{1,}:\/\// +function isNoProtocolUrl(url) { + return url && url.match(withProtocolRegExp) == null; +} + +function ensureProtocolInUrl(url, defaultProtocol) { + if (isProtocolRelativeUrl(url)) { + return `${defaultProtocol}${url}`; + } else if (isNoProtocolUrl(url)) { + return `${defaultProtocol}//${url}`; + } + return url; +} + +/** + * sometimes trying to access a field (protected?) triggers an exception + * Ex deepAccess(window, 'top.location.href') might throw if it crosses origins + * so here is a lenient version + */ +function safeDeepAccess(obj, path) { + try { + return deepAccess(obj, path) + } catch (_e) { + return null; + } +} + +function getPageUrl(refererInfo, window) { + refererInfo = refererInfo || getRefererInfo(); + let pageUrl = refererInfo.canonicalUrl || safeDeepAccess(window, 'top.location.href') || deepAccess(window, 'location.href'); + // Ensure the protocol is present (looks like sometimes the extracted pageUrl misses it) + if (pageUrl != null) { + const defaultProtocol = safeDeepAccess(window, 'top.location.protocol') || deepAccess(window, 'location.protocol'); + pageUrl = ensureProtocolInUrl(pageUrl, defaultProtocol); + } + return pageUrl; +} + registerBidder(spec); diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index fec0d1b6510..8fab37a433f 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -107,7 +107,7 @@ export const spec = { for (let i = 0; i < validBidRequests.length; i++) { let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER + let traff = bid.params.traffic || BANNER; let placement = { placementId: bid.params.placement_id, groupId: bid.params.group_id, @@ -116,19 +116,7 @@ export const spec = { eids: [], floor: {} }; - if (typeof bid.getFloor === 'function') { - let tmpFloor = {}; - for (let size of placement.sizes) { - tmpFloor = bid.getFloor({ - currency: 'USD', - mediaType: traff, - size: size - }); - if (tmpFloor) { - placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; - } - } - } + if (bid.schain) { placement.schain = bid.schain; } @@ -147,9 +135,7 @@ export const spec = { } if (traff === BANNER) { placement.sizes = bid.mediaTypes[BANNER].sizes - } - - if (traff === VIDEO) { + } else if (traff === VIDEO) { placement.sizes = bid.mediaTypes[VIDEO].playerSize; placement.playerSize = bid.mediaTypes[VIDEO].playerSize; placement.minduration = bid.mediaTypes[VIDEO].minduration; @@ -167,6 +153,20 @@ export const spec = { placement.api = bid.mediaTypes[VIDEO].api; placement.linearity = bid.mediaTypes[VIDEO].linearity; } + if (typeof bid.getFloor === 'function') { + let tmpFloor = {}; + for (let size of placement.sizes) { + tmpFloor = bid.getFloor({ + currency: 'USD', + mediaType: traff, + size: size + }); + if (tmpFloor) { + placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; + } + } + } + placements.push(placement); } return { diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 7ee8b1b7681..a6b631372e0 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -93,7 +93,7 @@ export const spec = { copyOptProperty(format[0].h, video, 'h'); } - copyOptProperty(bid.params.position, video, 'pos'); + copyOptProperty(bid.params.position || videoData.pos, video, 'pos'); copyOptProperty(bid.params.mimes || videoData.mimes, video, 'mimes'); copyOptProperty(bid.params.maxduration || videoData.maxduration, video, 'maxduration'); copyOptProperty(bid.params.protocols || videoData.protocols, video, 'protocols'); @@ -105,7 +105,7 @@ export const spec = { const format = convertSizes(bannerData.sizes || bid.sizes); const banner = {format: format}; - copyOptProperty(bid.params.position, banner, 'pos'); + copyOptProperty(bid.params.position || bannerData.pos, banner, 'pos'); imp.banner = banner; } diff --git a/modules/cpexIdSystem.js b/modules/cpexIdSystem.js index 4600601cb11..f3ddc85ed31 100644 --- a/modules/cpexIdSystem.js +++ b/modules/cpexIdSystem.js @@ -8,8 +8,6 @@ import { submodule } from '../src/hook.js' import { getStorageManager } from '../src/storageManager.js' -window.top.cpexIdVersion = '0.0.3' - // Returns StorageManager export const storage = getStorageManager({ gvlid: 570, moduleName: 'cpexId' }) @@ -18,6 +16,7 @@ const getId = () => { return storage.getCookie('caid') || storage.getDataFromLoc /** @type {Submodule} */ export const cpexIdSubmodule = { + version: '0.0.4', /** * used to link submodule with config * @type {string} diff --git a/modules/dacIdSystem.md b/modules/dacIdSystem.md index b422d0a536d..0239b4557e9 100644 --- a/modules/dacIdSystem.md +++ b/modules/dacIdSystem.md @@ -1,11 +1,11 @@ -## DAC User ID Submodule +## AudienceOne User ID Submodule -DAC ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. +AudienceOne ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. Please contact D.A.Consortium Inc. before using this ID. -## Building Prebid with DAC ID Support +## Building Prebid with AudienceOne ID Support -First, make sure to add the DAC ID submodule to your Prebid.js package with: +First, make sure to add the AudienceOne ID submodule to your Prebid.js package with: ``` gulp build --modules=dacIdSystem diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 21206109ee0..17e210d5358 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -9,6 +9,7 @@ import * as utils from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; import { uspDataHandler } from '../src/adapterManager.js'; +import { loadExternalScript } from '../src/adloader.js'; const MODULE_NAME = 'ftrackId'; const LOG_PREFIX = 'FTRACK - '; @@ -17,7 +18,6 @@ const VENDOR_ID = null; const LOCAL_STORAGE = 'html5'; const FTRACK_STORAGE_NAME = 'ftrackId'; const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`; -const FTRACK_URL = 'https://d9.flashtalking.com/d9core'; const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); let consentInfo = { @@ -102,11 +102,8 @@ export const ftrackIdSubmodule = { } } - if (config.params && config.params.url && config.params.url === FTRACK_URL) { - var ftrackScript = document.createElement('script'); - ftrackScript.setAttribute('src', config.params.url); - window.document.body.appendChild(ftrackScript); - } + // Creates an async script element and appends it to the document + loadExternalScript(config.params.url, MODULE_NAME); } }; }, @@ -146,8 +143,8 @@ export const ftrackIdSubmodule = { utils.logWarn(LOG_PREFIX + 'config.storage.name recommended to be "' + FTRACK_STORAGE_NAME + '".'); } - if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url') || config.params.url !== FTRACK_URL) { - utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run. Url should be "' + FTRACK_URL + '".'); + if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url')) { + utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run.'); return false; } diff --git a/modules/gravitoIdSystem.js b/modules/gravitoIdSystem.js new file mode 100644 index 00000000000..30fa3abd6d2 --- /dev/null +++ b/modules/gravitoIdSystem.js @@ -0,0 +1,57 @@ +/** + * This module adds gravitompId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/gravitoIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +export const cookieKey = 'gravitompId'; + +export const gravitoIdSystemSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'gravitompId', + + /** + * performs action to obtain id + * @function + * @returns { {id: {gravitompId: string}} | undefined } + */ + getId: function() { + const newId = storage.getCookie(cookieKey); + if (!newId) { + return undefined; + } + const result = { + gravitompId: newId + } + return {id: result}; + }, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param { {gravitompId: string} } value + * @returns { {gravitompId: {id: string} } | undefined } + */ + decode: function(value) { + if (value && typeof value === 'object') { + const result = {}; + if (value.gravitompId) { + result.id = value.gravitompId + } + return {gravitompId: result}; + } + return undefined; + }, + +} + +submodule('userId', gravitoIdSystemSubmodule); diff --git a/modules/gravitoIdSystem.md b/modules/gravitoIdSystem.md new file mode 100644 index 00000000000..af4946779c5 --- /dev/null +++ b/modules/gravitoIdSystem.md @@ -0,0 +1,28 @@ +## Gravito User ID Submodule + +Gravito ID, provided by [Gravito Ltd.](https://gravito.net), is ID for ad targeting by using 1st party cookie. +Please contact Gravito Ltd. before using this ID. + +## Building Prebid with Gravito ID Support + +First, make sure to add the Gravito ID submodule to your Prebid.js package with: + +``` +gulp build --modules=gravitoIdSystem +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'gravitompId' + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"gravitompId"` | diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index ff74f26a2ba..993ddd927b0 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -289,6 +289,10 @@ export const spec = { const siteContent = request.site.content || {}; request.site.content = mergeDeep(siteContent, { data }); } + const id = deepAccess(site, 'content.id'); + if (id) { + request.site.content = {...request.site.content, id}; + } } return { diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 0bf805fb94e..bc112989426 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -4,6 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; /** * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter @@ -35,7 +36,10 @@ var pageView = { timezoneOffset: new Date().getTimezoneOffset(), language: window.navigator.language, vendor: window.navigator.vendor, - pageUrl: window.top.location.href, + pageUrl: (() => { + const ri = getRefererInfo(); + return ri.canonicalUrl || ri.referer; + })(), screenWidth: x, screenHeight: y }; diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 9de2e2b2d32..a0453466b87 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -9,9 +9,12 @@ import {Renderer} from '../src/Renderer.js'; import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'improvedigital'; -const REQUEST_URL = 'https://ad.360yield.com/pb'; const CREATIVE_TTL = 300; +const AD_SERVER_URL = 'https://ad.360yield.com/pb'; +const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; +const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; + const VIDEO_PARAMS = { DEFAULT_MIMES: ['video/mp4'], SUPPORTED_PROPERTIES: ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', @@ -57,6 +60,7 @@ export const spec = { gvlid: 253, aliases: ['id'], supportedMediaTypes: [BANNER, NATIVE, VIDEO], + syncStore: { extendMode: false, placementId: null }, /** * Determines whether or not the given bid request is valid. @@ -77,7 +81,6 @@ export const spec = { */ buildRequests(bidRequests, bidderRequest) { const request = { - id: getUniqueIdentifierStr(), cur: [config.getConfig('currency.adServerCurrency') || 'USD'], ext: { improvedigital: { @@ -100,7 +103,7 @@ export const spec = { // Coppa const coppa = config.getConfig('coppa'); if (typeof coppa === 'boolean') { - deepSetValue(request, 'regs.coppa', ID_UTIL.toBit(coppa)); + deepSetValue(request, 'regs.coppa', Number(coppa)); } if (bidderRequest) { @@ -108,7 +111,7 @@ export const spec = { const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - deepSetValue(request, 'regs.ext.gdpr', ID_UTIL.toBit(gdprConsent.gdprApplies)); + deepSetValue(request, 'regs.ext.gdpr', Number(gdprConsent.gdprApplies)); } deepSetValue(request, 'user.ext.consent', gdprConsent.consentString); @@ -144,6 +147,9 @@ export const spec = { deepSetValue(request, 'source.ext.schain', bidRequest0.schain); deepSetValue(request, 'source.tid', bidRequest0.transactionId); + // Save a placement id to send it to the ad server when fetching the user syncs + this.syncStore.placementId = this.syncStore.placementId || bidRequest0.params.placementId; + if (bidRequest0.userId) { const eids = createEidsArray(bidRequest0.userId); deepSetValue(request, 'user.ext.eids', eids.length ? eids : undefined); @@ -174,7 +180,7 @@ export const spec = { return; } const bidRequest = getBidRequest(bidObject.impid, [bidderRequest]); - const idExt = deepAccess(bidObject, `ext.${BIDDER_CODE}`); + const idExt = deepAccess(bidObject, `ext.${BIDDER_CODE}`, {}); const bid = { requestId: bidObject.impid, @@ -210,57 +216,100 @@ export const spec = { * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs(syncOptions, serverResponses) { - if (syncOptions.pixelEnabled) { - const syncs = []; + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (config.getConfig('coppa') === true || !ID_UTIL.hasPurpose1Consent(gdprConsent)) { + return []; + } + + const syncs = []; + if ((this.syncStore.extendMode || !syncOptions.pixelEnabled) && syncOptions.iframeEnabled) { + const { gdprApplies, consentString } = gdprConsent || {}; + syncs.push({ + type: 'iframe', + url: IFRAME_SYNC_URL + + `?placement_id=${this.syncStore.placementId}` + + (this.syncStore.extendMode ? '&pbs=1' : '') + + (typeof gdprApplies === 'boolean' ? `&gdpr=${Number(gdprApplies)}` : '') + + (consentString ? `&gdpr_consent=${consentString}` : '') + + (uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '') + }); + } else if (syncOptions.pixelEnabled) { serverResponses.forEach(response => { const syncArr = deepAccess(response, `body.ext.${BIDDER_CODE}.sync`, []); - syncArr.forEach(syncElement => { - if (syncs.indexOf(syncElement) === -1) { - syncs.push(syncElement); + syncArr.forEach(url => { + if (!syncs.some(sync => sync.url === url)) { + syncs.push({ type: 'image', url }); } }); }); - return syncs.map(sync => ({ type: 'image', url: sync })); } - return []; + + return syncs; } }; registerBidder(spec); const ID_REQUEST = { - buildServerRequests(requestObject, bidRequests, bidderRequest) { + buildServerRequests(basicRequest, bidRequests, bidderRequest) { + const globalExtendMode = config.getConfig('improvedigital.extend') === true; const requests = []; - if (config.getConfig('improvedigital.singleRequest') === true) { - requestObject.imp = bidRequests.map((bidRequest) => this.buildImp(bidRequest)); - requests[0] = this.formatRequest(requestObject, bidderRequest); - } else { - bidRequests.map((bidRequest) => { - const request = deepClone(requestObject); - request.id = bidRequest.bidId || getUniqueIdentifierStr(); - request.imp = [this.buildImp(bidRequest)]; - deepSetValue(request, 'source.tid', bidRequest.transactionId); - requests.push(this.formatRequest(request, bidderRequest)); - }); + const singleRequestMode = config.getConfig('improvedigital.singleRequest') === true; + + const extendImps = []; + const adServerImps = []; + + function formatRequest(imps, transactionId, extendMode) { + const request = deepClone(basicRequest); + request.imp = imps; + request.id = getUniqueIdentifierStr(); + if (transactionId) { + deepSetValue(request, 'source.tid', transactionId); + } + return { + method: 'POST', + url: extendMode ? EXTEND_URL : AD_SERVER_URL, + data: JSON.stringify(request), + bidderRequest + } + }; + + bidRequests.map((bidRequest) => { + const extendModeEnabled = this.isExtendModeEnabled(globalExtendMode, bidRequest.params); + const imp = this.buildImp(bidRequest, extendModeEnabled); + if (singleRequestMode) { + extendModeEnabled ? extendImps.push(imp) : adServerImps.push(imp); + } else { + requests.push(formatRequest([imp], bidRequest.transactionId, extendModeEnabled)); + } + }); + + if (!singleRequestMode) { + return requests; + } + // In the single request mode, split imps between those going to the ad server and those going to extend server + if (extendImps.length) { + requests.push(formatRequest(extendImps, null, true)); + } + if (adServerImps.length) { + requests.push(formatRequest(adServerImps, null, false)); } return requests; }, - formatRequest(request, bidderRequest) { - return { - method: 'POST', - url: REQUEST_URL, - data: JSON.stringify(request), - bidderRequest + isExtendModeEnabled(globalExtendMode, bidParams) { + const extendMode = typeof bidParams.extend === 'boolean' ? bidParams.extend : globalExtendMode; + if (extendMode && !spec.syncStore.extendMode) { + spec.syncStore.extendMode = true; } + return extendMode; }, - buildImp(bidRequest) { + buildImp(bidRequest, extendMode) { const imp = { id: getBidIdParameter('bidId', bidRequest) || getUniqueIdentifierStr(), - secure: ID_UTIL.toBit(window.location.protocol === 'https:'), + secure: Number(window.location.protocol === 'https:'), }; // Floor @@ -271,15 +320,19 @@ const ID_REQUEST = { deepSetValue(imp, 'bidfloorcur', bidFloorCur ? bidFloorCur.toUpperCase() : undefined); } + const bidderParamsPath = extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; const placementId = getBidIdParameter('placementId', bidRequest.params); if (placementId) { - deepSetValue(imp, 'ext.bidder.placementId', placementId); + deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId); + if (extendMode) { + deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId); + } } else { - deepSetValue(imp, 'ext.bidder.publisherId', getBidIdParameter('publisherId', bidRequest.params)); - deepSetValue(imp, 'ext.bidder.placementKey', getBidIdParameter('placementKey', bidRequest.params)); + deepSetValue(imp, `${bidderParamsPath}.publisherId`, getBidIdParameter('publisherId', bidRequest.params)); + deepSetValue(imp, `${bidderParamsPath}.placementKey`, getBidIdParameter('placementKey', bidRequest.params)); } - deepSetValue(imp, 'ext.bidder.keyValues', getBidIdParameter('keyValues', bidRequest.params) || undefined); + deepSetValue(imp, `${bidderParamsPath}.keyValues`, getBidIdParameter('keyValues', bidRequest.params) || undefined); // Adding GPID const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || @@ -374,7 +427,7 @@ const ID_REQUEST = { const assetParams = nativeParams[i]; const asset = { id: assetOrtbParams.id, - required: ID_UTIL.toBit(assetParams.required), + required: Number(assetParams.required), }; switch (assetOrtbParams.assetType) { case NATIVE_DATA.ASSET_TYPES.TITLE: @@ -458,9 +511,10 @@ const ID_RESPONSE = { this.buildNativeAd(bid, bidRequest, bidResponse) } } else { - if (bidResponse.adm.search(/^ { + const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); + + const requestParams = { + _vzPlacementId: bidRequest.params.placementId, + sizes: sizes, + _slotBidId: bidRequest.bidId, + _rqsrc: bidderRequest.refererInfo.referer, + }; + + const payload = { + q: encodeURI(JSON.stringify(requestParams)) + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse) { + const response = serverResponse.body; + const bids = []; + if (isEmpty(response)) { + return bids; + } + const responseBid = { + requestId: response.slotBidId, + cpm: response.cpm, + currency: response.currency || DEFAULT_CURRENCY, + width: response.adWidth, + height: response.adHeight, + ttl: CREATIVE_TTL, + creativeId: response.creativeId || 0, + netRevenue: response.netRevenue || false, + meta: { + mediaType: response.mediaType || BANNER, + advertiserDomains: response.advertiserDomains || [] + }, + ad: response.ad + }; + bids.push(responseBid); + return bids; + } + +}; + +registerBidder(spec); diff --git a/modules/vertozBidAdapter.md b/modules/incrxBidAdapter.md similarity index 54% rename from modules/vertozBidAdapter.md rename to modules/incrxBidAdapter.md index 100492da58b..7feda2b2b6d 100644 --- a/modules/vertozBidAdapter.md +++ b/modules/incrxBidAdapter.md @@ -1,16 +1,16 @@ # Overview ``` -Module Name: Vertoz Bidder Adapter +Module Name: IncrementX Bidder Adapter Module Type: Bidder Adapter Maintainer: prebid-team@vertoz.com ``` # Description -Connects to Vertoz exchange for bids. -Vertoz Bidder adapter supports Banner ads. -Use bidder code ```vertoz``` for all Vertoz traffic. +Connects to IncrementX exchange for bids. +IncrementX Bidder adapter supports Banner ads. +Use bidder code ```incrementx``` for all IncrementX traffic. # Test Parameters ``` @@ -20,9 +20,9 @@ var adUnits = [ code: 'banner-div', sizes: [[300, 250], [300,600]], // a display size(s) bids: [{ - bidder: 'vertoz', + bidder: 'incrementx', params: { - placementId: 'VZ-HB-B784382V6C6G3C' + placementId: 'PNX-HB-F796830VCF3C4B' } }] }, diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index 781bca90660..6f0023498aa 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -1,4 +1,5 @@ import { createTrackPixelHtml } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'inskin'; @@ -211,9 +212,9 @@ export const spec = { bidPrice: bidsMap[e.data.bidId].price, serverResponse }; - const script = document.createElement('script'); - script.src = 'https://cdn.inskinad.com/isfe/publishercode/' + bidsMap[e.data.bidId].params.siteId + '/default.js?autoload&id=' + id; - document.getElementsByTagName('head')[0].appendChild(script); + + const url = 'https://cdn.inskinad.com/isfe/publishercode/' + bidsMap[e.data.bidId].params.siteId + '/default.js?autoload&id=' + id; + loadExternalScript(url, BIDDER_CODE); }); } diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 7cec6172ba4..1a9321b6852 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -4,6 +4,7 @@ import { deepAccess, deepClone, deepSetValue, + getGptSlotInfoForAdUnitCode, hasDeviceAccess, inIframe, isArray, @@ -39,11 +40,12 @@ const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; const MAX_REQUEST_SIZE = 8000; const MAX_REQUEST_LIMIT = 4; +const OUTSTREAM_MINIMUM_PLAYER_SIZE = [300, 250]; const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; -const RENDERER_URL = 'https://js-sec.indexww.com/htv/video-player.js'; + const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; export const ERROR_CODES = { BID_SIZE_INVALID_FORMAT: 1, @@ -834,7 +836,8 @@ function buildIXDiag(validBidRequests) { allu: 0, ren: false, version: '$prebid.version$', - userIds: _getUserIds(validBidRequests[0]) + userIds: _getUserIds(validBidRequests[0]), + url: window.location.href.split('?')[0] }; // create ad unit map and collect the required diag properties @@ -856,12 +859,10 @@ function buildIXDiag(validBidRequests) { if (deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { ixdiag.ou++; - // renderer only needed for outstream - - const hasRenderer = typeof (deepAccess(bid, 'renderer') || deepAccess(bid, 'mediaTypes.video.renderer')) === 'object'; - // if any one ad unit is missing renderer, set ren status to false in diag - ixdiag.ren = ixdiag.ren && hasRenderer ? (deepAccess(ixdiag, 'ren')) : hasRenderer; + if (isIndexRendererPreferred(bid)) { + ixdiag.ren = true; + } } if (deepAccess(bid, 'mediaTypes.video.context') === 'instream') { @@ -966,7 +967,7 @@ function getPageUrl() { * @returns {string} */ function detectParamsType(validBidRequest) { - if (deepAccess(validBidRequest, 'params.video') && deepAccess(validBidRequest, 'mediaTypes.video')) { + if (deepAccess(validBidRequest, 'mediaTypes.video') && bidToVideoImp(validBidRequest).video) { return VIDEO; } @@ -1128,24 +1129,18 @@ function getCachedErrors() { /** * - * Initialize Outstream Renderer + * Initialize IX Outstream Renderer * @param {Object} bid */ function outstreamRenderer(bid) { - bid.renderer.push(() => { - var config = { - width: bid.width, - height: bid.height, - timeout: 3000 - }; - - // IXOutstreamPlayer supports both vastUrl and vastXml, so we can pass either. - // Since vastUrl is going to be deprecated from exchange response, vastXml takes priority. - if (bid.vastXml) { - window.IXOutstreamPlayer(bid.vastXml, bid.adUnitCode, config); - } else { - window.IXOutstreamPlayer(bid.vastUrl, bid.adUnitCode, config); + bid.renderer.push(function () { + const adUnitCode = bid.adUnitCode; + const divId = document.getElementById(adUnitCode) ? adUnitCode : getGptSlotInfoForAdUnitCode(adUnitCode).divId; + if (!divId) { + logWarn(`IX Bid Adapter: adUnitCode: ${divId} not found on page.`); + return; } + window.createIXPlayer(divId, bid); }); } @@ -1154,10 +1149,10 @@ function outstreamRenderer(bid) { * @param {string} id * @returns {Renderer} */ -function createRenderer(id) { +function createRenderer(id, renderUrl) { const renderer = Renderer.install({ id: id, - url: RENDERER_URL, + url: renderUrl, loaded: false }); @@ -1165,11 +1160,37 @@ function createRenderer(id) { renderer.setRender(outstreamRenderer); } catch (err) { logWarn('Prebid Error calling setRender on renderer', err); + return null; + } + + if (!renderUrl) { + logWarn('Outstream renderer URL not found'); + return null; } return renderer; } +/** + * Returns whether our renderer could potentially be used. + * @param {*} bid bid object + */ +function isIndexRendererPreferred(bid) { + if (deepAccess(bid, 'mediaTypes.video.context') !== 'outstream') { + return false; + } + + // ad unit renderer could be on the adUnit.mediaTypes.video level or adUnit level + let renderer = deepAccess(bid, 'mediaTypes.video.renderer'); + if (!renderer) { + renderer = deepAccess(bid, 'renderer'); + } + + const isValid = !!(typeof (renderer) === 'object' && renderer.url && renderer.render); + // if renderer on the adunit is not valid or it's only a backup, our renderer may be used + return !isValid || renderer.backupOnly; +} + export const spec = { code: BIDDER_CODE, @@ -1253,6 +1274,17 @@ export const spec = { return false; } } + + const videoImp = bidToVideoImp(bid).video; + if (deepAccess(bid, 'mediaTypes.video.context') === OUTSTREAM && isIndexRendererPreferred(bid) && videoImp) { + const outstreamPlayerSize = deepAccess(videoImp, 'playerSize')[0]; + const isValidSize = outstreamPlayerSize[0] >= OUTSTREAM_MINIMUM_PLAYER_SIZE[0] && outstreamPlayerSize[1] >= OUTSTREAM_MINIMUM_PLAYER_SIZE[1]; + if (!isValidSize) { + logError(`IX Bid Adapter: ${mediaTypeVideoPlayerSize} is an invalid size for IX outstream renderer`); + return false; + } + } + return true; }, @@ -1363,8 +1395,12 @@ export const spec = { const bidRequest = getBidRequest(innerBids[j].impid, requestBid.imp, bidderRequest.validBidRequests); bid = parseBid(innerBids[j], responseBody.cur, bidRequest); - if (!deepAccess(bid, 'mediaTypes.video.renderer') && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { - bid.renderer = createRenderer(innerBids[j].bidId); + if (bid.mediaType === VIDEO && isIndexRendererPreferred(bidRequest)) { + const renderUrl = deepAccess(responseBody, 'ext.videoplayerurl'); + bid.renderer = createRenderer(innerBids[j].bidId, renderUrl); + if (!bid.renderer) { + continue; + } } bids.push(bid); diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index 59b699bad2d..415fdc9db65 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -70,10 +70,10 @@ object are detailed here. | siteId | Required | String | An IX-specific identifier that is associated with this ad unit. It will be associated to the single size, if the size is provided. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` | size | Optional (Deprecated)| Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]` | video | Optional | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. Properties not defined at this level, will be pulled from the Adunit level. -|video.w| Required | Integer | The video player size width in pixels that will be passed to demand partners. -|video.h| Required | Integer | The video player size height in pixels that will be passed to demand partners. -|video.playerSize| Optional* | Integer | The video player size that will be passed to demand partners. * In the absence of `video.w` and `video.h`, this field is required. -| video.mimes | Required | String[] | Array list of content MIME types supported. Popular MIME types include, but are not limited to, `"video/x-ms- wmv"` for Windows Media and `"video/x-flv"` for Flash Video. +|video.w| Required | Integer | The width of the video player in pixels that will be passed to demand partners.
*If you are using Index’s outstream player and have placed the `video` object at the `bidder` level, this is a required field. You must define the size of the video player using the `video.w` and `video.h` parameters, with a minimum video player size of 300 x 250. +|video.h| Required | Integer | The height of the video player in pixels that will be passed to demand partners.
*If you are using Index’s outstream player and have placed the `video` object at the `bidder` level, this is a required field. You must define the size of the video player using the `video.w` and `video.h` parameters, with a minimum video player size of 300 x 250. +|video.playerSize| Optional* | Array[Integer,Integer] | The video player size that will be passed to demand partners.
*If you are using Index’s outstream player and have placed the `video` object at the `adUnit` level, this is a required field. You must define the size of the video player using this parameter, with a minimum video player size of 300 x 250. +| video.mimes | Required | String[] | If you are using Index’s outstream video player and want to learn more about what is supported, see [List of supported OpenRTB bid request fields for Sellers](https://kb.indexexchange.com/publishers/openrtb_integration/list_of_supported_openrtb_bid_request_fields_for_sellers.htm#Video). |video.minduration| Required | Integer | Minimum video ad duration in seconds. |video.maxduration| Required | Integer | Maximum video ad duration in seconds. |video.protocol / video.protocols| Required | Integer / Integer[] | Either a single protocol provided as an integer, or protocols provided as a list of integers. `2` - VAST 2.0, `3` - VAST 3.0, `5` - VAST 2.0 Wrapper, `6` - VAST 3.0 Wrapper @@ -111,9 +111,7 @@ Both video and banner params will be read from the `adUnits[].mediaTypes.video` The examples in this guide assume the following starting configuration (you may remove banner or video, if either does not apply). -In regards to video, `context` can either be `'instream'` or `'outstream'`. Note that `outstream` requires additional configuration on the adUnit. - - +In regards to video, `context` can either be `'instream'` or `'outstream'`. ```javascript var adUnits = [{ @@ -195,9 +193,9 @@ var adUnits = [{ context: 'instream', playerSize: [300, 250], mimes: [ - 'video/mp4', - 'video/webm' - ], + 'video/mp4', + 'video/webm' + ], minduration: 0, maxduration: 60, protocols: [6] @@ -224,45 +222,47 @@ Please note that you can re-use the existing `siteId` within the same flex position. **Video (Outstream):** -Note that currently, outstream video rendering must be configured by the publisher. In the adUnit, a `renderer` object must be defined, which includes a `url` pointing to the video rendering script, and a `render` function for creating the video player. See http://prebid.org/dev-docs/show-outstream-video-ads.html for more information. + +Publishers have two options to receive outstream video demand from Index: +* Using Index’s outstream video player +* In an outstream video configuration set up by the publisher. For more information, see [Prebid’s documentation on how to show video ads.](https://docs.prebid.org/dev-docs/show-outstream-video-ads.html) + +**Index’s outstream video player** +Publishers who are using Index as a bidding adapter in Prebid.js can show outstream video ads on their site from us by using Index’s outstream video player. This allows a video ad to display inside of a video player and can be placed anywhere on a publisher’s site, such as in-article, in-feed, and more. + +Define a new `video` object for our outstream video player at either the adUnit level or the `bidder` level. If you are setting it at the bidder level, define the size of the video player using the parameters `video.h` and `video.w`. If you are setting it at the `adUnit` level, define the size using video.playerSize. + +For more information on how to structure the `video` object, refer to the following code example: + ```javascript var adUnits = [{ - code: 'video-div-a', + code: 'div-gpt-ad-1571167646410-1', mediaTypes: { video: { + playerSize: [640, 360], context: 'outstream', - playerSize: [300, 250], - mimes: [ - 'video/mp4', - 'video/webm' - ], - minduration: 0, - maxduration: 60, - protocols: [6] - } - }, - renderer: { - url: 'https://test.com/my-video-player.js', - render: function (bid) { - ... + api: [2], + protocols: [2, 3, 5, 6], + minduration: 5, + maxduration: 30, + mimes: ['video/mp4', 'application/javascript'], + placement: 3 } }, bids: [{ bidder: 'ix', params: { - siteId: '12345', - video: { - // If required, use this to override mediaTypes.video.XX properties - } + siteId: '715964' } }] }]; ``` +Please note that your use of the outstream video player will be governed by and subject to the terms and conditions of i) any master services or license agreement entered into by you and Index Exchange; ii) the information provided on our knowledge base linked [here](https://kb.indexexchange.com/publishers/prebid_integration/outstream_video_prebidjs.htm) and [here](https://kb.indexexchange.com/publishers/guidelines/standard_contractual_clauses.htm), and iii) our [Privacy Policy](https://www.indexexchange.com/privacy/). Your use of Index’s outstream video player constitutes your acknowledgement and acceptance of the foregoing. #### Video Caching -Note that the IX adapter expects a client-side Prebid Cache to be enabled for video bidding. +Note that the IX adapter expects a client-side Prebid Cache to be enabled for instream video bidding. ``` pbjs.setConfig({ @@ -293,21 +293,21 @@ pbjs.setConfig({ By default, the IX bidding adapter bids on all banner sizes available in the ad unit when configured to at least one banner size. If you want the IX bidding adapter to only bid on the banner size it’s configured to, switch off this feature using `detectMissingSizes`. ``` pbjs.setConfig({ - ix: { - detectMissingSizes: false - } - }); + ix: { + detectMissingSizes: false + } +}); ``` OR ``` pbjs.setBidderConfig({ - bidders: ["ix"], - config: { - ix: { - detectMissingSizes: false - } - } - }); + bidders: ["ix"], + config: { + ix: { + detectMissingSizes: false + } + } +}); ``` ### 2. Include `ixBidAdapter` in your build process diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 90ea17395f7..fb55add910f 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -184,6 +184,7 @@ export const spec = { let ids = fetchIds_(); let eids = []; let miscDims = internal.getMiscDims(); + let schain = deepAccess(validBidRequests[0], 'schain'); // all available user ids are sent to our backend in the standard array layout: if (validBidRequests[0].userId) { @@ -209,6 +210,7 @@ export const spec = { mkeywords: miscDims.mkeywords, bids: bids, eids: eids, + schain: schain, pricegranularity: pg, cfg: jixieCfgBlob }, ids); diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 098e38b2c43..1842231721b 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -70,27 +70,42 @@ export const spec = { const bidResponses = []; for (let bidId in bids) { let adUnit = bids[bidId]; - let meta; + let meta = { + mediaType: BANNER + }; + if (adUnit.metadata && adUnit.metadata.landingPageDomain) { - meta = { - clickUrl: adUnit.metadata.landingPageDomain[0], - advertiserDomains: adUnit.metadata.landingPageDomain - }; + meta.clickUrl = adUnit.metadata.landingPageDomain[0]; + meta.advertiserDomains = adUnit.metadata.landingPageDomain; + } + + if (adUnit.mediaType && SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType)) { + meta.mediaType = adUnit.mediaType; } - bidResponses.push({ + + const bidResponse = { requestId: bidId, cpm: Number(adUnit.cpm), width: adUnit.width, height: adUnit.height, - ad: adUnit.adm, ttl: 300, creativeId: adUnit.id, dealId: adUnit.targetingCustom, netRevenue: true, currency: adUnit.currency || bidRequest.currency, + mediaType: meta.mediaType, meta: meta - }); + }; + + if (meta.mediaType == VIDEO) { + bidResponse.vastXml = adUnit.adm; + } else { + bidResponse.ad = adUnit.adm; + } + + bidResponses.push(bidResponse); } + return bidResponses; }, getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 80aa038a9f7..5fc28c47ac5 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -1,4 +1,12 @@ -import { deepAccess, isStr, replaceAuctionPrice, triggerPixel, isArray, parseQueryStringParameters, getWindowSelf } from '../src/utils.js'; +import { + deepAccess, + getWindowSelf, + isArray, + isStr, + parseQueryStringParameters, + replaceAuctionPrice, + triggerPixel +} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -6,19 +14,26 @@ import {getRefererInfo} from '../src/refererDetection.js'; const BIDDER_CODE = 'kobler'; const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; +const DEV_BIDDER_ENDPOINT = 'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'; const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; const SUPPORTED_CURRENCY = 'USD'; const DEFAULT_TIMEOUT = 1000; const TIME_TO_LIVE_IN_SECONDS = 10 * 60; export const isBidRequestValid = function (bid) { - return !!(bid && bid.bidId && bid.params && bid.params.placementId); + if (!bid || !bid.bidId) { + return false; + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes', bid.sizes); + return isArray(sizes) && sizes.length > 0; }; export const buildRequests = function (validBidRequests, bidderRequest) { + const bidderEndpoint = isTest(validBidRequests[0]) ? DEV_BIDDER_ENDPOINT : BIDDER_ENDPOINT; return { method: 'POST', - url: BIDDER_ENDPOINT, + url: bidderEndpoint, data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), options: { contentType: 'application/json' @@ -27,14 +42,11 @@ export const buildRequests = function (validBidRequests, bidderRequest) { }; export const interpretResponse = function (serverResponse) { - const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; const res = serverResponse.body; const bids = [] if (res) { res.seatbid.forEach(sb => { sb.bid.forEach(b => { - const adWithCorrectCurrency = b.adm - .replace(/\${AUCTION_PRICE_CURRENCY}/g, adServerPriceCurrency); bids.push({ requestId: b.impid, cpm: b.price, @@ -45,7 +57,7 @@ export const interpretResponse = function (serverResponse) { dealId: b.dealid, netRevenue: true, ttl: TIME_TO_LIVE_IN_SECONDS, - ad: adWithCorrectCurrency, + ad: b.adm, nurl: b.nurl, meta: { advertiserDomains: b.adomain @@ -58,13 +70,15 @@ export const interpretResponse = function (serverResponse) { }; export const onBidWon = function (bid) { - const cpm = bid.cpm || 0; - const cpmCurrency = bid.currency || SUPPORTED_CURRENCY; + // We intentionally use the price set by the publisher to replace the ${AUCTION_PRICE} macro + // instead of the `originalCpm` here. This notification is not used for billing, only for extra logging. + const publisherPrice = bid.cpm || 0; + const publisherCurrency = bid.currency || config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; const adServerPrice = deepAccess(bid, 'adserverTargeting.hb_pb', 0); const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; if (isStr(bid.nurl) && bid.nurl !== '') { - const winNotificationUrl = replaceAuctionPrice(bid.nurl, bid.originalCpm || cpm) - .replace(/\${AUCTION_PRICE_CURRENCY}/g, cpmCurrency) + const winNotificationUrl = replaceAuctionPrice(bid.nurl, publisherPrice) + .replace(/\${AUCTION_PRICE_CURRENCY}/g, publisherCurrency) .replace(/\${AD_SERVER_PRICE}/g, adServerPrice) .replace(/\${AD_SERVER_PRICE_CURRENCY}/g, adServerPriceCurrency); triggerPixel(winNotificationUrl); @@ -73,17 +87,13 @@ export const onBidWon = function (bid) { export const onTimeout = function (timeoutDataArray) { if (isArray(timeoutDataArray)) { - const refererInfo = getRefererInfo(); - const pageUrl = (refererInfo && refererInfo.referer) - ? refererInfo.referer - : window.location.href; + const pageUrl = getPageUrlFromRefererInfo(); timeoutDataArray.forEach(timeoutData => { const query = parseQueryStringParameters({ ad_unit_code: timeoutData.adUnitCode, auction_id: timeoutData.auctionId, bid_id: timeoutData.bidId, timeout: timeoutData.timeout, - placement_id: deepAccess(timeoutData, 'params.0.placementId'), page_url: pageUrl, }); const timeoutNotificationUrl = `${TIMEOUT_NOTIFICATION_ENDPOINT}?${query}`; @@ -92,13 +102,28 @@ export const onTimeout = function (timeoutDataArray) { } }; -function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { - const imps = validBidRequests.map(buildOpenRtbImpObject); - const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; - const pageUrl = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) +function getPageUrlFromRefererInfo() { + const refererInfo = getRefererInfo(); + return (refererInfo && refererInfo.referer) + ? refererInfo.referer + : window.location.href; +} + +function getPageUrlFromRequest(validBidRequest, bidderRequest) { + // pageUrl is considered only when testing to ensure that non-test requests always contain the correct URL + if (isTest(validBidRequest) && config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + return (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href; +} +function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { + const imps = validBidRequests.map(buildOpenRtbImpObject); + const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; + const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) const request = { id: bidderRequest.auctionId, at: 1, @@ -106,13 +131,12 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { cur: [SUPPORTED_CURRENCY], imp: imps, device: { - devicetype: getDevice(), - geo: getGeo(validBidRequests[0]) + devicetype: getDevice() }, site: { page: pageUrl, }, - test: getTest(validBidRequests[0]) + test: getTestAsNumber(validBidRequests[0]) }; return JSON.stringify(request); @@ -128,14 +152,8 @@ function buildOpenRtbImpObject(validBidRequest) { banner: { format: buildFormatArray(sizes), w: mainSize[0], - h: mainSize[1], - ext: { - kobler: { - pos: getPosition(validBidRequest) - } - } + h: mainSize[1] }, - tagid: validBidRequest.params.placementId, bidfloor: floorInfo.floor, bidfloorcur: floorInfo.currency, pmp: buildPmpObject(validBidRequest) @@ -157,17 +175,12 @@ function getDevice() { return 2; // personal computers } -function getGeo(validBidRequest) { - if (validBidRequest.params.zip) { - return { - zip: validBidRequest.params.zip - }; - } - return {}; +function getTestAsNumber(validBidRequest) { + return isTest(validBidRequest) ? 1 : 0; } -function getTest(validBidRequest) { - return validBidRequest.params.test ? 1 : 0; +function isTest(validBidRequest) { + return validBidRequest.params && validBidRequest.params.test === true; } function getSizes(validBidRequest) { @@ -188,10 +201,6 @@ function buildFormatArray(sizes) { }); } -function getPosition(validBidRequest) { - return parseInt(validBidRequest.params.position) || 0; -} - function getFloorInfo(validBidRequest, mainSize) { if (typeof validBidRequest.getFloor === 'function') { const sizeParam = mainSize[0] === 0 && mainSize[1] === 0 ? '*' : mainSize; @@ -209,11 +218,11 @@ function getFloorInfo(validBidRequest, mainSize) { } function getFloorPrice(validBidRequest) { - return parseFloat(validBidRequest.params.floorPrice) || 0.0; + return parseFloat(deepAccess(validBidRequest, 'params.floorPrice', 0.0)); } function buildPmpObject(validBidRequest) { - if (validBidRequest.params.dealIds) { + if (validBidRequest.params && validBidRequest.params.dealIds && isArray(validBidRequest.params.dealIds)) { return { deals: validBidRequest.params.dealIds.map(dealId => { return { diff --git a/modules/koblerBidAdapter.md b/modules/koblerBidAdapter.md index 7a7b2388367..63b0c3ead68 100644 --- a/modules/koblerBidAdapter.md +++ b/modules/koblerBidAdapter.md @@ -12,14 +12,15 @@ This adapter currently only supports Banner Ads. # Parameters -| Parameter (in `params`) | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| placementId | Required | String | The identifier of the placement, it has to be issued by Kobler. | `'xjer0ch8'` | -| zip | Optional | String | Zip code of the user or the medium. When multiple ad units are submitted together, it is enough to set this parameter on the first one. | `'102 22'` | -| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Defaults to false. | `true` | -| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | -| position | Optional | Integer | The position of the ad unit. Can be used to differentiate between ad units if the same placement ID is used across multiple ad units. The first ad unit should have a `position` of 0, the second one should have a `position` of 1 and so on. Defaults to 0. | `1` | -| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | +| Parameter (in `params`) | Scope | Type | Description | Example | +|-------------------------|----------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------| +| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Enables providing a custom URL through config.pageUrl. Defaults to false. | `true` | +| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | +| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | + +## Implicit parameters + +Kobler identifies the placement using the combination of the page URL and the allowed sizes. As a result, it's important that the correct sizes are provided in `banner.sizes` in order for Kobler to correctly identify the placement. The main, desired format should be the first element of this array. # Test Parameters ```javascript @@ -31,17 +32,14 @@ This adapter currently only supports Banner Ads. } }, bids: [{ - bidder: 'kobler', - params: { - placementId: 'k5H7et3R0' - } + bidder: 'kobler' }] }]; ``` In order to see a sample bid from Kobler (without a proper setup), you have to also do the following: -- Change the [`refererInfo` function](https://github.com/prebid/Prebid.js/blob/master/src/refererDetection.js) to return `'https://www.tv2.no/a/11734615'` as a [`referer`](https://github.com/prebid/Prebid.js/blob/caead3ccccc448e4cd09d074fd9f8833f56fe9b3/src/refererDetection.js#L169). This is necessary because Kobler only bids on recognized articles. -- Change the adapter's [`BIDDER_ENDPOINT`](https://github.com/prebid/Prebid.js/blob/master/modules/koblerBidAdapter.js#L8) to `'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'`. This endpoint belongs to the development server that is set up to always return a bid for the correct `placementId` and page URL combination. +- Set the `test` parameter to `true`. +- Set `config.pageUrl` to `'https://www.tv2.no/mening-og-analyse/14555348/'`. This is necessary because Kobler only bids on recognized articles. Kobler runs its own test campaign to make sure there is always a bid for this specific page URL. # Test Optional Parameters ```javascript @@ -55,11 +53,8 @@ In order to see a sample bid from Kobler (without a proper setup), you have to a bids: [{ bidder: 'kobler', params: { - placementId: 'k5H7et3R0', - zip: '102 22', test: true, floorPrice: 5.0, - position: 1, dealIds: ['abc328745', 'mxw243253'] } }] diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js index 8d62b0ffba7..4520bad0f3a 100644 --- a/modules/mantisBidAdapter.js +++ b/modules/mantisBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; export const storage = getStorageManager({bidderCode: 'mantis'}); @@ -10,12 +11,7 @@ function inIframe() { return true; } } -function pixel(url, parent) { - var img = document.createElement('img'); - img.src = url; - img.style.cssText = 'display:none !important;'; - (parent || document.body).appendChild(img); -} + export function onVisible(win, element, doOnVisible, time, pct) { var started = null; var notified = false; @@ -301,9 +297,9 @@ export function iframePostMessage (win, name, callback) { onMessage('iframe', function (data) { if (window.$sf) { - sfPostMessage(window.$sf, data.width, data.height, () => pixel(data.pixel)); + sfPostMessage(window.$sf, data.width, data.height, () => ajax(data.pixel)); } else { - iframePostMessage(window, data.frame, () => pixel(data.pixel)); + iframePostMessage(window, data.frame, () => ajax(data.pixel)); } }); diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 7bd86879e9d..d7f5f712252 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -36,10 +36,11 @@ function readnavIDFromCookie() { function readnvgnavFromLocalStorage() { var i; - const query = '^nvg|^nav'; + const query = /[nvga]{3}\d+/; for (i in window.localStorage) { if (i.match(query) || (!query && typeof i === 'string')) { - return storage.getDataFromLocalStorage(i.match(query).input); + const naveggId = storage.getDataFromLocalStorage(i.match(query).input).split('|')[0] + return naveggId.split('_')[0]; } } } diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index ae9cc4c818f..661a14ca0ef 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -18,6 +18,11 @@ export const novatiqIdSubmodule = { * @type {string} */ name: 'novatiq', + /** + * used to specify vendor id + * @type {number} + */ + gvlid: 1119, /** * decode the stored id value for passing to bid requests @@ -202,7 +207,7 @@ export const novatiqIdSubmodule = { let sharedId = null; if (this.useSharedId(configParams)) { let cookieOrStorageID = this.getCookieOrStorageID(configParams); - const storage = getStorageManager({moduleName: 'pubCommonId'}); + const storage = getStorageManager({gvlid: this.gvlid, moduleName: 'pubCommonId'}); // first check local storage if (storage.hasLocalStorage()) { diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 295a0042f4b..746fc1eec03 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -10,7 +10,29 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.2.11'; +const ADAPTER_VERSION = '1.2.12'; + +function getClientWidth() { + const documentElementClientWidth = window.top.document.documentElement.clientWidth + ? window.top.document.documentElement.clientWidth + : 0 + const innerWidth = window.top.innerWidth ? window.top.innerWidth : 0 + const outerWidth = window.top.outerWidth ? window.top.outerWidth : 0 + const screenWidth = window.top.screen.width ? window.top.screen.width : 0 + + return documentElementClientWidth || innerWidth || outerWidth || screenWidth +} + +function getClientHeight() { + const documentElementClientHeight = window.top.document.documentElement.clientHeight + ? window.top.document.documentElement.clientHeight + : 0 + const innerHeight = window.top.innerHeight ? window.top.innerHeight : 0 + const outerHeight = window.top.outerHeight ? window.top.outerHeight : 0 + const screenHeight = window.top.screen.height ? window.top.screen.height : 0 + + return documentElementClientHeight || innerHeight || outerHeight || screenHeight +} function isBidRequestValid(bid) { const adUnitSizes = getAdUnitSizes(bid); @@ -60,6 +82,10 @@ function buildRequests(validBidRequests, bidderRequest) { ext: { adapterversion: ADAPTER_VERSION, prebidversion: '$prebid.version$' + }, + device: { + w: getClientWidth(), + h: getClientHeight() } }; diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index db381555ef9..8312dacbdbb 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -151,9 +151,6 @@ function interpretResponse(serverResponse, bidRequest) { } bidResponses.push(bidResponse); } - - // eslint-disable-next-line no-console - console.log(JSON.stringify(bidResponses)); return bidResponses; } diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js new file mode 100644 index 00000000000..f93dd39a484 --- /dev/null +++ b/modules/taboolaBidAdapter.js @@ -0,0 +1,265 @@ +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {getWindowSelf, getWindowTop} from '../src/utils.js' +import {getStorageManager} from '../src/storageManager.js'; + +const BIDDER_CODE = 'taboola'; +const GVLID = 42; +const CURRENCY = 'USD'; +export const END_POINT_URL = 'http://hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; +const USER_ID = 'user-id'; +const STORAGE_KEY = `taboola global:${USER_ID}`; +const COOKIE_KEY = 'trc_cookie_storage'; + +/** + * extract User Id by that order: + * 1. local storage + * 2. first party cookie + * 3. rendered trc + * 4. new user set it to 0 + */ +export const userData = { + storageManager: getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}), + getUserId: () => { + const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; + + try { + return getFromLocalStorage() || getFromCookie() || getFromTRC(); + } catch (ex) { + return 0; + } + }, + getFromCookie() { + const {cookiesAreEnabled, getCookie} = userData.storageManager; + if (cookiesAreEnabled()) { + const cookieData = getCookie(COOKIE_KEY); + const userId = userData.getCookieDataByKey(cookieData, USER_ID); + if (userId) { + return userId; + } + } + }, + getCookieDataByKey(cookieData, key) { + const [, value = ''] = cookieData.split(`${key}=`) + return value; + }, + getFromLocalStorage() { + const {hasLocalStorage, localStorageIsEnabled, getDataFromLocalStorage} = userData.storageManager; + + if (hasLocalStorage() && localStorageIsEnabled()) { + return getDataFromLocalStorage(STORAGE_KEY); + } + }, + getFromTRC() { + return window.TRC ? window.TRC.user_id : 0; + } +} + +export const internal = { + getPageUrl: (refererInfo = {}) => { + if (refererInfo.canonicalUrl) { + return refererInfo.canonicalUrl; + } + + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + try { + return getWindowTop().location.href; + } catch (e) { + return getWindowSelf().location.href; + } + }, + getReferrer: (refererInfo = {}) => { + if (refererInfo.referer) { + return refererInfo.referer; + } + + try { + return getWindowTop().document.referrer; + } catch (e) { + return getWindowSelf().document.referrer; + } + } +} + +export const spec = { + supportedMediaTypes: [BANNER], + gvlid: GVLID, + code: BIDDER_CODE, + isBidRequestValid: (bidRequest) => { + return !!(bidRequest.sizes && + bidRequest.params && + bidRequest.params.publisherId && + bidRequest.params.tagId); + }, + buildRequests: (validBidRequests, bidderRequest) => { + const [bidRequest] = validBidRequests; + const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; + const {publisherId} = bidRequest.params; + const site = getSiteProperties(bidRequest.params, refererInfo); + const device = {ua: navigator.userAgent}; + const imps = getImps(validBidRequests); + const user = { + buyeruid: userData.getUserId(gdprConsent, uspConsent), + ext: {} + }; + const regs = { + coppa: 0, + ext: {} + }; + + if (gdprConsent.gdprApplies) { + user.ext.consent = bidderRequest.gdprConsent.consentString; + regs.ext.gdpr = 1; + } + + if (uspConsent) { + regs.ext.us_privacy = uspConsent; + } + + if (config.getConfig('coppa')) { + regs.coppa = 1 + } + + const ortb2 = config.getConfig('ortb2') || { + badv: [], + bcat: [] + }; + + const request = { + id: bidderRequest.auctionId, + imp: imps, + site, + device, + source: {fd: 1}, + tmax: bidderRequest.timeout, + bcat: ortb2.bcat, + badv: ortb2.badv, + user, + regs + }; + + const url = [END_POINT_URL, publisherId].join('/'); + + return { + url, + method: 'POST', + data: JSON.stringify(request), + bids: validBidRequests, + options: { + withCredentials: false + }, + }; + }, + interpretResponse: (serverResponse, {bids}) => { + if (!bids) { + return []; + } + + const {bidResponses, cur: currency} = getBidResponses(serverResponse); + + if (!bidResponses) { + return []; + } + + return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); + }, +}; + +function getSiteProperties({publisherId, bcat = []}, refererInfo) { + const {getPageUrl, getReferrer} = internal; + return { + id: publisherId, + name: publisherId, + domain: window.location.host, + page: getPageUrl(refererInfo), + ref: getReferrer(refererInfo), + publisher: { + id: publisherId + }, + content: { + language: navigator.language + } + } +} + +function getImps(validBidRequests) { + return validBidRequests.map((bid, id) => { + const {tagId, bidfloor = null, bidfloorcur = CURRENCY} = bid.params; + + return { + id: id + 1, + banner: getBanners(bid), + tagid: tagId, + bidfloor, + bidfloorcur, + }; + }); +} + +function getBanners(bid) { + return getSizes(bid.sizes); +} + +function getSizes(sizes) { + return { + format: sizes.map(size => { + return { + w: size[0], + h: size[1] + } + }) + } +} + +function getBidResponses({body}) { + if (!body) { + return []; + } + + const {seatbid, cur} = body; + + if (!seatbid.length || !seatbid[0].bid) { + return []; + } + + return { + bidResponses: seatbid[0].bid, + cur + }; +} + +function getBid(requestId, currency, bidResponse) { + if (!bidResponse) { + return; + } + + const { + price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} + } = bidResponse; + + if (advertiserDomains && advertiserDomains.length > 0) { + meta.advertiserDomains = advertiserDomains + } + + return { + requestId, + ttl: 360, + mediaType: BANNER, + cpm, + creativeId, + currency, + ad, + width, + height, + meta, + netRevenue: false + }; +} + +registerBidder(spec); diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md new file mode 100644 index 00000000000..a4213355049 --- /dev/null +++ b/modules/taboolaBidAdapter.md @@ -0,0 +1,49 @@ +# Overview + +``` +Module Name: Taboola Adapter +Module Type: Bidder Adapter +Maintainer: prebid@taboola.com +``` + +# Description + +Module that connects to Taboola bidder to fetch bids. +- Supports `display` format +- Uses `OpenRTB` standard + +The Taboola Bidding adapter requires setup before beginning. Please contact us on prebid@taboola.com + +# Test Display Parameters +``` javascript + var adUnits = [{ + code: 'your-unit-container-id', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'taboola', + params: { + tagId: 'tester-placement', // Placement Name + publisherId: 'tester-pub', // your-publisher-id + bidfloor: 0.25, // Optional - default is null + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'] // Optional - default is [] + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Description | Example | Type | +|----------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `tagId` | required | Tag ID / Placement Name
(as provided by Taboola) | `'Below The Article'` | `String` | +| `publisherId` | required | Alphabetic Publisher ID
(as provided by Taboola) | `'acme-publishing'` | `String` | +| `bcat` | optional | List of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `'example.com'` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | + + diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 9c995a52fe3..2f076088c3e 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -314,6 +314,12 @@ export const USER_IDS_CONFIG = { return data.envelope; } }, + + // Gravito MP ID + 'gravitompId': { + source: 'gravito.net', + atype: 1 + }, }; // this function will create an eid object for the given UserId sub-module diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 16f56ae7b9d..4b2338fa3eb 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -166,6 +166,9 @@ pbjs.setConfig({ }, { name: "dacId" + }, + { + name: "gravitompId" } ], syncDelay: 5000, diff --git a/modules/videoheroesBidAdapter.js b/modules/videoheroesBidAdapter.js new file mode 100644 index 00000000000..edcdd698153 --- /dev/null +++ b/modules/videoheroesBidAdapter.js @@ -0,0 +1,267 @@ +import { isEmpty, parseUrl, isStr, triggerPixel } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'videoheroes'; +const DEFAULT_CUR = 'USD'; +const ENDPOINT_URL = `https://point.contextualadv.com/?t=2&partner=hash`; + +const NATIVE_ASSETS_IDS = { 1: 'title', 2: 'icon', 3: 'image', 4: 'body', 5: 'sponsoredBy', 6: 'cta' }; +const NATIVE_ASSETS = { + title: { id: 1, name: 'title' }, + icon: { id: 2, type: 1, name: 'img' }, + image: { id: 3, type: 3, name: 'img' }, + body: { id: 4, type: 2, name: 'data' }, + sponsoredBy: { id: 5, type: 1, name: 'data' }, + cta: { id: 6, type: 12, name: 'data' } +}; + +export const spec = { + code: BIDDER_CODE, + 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: (bid) => { + return !!(bid.params.placementId && bid.params.placementId.toString().length === 32); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests.length === 0 || !bidderRequest) return []; + + const endpointURL = ENDPOINT_URL.replace('hash', validBidRequests[0].params.placementId); + + let imp = validBidRequests.map(br => { + let impObject = { + id: br.bidId, + secure: 1 + }; + + if (br.mediaTypes.banner) { + impObject.banner = createBannerRequest(br); + } else if (br.mediaTypes.video) { + impObject.video = createVideoRequest(br); + } else if (br.mediaTypes.native) { + impObject.native = { + id: br.transactionId, + ver: '1.2', + request: createNativeRequest(br) + }; + } + return impObject; + }); + + let w = window; + let l = w.document.location.href; + let r = w.document.referrer; + + let loopChecker = 0; + while (w !== w.parent) { + if (++loopChecker == 10) break; + try { + w = w.parent; + l = w.location.href; + r = w.document.referrer; + } catch (e) { + break; + } + } + + let page = l || bidderRequest.refererInfo.referer; + + let data = { + id: bidderRequest.bidderRequestId, + cur: [ DEFAULT_CUR ], + device: { + w: screen.width, + h: screen.height, + language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', + ua: navigator.userAgent, + }, + site: { + domain: parseUrl(page).hostname, + page: page, + }, + tmax: bidderRequest.timeout || config.getConfig('bidderTimeout') || 500, + imp + }; + + if (r) { + data.site.ref = r; + } + + if (bidderRequest.gdprConsent) { + data['regs'] = {'ext': {'gdpr': bidderRequest.gdprConsent.gdprApplies ? 1 : 0}}; + data['user'] = {'ext': {'consent': bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : ''}}; + } + + if (bidderRequest.uspConsent !== undefined) { + if (!data['regs'])data['regs'] = {'ext': {}}; + data['regs']['ext']['us_privacy'] = bidderRequest.uspConsent; + } + + if (config.getConfig('coppa') === true) { + if (!data['regs'])data['regs'] = {'coppa': 1}; + else data['regs']['coppa'] = 1; + } + + if (validBidRequests[0].schain) { + data['source'] = {'ext': {'schain': validBidRequests[0].schain}}; + } + + return { + method: 'POST', + url: endpointURL, + data: data + }; + }, + + /** + * 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: (serverResponse) => { + if (!serverResponse || isEmpty(serverResponse.body)) return []; + + let bids = []; + serverResponse.body.seatbid.forEach(response => { + response.bid.forEach(bid => { + let mediaType = bid.ext && bid.ext.mediaType ? bid.ext.mediaType : 'banner'; + + let bidObj = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: 1200, + currency: DEFAULT_CUR, + netRevenue: true, + creativeId: bid.crid, + dealId: bid.dealid || null, + mediaType: mediaType + }; + + switch (mediaType) { + case 'video': + bidObj.vastUrl = bid.adm; + break; + case 'native': + bidObj.native = parseNative(bid.adm); + break; + default: + bidObj.ad = bid.adm; + } + + bids.push(bidObj); + }); + }); + + return bids; + }, + + onBidWon: (bid) => { + if (isStr(bid.nurl) && bid.nurl !== '') { + triggerPixel(bid.nurl); + } + } +}; + +const parseNative = adm => { + let bid = { + clickUrl: adm.native.link && adm.native.link.url, + impressionTrackers: adm.native.imptrackers || [], + clickTrackers: (adm.native.link && adm.native.link.clicktrackers) || [], + jstracker: adm.native.jstracker || [] + }; + adm.native.assets.forEach(asset => { + let kind = NATIVE_ASSETS_IDS[asset.id]; + let content = kind && asset[NATIVE_ASSETS[kind].name]; + if (content) { + bid[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return bid; +} + +const createNativeRequest = br => { + let impObject = { + ver: '1.2', + assets: [] + }; + + let keys = Object.keys(br.mediaTypes.native); + + for (let key of keys) { + const props = NATIVE_ASSETS[key]; + if (props) { + const asset = { + required: br.mediaTypes.native[key].required ? 1 : 0, + id: props.id, + [props.name]: {} + }; + + if (props.type) asset[props.name]['type'] = props.type; + if (br.mediaTypes.native[key].len) asset[props.name]['len'] = br.mediaTypes.native[key].len; + if (br.mediaTypes.native[key].sizes && br.mediaTypes.native[key].sizes[0]) { + asset[props.name]['w'] = br.mediaTypes.native[key].sizes[0]; + asset[props.name]['h'] = br.mediaTypes.native[key].sizes[1]; + } + + impObject.assets.push(asset); + } + } + + return impObject; +} + +const createBannerRequest = br => { + let size = []; + + if (br.mediaTypes.banner.sizes && Array.isArray(br.mediaTypes.banner.sizes)) { + if (Array.isArray(br.mediaTypes.banner.sizes[0])) { size = br.mediaTypes.banner.sizes[0]; } else { size = br.mediaTypes.banner.sizes; } + } else size = [300, 250]; + + return { id: br.transactionId, w: size[0], h: size[1] }; +}; + +const createVideoRequest = br => { + let videoObj = {id: br.transactionId}; + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity']; + + for (let param of supportParamsList) { + if (br.mediaTypes.video[param] !== undefined) { + videoObj[param] = br.mediaTypes.video[param]; + } + } + + if (br.mediaTypes.video.playerSize && Array.isArray(br.mediaTypes.video.playerSize)) { + if (Array.isArray(br.mediaTypes.video.playerSize[0])) { + videoObj.w = br.mediaTypes.video.playerSize[0][0]; + videoObj.h = br.mediaTypes.video.playerSize[0][1]; + } else { + videoObj.w = br.mediaTypes.video.playerSize[0]; + videoObj.h = br.mediaTypes.video.playerSize[1]; + } + } else { + videoObj.w = 640; + videoObj.h = 480; + } + + return videoObj; +} + +registerBidder(spec); diff --git a/modules/videoheroesBidAdapter.md b/modules/videoheroesBidAdapter.md new file mode 100644 index 00000000000..f2a2ca9f7ba --- /dev/null +++ b/modules/videoheroesBidAdapter.md @@ -0,0 +1,134 @@ +# Overview + +``` +Module Name: Video Heroes Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@videoheroes.tv +``` + +# Description + +Module which connects to VideoHeroes SSP demand sources + +# Test Parameters + +250x300 banner test +``` +var adUnits = [{ + code: 'videoheroes-prebid', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'videoheroes', + params : { + placementId : "1a8d9c22db19906cb8a5fd4518d05f62" // test placementId, please replace after test + } + }] +}]; +``` + +native test +``` +var adUnits = [{ + code: 'videoheroes-native-prebid', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'videoheroes', + params: { + placementId : "1a8d9c22db19906cb8a5fd4518d05f62" // test placementId, please replace after test + } + }] +}]; +``` + +video test +``` +var adUnits = [{ + code: 'videoheroes-video-prebid', + mediaTypes: { + video: { + minduration:1, + maxduration:999, + boxingallowed:1, + skip:0, + mimes:[ + 'application/javascript', + 'video/mp4' + ], + playerSize: [[768, 1024]], + protocols:[ + 2,3 + ], + linearity:1, + api:[ + 1, + 2 + ] + } + }, + bids: [{ + bidder: 'videoheroes', + params: { + placementId : "1a8d9c22db19906cb8a5fd4518d05f62" // test placementId, please replace after test + } + }] +}]; +``` + +# Bid Parameters +## Banner + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `placementId` | required | String | The placement ID from Video Heroes | "1a8d9c22db19906cb8a5fd4518d05f62" + + +# Ad Unit and page Setup: + +```html + + + +``` diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index d268f7a9d64..ffa4a290072 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -12,13 +12,9 @@ const GVLID = 380; const COOKIE_SYNC_FALLBACK_URLS = [ 'https://x.bidswitch.net/sync?ssp=vidoomy', 'https://ib.adnxs.com/getuid?https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', - 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D', - 'https://sync.1rx.io/usersync2/vidoomy?redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DUN%26uid%3D%5BRX_UUID%5D', - 'https://rtb.openx.net/sync/prebid?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&r=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dopenx%26uid%3D$%7BUID%7D', - 'https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=&predirect=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D', + 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D', 'https://cm.adform.net/cookie?redirect_url=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', - 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}', - 'https://ap.lijit.com/pixel?redir=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID' + 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}' ]; const isBidRequestValid = bid => { diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 87fdbe1396f..114bb86b4d6 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -126,9 +126,10 @@ export const spec = { } provideEids(request, payload); + const url = params.shortname ? ENDPOINT_URL.concat('?shortname=', params.shortname) : ENDPOINT_URL; return { method: 'POST', - url: ENDPOINT_URL, + url: url, data: JSON.stringify(payload), }; }, diff --git a/package-lock.json b/package-lock.json index b620b35e945..77c6e87eb8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "6.25.0-pre", + "version": "6.28.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "6.22.0-pre", + "version": "6.25.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -21,7 +21,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "2.3.1" + "live-connect-js": "2.3.3" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -15328,9 +15328,9 @@ "dev": true }, "node_modules/live-connect-js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.3.1.tgz", - "integrity": "sha512-4IT8NEOOTNmoYpw5CTxdugSF2w9xqfOujrEqx6zLPdTT3xq/lLdxxvRTREDi+qYHDsCDovdiNO3uOSoemdTCdA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.3.3.tgz", + "integrity": "sha512-WfY6v1jVutW/OGPvm0OaWAHc0MvB5MDdSuniZ+n9cpCltArWHTJtAsjWe8T+ACmdLAlZS9z7hzL3ntLnq+J0yQ==", "dependencies": { "tiny-hashes": "1.0.1" }, @@ -35433,9 +35433,9 @@ "dev": true }, "live-connect-js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.3.1.tgz", - "integrity": "sha512-4IT8NEOOTNmoYpw5CTxdugSF2w9xqfOujrEqx6zLPdTT3xq/lLdxxvRTREDi+qYHDsCDovdiNO3uOSoemdTCdA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.3.3.tgz", + "integrity": "sha512-WfY6v1jVutW/OGPvm0OaWAHc0MvB5MDdSuniZ+n9cpCltArWHTJtAsjWe8T+ACmdLAlZS9z7hzL3ntLnq+J0yQ==", "requires": { "tiny-hashes": "1.0.1" } diff --git a/package.json b/package.json index 4852cfd9ca7..c495eebf16c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.25.0-pre", + "version": "6.28.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -126,7 +126,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "2.3.1" + "live-connect-js": "2.3.3" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index 80916e41324..ae891966ee1 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -3,6 +3,10 @@ import { ajax } from './ajax.js'; import { logMessage, _each } from './utils.js'; import * as events from './events.js' +export const _internal = { + ajax +}; + const { EVENTS: { AUCTION_INIT, @@ -61,7 +65,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } } function _callEndpoint({ eventType, args, callback }) { - ajax(url, callback, JSON.stringify({ eventType, args })); + _internal.ajax(url, callback, JSON.stringify({ eventType, args })); } function _enqueue({ eventType, args }) { diff --git a/src/adloader.js b/src/adloader.js index db128c6d7ba..17a536ad9dc 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -10,7 +10,10 @@ const _approvedLoadExternalJSList = [ 'adagio', 'browsi', 'brandmetrics', - 'justtag' + 'justtag', + 'akamaidap', + 'ftrackId', + 'inskin' ] /** diff --git a/test/mocks/analyticsStub.js b/test/mocks/analyticsStub.js new file mode 100644 index 00000000000..1023db882e8 --- /dev/null +++ b/test/mocks/analyticsStub.js @@ -0,0 +1,13 @@ +import {_internal} from '../../src/AnalyticsAdapter.js'; + +before(() => { + // stub out analytics networking to avoid random events polluting the global xhr mock + disableAjaxForAnalytics(); +}) + +export function disableAjaxForAnalytics() { + sinon.stub(_internal, 'ajax').callsFake(() => null); +} +export function enableAjaxForAnalytics() { + _internal.ajax.restore(); +} diff --git a/test/mocks/xhr.js b/test/mocks/xhr.js index 9fb8fe87fa0..424100f870c 100644 --- a/test/mocks/xhr.js +++ b/test/mocks/xhr.js @@ -1,3 +1,4 @@ +import {getUniqueIdentifierStr} from '../../src/utils.js'; export let server = sinon.createFakeServer(); export let xhr = global.XMLHttpRequest; @@ -7,3 +8,24 @@ beforeEach(function() { server = sinon.createFakeServer(); xhr = global.XMLHttpRequest; }); + +const bid = getUniqueIdentifierStr().substring(4); +let fid = 0; + +/* eslint-disable */ +afterEach(function () { + if (this?.currentTest?.state === 'failed') { + const prepend = (() => { + const preamble = `[Failure ${bid}-${fid++}]`; + return (s) => s.split('\n').map(s => `${preamble} ${s}`).join('\n'); + })(); + + + console.log(prepend(`XHR mock state after failure (for test '${this.currentTest.fullTitle()}'): ${server.requests.length} requests`)) + server.requests.forEach((req, i) => { + console.log(prepend(`Request #${i}:`)); + console.log(prepend(JSON.stringify(req, null, 2))); + }) + } +}); +/* eslint-enable */ diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index d9199f47af9..f5dac22cb58 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import { server } from 'test/mocks/xhr.js'; +import {disableAjaxForAnalytics, enableAjaxForAnalytics} from '../mocks/analyticsStub.js'; const REQUEST_BIDS = CONSTANTS.EVENTS.REQUEST_BIDS; const BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; @@ -25,6 +26,9 @@ FEATURE: Analytics Adapters API AND an \`example\` instance of \`AnalyticsAdapter\`\n`, () => { let adapter; + before(enableAjaxForAnalytics); + after(disableAjaxForAnalytics); + beforeEach(function () { adapter = new AnalyticsAdapter(config); }); diff --git a/test/spec/modules/adriverIdSystem_spec.js b/test/spec/modules/adriverIdSystem_spec.js index 8780c79f719..29d965d5ed4 100644 --- a/test/spec/modules/adriverIdSystem_spec.js +++ b/test/spec/modules/adriverIdSystem_spec.js @@ -67,12 +67,16 @@ describe('AdriverIdSystem', function () { let request = server.requests[0]; request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ adrcid: response.adrcid })); - let now = new Date(); - now.setTime(now.getTime() + 86400 * 1825 * 1000); + let expectedExpiration = new Date(); + expectedExpiration.setTime(expectedExpiration.getTime() + 86400 * 1825 * 1000); const minimalDate = new Date(0).toString(); + function dateStringFor(date, maxDeltaMs = 2000) { + return sinon.match((val) => Math.abs(date.getTime() - new Date(val).getTime()) <= maxDeltaMs) + } + if (response.adrcid) { - expect(setCookieStub.calledWith('adrcid', response.adrcid, now.toUTCString())).to.be.true; + expect(setCookieStub.calledWith('adrcid', response.adrcid, dateStringFor(expectedExpiration))).to.be.true; expect(setLocalStorageStub.calledWith('adrcid', response.adrcid)).to.be.true; } else { expect(setCookieStub.calledWith('adrcid', '', minimalDate)).to.be.false; diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 117a6d5966a..2600b7c4595 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -18,6 +18,7 @@ const aliasEP = { bidsxchange: 'https://ghb.hbd.bidsxchange.com/v2/auction/', streamkey: 'https://ghb.hb.streamkey.net/v2/auction/', janet: 'https://ghb.bidder.jmgads.com/v2/auction/', + pgam: 'https://ghb.pgamssp.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index 3e68a10f56e..b5365e8d21a 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -91,6 +91,20 @@ describe('Adyoulike Adapter', function () { } }, }, + 'schain': { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } + }, 'transactionId': 'bid_id_0_transaction_id' } ]; @@ -614,7 +628,7 @@ describe('Adyoulike Adapter', function () { expect(request.url).to.contain(getEndpoint()); }); - it('should add gdpr/usp consent information to the request', function () { + it('should add gdpr/usp consent information and SChain to the request', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let uspConsentData = '1YCC'; let bidderRequest = { @@ -637,6 +651,7 @@ describe('Adyoulike Adapter', function () { expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; expect(payload.uspConsent).to.exist.and.to.equal(uspConsentData); + expect(payload.Bids.bid_id_0.SChain).to.exist.and.to.deep.equal(bidRequestWithSinglePlacement[0].schain); }); it('should not set a default value for gdpr consentRequired', function () { diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js index b350c2bb529..d7fd3d34099 100644 --- a/test/spec/modules/akamaiDapRtdProvider_spec.js +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -1,13 +1,14 @@ import {config} from 'src/config.js'; -import {SEGMENTS_STORAGE_KEY, TOKEN_STORAGE_KEY, dapUtils, addRealTimeData, getRealTimeData, akamaiDapRtdSubmodule, storage} from 'modules/akamaiDapRtdProvider.js'; +import { + dapUtils, + generateRealTimeData, + akamaiDapRtdSubmodule, + storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP +} from 'modules/akamaiDapRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; -import logMessage from 'src/utils.js' const responseHeader = {'Content-Type': 'application/json'}; describe('akamaiDapRtdProvider', function() { - let getDataFromLocalStorageStub; - let getDapTokenStub; - const testReqBidsConfigObj = { adUnits: [ { @@ -18,7 +19,21 @@ describe('akamaiDapRtdProvider', function() { const onDone = function() { return true }; - const onSuccess = function() { return ('request', 200, 'success') }; + const sampleGdprConsentConfig = { + 'gdpr': { + 'consentString': null, + 'vendorData': {}, + 'gdprApplies': true + } + }; + + const sampleUspConsentConfig = { + 'usp': '1YYY' + }; + + const sampleIdentity = { + type: 'dap-signature:1.0.0' + }; const cmoduleConfig = { 'name': 'dap', @@ -28,8 +43,19 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 503, - 'tokenTtl': 5 + 'segtax': 503 + } + } + + const emoduleConfig = { + 'name': 'dap', + 'waitForIt': true, + 'params': { + 'apiHostname': 'prebid.dap.akadns.net', + 'apiVersion': 'x1', + 'domain': 'prebid.org', + 'identityType': 'dap-signature:1.0.0', + 'segtax': 504 } } @@ -37,81 +63,169 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 503 + 'segtax': 503, + 'identity': sampleIdentity } - const sampleIdentity = { - type: 'dap-signature:1.0.0' + + const esampleConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 504, + 'identity': sampleIdentity + } + let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds + const sampleCachedToken = {'expires_at': cacheExpiry, 'token': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM'}; + const cachedEncryptedMembership = {'expires_at': cacheExpiry, 'encryptedSegments': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..IvnIUQDqWBVYIS0gbcE9bw.Z4NZGvtogWaWlGH4e-GdYKe_PUc15M2x3Bj85rMWsN1A17mIxQIMOfg2hsQ2tgieLu5LggWPmsFu1Wbph6P0k3kOu1dVReoIhOHzxw50rP0DLHKaEZ5mLMJ7Lcosvwh4miIfFuCHlsX7J0sFgOTAp0zGo1S_UsHLtev1JflhjoSB0AoX95ALbAnyctirPuLJM8gZ1vXTiZ01jpvucGyR1lM4cWjPOeD8jPtgwaPGgSRZXE-3X2Cqy7z4Giam5Uqu74LPWTBuKtUQTGyAXA5QJoP7xwTbsU4O1f69lu3fWNqC92GijeTH1A4Zd_C-WXxWuQlDEURjlkWQoaqTHka2OqlnwukEQIf_v0r5KQQX64CTLhEUH91jeD0-E9ClcIP7pwOLxxqiKoaBmx8Mrnm_6Agj5DtTA1rusy3AL63sI_rsUxrmLrVt0Wft4aCfRkW8QpQxu8clFdOmce0NNCGeBCyCPVw9d9izrILlXJ6rItU2cpFrcbz8uw2otamF5eOFCOY3IzHedWVNNuKHFIUVC_xYSlsYvQ8f2QIP1eiMbmukcuPzmTzjw1h1_7IKaj-jJkXrnrY-TdDgX_4-_Z3rmbpXK2yTR7dBrsg-ubqFbgbKic1b4zlQEO_LbBlgPl3DYdWEuJ8CY2NUt1GfpATQGsufS2FTY1YGw_gkPe3q04l_cgLafDoxHvHh_t_0ZgPjciW82gThB_kN4RP7Mc3krVcXl_P6N1VbV07xyx0hCyVsrrxbLslI8q9wYDiLGci7mNmByM5j7SXV9jPwwPkHtn0HfMJlw2PFbIDPjgG3h7sOyLcBIJTTvuUIgpHPIkRWLIl_4FlIucXbJ7orW2nt5BWleBVHgumzGcnl9ZNcZb3W-dsdYPSOmuj0CY28MRTP2oJ1rzLInbDDpIRffJBtR7SS4nYyy7Vi09PtBigod5YNz1Q0WDSJxr8zeH_aKFaXInw7Bfo_U0IAcLiRgcT0ogsMLeQRjRFy27mr4XNJv3NtHhbdjDAwF2aClCktXyXbQaVdsPH2W71v6m2Q9rB5GQWOktw2s5f-4N1-_EBPGq6TgjF-aJZP22MJVwp1pimT50DfOzoeEqDwi862NNwNNoHmcObH0ZfwAXlhRxsgupNBe20-MNNABj2Phlfv4DUrtQbMdfCnNiypzNCmoTb7G7c_o5_JUwoV_GVkwUtvmi_IUm05P4GeMASSUw8zDKVRAj9h31C2cabM8RjMHGhkbCWpUP2pcz9zlJ7Y76Dh3RLnctfTw7DG9U4w4UlaxNZOgLUiSrGwfyapuSiuGUpuOJkBBLiHmEqAGI5C8oJpcVRccNlHxJAYowgXyFopD5Fr-FkXmv8KMkS0h5C9F6KihmDt5sqDD0qnjM0hHJgq01l7wjVnhEmPpyD-6auFQ-xDnbh1uBOJ_0gCVbRad--FSa5p-dXenggegRxOvZXJ0iAtM6Fal5Og-RCjexIHa9WhVbXhQBJpkSTWwAajZJ64eQ.yih49XB51wE-Xob7COT9OYqBrzBmIMVCQbLFx2UdzkI'}; + const cachedMembership = {'expires_at': cacheExpiry, 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', 'cohorts': ['9', '11', '13']}; + const rtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const encRtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + segtax: 504, + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [] + }; + + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj] + } + } + } + }; + + let membership = { + said: cachedMembership.said, + cohorts: cachedMembership.cohorts, + attributes: null + }; + let encMembership = { + encryptedSegments: cachedEncryptedMembership.encryptedSegments + }; + encRtdUserObj.segment.push({ id: encMembership.encryptedSegments }); + const cachedEncRtd = { + rtd: { + ortb2: { + user: { + data: [encRtdUserObj] + } + } + } }; beforeEach(function() { config.resetConfig(); - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') + storage.removeDataFromLocalStorage(DAP_TOKEN); + storage.removeDataFromLocalStorage(DAP_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_SS_ID); }); afterEach(function () { - getDataFromLocalStorageStub.restore(); }); describe('akamaiDapRtdSubmodule', function() { it('successfully instantiates', function () { - expect(akamaiDapRtdSubmodule.init()).to.equal(true); + expect(akamaiDapRtdSubmodule.init()).to.equal(true); }); }); describe('Get Real-Time Data', function() { it('gets rtd from local storage cache', function() { - const rtdConfig = { - params: { - segmentCache: true - } - }; - - const bidConfig = {}; + let dapGetMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership) + let dapGetRtdObjStub = sinon.stub(dapUtils, 'dapGetRtdObj').returns(cachedRtd) + let dapGetEncryptedMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership) + let dapGetEncryptedRtdObjStub = sinon.stub(dapUtils, 'dapGetEncryptedRtdObj').returns(cachedEncRtd) + let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs') + try { + const bidConfig = {}; + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + expect(config.getConfig().ortb2).to.be.undefined; + generateRealTimeData(bidConfig, () => {}, emoduleConfig, {}); - const rtdUserObj1 = { - name: 'www.dataprovider3.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1918' - }, - { - id: '1939' - } - ] - }; - - const cachedRtd = { - rtd: { - ortb2: { - user: { - data: [rtdUserObj1] - } - } - } - }; - - getDataFromLocalStorageStub.withArgs(SEGMENTS_STORAGE_KEY).returns(JSON.stringify(cachedRtd)); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + generateRealTimeData(bidConfig, () => {}, cmoduleConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + } finally { + dapGetRtdObjStub.restore() + dapGetMembershipFromLocalStorageStub.restore() + dapGetEncryptedRtdObjStub.restore() + dapGetEncryptedMembershipFromLocalStorageStub.restore() + callDapApisStub.restore() + } + }); + }); - expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + describe('calling DAP APIs', function() { + it('Calls callDapAPIs for unencrypted segments flow', function() { + const bidConfig = {}; + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + let dapExtractExpiryFromTokenStub = sinon.stub(dapUtils, 'dapExtractExpiryFromToken').returns(cacheExpiry) + try { + expect(config.getConfig().ortb2).to.be.undefined; + dapUtils.callDapAPIs(bidConfig, () => {}, cmoduleConfig, {}); + let membership = {'cohorts': ['9', '11', '13'], 'said': 'sample-said'} + let membershipRequest = server.requests[0]; + membershipRequest.respond(200, responseHeader, JSON.stringify(membership)); + let tokenWithExpiry = 'Sample-token-with-exp' + let tokenizeRequest = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; + tokenizeRequest.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + let data = dapUtils.dapGetRtdObj(membership, cmoduleConfig.params.segtax); + expect(config.getConfig().ortb2.user.data).to.deep.include.members(data.rtd.ortb2.user.data); + } finally { + dapExtractExpiryFromTokenStub.restore(); + } }); - it('should initalise and return with config', function () { - expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) + it('Calls callDapAPIs for encrypted segments flow', function() { + const bidConfig = {}; + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + let dapExtractExpiryFromTokenStub = sinon.stub(dapUtils, 'dapExtractExpiryFromToken').returns(cacheExpiry) + try { + expect(config.getConfig().ortb2).to.be.undefined; + dapUtils.callDapAPIs(bidConfig, () => {}, emoduleConfig, {}); + let encMembership = 'Sample-enc-token'; + let membershipRequest = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + membershipRequest.respond(200, responseHeader, JSON.stringify(encMembership)); + let tokenWithExpiry = 'Sample-token-with-exp' + let tokenizeRequest = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; + tokenizeRequest.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + let data = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, emoduleConfig.params.segtax); + expect(config.getConfig().ortb2.user.data).to.deep.include.members(data.rtd.ortb2.user.data); + } finally { + dapExtractExpiryFromTokenStub.restore(); + } }); }); describe('dapTokenize', function () { it('dapTokenize error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -121,10 +235,10 @@ describe('akamaiDapRtdProvider', function() { it('dapTokenize success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -135,40 +249,54 @@ describe('akamaiDapRtdProvider', function() { describe('dapTokenize and dapMembership incorrect params', function () { it('Onerror and config are null', function () { - expect(dapUtils.dapTokenize(null, 'identity', null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(null, 'identity', null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); const config = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', 'segtax': 503 }; + const encConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 1, + 'domain': '', + 'segtax': 504 + }; let identity = { type: 'dap-signature:1.0.0' }; - expect(dapUtils.dapTokenize(config, identity, null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(config, 'token', null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(config, identity, onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(config, 'token', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(encConfig, 'token', onDone, null, null)).to.be.equal(undefined); }); + }); - it('dapGetToken success', function () { - let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns(onSuccess); - expect(dapUtils.dapGetToken(sampleConfig, 'token', - function(token, status, xhr) { - }, - function(xhr, status, error) { - } - )).to.be.equal(null); - dapTokenizeStub.restore(); + describe('Getting dapTokenize, dapMembership and dapEncryptedMembership from localstorage', function () { + it('dapGetTokenFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + expect(dapUtils.dapGetTokenFromLocalStorage(60)).to.be.equal(sampleCachedToken.token); + }); + + it('dapGetMembershipFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(cachedMembership)); + expect(JSON.stringify(dapUtils.dapGetMembershipFromLocalStorage())).to.be.equal(JSON.stringify(membership)); + }); + + it('dapGetEncryptedMembershipFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)); + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.be.equal(JSON.stringify(encMembership)); }); }); describe('dapMembership', function () { it('dapMembership success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -178,10 +306,38 @@ describe('akamaiDapRtdProvider', function() { it('dapMembership error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + describe('dapEncMembership', function () { + it('dapEncMembership success callback', function () { + let configAsync = JSON.parse(JSON.stringify(esampleConfig)); + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapEncMembership error callback', function () { + let configAsync = JSON.parse(JSON.stringify(esampleConfig)); + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -192,55 +348,247 @@ describe('akamaiDapRtdProvider', function() { describe('dapMembership', function () { it('should invoke the getDapToken and getDapMembership', function () { - let config = { - api_hostname: cmoduleConfig.params.apiHostname, - api_version: cmoduleConfig.params.apiVersion, - domain: cmoduleConfig.params.domain, - segtax: cmoduleConfig.params.segtax - }; - let identity = { - type: cmoduleConfig.params.identityType - }; - let membership = { said: 'item.said1', cohorts: 'item.cohorts', attributes: null }; - let getDapTokenStub = sinon.stub(dapUtils, 'dapGetToken').returns('token3'); - let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembership').returns(membership); - let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns('response', 200, 'request'); - getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); - expect(getDapTokenStub.calledOnce).to.be.equal(true); - expect(getDapMembershipStub.calledOnce).to.be.equal(true); - getDapTokenStub.restore(); - getDapMembershipStub.restore(); - dapTokenizeStub.restore(); + let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership); + let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs'); + try { + generateRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); + expect(getDapMembershipStub.calledOnce).to.be.equal(true); + } finally { + getDapMembershipStub.restore(); + callDapApisStub.restore(); + } }); }); - describe('dapMembershipToRtbSegment', function () { - it('dapMembershipToRtbSegment', function () { - let membership1 = { - said: 'item.said1', - cohorts: 'item.cohorts', - attributes: null + describe('dapEncMembership test', function () { + it('should invoke the getDapToken and getEncDapMembership', function () { + let encMembership = { + encryptedSegments: 'enc.seg', }; + + let getDapEncMembershipStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership); + let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs'); + try { + generateRealTimeData(testReqBidsConfigObj, onDone, emoduleConfig); + expect(getDapEncMembershipStub.calledOnce).to.be.equal(true); + } finally { + getDapEncMembershipStub.restore(); + callDapApisStub.restore(); + } + }); + }); + + describe('dapGetRtdObj test', function () { + it('dapGetRtdObj', function () { const config = { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', - tokenTtl: 5, segtax: 503 }; - let identity = { - type: 'dap-signature:1.0.0' - }; - - expect(dapUtils.dapGetMembership(config, 'token')).to.equal(null) + expect(dapUtils.dapRefreshMembership(config, 'token', onDone)).to.equal(undefined) const membership = {cohorts: ['1', '5', '7']} - expect(dapUtils.dapMembershipToRtbSegment(membership, config)).to.not.equal(undefined); + expect(dapUtils.dapGetRtdObj(membership, config.segtax)).to.not.equal(undefined); + }); + }); + + describe('checkAndAddRealtimeData test', function () { + it('add realtime data for segtax 503 and 504', function () { + dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); + dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + dapUtils.checkAndAddRealtimeData(cachedRtd, 503); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + }); + }); + + describe('dapExtractExpiryFromToken test', function () { + it('test dapExtractExpiryFromToken function', function () { + let tokenWithoutExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithoutExpiry)).to.equal(undefined); + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithExpiry)).to.equal(1643830369); + }); + }); + + describe('dapRefreshToken test', function () { + it('test dapRefreshToken success response', function () { + dapUtils.dapRefreshToken(sampleConfig, true, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, JSON.stringify(sampleCachedToken.token)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).token).to.be.equal(sampleCachedToken.token); + }); + + it('test dapRefreshToken success response with deviceid 100', function () { + dapUtils.dapRefreshToken(esampleConfig, true, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-100'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(storage.getDataFromLocalStorage('dap_deviceId100')).to.be.equal(sampleCachedToken.token); + }); + + it('test dapRefreshToken success response with exp claim', function () { + dapUtils.dapRefreshToken(sampleConfig, true, onDone) + let request = server.requests[0]; + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; + request.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(1643830359); + }); + + it('test dapRefreshToken error response', function () { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(cacheExpiry);// Since the expiry is same, the token is not updated in the cache + }); + }); + + describe('dapRefreshEncryptedMembership test', function () { + it('test dapRefreshEncryptedMembership success response', function () { + let expiry = Math.round(Date.now() / 1000.0) + 3600; // in seconds + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw'; + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + request.respond(200, responseHeader, encMembership); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(expiry); + }); + + it('test dapRefreshEncryptedMembership success response with exp claim', function () { + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY'; + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + request.respond(200, responseHeader, encMembership); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(1643830630); + }); + + it('test dapRefreshEncryptedMembership error response', function () { + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(config.getConfig().ortb2).to.be.equal(undefined); + }); + + it('test dapRefreshEncryptedMembership 403 error response', function () { + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + let requestTokenize = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + requestTokenize.respond(200, responseHeader, ''); + let requestMembership = server.requests[2]; + requestMembership.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); + }); + }); + + describe('dapRefreshMembership test', function () { + it('test dapRefreshMembership success response', function () { + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU'} + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(membership)); + let rtdObj = dapUtils.dapGetRtdObj(membership, 503); + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + }); + + it('test dapRefreshMembership success response with exp claim', function () { + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE'}; + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(membership)); + let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)).expires_at).to.be.equal(1647971548); + }); + + it('test dapRefreshMembership 400 error response', function () { + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(config.getConfig().ortb2).to.be.equal(undefined); + }); + + it('test dapRefreshMembership 403 error response', function () { + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE); + }); + }); + + describe('dapGetEncryptedMembershipFromLocalStorage test', function () { + it('test dapGetEncryptedMembershipFromLocalStorage function with valid cache', function () { + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)) + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.equal(JSON.stringify(encMembership)); + }); + + it('test dapGetEncryptedMembershipFromLocalStorage function with invalid cache', function () { + let expiry = Math.round(Date.now() / 1000.0) - 100; // in seconds + let encMembership = {'expiry': expiry, 'encryptedSegments': cachedEncryptedMembership.encryptedSegments} + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(encMembership)) + expect(dapUtils.dapGetEncryptedMembershipFromLocalStorage()).to.equal(null); + }); + }); + + describe('Akamai-DAP-SS-ID test', function () { + it('Akamai-DAP-SS-ID present in response header', function () { + let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + let sampleSSID = 'Test_SSID_Spec'; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + responseHeader['Akamai-DAP-SS-ID'] = sampleSSID; + request.respond(200, responseHeader, ''); + expect(storage.getDataFromLocalStorage(DAP_SS_ID)).to.be.equal(JSON.stringify(sampleSSID)); + }); + + it('Test if Akamai-DAP-SS-ID is present in request header', function () { + let expiry = Math.round(Date.now() / 1000.0) + 100; // in seconds + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify('Test_SSID_Spec')) + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + let ssidHeader = request.requestHeaders['Akamai-DAP-SS-ID']; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(ssidHeader).to.be.equal('Test_SSID_Spec'); + }); + }); + + describe('Test gdpr and usp consent handling', function () { + it('Gdpr applies and gdpr consent string not present', function () { + expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(false); + }); + + it('Gdpr applies and gdpr consent string is present', function () { + sampleGdprConsentConfig.gdpr.consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(true); + }); + + it('USP consent present and user have opted out', function () { + expect(akamaiDapRtdSubmodule.init(null, sampleUspConsentConfig)).to.equal(false); + }); + + it('USP consent present and user have not been provided with option to opt out', function () { + expect(akamaiDapRtdSubmodule.init(null, {'usp': '1NYY'})).to.equal(false); + }); + + it('USP consent present and user have not opted out', function () { + expect(akamaiDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); }); }); }); diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index 832ad2707d3..9de13afc2ee 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -115,7 +115,7 @@ describe('BeOp Bid Adapter tests', () => { }, 'refererInfo': { - 'canonicalUrl': 'http://test.te' + 'canonicalUrl': 'test.te' } }; @@ -124,7 +124,20 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.tc_string).to.exist; expect(payload.tc_string).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='); expect(payload.url).to.exist; - expect(payload.url).to.equal('http://localhost:9876/context.html'); + // check that the protocol is added correctly + expect(payload.url).to.equal('http://test.te'); + }); + + it('should not prepend the protocol in page url if already present', function () { + const bidderRequest = { + 'refererInfo': { + 'canonicalUrl': 'https://test.te' + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.url).to.exist; + expect(payload.url).to.equal('https://test.te'); }); }); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 53169326d3b..ebc9879bb84 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -72,6 +72,7 @@ describe('Conversant adapter tests', function() { video: { context: 'instream', playerSize: [632, 499], + pos: 3 } }, placementCode: 'pcode003', @@ -108,12 +109,14 @@ describe('Conversant adapter tests', function() { { bidder: 'conversant', params: { - site_id: siteId + site_id: siteId, + position: 2, }, mediaTypes: { video: { context: 'instream', - mimes: ['video/mp4', 'video/x-flv'] + mimes: ['video/mp4', 'video/x-flv'], + pos: 7, } }, placementCode: 'pcode005', @@ -147,6 +150,23 @@ describe('Conversant adapter tests', function() { bidId: 'bid006', bidderRequestId: '117d765b87bed38', auctionId: 'req000' + }, + { + bidder: 'conversant', + params: { + site_id: siteId + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [468, 60]], + pos: 5 + } + }, + placementCode: 'pcode001', + transactionId: 'tx001', + bidId: 'bid007', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' } ]; @@ -245,7 +265,7 @@ describe('Conversant adapter tests', function() { expect(payload).to.have.property('id', 'req000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); - expect(payload.imp).to.be.an('array').with.lengthOf(7); + expect(payload.imp).to.be.an('array').with.lengthOf(8); expect(payload.imp[0]).to.have.property('id', 'bid000'); expect(payload.imp[0]).to.have.property('secure', 1); @@ -287,7 +307,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[3]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[3]).to.not.have.property('tagid'); expect(payload.imp[3]).to.have.property('video'); - expect(payload.imp[3].video).to.not.have.property('pos'); + expect(payload.imp[3].video).to.have.property('pos', 3); expect(payload.imp[3].video).to.have.property('w', 632); expect(payload.imp[3].video).to.have.property('h', 499); expect(payload.imp[3].video).to.have.property('mimes'); @@ -325,7 +345,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[5]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[5]).to.not.have.property('tagid'); expect(payload.imp[5]).to.have.property('video'); - expect(payload.imp[5].video).to.not.have.property('pos'); + expect(payload.imp[5].video).to.have.property('pos', 2); expect(payload.imp[5].video).to.not.have.property('w'); expect(payload.imp[5].video).to.not.have.property('h'); expect(payload.imp[5].video).to.have.property('mimes'); @@ -345,6 +365,17 @@ describe('Conversant adapter tests', function() { expect(payload.imp[6].ext).to.have.property('data'); expect(payload.imp[6].ext.data).to.have.property('pbadslot'); + expect(payload.imp[7]).to.have.property('id', 'bid007'); + expect(payload.imp[7]).to.have.property('secure', 1); + expect(payload.imp[7]).to.have.property('bidfloor', 0); + expect(payload.imp[7]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[7]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[7]).to.not.have.property('tagid'); + expect(payload.imp[7]).to.have.property('banner'); + expect(payload.imp[7].banner).to.have.property('pos', 5); + expect(payload.imp[7].banner).to.have.property('format'); + expect(payload.imp[7].banner.format).to.deep.equal([{w: 728, h: 90}, {w: 468, h: 60}]); + expect(payload).to.have.property('site'); expect(payload.site).to.have.property('id', siteId); expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index ff7b0aad48c..8e203510b10 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -305,6 +305,15 @@ let bid_request = { } describe('DatablocksAdapter', function() { + before(() => { + // stub out queue metric to avoid it polluting the global xhr mock during other tests + sinon.stub(spec, 'queue_metric').callsFake(() => null); + }); + + after(() => { + spec.queue_metric.restore(); + }); + 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') @@ -377,14 +386,6 @@ describe('DatablocksAdapter', function() { }); }) - 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); diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index 407ceb305a2..76f77505105 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -51,7 +51,7 @@ describe('finteza analytics adapter', function () { describe('track', () => { describe('bid request', () => { - it('builds and sends data', function () { + it('builds and sends request data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -95,7 +95,7 @@ describe('finteza analytics adapter', function () { }); describe('bid response', () => { - it('builds and sends data', function () { + it('builds and sends response data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -154,7 +154,7 @@ describe('finteza analytics adapter', function () { }); describe('bid won', () => { - it('builds and sends data', function () { + it('builds and sends bid won data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -194,7 +194,7 @@ describe('finteza analytics adapter', function () { }); describe('bid timeout', () => { - it('builds and sends data', function () { + it('builds and sends timeout data', function () { const bidderCode = 'biDDer789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index b0843ebb99e..f2b0456a849 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -1,6 +1,7 @@ import { ftrackIdSubmodule } from 'modules/ftrackIdSystem.js'; import * as utils from 'src/utils.js'; import { uspDataHandler } from 'src/adapterManager.js'; +import { loadExternalScript } from 'src/adloader.js'; let expect = require('chai').expect; let server; @@ -95,15 +96,7 @@ describe('FTRACK ID System', () => { delete configMock1.params.url; ftrackIdSubmodule.isConfigOk(configMock1); - expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run. Url should be "https://d9.flashtalking.com/d9core".`); - }); - - it(`should be rejected if 'storage.param.url' does not exist or is not 'https://d9.flashtalking.com/d9core'`, () => { - let configMock1 = JSON.parse(JSON.stringify(configMock)); - configMock1.params.url = 'https://d9.NOT.flashtalking.com/d9core'; - - ftrackIdSubmodule.isConfigOk(configMock1); - expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run. Url should be "https://d9.flashtalking.com/d9core".`); + expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run.`); }); }); @@ -154,25 +147,21 @@ describe('FTRACK ID System', () => { }); it(`should be the only method that gets a new ID aka hits the D9 endpoint`, () => { - let appendChildStub = sinon.stub(window.document.body, 'appendChild'); - ftrackIdSubmodule.getId(configMock, null, null).callback(); - expect(window.document.body.appendChild.called).to.be.ok; - let actualScriptTag = window.document.body.appendChild.args[0][0]; - expect(actualScriptTag.tagName.toLowerCase()).to.equal('script'); - expect(actualScriptTag.getAttribute('src')).to.equal('https://d9.flashtalking.com/d9core'); - appendChildStub.resetHistory(); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://d9.flashtalking.com/d9core'); + loadExternalScript.resetHistory(); ftrackIdSubmodule.decode('value', configMock); - expect(window.document.body.appendChild.called).to.not.be.ok; - expect(window.document.body.appendChild.args).to.deep.equal([]); - appendChildStub.resetHistory(); + expect(loadExternalScript.called).to.not.be.ok; + expect(loadExternalScript.args).to.deep.equal([]); + loadExternalScript.resetHistory(); ftrackIdSubmodule.extendId(configMock, null, {cache: {id: ''}}); - expect(window.document.body.appendChild.called).to.not.be.ok; - expect(window.document.body.appendChild.args).to.deep.equal([]); + expect(loadExternalScript.called).to.not.be.ok; + expect(loadExternalScript.args).to.deep.equal([]); - appendChildStub.restore(); + loadExternalScript.restore(); }); describe(`should use the "ids" setting in the config:`, () => { diff --git a/test/spec/modules/gravitoIdSystem_spec.js b/test/spec/modules/gravitoIdSystem_spec.js new file mode 100644 index 00000000000..e904355f7f1 --- /dev/null +++ b/test/spec/modules/gravitoIdSystem_spec.js @@ -0,0 +1,51 @@ +import { gravitoIdSystemSubmodule, storage, cookieKey } from 'modules/gravitoIdSystem.js'; + +const GRAVITOID_TEST_VALUE = 'gravitompIdTest'; +const GRAVITOID_TEST_OBJ = { + gravitompId: GRAVITOID_TEST_VALUE +}; + +describe('gravitompId module', function () { + let getCookieStub; + + beforeEach(function (done) { + getCookieStub = sinon.stub(storage, 'getCookie'); + done(); + }); + + afterEach(function () { + getCookieStub.restore(); + }); + + const cookieTestCasesForEmpty = [ + undefined, + null, + '' + ] + + describe('getId()', function () { + it('should return the gravitompId when it exists in cookie', function () { + getCookieStub.withArgs(cookieKey).returns(GRAVITOID_TEST_VALUE); + const id = gravitoIdSystemSubmodule.getId(); + expect(id).to.be.deep.equal({id: {gravitompId: GRAVITOID_TEST_VALUE}}); + }); + + cookieTestCasesForEmpty.forEach(testCase => it('should return the gravitompId when it not exists in cookie', function () { + getCookieStub.withArgs(cookieKey).returns(testCase); + const id = gravitoIdSystemSubmodule.getId(); + expect(id).to.be.deep.equal(undefined); + })); + }); + + describe('decode()', function () { + it('should return the gravitompId when it exists in cookie', function () { + const decoded = gravitoIdSystemSubmodule.decode(GRAVITOID_TEST_OBJ); + expect(decoded).to.be.deep.equal({gravitompId: {id: GRAVITOID_TEST_VALUE}}); + }); + + it('should return the undefined when decode id is not "string"', function () { + const decoded = gravitoIdSystemSubmodule.decode(1); + expect(decoded).to.equal(undefined); + }); + }); +}); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 38e50be9212..61776f58682 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -632,6 +632,17 @@ describe('TheMediaGrid Adapter', function () { getConfigStub.restore(); }); + it('should have site.content.id filled from config ortb2.site.content.id', function () { + const contentId = 'jw_abc'; + + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.site' ? { content: { id: contentId } } : null); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const payload = parseRequest(request.data); + expect(payload.site.content.id).to.equal(contentId); + getConfigStub.restore(); + }); + it('should be right tmax when timeout in config is less then timeout in bidderRequest', function() { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'bidderTimeout' ? 2000 : null); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 8004c8d6f62..b6e5ab86de5 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -3,10 +3,13 @@ import { spec } from 'modules/improvedigitalBidAdapter.js'; import { config } from 'src/config.js'; import { deepClone } from 'src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; +import { deepSetValue } from '../../../src/utils'; describe('Improve Digital Adapter Tests', function () { const METHOD = 'POST'; - const URL = 'https://ad.360yield.com/pb'; + const AD_SERVER_URL = 'https://ad.360yield.com/pb'; + const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; + const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; const INSTREAM_TYPE = 1; const OUTSTREAM_TYPE = 3; @@ -28,21 +31,34 @@ describe('Improve Digital Adapter Tests', function () { sizes: [[300, 250], [160, 600]] }; + const extendBidRequest = deepClone(simpleBidRequest); + extendBidRequest.params.extend = true; + const videoParams = { skip: 1, skipmin: 5, skipafter: 30 } - const instreamBidRequest = deepClone(simpleBidRequest); - instreamBidRequest.mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480] + const instreamBidRequest = { + bidder: 'improvedigital', + params: { + placementId: 123456 + }, + adUnitCode: 'video1', + transactionId: 'vf183e871-fbed-45f0-a427-c8a63c4c01eb', + bidId: '33e9500b21129f', + bidderRequestId: 'v2772c1e566670b', + auctionId: 'v192721e36a0239', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } } }; - const outstreamBidRequest = deepClone(simpleBidRequest); + const outstreamBidRequest = deepClone(instreamBidRequest); outstreamBidRequest.mediaTypes = { video: { context: 'outstream', @@ -89,6 +105,10 @@ describe('Improve Digital Adapter Tests', function () { bids: [simpleBidRequest] }; + const extendBidderRequest = { + bids: [extendBidRequest] + }; + const instreamBidderRequest = { bids: [instreamBidRequest] }; @@ -105,14 +125,17 @@ describe('Improve Digital Adapter Tests', function () { bids: [nativeBidRequest] }; + const gdprConsent = { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', + }; + const bidderRequestGdpr = { bids: [simpleBidRequest], - gdprConsent: { - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', - vendorData: {}, - gdprApplies: true, - addtlConsent: '1~1.35.41.101', - }, + gdprConsent }; const bidderRequestReferrer = { @@ -162,13 +185,22 @@ describe('Improve Digital Adapter Tests', function () { }); describe('buildRequests', function () { + let getConfigStub = null; + + afterEach(function () { + if (getConfigStub) { + getConfigStub.restore(); + getConfigStub = null; + } + }); + it('should make a well-formed request objects', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); - expect(request.url).to.equal(URL); + expect(request.url).to.equal(AD_SERVER_URL); expect(request.bidderRequest).to.deep.equal(bidderRequest); const payload = JSON.parse(request.data); @@ -178,7 +210,7 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.cur).to.be.an('array'); expect(payload.regs).to.not.exist; expect(payload.schain).to.not.exist; - expect(payload.source).to.be.an('object'); + expect(payload.source).to.deep.equal({ ext: {}, tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb' }); expect(payload.device).to.be.an('object'); expect(payload.user).to.not.exist; expect(payload.imp).to.deep.equal([ @@ -198,16 +230,15 @@ describe('Improve Digital Adapter Tests', function () { } } ]); - getConfigStub.restore(); }); it('should make a well-formed request object for multi-format ad unit', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([multiFormatBidRequest], multiFormatBidderRequest)[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); - expect(request.url).to.equal(URL); + expect(request.url).to.equal(AD_SERVER_URL); expect(request.bidderRequest).to.deep.equal(multiFormatBidderRequest); const payload = JSON.parse(request.data); @@ -239,7 +270,28 @@ describe('Improve Digital Adapter Tests', function () { } } ]); - getConfigStub.restore(); + }); + + it('should make a well-formed native request', function () { + const payload = JSON.parse(spec.buildRequests([nativeBidRequest])[0].data); + expect(payload.imp[0].native).to.deep.equal({ + ver: '1.2', + request: '{\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":3,\"required\":1,\"data\":{\"type\":2}}]}' + }); + }); + + it('should not make native request when nativeParams is undefined', function () { + const request = deepClone(nativeBidRequest); + delete request.nativeParams; + const payload = JSON.parse(spec.buildRequests([request])[0].data); + expect(payload.imp[0].native).to.not.exist; + }); + + it('should not make native request when no assets', function () { + const request = deepClone(nativeBidRequest); + request.nativeParams = {}; + const payload = JSON.parse(spec.buildRequests([request])[0].data); + expect(payload.imp[0].native).to.not.exist; }); it('should make a well-formed native request', function () { @@ -284,10 +336,9 @@ describe('Improve Digital Adapter Tests', function () { it('should add currency', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); + getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.cur).to.deep.equal(['JPY']); - getConfigStub.restore(); }); it('should add bid floor', function () { @@ -320,7 +371,7 @@ describe('Improve Digital Adapter Tests', function () { const bidRequest = Object.assign({}, simpleBidRequest); const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data); expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); - expect(payload.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(payload.user.ext.consent).to.equal('CONSENT'); expect(payload.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]); }); @@ -339,6 +390,18 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.regs.ext.us_privacy).to.equal('1YYY'); }); + it('should add COPPA flag', function () { + getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('coppa').returns(true); + let bidRequest = Object.assign({}, simpleBidRequest); + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data); + expect(payload.regs.coppa).to.equal(1); + getConfigStub.withArgs('coppa').returns(false); + bidRequest = Object.assign({}, simpleBidRequest); + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data); + expect(payload.regs.coppa).to.equal(0); + }); + it('should add referrer', function () { const bidRequest = Object.assign({}, simpleBidRequest); const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; @@ -361,7 +424,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should not add video params for banner', function () { - const bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); + const bidRequest = deepClone(simpleBidRequest); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest], bidderRequest)[0]; const payload = JSON.parse(request.data); @@ -369,11 +432,11 @@ describe('Improve Digital Adapter Tests', function () { }); it('should add correct placement value for instream and outstream video', function () { - let bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); + let bidRequest = deepClone(simpleBidRequest); let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].video).to.not.exist; - bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); + bidRequest = deepClone(simpleBidRequest); bidRequest.mediaTypes = { video: { context: 'instream', @@ -388,7 +451,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set video params for instream', function() { - const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + const bidRequest = deepClone(instreamBidRequest); delete bidRequest.mediaTypes.video.playerSize; const videoParams = { mimes: ['video/mp4'], @@ -411,7 +474,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set video playerSize over video params', () => { - const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + const bidRequest = deepClone(instreamBidRequest); bidRequest.params.video = { w: 1024, h: 640 } @@ -422,7 +485,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set skip params only if skip=1', function() { - const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + const bidRequest = deepClone(instreamBidRequest); // 1 const videoTest = { skip: 1, @@ -456,7 +519,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should ignore invalid/unexpected video params', function() { - const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + const bidRequest = deepClone(instreamBidRequest); // 1 const videoTest = { skip: 1, @@ -472,7 +535,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set video params for outstream', function() { - const bidRequest = JSON.parse(JSON.stringify(outstreamBidRequest)); + const bidRequest = deepClone(outstreamBidRequest); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest])[0]; const payload = JSON.parse(request.data); @@ -486,7 +549,7 @@ describe('Improve Digital Adapter Tests', function () { }); // it('should set video params for multi-format', function() { - const bidRequest = JSON.parse(JSON.stringify(multiFormatBidRequest)); + const bidRequest = deepClone(multiFormatBidRequest); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest])[0]; const payload = JSON.parse(request.data); @@ -536,19 +599,34 @@ describe('Improve Digital Adapter Tests', function () { }); it('should return one request in a single request mode', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.singleRequest').returns(true); - const requests = spec.buildRequests([ - simpleBidRequest, - simpleSmartTagBidRequest - ], bidderRequest); + const requests = spec.buildRequests([ simpleBidRequest, instreamBidRequest ], bidderRequest); expect(requests).to.be.an('array'); expect(requests.length).to.equal(1); - getConfigStub.restore(); + expect(requests[0].url).to.equal(AD_SERVER_URL); + const request = JSON.parse(requests[0].data); + expect(request.imp.length).to.equal(2); + expect(request.imp[0].banner).to.exist; + expect(request.imp[1].video).to.exist; + }); + + it('should create one request per endpoint in a single request mode', function () { + getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.singleRequest').returns(true); + const requests = spec.buildRequests([ extendBidRequest, simpleBidRequest, instreamBidRequest ], bidderRequest); + expect(requests).to.be.an('array'); + expect(requests.length).to.equal(2); + expect(requests[0].url).to.equal(EXTEND_URL); + expect(requests[1].url).to.equal(AD_SERVER_URL); + const adServerRequest = JSON.parse(requests[1].data); + expect(adServerRequest.imp.length).to.equal(2); + expect(adServerRequest.imp[0].banner).to.exist; + expect(adServerRequest.imp[1].video).to.exist; }); it('should set Prebid sizes in bid request', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; const payload = JSON.parse(request.data); @@ -558,11 +636,10 @@ describe('Improve Digital Adapter Tests', function () { { w: 160, h: 600 } ] }); - getConfigStub.restore(); }); it('should not add single size filter when using Prebid sizes', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const bidRequest = Object.assign({}, simpleBidRequest); const size = { @@ -578,7 +655,6 @@ describe('Improve Digital Adapter Tests', function () { { w: 160, h: 600 } ] }); - getConfigStub.restore(); }); it('should set GPID and Instl Signal', function () { @@ -620,29 +696,27 @@ describe('Improve Digital Adapter Tests', function () { }); it('should not set site when app is defined in FPD', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('ortb2.app').returns({ content: 'XYZ' }); let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; let payload = JSON.parse(request.data); expect(payload.site).does.not.exist; expect(payload.app).does.exist; expect(payload.app.content).does.exist.and.equal('XYZ'); - getConfigStub.restore(); }); it('should not set site when app is defined in CONFIG', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns({ content: 'XYZ' }); let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; let payload = JSON.parse(request.data); expect(payload.site).does.not.exist; expect(payload.app).does.exist; expect(payload.app.content).does.exist.and.equal('XYZ'); - getConfigStub.restore(); }); it('should set correct site params', function () { - let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('site').returns({ content: 'XYZ', page: 'https://improveditigal.com/', @@ -669,11 +743,10 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.site.content).does.exist.and.equal('ZZZ'); expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); expect(payload.site.domain).does.exist.and.equal('blah.com'); - getConfigStub.restore(); }); it('should set pageUrl as site param', function () { - let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('pageUrl').returns('https://improvidigital.com/test-page'); let request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; let payload = JSON.parse(request.data); @@ -686,17 +759,64 @@ describe('Improve Digital Adapter Tests', function () { payload = JSON.parse(request.data); expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); expect(payload.site.domain).does.exist.and.equal('blah.com'); - getConfigStub.restore(); }); it('should set site when app not available', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns(undefined); let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; let payload = JSON.parse(request.data); expect(payload.site).does.exist; expect(payload.app).does.not.exist; - getConfigStub.restore(); + }); + + it('should set extend params when extend mode enabled from global configuration', function () { + getConfigStub = sinon.stub(config, 'getConfig'); + const bannerRequest = deepClone(simpleBidRequest); + const keyValues = { testKey: [ 'testValue' ] }; + bannerRequest.params.keyValues = keyValues; + + getConfigStub.withArgs('improvedigital.extend').returns(true); + const requests = spec.buildRequests([bannerRequest, instreamBidRequest], bidderRequest); + expect(requests[0].method).to.equal(METHOD); + expect(requests[0].url).to.equal(EXTEND_URL); + expect(requests[1].url).to.equal(EXTEND_URL); + // banner + let payload = JSON.parse(requests[0].data); + expect(payload.imp[0].ext.bidder).to.not.exist; + expect(payload.imp[0].ext.prebid.bidder.improvedigital).to.deep.equal({ + placementId: 1053688, + keyValues + }); + expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal('1053688'); + // video + payload = JSON.parse(requests[1].data); + expect(payload.imp[0].ext.bidder).to.not.exist; + expect(payload.imp[0].ext.prebid.bidder.improvedigital.placementId).to.equal(123456); + expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal('123456'); + }); + + it('should set extend url when extend mode enabled in adunit params', function () { + const bidRequest = deepClone(extendBidRequest); + let request = spec.buildRequests([bidRequest], { bids: [bidRequest] })[0]; + expect(request.url).to.equal(EXTEND_URL); + + getConfigStub = sinon.stub(config, 'getConfig'); + + // adunit param takes precedence over the global config + getConfigStub.withArgs('improvedigital.extend').returns(false); + request = spec.buildRequests([bidRequest], { bids: [bidRequest] })[0]; + expect(request.url).to.equal(EXTEND_URL); + + bidRequest.params.extend = false; + getConfigStub.withArgs('improvedigital.extend').returns(true); + request = spec.buildRequests([bidRequest], { bids: [bidRequest] })[0]; + expect(request.url).to.equal(AD_SERVER_URL); + + const requests = spec.buildRequests([bidRequest, instreamBidRequest], { bids: [bidRequest, instreamBidRequest] }); + expect(requests.length).to.equal(2); + expect(requests[0].url).to.equal(AD_SERVER_URL); + expect(requests[1].url).to.equal(EXTEND_URL); }); }); @@ -730,7 +850,16 @@ describe('Improve Digital Adapter Tests', function () { ], 'seat': 'improvedigital' } - ] + ], + ext: { + improvedigital: { + sync: [ + 'https://link1', + 'https://link2', + 'https://link3', + ] + } + } } }; @@ -790,7 +919,7 @@ describe('Improve Digital Adapter Tests', function () { sync: [ 'https://link1', 'https://link2', - 'https://link3', + 'https://link4', ] } } @@ -989,7 +1118,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set dealId correctly', function () { - const response = JSON.parse(JSON.stringify(serverResponse)); + const response = deepClone(serverResponse); let bids; delete response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id; @@ -1019,14 +1148,14 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set currency', function () { - const response = JSON.parse(JSON.stringify(serverResponse)); + const response = deepClone(serverResponse); response.body.cur = 'eur'; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].currency).to.equal('EUR'); }); it('should return empty array for bad response or no price', function () { - let response = JSON.parse(JSON.stringify(serverResponse)); + let response = deepClone(serverResponse); let bids; // Price missing or 0 @@ -1041,13 +1170,13 @@ describe('Improve Digital Adapter Tests', function () { expect(bids).to.deep.equal([]); // errorCode present - response = JSON.parse(JSON.stringify(serverResponse)); + response = deepClone(serverResponse); response.body.seatbid[0].bid[0].errorCode = undefined; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); // adm and native missing - response = JSON.parse(JSON.stringify(serverResponse)); + response = deepClone(serverResponse); delete response.body.seatbid[0].bid[0].adm; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); @@ -1057,7 +1186,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set netRevenue', function () { - const response = JSON.parse(JSON.stringify(serverResponse)); + const response = deepClone(serverResponse); response.body.seatbid[0].bid[0].ext.improvedigital.is_net = true; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].netRevenue).to.equal(true); @@ -1065,7 +1194,7 @@ describe('Improve Digital Adapter Tests', function () { it('should set advertiserDomains', function () { const adomain = ['domain.com']; - const response = JSON.parse(JSON.stringify(serverResponse)); + const response = deepClone(serverResponse); response.body.seatbid[0].bid[0].adomain = adomain; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].meta.advertiserDomains).to.equal(adomain); @@ -1108,10 +1237,19 @@ describe('Improve Digital Adapter Tests', function () { }); it('should return a well-formed outstream video bid for multi-format ad unit', function () { - const bids = spec.interpretResponse(serverResponseVideo, {bidderRequest: multiFormatBidderRequest}); + const videoResponse = deepClone(serverResponseVideo); + let bids = spec.interpretResponse(videoResponse, {bidderRequest: multiFormatBidderRequest}); expect(bids[0].renderer).to.exist; delete (bids[0].renderer); expect(bids).to.deep.equal(expectedBidOutstreamVideo); + + videoResponse.body.seatbid[0].bid[0].adm = '

Ad from IncrementX

', + slotBidId: 'bid-id-123456', + nurl: 'htt://nurl.com', + statusText: 'Success' + } + }; + + let expectedResponse = [{ + requestId: 'bid-id-123456', + cpm: '0.7', + currency: 'USD', + netRevenue: false, + width: '300', + height: '250', + creativeId: 0, + ttl: 300, + ad: '

Ad from IncrementX

', + meta: { + mediaType: 'banner', + advertiserDomains: [] + } + }]; + + it('should correctly interpret valid banner response', function () { + let result = spec.interpretResponse(serverResponse); + expect(result).to.deep.equal(expectedResponse); + }); + }); +}); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 0e1a854b67c..9fe3bd8fb22 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -193,6 +193,35 @@ describe('IndexexchangeAdapter', function () { } ]; + const DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS = [ + { + bidder: 'ix', + params: { + siteId: '456' + }, + sizes: [400, 100], + mediaTypes: { + video: { + context: 'instream', + playerSize: [[400, 100]], + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [2] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_VIDEO_VALID_BID = [ { bidder: 'ix', @@ -236,7 +265,7 @@ describe('IndexexchangeAdapter', function () { mediaTypes: { video: { context: 'outstream', - playerSize: [600, 700] + playerSize: [[600, 700]] }, banner: { sizes: [[300, 250], [300, 600], [400, 500]] @@ -271,7 +300,7 @@ describe('IndexexchangeAdapter', function () { mediaTypes: { video: { context: 'outstream', - playerSize: [300, 250] + playerSize: [[300, 250]] }, banner: { sizes: [[300, 250], [300, 600]] @@ -371,7 +400,10 @@ describe('IndexexchangeAdapter', function () { ], seat: '3971' } - ] + ], + ext: { + videoplayerurl: 'https://test.com/video-renderer.js' + } }; const DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET = { @@ -564,6 +596,24 @@ describe('IndexexchangeAdapter', function () { }); describe('isBidRequestValid', function () { + it('should return false if outstream player size is less than 300x250 and IX renderer is preferred', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + bid.mediaTypes.video.playerSize = [[300, 249]]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true if outstream player size is less than 300x250 and IX renderer is not preferred', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.renderer = { + url: 'test', + render: () => {} + }; + bid.mediaTypes.video.context = 'outstream'; + bid.mediaTypes.video.playerSize = [[300, 249]]; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return true when required params found for a banner or video ad', function () { expect(spec.isBidRequestValid(DEFAULT_BANNER_VALID_BID[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_VIDEO_VALID_BID[0])).to.equal(true); @@ -624,7 +674,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes = { video: { - playerSize: [300, 250] + playerSize: [[300, 250]] } }; bid.params.size = [100, 200]; @@ -695,22 +745,6 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should set bid[].renderer if renderer not defined at mediaType.video level', function () { - const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: DEFAULT_MULTIFORMAT_BANNER_VALID_BID - }); - expect(bid[0].renderer).to.be.exist; - }); - - it('should not set bid[].renderer if renderer defined at mediaType.video level', function () { - const outstreamAdUnit = DEFAULT_MULTIFORMAT_BANNER_VALID_BID; - outstreamAdUnit[0].mediaTypes.video.renderer = {} - const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: DEFAULT_MULTIFORMAT_BANNER_VALID_BID - }); - expect(bid[0].renderer).to.be.undefined; - }); - it('should return false when there is only bidFloor', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; @@ -1835,7 +1869,6 @@ describe('IndexexchangeAdapter', function () { describe('request should contain both banner and video requests', function () { const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]]); - it('should have banner request', () => { const bannerImpression = JSON.parse(request[0].data.r).imp[0]; @@ -2177,6 +2210,16 @@ describe('IndexexchangeAdapter', function () { const { ext: { gpid } } = JSON.parse(requests[0].data.r).imp[0]; expect(gpid).to.equal(GPID); }); + + it('should build video request when if video obj is not provided at params level', () => { + const request = spec.buildRequests([DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0]]); + const videoImpression = JSON.parse(request[0].data.r).imp[0]; + + expect(JSON.parse(request[0].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(videoImpression.id).to.equal(DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0].bidId); + expect(videoImpression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0].mediaTypes.video.playerSize[0][0]); + expect(videoImpression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0].mediaTypes.video.playerSize[0][1]); + }); }); describe('buildRequestMultiFormat', function () { @@ -2246,8 +2289,8 @@ describe('IndexexchangeAdapter', function () { expect(JSON.parse(request[1].data.r).imp).to.have.lengthOf(1); expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); expect(videoImpression.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); - expect(videoImpression.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0]); - expect(videoImpression.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[1]); + expect(videoImpression.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0][0]); + expect(videoImpression.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0][1]); }); it('should contain all correct IXdiag properties', function () { @@ -2255,10 +2298,11 @@ describe('IndexexchangeAdapter', function () { expect(diagObj.iu).to.equal(0); expect(diagObj.nu).to.equal(0); expect(diagObj.ou).to.equal(2); - expect(diagObj.ren).to.equal(false); + expect(diagObj.ren).to.equal(true); expect(diagObj.mfu).to.equal(2); expect(diagObj.allu).to.equal(2); expect(diagObj.version).to.equal('$prebid.version$'); + expect(diagObj.url).to.equal('http://localhost:9876/context.html') }); }); }); @@ -2489,6 +2533,68 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should set bid[].renderer if renderer not defined at mediaType.video level', function () { + const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { + data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: DEFAULT_MULTIFORMAT_BANNER_VALID_BID + }); + expect(bid[0].renderer).to.exist; + }); + + it('should set renderer URL by parsing video response', function () { + const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { + data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: DEFAULT_MULTIFORMAT_BANNER_VALID_BID + }); + expect(bid[0].renderer.url).to.equal(DEFAULT_VIDEO_BID_RESPONSE.ext.videoplayerurl); + }); + + it('should not set bid[].renderer if renderer defined at mediaType.video level', function () { + let outstreamAdUnit = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID); + outstreamAdUnit[0].mediaTypes.video.renderer = { + url: 'test', + render: function() {} + }; + const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { + data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: outstreamAdUnit + }); + expect(bid[0].renderer).to.be.undefined; + }); + + it('should not set bid[].renderer if renderer defined at the ad unit level', function () { + let outstreamAdUnit = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID); + outstreamAdUnit[0].renderer = { + url: 'test', + render: function() {} + }; + const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { + data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: outstreamAdUnit + }); + expect(bid[0].renderer).to.be.undefined; + }); + + it('should set bid[].renderer if ad unit renderer is invalid', function () { + let outstreamAdUnit = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID); + outstreamAdUnit[0].mediaTypes.video.renderer = { + url: 'test' + }; + const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { + data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: outstreamAdUnit + }); + expect(bid[0].renderer).to.exist; + }); + + it('should set bid[].renderer if ad unit renderer is a backup', function () { + let outstreamAdUnit = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID); + outstreamAdUnit[0].mediaTypes.video.renderer = { + url: 'test', + render: function() {}, + backupOnly: true + }; + const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { + data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: outstreamAdUnit + }); + expect(bid[0].renderer).to.exist; + }); + it('should get correct bid response for video ad and set bid.vastXml when mtype is 2 (video)', function () { const expectedParse = [ { @@ -2710,7 +2816,7 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('LocalStorage ixdiag', () => { + describe('LocalStorage error codes', () => { let TODAY = new Date().toISOString().slice(0, 10); const key = 'ixdiag'; @@ -2837,7 +2943,7 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.be.true; spec.buildRequests([bid]); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.VIDEO_DURATION_INVALID]: 2 } }); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.VIDEO_DURATION_INVALID]: 3 } }); }); it('should increment errors for errorCode', () => { diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index 0a6ba272970..bc44e594864 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -285,6 +285,23 @@ describe('jixie Adapter', function () { expect(payload.device).to.deep.include(content); }); + it('schain info should be accessible when available', function () { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'ssp.test', + sid: '00001', + hp: 1 + }] + }; + const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { schain: schain }); + const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + const payload = JSON.parse(request.data); + expect(payload.schain).to.deep.equal(schain); + expect(payload.schain).to.deep.include(schain); + }); + it('should populate eids when supported userIds are available', function () { const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { userId: { diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 6f5a0008783..1515ed0820e 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -451,6 +451,17 @@ describe('kargo adapter tests', function () { adm: '
', width: 300, height: 250, + mediaType: 'banner', + metadata: {}, + currency: 'EUR' + }, + 5: { + id: 'bar', + cpm: 2.5, + adm: '', + width: 300, + height: 250, + mediaType: 'video', metadata: {}, currency: 'EUR' } @@ -476,6 +487,11 @@ describe('kargo adapter tests', function () { params: { placementId: 'bar' } + }, { + bidId: 5, + params: { + placementId: 'bar' + } }] }); var expectation = [{ @@ -489,7 +505,10 @@ describe('kargo adapter tests', function () { dealId: undefined, netRevenue: true, currency: 'USD', - meta: undefined + mediaType: 'banner', + meta: { + mediaType: 'banner' + } }, { requestId: '2', cpm: 2.5, @@ -501,7 +520,9 @@ describe('kargo adapter tests', function () { dealId: 'dmpmptest1234', netRevenue: true, currency: 'USD', + mediaType: 'banner', meta: { + mediaType: 'banner', clickUrl: 'https://foobar.com', advertiserDomains: ['https://foobar.com'] } @@ -516,7 +537,10 @@ describe('kargo adapter tests', function () { dealId: undefined, netRevenue: true, currency: 'USD', - meta: undefined + mediaType: 'banner', + meta: { + mediaType: 'banner' + } }, { requestId: '4', cpm: 2.5, @@ -528,7 +552,25 @@ describe('kargo adapter tests', function () { dealId: undefined, netRevenue: true, currency: 'EUR', - meta: undefined + mediaType: 'banner', + meta: { + mediaType: 'banner' + } + }, { + requestId: '5', + cpm: 2.5, + width: 300, + height: 250, + vastXml: '', + ttl: 300, + creativeId: 'bar', + dealId: undefined, + netRevenue: true, + currency: 'EUR', + mediaType: 'video', + meta: { + mediaType: 'video' + } }]; expect(resp).to.deep.equal(expectation); }); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 76c2c287989..8b45fead744 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -16,7 +16,7 @@ function createBidderRequest(auctionId, timeout, pageUrl) { } function createValidBidRequest(params, bidId, sizes) { - return { + const validBidRequest = { adUnitCode: 'adunit-code', bidId: bidId || '22c4871113f461', bidder: 'kobler', @@ -28,11 +28,12 @@ function createValidBidRequest(params, bidId, sizes) { sizes: sizes || [[300, 250], [320, 100]] } }, - params: params || { - placementId: 'tpw58278' - }, transactionTd: '04314114-15bd-4638-8664-bdb8bdc60bff' }; + if (params) { + validBidRequest.params = params; + } + return validBidRequest; } describe('KoblerAdapter', function () { @@ -56,7 +57,7 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should not accept a request without params as valid', function () { + it('should not accept a request without mediaTypes and sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589' }; @@ -66,11 +67,56 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should not accept a request without placementId as valid', function () { + it('should not accept a request without mediaTypes and string sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', - params: { - someParam: 'abc' + sizes: 'string' + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes and empty sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [] + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes.banner and sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: {} + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes.banner and empty sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [], + mediaTypes: {} + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and string mediaTypes.banner as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: 'string' } }; @@ -79,12 +125,67 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should accept a request with bidId and placementId as valid', function () { + it('should not accept a request without sizes and mediaTypes.banner.sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', - params: { - someParam: 'abc', - placementId: '8bde0923-1409-4253-9594-495b58d931ba' + mediaTypes: { + banner: {} + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and string mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: 'string' + } + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and empty mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: [] + } + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should accept a request with bidId and sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [[5, 5]] + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + + it('should accept a request with bidId and mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: [[0, 0]] + } } }; @@ -114,13 +215,10 @@ describe('KoblerAdapter', function () { const firstSize = [400, 800]; const secondSize = [450, 950]; const sizes = [firstSize, secondSize]; - const placementId = 'tsjs86325'; const bidId = '3a56a019-4835-4f75-811c-76fac6853a2c'; const validBidRequests = [ createValidBidRequest( - { - placementId: placementId - }, + undefined, bidId, sizes ) @@ -132,7 +230,6 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp.length).to.be.equal(1); expect(openRtbRequest.imp[0].id).to.be.equal(bidId); - expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); expect(openRtbRequest.imp[0].banner.w).to.be.equal(firstSize[0]); expect(openRtbRequest.imp[0].banner.h).to.be.equal(firstSize[1]); expect(openRtbRequest.imp[0].banner.format.length).to.be.equal(2); @@ -163,40 +260,31 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp[0].banner.format[0].h).to.be.equal(0); }); - it('should use 0 as default position', function () { - const validBidRequests = [createValidBidRequest()]; - const bidderRequest = createBidderRequest(); - - const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); - - expect(openRtbRequest.imp.length).to.be.equal(1); - expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(0); - }); - - it('should read zip from valid bid requests', function () { - const zip = '700 02'; + it('should read test from valid bid requests', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'nmah8324234', - zip: zip + test: true } ) ]; const bidderRequest = createBidderRequest(); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'); - expect(openRtbRequest.device.geo.zip).to.be.equal(zip); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('example.com'); + expect(openRtbRequest.test).to.be.equal(1); }); - it('should read test from valid bid requests', function () { + it('should read pageUrl from config when testing', function () { + config.setConfig({ + pageUrl: 'https://testing-url.com' + }); const validBidRequests = [ createValidBidRequest( { - placementId: 'zwop842799', test: true } ) @@ -204,49 +292,40 @@ describe('KoblerAdapter', function () { const bidderRequest = createBidderRequest(); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('https://testing-url.com'); expect(openRtbRequest.test).to.be.equal(1); }); - it('should read floorPrice from valid bid requests', function () { - const floorPrice = 4.343; + it('should not read pageUrl from config when not testing', function () { + config.setConfig({ + pageUrl: 'https://testing-url.com' + }); const validBidRequests = [ - createValidBidRequest( - { - placementId: 'oqr3224234', - floorPrice: floorPrice - } - ) + createValidBidRequest() ]; - const bidderRequest = createBidderRequest(); + const bidderRequest = createBidderRequest( + 'f85d61cc-ed11-4b6c-aefb-87943263cedb', + 2000, + 'https://non-testing-url.net' + ); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid.essrtb.com/bid/prebid_rtb_call'); - expect(openRtbRequest.imp.length).to.be.equal(1); - expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPrice); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('https://non-testing-url.net'); + expect(openRtbRequest.test).to.be.equal(0); }); - it('should read position from valid bid requests', function () { - const placementId = 'yzksf234592'; + it('should read floorPrice from valid bid requests', function () { + const floorPrice = 4.343; const validBidRequests = [ createValidBidRequest( { - placementId: placementId, - position: 1 - } - ), - createValidBidRequest( - { - placementId: placementId, - position: 2 - } - ), - createValidBidRequest( - { - placementId: placementId, - position: 3 + floorPrice: floorPrice } ) ]; @@ -255,13 +334,8 @@ describe('KoblerAdapter', function () { const result = spec.buildRequests(validBidRequests, bidderRequest); const openRtbRequest = JSON.parse(result.data); - expect(openRtbRequest.imp.length).to.be.equal(3); - expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(1); - expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); - expect(openRtbRequest.imp[1].banner.ext.kobler.pos).to.be.equal(2); - expect(openRtbRequest.imp[1].tagid).to.be.equal(placementId); - expect(openRtbRequest.imp[2].banner.ext.kobler.pos).to.be.equal(3); - expect(openRtbRequest.imp[2].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPrice); }); it('should read dealIds from valid bid requests', function () { @@ -270,13 +344,11 @@ describe('KoblerAdapter', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'rsl1239823', dealIds: dealIds1 } ), createValidBidRequest( { - placementId: 'pqw234232', dealIds: dealIds2 } ) @@ -353,10 +425,7 @@ describe('KoblerAdapter', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'pcha322364', - zip: '0015', floorPrice: 5.6234, - position: 1, dealIds: ['623472534328234'] }, '953ee65d-d18a-484f-a840-d3056185a060', @@ -364,19 +433,14 @@ describe('KoblerAdapter', function () { ), createValidBidRequest( { - placementId: 'sdfgoi32y4', floorPrice: 3.2543, - position: 2, dealIds: ['92368234753283', '263845832942'] }, '8320bf79-9d90-4a17-87c6-5d505706a921', [[400, 500], [200, 250], [300, 350]] ), createValidBidRequest( - { - placementId: 'gwms2738647', - position: 3 - }, + undefined, 'd0de713b-32e3-4191-a2df-a007f08ffe72', [[800, 900]] ) @@ -406,14 +470,8 @@ describe('KoblerAdapter', function () { } ], w: 400, - h: 600, - ext: { - kobler: { - pos: 1 - } - } + h: 600 }, - tagid: 'pcha322364', bidfloor: 5.6234, bidfloorcur: 'USD', pmp: { @@ -442,14 +500,8 @@ describe('KoblerAdapter', function () { } ], w: 400, - h: 500, - ext: { - kobler: { - pos: 2 - } - } + h: 500 }, - tagid: 'sdfgoi32y4', bidfloor: 3.2543, bidfloorcur: 'USD', pmp: { @@ -473,24 +525,15 @@ describe('KoblerAdapter', function () { } ], w: 800, - h: 900, - ext: { - kobler: { - pos: 3 - } - } + h: 900 }, - tagid: 'gwms2738647', bidfloor: 0, bidfloorcur: 'USD', pmp: {} } ], device: { - devicetype: 2, - geo: { - zip: '0015' - } + devicetype: 2 }, site: { page: 'bid.kobler.no' @@ -531,7 +574,7 @@ describe('KoblerAdapter', function () { dealid: '', w: 320, h: 250, - adm: '', + adm: '', adomain: [ 'https://kobler.no' ] @@ -544,7 +587,7 @@ describe('KoblerAdapter', function () { dealid: '2783483223432342', w: 580, h: 400, - adm: '', + adm: '', adomain: [ 'https://bid.kobler.no' ] @@ -568,7 +611,7 @@ describe('KoblerAdapter', function () { dealId: '', netRevenue: true, ttl: 600, - ad: '', + ad: '', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', meta: { advertiserDomains: [ @@ -586,7 +629,7 @@ describe('KoblerAdapter', function () { dealId: '2783483223432342', netRevenue: true, ttl: 600, - ad: '', + ad: '', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=nbashgufvishdafjk23432&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', meta: { advertiserDomains: [ @@ -628,6 +671,7 @@ describe('KoblerAdapter', function () { } }); spec.onBidWon({ + originalCpm: 1.532, cpm: 8.341, currency: 'NOK', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', @@ -670,22 +714,14 @@ describe('KoblerAdapter', function () { auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ef236c6c-e934-406b-a877-d7be8e8a839a', timeout: 100, - params: [ - { - placementId: 'xrwg62731', - } - ], + params: [], }, { adUnitCode: 'adunit-code-2', auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ca4121c8-9a4a-46ba-a624-e9b64af206f2', timeout: 100, - params: [ - { - placementId: 'bc482234', - } - ], + params: [], } ]); @@ -693,12 +729,12 @@ describe('KoblerAdapter', function () { expect(utils.triggerPixel.getCall(0).args[0]).to.be.equal( 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code&' + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ef236c6c-e934-406b-a877-d7be8e8a839a&timeout=100&' + - 'placement_id=xrwg62731&page_url=' + encodeURIComponent(getRefererInfo().referer) + 'page_url=' + encodeURIComponent(getRefererInfo().referer) ); expect(utils.triggerPixel.getCall(1).args[0]).to.be.equal( 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code-2&' + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ca4121c8-9a4a-46ba-a624-e9b64af206f2&timeout=100&' + - 'placement_id=bc482234&page_url=' + encodeURIComponent(getRefererInfo().referer) + 'page_url=' + encodeURIComponent(getRefererInfo().referer) ); }); }); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 4893f7cbb30..1ea7b5be4ad 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -46,7 +46,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[1]; - expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); + expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*/); const response = { unifiedId: 'a_unified_id', segments: [123, 234] @@ -66,7 +66,7 @@ describe('LiveIntentId', function() { consentString: 'consentDataString' }) liveIntentIdSubmodule.getId(defaultConfigParams); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&gdpr_consent=consentDataString.*/); + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString.*/); }); it('should fire an event when getId and a hash is provided', function() { diff --git a/test/spec/modules/loglyliftBidAdapter_spec.js b/test/spec/modules/loglyliftBidAdapter_spec.js index baa6ff05f39..d091c4b042d 100644 --- a/test/spec/modules/loglyliftBidAdapter_spec.js +++ b/test/spec/modules/loglyliftBidAdapter_spec.js @@ -50,6 +50,12 @@ describe('loglyliftBidAdapter', function () { }, sponsoredBy: { required: true + }, + cta: { + required: true + }, + privacyLink: { + required: true } } } @@ -114,6 +120,8 @@ describe('loglyliftBidAdapter', function () { ], sponsoredBy: 'logly', title: 'Native Title', + privacyLink: 'https://www.logly.co.jp/optout.html', + cta: '詳細はこちら', } }], } diff --git a/test/spec/modules/medianetRtdProvider_spec.js b/test/spec/modules/medianetRtdProvider_spec.js index 7d73ecd5d44..82ea2107a89 100644 --- a/test/spec/modules/medianetRtdProvider_spec.js +++ b/test/spec/modules/medianetRtdProvider_spec.js @@ -1,6 +1,7 @@ import * as medianetRTD from '../../../modules/medianetRtdProvider.js'; import * as sinon from 'sinon'; import { assert } from 'chai'; +import * as utils from '../../../src/utils.js'; let sandbox; let setDataSpy; @@ -19,6 +20,9 @@ const conf = { describe('medianet realtime module', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); + const insertStub = sandbox.stub(utils, 'insertElement') + insertStub.withArgs(sinon.match.any, sinon.match.any, 'head') + .returns(() => void 0) window.mnjs = window.mnjs || {}; window.mnjs.que = window.mnjs.que || []; window.mnjs.setData = setDataSpy = sandbox.spy(); diff --git a/test/spec/modules/naveggIdSystem_spec.js b/test/spec/modules/naveggIdSystem_spec.js index c0973a05372..2c4f1cda859 100644 --- a/test/spec/modules/naveggIdSystem_spec.js +++ b/test/spec/modules/naveggIdSystem_spec.js @@ -1,6 +1,15 @@ import { naveggIdSubmodule, storage } from 'modules/naveggIdSystem.js'; describe('naveggId', function () { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + afterEach(() => { + sandbox.restore(); + }); + it('should NOT find navegg id', function () { let id = naveggIdSubmodule.getId(); @@ -14,8 +23,23 @@ describe('naveggId', function () { }) it('getId() should return "test-nvggid" id from local storage NAVEGG_ID', function() { - sinon.stub(storage, 'getDataFromLocalStorage').withArgs('nvggid').returns('test-ninvggidd'); + storage.getDataFromLocalStorage.callsFake(() => 'test-ninvggidd') + let id = naveggIdSubmodule.getId(); expect(id).to.be.deep.equal({id: 'test-ninvggidd'}) }) + + it('getId() should return "test-nvggid" id from local storage NAV0', function() { + storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nav0') + + let id = naveggIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: 'nvgid-nav0'}) + }) + + it('getId() should return "test-nvggid" id from local storage NVG0', function() { + storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nvg0') + + let id = naveggIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: 'nvgid-nvg0'}) + }) }); diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index 0d1a530044f..028c780d9fb 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -226,6 +226,15 @@ describe('OguryBidAdapter', function () { }); describe('buildRequests', function () { + const stubbedWidth = 200 + const stubbedHeight = 600 + const stubbedWidthMethod = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { + return stubbedWidth; + }); + const stubbedHeightMethod = sinon.stub(window.top.document.documentElement, 'clientHeight').get(function() { + return stubbedHeight; + }); + const defaultTimeout = 1000; const expectedRequestObject = { id: bidRequests[0].auctionId, @@ -270,10 +279,19 @@ describe('OguryBidAdapter', function () { }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.2.11' + adapterversion: '1.2.12' + }, + device: { + w: stubbedWidth, + h: stubbedHeight } }; + after(function() { + stubbedWidthMethod.restore(); + stubbedHeightMethod.restore(); + }); + it('sends bid request to ENDPOINT via POST', function () { const validBidRequests = utils.deepClone(bidRequests) @@ -290,6 +308,166 @@ describe('OguryBidAdapter', function () { expect(request.data.regs.ext.gdpr).to.be.a('number'); }); + describe('getClientWidth', () => { + function testGetClientWidth(testGetClientSizeParams) { + const stubbedClientWidth = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { + return testGetClientSizeParams.docClientSize + }) + + const stubbedInnerWidth = sinon.stub(window.top, 'innerWidth').get(function() { + return testGetClientSizeParams.innerSize + }) + + const stubbedOuterWidth = sinon.stub(window.top, 'outerWidth').get(function() { + return testGetClientSizeParams.outerSize + }) + + const stubbedWidth = sinon.stub(window.top.screen, 'width').get(function() { + return testGetClientSizeParams.screenSize + }) + + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data.device.w).to.equal(testGetClientSizeParams.expectedSize); + + stubbedClientWidth.restore(); + stubbedInnerWidth.restore(); + stubbedOuterWidth.restore(); + stubbedWidth.restore(); + } + + it('should get documentElementClientWidth by default', () => { + testGetClientWidth({ + docClientSize: 22, + innerSize: 50, + outerSize: 45, + screenSize: 10, + expectedSize: 22, + }) + }) + + it('should get innerWidth as first fallback', () => { + testGetClientWidth({ + docClientSize: undefined, + innerSize: 700, + outerSize: 650, + screenSize: 10, + expectedSize: 700, + }) + }) + + it('should get outerWidth as second fallback', () => { + testGetClientWidth({ + docClientSize: undefined, + innerSize: undefined, + outerSize: 650, + screenSize: 10, + expectedSize: 650, + }) + }) + + it('should get screenWidth as last fallback', () => { + testGetClientWidth({ + docClientSize: undefined, + innerSize: undefined, + outerSize: undefined, + screenSize: 10, + expectedSize: 10, + }); + }); + + it('should return 0 if all window width values are undefined', () => { + testGetClientWidth({ + docClientSize: undefined, + innerSize: undefined, + outerSize: undefined, + screenSize: undefined, + expectedSize: 0, + }); + }); + }); + + describe('getClientHeight', () => { + function testGetClientHeight(testGetClientSizeParams) { + const stubbedClientHeight = sinon.stub(window.top.document.documentElement, 'clientHeight').get(function() { + return testGetClientSizeParams.docClientSize + }) + + const stubbedInnerHeight = sinon.stub(window.top, 'innerHeight').get(function() { + return testGetClientSizeParams.innerSize + }) + + const stubbedOuterHeight = sinon.stub(window.top, 'outerHeight').get(function() { + return testGetClientSizeParams.outerSize + }) + + const stubbedHeight = sinon.stub(window.top.screen, 'height').get(function() { + return testGetClientSizeParams.screenSize + }) + + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data.device.h).to.equal(testGetClientSizeParams.expectedSize); + + stubbedClientHeight.restore(); + stubbedInnerHeight.restore(); + stubbedOuterHeight.restore(); + stubbedHeight.restore(); + } + + it('should get documentElementClientHeight by default', () => { + testGetClientHeight({ + docClientSize: 420, + innerSize: 500, + outerSize: 480, + screenSize: 230, + expectedSize: 420, + }); + }); + + it('should get innerHeight as first fallback', () => { + testGetClientHeight({ + docClientSize: undefined, + innerSize: 500, + outerSize: 480, + screenSize: 230, + expectedSize: 500, + }); + }); + + it('should get outerHeight as second fallback', () => { + testGetClientHeight({ + docClientSize: undefined, + innerSize: undefined, + outerSize: 480, + screenSize: 230, + expectedSize: 480, + }); + }); + + it('should get screenHeight as last fallback', () => { + testGetClientHeight({ + docClientSize: undefined, + innerSize: undefined, + outerSize: undefined, + screenSize: 230, + expectedSize: 230, + }); + }); + + it('should return 0 if all window height values are undefined', () => { + testGetClientHeight({ + docClientSize: undefined, + innerSize: undefined, + outerSize: undefined, + screenSize: undefined, + expectedSize: 0, + }); + }); + }); + it('should not add gdpr infos if not present', () => { const bidderRequestWithoutGdpr = { ...bidderRequest, @@ -481,7 +659,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.2.11', + adapterVersion: '1.2.12', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -498,7 +676,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.2.11', + adapterVersion: '1.2.12', prebidVersion: '$prebid.version$' }] diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 084c533b5b5..5b9f35c5bcf 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -124,8 +124,6 @@ describe('ProxistoreBidAdapter', function () { bid.params['bidFloor'] = 1; let req = spec.buildRequests([bid], bidderRequest); data = JSON.parse(req.data); - // eslint-disable-next-line no-console - console.log(data.bids[0]); expect(data.bids[0].floor).equal(1); bid.getFloor = function () { return { currency: 'USD', floor: 1.0 }; diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 83a13f0db7b..c515f302c32 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,92 +1,91 @@ -import {expect} from 'chai'; -import {spec} from 'modules/sovrnBidAdapter.js'; -import {config} from 'src/config.js'; +import {expect} from 'chai' +import {spec} from 'modules/sovrnBidAdapter.js' +import {config} from 'src/config.js' import * as utils from 'src/utils.js' -const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; +const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$` const baseBidRequest = { - 'bidder': 'sovrn', - 'params': { - 'tagid': 403370 + bidder: 'sovrn', + params: { + tagid: 403370 }, - 'adUnitCode': 'adunit-code', - 'sizes': [ + adUnitCode: 'adunit-code', + sizes: [ [300, 250], [300, 600] ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', } const baseBidderRequest = { refererInfo: { referer: 'http://example.com/page.html', } -}; +} describe('sovrnBidAdapter', function() { describe('isBidRequestValid', function () { it('should return true when required params found', function () { - expect(spec.isBidRequestValid(baseBidRequest)).to.equal(true); - }); + expect(spec.isBidRequestValid(baseBidRequest)).to.equal(true) + }) it('should return false when tagid not passed correctly', function () { const bidRequest = { ...baseBidRequest, - 'params': { + params: { ...baseBidRequest.params, - 'tagid': 'ABCD' + tagid: 'ABCD' }, - }; + } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) it('should return false when require params are not passed', function () { const bidRequest = { ...baseBidRequest, - 'params': {} + params: {} } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) it('should return false when require video params are not passed', function () { const bidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { 'video': { } } } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) + }) describe('buildRequests', function () { describe('basic bid parameters', function() { - const request = spec.buildRequests([baseBidRequest], baseBidderRequest); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([baseBidRequest], baseBidderRequest) + const payload = JSON.parse(request.data) it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); + expect(request.method).to.equal('POST') + }) it('attaches source and version to endpoint URL as query params', function () { expect(request.url).to.equal(ENDPOINT) - }); + }) it('sets the proper banner object', function() { const bannerBidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { banner: {} } } const request = spec.buildRequests([bannerBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -105,7 +104,7 @@ describe('sovrnBidAdapter', function() { const startdelay = 0 const videoBidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { video: { mimes, protocols, @@ -117,7 +116,6 @@ describe('sovrnBidAdapter', function() { } } const request = spec.buildRequests([videoBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -131,9 +129,9 @@ describe('sovrnBidAdapter', function() { }) it('gets correct site info', function() { - expect(payload.site.page).to.equal('http://example.com/page.html'); - expect(payload.site.domain).to.equal('example.com'); - }); + expect(payload.site.page).to.equal('http://example.com/page.html') + expect(payload.site.domain).to.equal('example.com') + }) it('includes the ad unit code in the request', function() { const impression = payload.imp[0] @@ -142,22 +140,21 @@ describe('sovrnBidAdapter', function() { it('converts tagid to string', function () { expect(request.data).to.contain('"tagid":"403370"') - }); + }) }) it('accepts a single array as a size', function() { const singleSizeBidRequest = { ...baseBidRequest, - 'params': { - 'iv': 'vet' + params: { + iv: 'vet' }, - 'sizes': [300, 250], - 'mediaTypes': { + sizes: [300, 250], + mediaTypes: { banner: {} }, } const request = spec.buildRequests([singleSizeBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -176,22 +173,21 @@ describe('sovrnBidAdapter', function() { const request = spec.buildRequests([ivBidRequest], baseBidderRequest) expect(request.url).to.contain('iv=vet') - }); + }) it('sends gdpr info if exists', function () { const bidderRequest = { ...baseBidderRequest, - 'bidderCode': 'sovrn', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - 'consentString': 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', - 'gdprApplies': true + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true }, - 'bids': [baseBidRequest] - }; - + bids: [baseBidRequest] + } const { regs, user } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) expect(regs.ext.gdpr).to.exist.and.to.be.a('number') @@ -203,14 +199,13 @@ describe('sovrnBidAdapter', function() { it('should send us_privacy if bidderRequest has a value for uspConsent', function () { const bidderRequest = { ...baseBidderRequest, - 'bidderCode': 'sovrn', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'uspConsent': '1NYN', - 'bids': [baseBidRequest] + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + uspConsent: '1NYN', + bids: [baseBidRequest] } - const data = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) expect(data.regs.ext['us_privacy']).to.equal(bidderRequest.uspConsent) @@ -219,21 +214,20 @@ describe('sovrnBidAdapter', function() { it('should add schain if present', function() { const schainRequest = { ...baseBidRequest, - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ + schain: { + ver: '1.0', + complete: 1, + nodes: [ { - 'asi': 'directseller.com', - 'sid': '00001', - 'rid': 'BidRequest1', - 'hp': 1 + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1 } ] } } const schainRequests = [schainRequest, baseBidRequest] - const data = JSON.parse(spec.buildRequests(schainRequests, baseBidderRequest).data) expect(data.source.ext.schain.nodes.length).to.equal(1) @@ -248,7 +242,6 @@ describe('sovrnBidAdapter', function() { } } const criteoIdRequests = [criteoIdRequest, baseBidRequest] - const ext = JSON.parse(spec.buildRequests(criteoIdRequests, baseBidderRequest).data).user.ext const firstEID = ext.eids[0] const secondEID = ext.eids[1] @@ -294,7 +287,6 @@ describe('sovrnBidAdapter', function() { bidfloor: 2.00 } } - const request = spec.buildRequests([floorBid], baseBidderRequest) const payload = JSON.parse(request.data) @@ -309,7 +301,6 @@ describe('sovrnBidAdapter', function() { tagid: 1234, bidfloor: 2.00 } - const request = spec.buildRequests([floorBid], baseBidderRequest) const impression = JSON.parse(request.data).imp[0] @@ -359,7 +350,6 @@ describe('sovrnBidAdapter', function() { } } } - const request = spec.buildRequests([fpdBidRequest], baseBidderRequest) const payload = JSON.parse(request.data) @@ -381,7 +371,6 @@ describe('sovrnBidAdapter', function() { } } } - const request = spec.buildRequests([fpdBid], baseBidderRequest) const impression = JSON.parse(request.data).imp[0] @@ -390,102 +379,109 @@ describe('sovrnBidAdapter', function() { expect(impression.ext.deals).to.deep.equal(['seg1', 'seg2']) }) }) - }); + }) describe('interpretResponse', function () { - let response; + let response const baseResponse = { - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 728, - 'height': 90, - 'creativeId': 'creativelycreatedcreativecreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ttl': 90, - 'meta': { advertiserDomains: [] }, - 'ad': decodeURIComponent(``), + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 728, + height: 90, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 90, + meta: { advertiserDomains: [] }, + ad: decodeURIComponent(``), } const videoBid = { - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': 'key%3Dvalue', - 'h': 480, - 'w': 640 + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: 'key%3Dvalue', + h: 480, + w: 640 } const bannerBid = { - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': '', - 'h': 90, - 'w': 728 + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: '', + h: 90, + w: 728 } + beforeEach(function () { response = { body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ + id: '37386aade21a71', + seatbid: [{ + bid: [{ ...bannerBid }] }] } - }; - }); + } + }) it('should get the correct bid response', function () { const expectedResponse = { - ...baseResponse, - 'ttl': 60000, - }; - - const result = spec.interpretResponse(response); + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 728, + height: 90, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 60000, + meta: { advertiserDomains: [] }, + ad: decodeURIComponent(`>`) + } + const result = spec.interpretResponse(response) - expect(result[0]).to.have.deep.keys(expectedResponse) - }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) + }) it('crid should default to the bid id if not on the response', function () { - delete response.body.seatbid[0].bid[0].crid; + delete response.body.seatbid[0].bid[0].crid const expectedResponse = { ...baseResponse, - 'creativeId': response.body.seatbid[0].bid[0].id, - 'ad': decodeURIComponent(``), + creativeId: response.body.seatbid[0].bid[0].id, + ad: decodeURIComponent(``), } + const result = spec.interpretResponse(response) - const result = spec.interpretResponse(response); - - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when dealId is passed', function () { - response.body.seatbid[0].bid[0].dealid = 'baking'; + response.body.seatbid[0].bid[0].dealid = 'baking' const expectedResponse = { ...baseResponse, - 'dealId': 'baking', + dealId: 'baking', } - const result = spec.interpretResponse(response) - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when ttl is set', function () { - response.body.seatbid[0].bid[0].ext = { 'ttl': 480 } + response.body.seatbid[0].bid[0].ext = { ttl: 480 } const expectedResponse = { ...baseResponse, - 'ttl': 480, + ttl: 480, } - const result = spec.interpretResponse(response) expect(result[0]).to.deep.equal(expectedResponse) @@ -494,20 +490,19 @@ describe('sovrnBidAdapter', function() { it('handles empty bid response', function () { const response = { body: { - 'id': '37386aade21a71', - 'seatbid': [] + id: '37386aade21a71', + seatbid: [] } - }; - + } const result = spec.interpretResponse(response) - expect(result.length).to.equal(0); - }); + expect(result.length).to.equal(0) + }) it('should get the correct bid response with 2 different bids', function () { const expectedVideoResponse = { ...baseResponse, - 'vastXml': decodeURIComponent(videoBid.adm) + vastXml: decodeURIComponent(videoBid.adm) } delete expectedVideoResponse.ad @@ -533,90 +528,87 @@ describe('sovrnBidAdapter', function() { expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedResponse)) }) - }); + }) describe('interpretResponse video', function () { - let videoResponse; - const bidAdm = 'key%3Dvalue'; - const decodedBidAdm = decodeURIComponent(bidAdm); + let videoResponse + const bidAdm = 'key%3Dvalue' + const decodedBidAdm = decodeURIComponent(bidAdm) const baseVideoResponse = { - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 640, - 'height': 480, - 'creativeId': 'creativelycreatedcreativecreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'video', - 'ttl': 90, - 'meta': { advertiserDomains: [] }, - 'vastXml': decodedBidAdm + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 640, + height: 480, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'video', + ttl: 90, + meta: { advertiserDomains: [] }, + vastXml: decodedBidAdm } + beforeEach(function () { videoResponse = { body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': bidAdm, - 'h': 480, - 'w': 640 + id: '37386aade21a71', + seatbid: [{ + bid: [{ + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: bidAdm, + h: 480, + w: 640 }] }] } - }; - }); + } + }) it('should get the correct bid response', function () { const expectedResponse = { ...baseVideoResponse, - 'ttl': 60000, - }; - - const result = spec.interpretResponse(videoResponse); + ttl: 60000, + } + const result = spec.interpretResponse(videoResponse) expect(result[0]).to.have.deep.keys(expectedResponse) - }); + }) it('crid should default to the bid id if not on the response', function () { - delete videoResponse.body.seatbid[0].bid[0].crid; + delete videoResponse.body.seatbid[0].bid[0].crid const expectedResponse = { ...baseVideoResponse, - 'creativeId': videoResponse.body.seatbid[0].bid[0].id, + creativeId: videoResponse.body.seatbid[0].bid[0].id, } + const result = spec.interpretResponse(videoResponse) - const result = spec.interpretResponse(videoResponse); - - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when dealId is passed', function () { - videoResponse.body.seatbid[0].bid[0].dealid = 'baking'; + videoResponse.body.seatbid[0].bid[0].dealid = 'baking' const expectedResponse = { ...baseVideoResponse, - 'dealId': 'baking', + dealId: 'baking', } - const result = spec.interpretResponse(videoResponse) - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when ttl is set', function () { videoResponse.body.seatbid[0].bid[0].ext = { 'ttl': 480 } const expectedResponse = { ...baseVideoResponse, - 'ttl': 480, + ttl: 480, } - const result = spec.interpretResponse(videoResponse) expect(result[0]).to.deep.equal(expectedResponse) @@ -625,52 +617,51 @@ describe('sovrnBidAdapter', function() { it('handles empty bid response', function () { const response = { body: { - 'id': '37386aade21a71', - 'seatbid': [] + id: '37386aade21a71', + seatbid: [] } - }; - + } const result = spec.interpretResponse(response) - expect(result.length).to.equal(0); - }); - }); + expect(result.length).to.equal(0) + }) + }) describe('getUserSyncs ', function() { - const syncOptions = { iframeEnabled: true, pixelEnabled: false }; - const iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false }; + const syncOptions = { iframeEnabled: true, pixelEnabled: false } + const iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false } const serverResponse = [ { - 'body': { - 'id': '546956d68c757f', - 'seatbid': [ + body: { + id: '546956d68c757f', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': 'a_448326_16c2ada014224bee815a90d2248322f5', - 'impid': '2a3826aae345f4', - 'price': 1.0099999904632568, - 'nurl': 'http://localhost/rtb/impression?bannerid=220958&campaignid=3890&rtb_tid=15588614-75d2-40ab-b27e-13d2127b3c2e&rpid=1295&seatid=seat1&zoneid=448326&cb=26900712&tid=a_448326_16c2ada014224bee815a90d2248322f5', - 'adm': 'yo a creative', - 'crid': 'cridprebidrtb', - 'w': 160, - 'h': 600 + id: 'a_448326_16c2ada014224bee815a90d2248322f5', + impid: '2a3826aae345f4', + price: 1.0099999904632568, + nurl: 'http://localhost/rtb/impression?bannerid=220958&campaignid=3890&rtb_tid=15588614-75d2-40ab-b27e-13d2127b3c2e&rpid=1295&seatid=seat1&zoneid=448326&cb=26900712&tid=a_448326_16c2ada014224bee815a90d2248322f5', + adm: 'yo a creative', + crid: 'cridprebidrtb', + w: 160, + h: 600 }, { - 'id': 'a_430392_beac4c1515da4576acf6cb9c5340b40c', - 'impid': '3cf96fd26ed4c5', - 'price': 1.0099999904632568, - 'nurl': 'http://localhost/rtb/impression?bannerid=220957&campaignid=3890&rtb_tid=5bc0e68b-3492-448d-a6f9-26fa3fd0b646&rpid=1295&seatid=seat1&zoneid=430392&cb=62735099&tid=a_430392_beac4c1515da4576acf6cb9c5340b40c', - 'adm': 'yo a creative', - 'crid': 'cridprebidrtb', - 'w': 300, - 'h': 250 + id: 'a_430392_beac4c1515da4576acf6cb9c5340b40c', + impid: '3cf96fd26ed4c5', + price: 1.0099999904632568, + nurl: 'http://localhost/rtb/impression?bannerid=220957&campaignid=3890&rtb_tid=5bc0e68b-3492-448d-a6f9-26fa3fd0b646&rpid=1295&seatid=seat1&zoneid=430392&cb=62735099&tid=a_430392_beac4c1515da4576acf6cb9c5340b40c', + adm: 'yo a creative', + crid: 'cridprebidrtb', + w: 300, + h: 250 }, ] } ], - 'ext': { - 'iid': 13487408, + ext: { + iid: 13487408, sync: { pixels: [ { @@ -683,20 +674,19 @@ describe('sovrnBidAdapter', function() { } } }, - 'headers': {} + headers: {} } - ]; + ] it('should return if iid present on server response & iframe syncs enabled', function() { const expectedReturnStatement = { - 'type': 'iframe', - 'url': 'https://ap.lijit.com/beacon?informer=13487408', + type: 'iframe', + url: 'https://ap.lijit.com/beacon?informer=13487408', } + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse) - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse); - - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include gdpr consent string if present', function() { const gdprConsent = { @@ -704,64 +694,63 @@ describe('sovrnBidAdapter', function() { consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, ''); + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, '') - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include us privacy string if present', function() { - const uspString = '1NYN'; + const uspString = '1NYN' const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString); + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString) - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include all privacy strings if present', function() { const gdprConsent = { gdprApplies: 1, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } - const uspString = '1NYN'; + const uspString = '1NYN' const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, } const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString) expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) - }); + }) it('should not return if iid missing on server response', function() { - const returnStatement = spec.getUserSyncs(syncOptions, []); + const returnStatement = spec.getUserSyncs(syncOptions, []) - expect(returnStatement).to.be.empty; - }); + expect(returnStatement).to.be.empty + }) it('should not return if iframe syncs disabled', function() { - const returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse); + const returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse) - expect(returnStatement).to.be.empty; - }); + expect(returnStatement).to.be.empty + }) it('should include pixel syncs', function() { const pixelEnabledOptions = { iframeEnabled: false, pixelEnabled: true } - const otherResponce = { ...serverResponse, - 'body': { + body: { ...serverResponse.body, - 'ext': { - 'iid': 13487408, + ext: { + iid: 13487408, sync: { pixels: [ { @@ -778,33 +767,33 @@ describe('sovrnBidAdapter', function() { const returnStatement = spec.getUserSyncs(pixelEnabledOptions, [...serverResponse, otherResponce]) - expect(returnStatement.length).to.equal(4); + expect(returnStatement.length).to.equal(4) expect(returnStatement).to.deep.include.members([ { type: 'image', url: 'http://idprovider1.com' }, { type: 'image', url: 'http://idprovider2.com' }, { type: 'image', url: 'http://idprovider3.com' }, { type: 'image', url: 'http://idprovider4.com' } - ]); + ]) }) }) describe('prebid 3 upgrade', function() { const bidRequest = { ...baseBidRequest, - 'params': { - 'tagid': '403370' + params: { + tagid: '403370' }, - 'mediaTypes': { - 'banner': { - 'sizes': [ + mediaTypes: { + banner: { + sizes: [ [300, 250], [300, 600] ] } }, - }; - const request = spec.buildRequests([bidRequest], baseBidderRequest); - const payload = JSON.parse(request.data); + } + const request = spec.buildRequests([bidRequest], baseBidderRequest) + const payload = JSON.parse(request.data) it('gets sizes from mediaTypes.banner', function() { expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) @@ -813,8 +802,8 @@ describe('sovrnBidAdapter', function() { }) it('gets correct site info', function() { - expect(payload.site.page).to.equal('http://example.com/page.html'); - expect(payload.site.domain).to.equal('example.com'); + expect(payload.site.page).to.equal('http://example.com/page.html') + expect(payload.site.domain).to.equal('example.com') }) }) }) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js new file mode 100644 index 00000000000..1c4f0fa4c32 --- /dev/null +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -0,0 +1,455 @@ +import {expect} from 'chai'; +import {spec, internal, END_POINT_URL, userData} from 'modules/taboolaBidAdapter.js'; +import {config} from '../../../src/config' +import * as utils from '../../../src/utils' + +describe('Taboola Adapter', function () { + let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie; + + beforeEach(() => { + hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); + getCookie = sinon.stub(userData.storageManager, 'getCookie'); + getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); + localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); + + $$PREBID_GLOBAL$$.bidderSettings = { + taboola: { + storageAllowed: true + } + }; + }); + + afterEach(() => { + hasLocalStorage.restore(); + cookiesAreEnabled.restore(); + getCookie.restore(); + getDataFromLocalStorage.restore(); + localStorageIsEnabled.restore(); + + $$PREBID_GLOBAL$$.bidderSettings = {}; + }) + + const commonBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'placement name' + }, + bidId: 'aa43860a-4644-442a-b5e0-93f268cs4d19', + auctionId: '65746dca-26f3-4186-be13-dfa63469b1b7', + } + + const displayBidRequestParams = { + sizes: [[300, 250], [300, 600]] + } + + describe('isBidRequestValid', function () { + it('should fail when bid is invalid - tagId isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId' + }, + ...displayBidRequestParams + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail when bid is invalid - publisherId isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + tagId: 'below the article' + }, + ...displayBidRequestParams + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail when bid is invalid - sizes isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'below the article' + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should succeed when bid contains valid', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'below the article' + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + }) + + describe('buildRequests', function () { + const defaultBidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/ref', + canonicalUrl: 'https://example.com/' + } + } + + it('should build display request', function () { + const expectedData = { + 'imp': [{ + 'id': 1, + 'banner': { + format: [{ + w: displayBidRequestParams.sizes[0][0], + h: displayBidRequestParams.sizes[0][1] + }, + { + w: displayBidRequestParams.sizes[1][0], + h: displayBidRequestParams.sizes[1][1] + } + ] + }, + 'tagid': commonBidRequest.params.tagId, + 'bidfloor': null, + 'bidfloorcur': 'USD' + }], + 'site': { + 'id': commonBidRequest.params.publisherId, + 'name': commonBidRequest.params.publisherId, + 'domain': window.location.host, + 'page': commonBidderRequest.refererInfo.canonicalUrl, + 'ref': commonBidderRequest.refererInfo.referer, + 'publisher': {'id': commonBidRequest.params.publisherId}, + 'content': {'language': navigator.language} + }, + 'device': {'ua': navigator.userAgent}, + 'source': {'fd': 1}, + 'bcat': [], + 'badv': [], + 'user': { + 'buyeruid': 0, + 'ext': {}, + }, + 'regs': {'coppa': 0, 'ext': {}} + }; + + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + + expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequest.params.publisherId}`); + expect(res.data).to.deep.equal(JSON.stringify(expectedData)); + }) + + it('should pass optional parameters in request', function () { + const optionalParams = { + bidfloor: 0.25, + bidfloorcur: 'EUR' + }; + + const bidRequest = { + ...defaultBidRequest, + params: {...commonBidRequest.params, ...optionalParams} + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].bidfloor).to.deep.equal(0.25); + expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR'); + }); + + it('should pass bidder timeout', function () { + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.tmax).to.equal(500); + }); + + describe('handle privacy segments when building request', function () { + it('should pass GDPR consent', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + }; + + const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + uspConsent: 'consentString' + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.regs.ext.us_privacy).to.equal('consentString'); + }); + + it('should pass coppa consent', function () { + config.setConfig({coppa: true}) + + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) + const resData = JSON.parse(res.data); + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) + + describe('handle userid ', function () { + it('should get user id from local storage', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(true); + localStorageIsEnabled.returns(true); + + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(51525152); + }); + + it('should get user id from cookie if local storage isn`t defined', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); + getCookie.returns('taboola%20global%3Auser-id=12121212'); + + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + + expect(resData.user.buyeruid).to.equal('12121212'); + }); + + it('should get user id from TRC if local storage and cookie isn`t defined', function () { + hasLocalStorage.returns(false); + cookiesAreEnabled.returns(false); + localStorageIsEnabled.returns(false); + + window.TRC = { + user_id: 31313132 + }; + + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(window.TRC.user_id); + + delete window.TRC; + }); + + it('should get user id to be 0 if cookie, local storage, TRC isn`t defined', function () { + hasLocalStorage.returns(false); + cookiesAreEnabled.returns(false); + + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(0); + }); + + it('should set buyeruid to be 0 if it`s a new user', function () { + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(0); + }); + }); + }) + + describe('interpretResponse', function () { + const serverResponse = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'lurl': 'http://us-trc.taboola.com/sample' + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + } + }; + + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse(serverResponse, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should return empty array if no server response', function () { + const res = spec.interpretResponse({}, request) + expect(res).to.be.an('array').that.is.empty + }); + + it('should return empty array if server response without seatbid', function () { + const overriddenServerResponse = {...serverResponse}; + const seatbid = {...serverResponse.body.seatbid[0]}; + overriddenServerResponse.body.seatbid[0] = {}; + + const res = spec.interpretResponse(overriddenServerResponse, request) + expect(res).to.be.an('array').that.is.empty + + overriddenServerResponse.body.seatbid[0] = seatbid; + }); + + it('should return empty array if server response without bids', function () { + const overriddenServerResponse = {...serverResponse}; + const bid = [...serverResponse.body.seatbid[0].bid]; + overriddenServerResponse.body.seatbid[0].bid = {}; + + const res = spec.interpretResponse(overriddenServerResponse, request) + expect(res).to.be.an('array').that.is.empty + + overriddenServerResponse.body.seatbid[0].bid = bid; + }); + + it('should interpret display response', function () { + const [bid] = serverResponse.body.seatbid[0].bid; + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: bid.price, + creativeId: bid.crid, + ttl: 360, + netRevenue: false, + currency: serverResponse.body.cur, + mediaType: 'banner', + ad: bid.adm, + width: bid.w, + height: bid.h, + meta: { + 'advertiserDomains': bid.adomain + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + + describe('userData', function () { + // todo: add UT for getUserSyncs + }) + + describe('internal functions', function () { + describe('getPageUrl', function () { + let origPageUrl; + const bidderRequest = { + refererInfo: { + canonicalUrl: 'http://canonical.url' + } + }; + + beforeEach(function () { + // remember original pageUrl in config + origPageUrl = config.getConfig('pageUrl'); + + // unset pageUrl in config + config.setConfig({pageUrl: null}); + }); + + afterEach(function () { + // set original pageUrl to config + config.setConfig({pageUrl: origPageUrl}); + }); + + it('should handle empty or missing data', function () { + expect(internal.getPageUrl(undefined)).to.equal(utils.getWindowTop().location.href); + expect(internal.getPageUrl('')).to.equal(utils.getWindowTop().location.href); + }); + + it('should use "pageUrl" from config', function () { + config.setConfig({pageUrl: 'http://page.url'}); + + expect(internal.getPageUrl(undefined)).to.equal(config.getConfig('pageUrl')); + }); + + it('should use bidderRequest.refererInfo.canonicalUrl', function () { + expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); + + it('should prefer bidderRequest.refererInfo.canonicalUrl over "pageUrl" from config', () => { + config.setConfig({pageUrl: 'https://page.url'}); + + expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); + }); + + describe('getReferrer', function () { + it('should handle empty or missing data', function () { + expect(internal.getReferrer(undefined)).to.equal(utils.getWindowTop().document.referrer); + expect(internal.getReferrer('')).to.equal(utils.getWindowTop().document.referrer); + }); + + it('should use bidderRequest.refererInfo.referer', () => { + const bidderRequest = { + refererInfo: { + referer: 'foobar' + } + }; + + expect(internal.getReferrer(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.referer); + }); + }); + }) +}) diff --git a/test/spec/modules/videoheroesBidAdapter_spec.js b/test/spec/modules/videoheroesBidAdapter_spec.js new file mode 100644 index 00000000000..8f99ca4d17d --- /dev/null +++ b/test/spec/modules/videoheroesBidAdapter_spec.js @@ -0,0 +1,363 @@ +import { expect } from 'chai'; +import { spec } from 'modules/videoheroesBidAdapter.js'; + +const request_native = { + code: 'videoheroes-native-prebid', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bidder: 'videoheroes', + params: { + placementId: '1a8d9c22db19906cb8a5fd4518d05f62' + } +}; + +const request_banner = { + code: 'videoheroes-prebid', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidder: 'videoheroes', + params: { + placementId: '1a8d9c22db19906cb8a5fd4518d05f62' + } +} + +const bidRequest = { + gdprConsent: { + consentString: 'HFIDUYFIUYIUYWIPOI87392DSU', + gdprApplies: true + }, + uspConsent: 'uspConsentString', + bidderRequestId: 'testid', + refererInfo: { + referer: 'testdomain.com' + }, + timeout: 700 +} + +const request_video = { + code: 'videoheroes-video-prebid', + mediaTypes: { video: { + minduration: 1, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: [ + 'application/javascript', + 'video/mp4' + ], + playerSize: [[768, 1024]], + protocols: [ + 2, 3 + ], + linearity: 1, + api: [ + 1, + 2 + ] + } + }, + + bidder: 'videoheroes', + params: { + placementId: '1a8d9c22db19906cb8a5fd4518d05f62' + } + +} + +const response_banner = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'banner' + } + }] + }] +}; + +const response_video = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'video' + } + }], + }], +}; + +let imgData = { + url: `https://example.com/image`, + w: 1200, + h: 627 +}; + +const response_native = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: { native: + { + assets: [ + {id: 1, title: 'dummyText'}, + {id: 3, image: imgData}, + { + id: 5, + data: {value: 'organization.name'} + } + ], + link: {url: 'example.com'}, + imptrackers: ['tracker1.com', 'tracker2.com', 'tracker3.com'], + jstracker: 'tracker1.com' + } + }, + crid: 'crid', + ext: { + mediaType: 'native' + } + }], + }], +}; + +describe('VideoheroesBidAdapter', function() { + describe('isBidRequestValid', function() { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(request_banner)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, request_banner); + bid.params = { + 'IncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('build Native Request', function () { + const request = spec.buildRequests([request_native], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://point.contextualadv.com/?t=2&partner=1a8d9c22db19906cb8a5fd4518d05f62'); + }); + + it('Returns empty data if no valid requests are passed', function () { + let serverRequest = spec.buildRequests([]); + expect(serverRequest).to.be.an('array').that.is.empty; + }); + }); + + describe('build Banner Request', function () { + const request = spec.buildRequests([request_banner], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://point.contextualadv.com/?t=2&partner=1a8d9c22db19906cb8a5fd4518d05f62'); + }); + }); + + describe('build Video Request', function () { + const request = spec.buildRequests([request_video], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://point.contextualadv.com/?t=2&partner=1a8d9c22db19906cb8a5fd4518d05f62'); + }); + }); + + describe('interpretResponse', function () { + it('Empty response must return empty array', function() { + const emptyResponse = null; + let response = spec.interpretResponse(emptyResponse); + + expect(response).to.be.an('array').that.is.empty; + }) + + it('Should interpret banner response', function () { + const bannerResponse = { + body: response_banner + } + + const expectedBidResponse = { + requestId: response_banner.seatbid[0].bid[0].impid, + cpm: response_banner.seatbid[0].bid[0].price, + width: response_banner.seatbid[0].bid[0].w, + height: response_banner.seatbid[0].bid[0].h, + ttl: response_banner.ttl || 1200, + currency: response_banner.cur || 'USD', + netRevenue: true, + creativeId: response_banner.seatbid[0].bid[0].crid, + dealId: response_banner.seatbid[0].bid[0].dealid, + mediaType: 'banner', + ad: response_banner.seatbid[0].bid[0].adm + } + + let bannerResponses = spec.interpretResponse(bannerResponse); + + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.ad).to.equal(expectedBidResponse.ad); + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret video response', function () { + const videoResponse = { + body: response_video + } + + const expectedBidResponse = { + requestId: response_video.seatbid[0].bid[0].impid, + cpm: response_video.seatbid[0].bid[0].price, + width: response_video.seatbid[0].bid[0].w, + height: response_video.seatbid[0].bid[0].h, + ttl: response_video.ttl || 1200, + currency: response_video.cur || 'USD', + netRevenue: true, + creativeId: response_video.seatbid[0].bid[0].crid, + dealId: response_video.seatbid[0].bid[0].dealid, + mediaType: 'video', + vastUrl: response_video.seatbid[0].bid[0].adm + } + + let videoResponses = spec.interpretResponse(videoResponse); + + expect(videoResponses).to.be.an('array').that.is.not.empty; + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.vastUrl).to.equal(expectedBidResponse.vastUrl) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret native response', function () { + const nativeResponse = { + body: response_native + } + + const expectedBidResponse = { + requestId: response_native.seatbid[0].bid[0].impid, + cpm: response_native.seatbid[0].bid[0].price, + width: response_native.seatbid[0].bid[0].w, + height: response_native.seatbid[0].bid[0].h, + ttl: response_native.ttl || 1200, + currency: response_native.cur || 'USD', + netRevenue: true, + creativeId: response_native.seatbid[0].bid[0].crid, + dealId: response_native.seatbid[0].bid[0].dealid, + mediaType: 'native', + native: {clickUrl: response_native.seatbid[0].bid[0].adm.native.link.url} + } + + let nativeResponses = spec.interpretResponse(nativeResponse); + + expect(nativeResponses).to.be.an('array').that.is.not.empty; + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + }); +}) diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 20113a63994..d439da8e711 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -25,6 +25,25 @@ describe('Zeta Ssp Bid Adapter', function () { } ]; + const params = { + user: { + uid: 222, + buyeruid: 333 + }, + tags: { + someTag: 444, + }, + sid: 'publisherId', + shortname: 'test_shortname', + site: { + page: 'testPage' + }, + app: { + bundle: 'testBundle' + }, + test: 1 + }; + const bannerRequest = [{ bidId: 12345, auctionId: 67890, @@ -41,18 +60,7 @@ describe('Zeta Ssp Bid Adapter', function () { consentString: 'consentString' }, uspConsent: 'someCCPAString', - params: { - placement: 111, - user: { - uid: 222, - buyeruid: 333 - }, - tags: { - someTag: 444, - sid: 'publisherId' - }, - test: 1 - }, + params: params, userIdAsEids: eids }]; @@ -72,18 +80,7 @@ describe('Zeta Ssp Bid Adapter', function () { refererInfo: { referer: 'http://www.zetaglobal.com/page?param=video' }, - params: { - placement: 111, - user: { - uid: 222, - buyeruid: 333 - }, - tags: { - someTag: 444, - sid: 'publisherId' - }, - test: 1 - }, + params: params }]; it('Test the bid validation function', function () { @@ -269,4 +266,22 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.imp[0].banner).to.be.undefined; }); + + it('Test required params in banner request', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + expect(request.url).to.eql('https://ssp.disqus.com/bid?shortname=test_shortname'); + expect(payload.ext.sid).to.eql('publisherId'); + expect(payload.ext.tags.someTag).to.eql(444); + expect(payload.ext.tags.shortname).to.be.undefined; + }); + + it('Test required params in video request', function () { + const request = spec.buildRequests(videoRequest, videoRequest[0]); + const payload = JSON.parse(request.data); + expect(request.url).to.eql('https://ssp.disqus.com/bid?shortname=test_shortname'); + expect(payload.ext.sid).to.eql('publisherId'); + expect(payload.ext.tags.someTag).to.eql(444); + expect(payload.ext.tags.shortname).to.be.undefined; + }); }); diff --git a/test/test_deps.js b/test/test_deps.js index 8b8c9fd3a0f..77fbad93e1c 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -7,3 +7,4 @@ window.process = { require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); require('test/mocks/xhr.js'); +require('test/mocks/analyticsStub.js');