diff --git a/integrationExamples/gpt/raynRtdProvider_example.html b/integrationExamples/gpt/raynRtdProvider_example.html index 2d43c37513a..7965daa6e85 100644 --- a/integrationExamples/gpt/raynRtdProvider_example.html +++ b/integrationExamples/gpt/raynRtdProvider_example.html @@ -6,6 +6,7 @@ "3": ["264", "267", "261"], "4": ["438"] }, + "103015": ['agdv23', 'avscg3'], "903555595": { "7": { "2": ["51", "246"] diff --git a/libraries/adagioUtils/adagioUtils.js b/libraries/adagioUtils/adagioUtils.js new file mode 100644 index 00000000000..c2614c45d0c --- /dev/null +++ b/libraries/adagioUtils/adagioUtils.js @@ -0,0 +1,35 @@ +import { + canAccessWindowTop, + generateUUID, + getWindowSelf, + getWindowTop, + isSafeFrameWindow +} from '../../src/utils.js'; + +/** + * Returns the best Window object to use with ADAGIO. + * @returns {Window} window.top or window.self object + */ +export function getBestWindowForAdagio() { + return (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); +} + +/** + * Returns the window.ADAGIO global object used to store Adagio data. + * This object is created in window.top if possible, otherwise in window.self. + */ +export const _ADAGIO = (function() { + const w = getBestWindowForAdagio(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); + w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.versions = w.ADAGIO.versions || {}; + w.ADAGIO.versions.pbjs = '$prebid.version$'; + w.ADAGIO.windows = w.ADAGIO.windows || []; + w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); + + return w.ADAGIO; +})(); diff --git a/libraries/appnexusUtils/anUtils.js b/libraries/appnexusUtils/anUtils.js index 7897cfc0e0e..1d04711bd0f 100644 --- a/libraries/appnexusUtils/anUtils.js +++ b/libraries/appnexusUtils/anUtils.js @@ -22,6 +22,7 @@ export const appnexusAliases = [ { code: 'adasta', gvlid: 32 }, { code: 'beintoo', gvlid: 618 }, { code: 'projectagora', gvlid: 1032 }, + { code: 'stailamedia', gvlid: 32 }, { code: 'uol', gvlid: 32 }, { code: 'adzymic', gvlid: 723 }, ]; diff --git a/libraries/autoplayDetection/autoplay.js b/libraries/autoplayDetection/autoplay.js index 99922da7b49..3ca4c4a8d11 100644 --- a/libraries/autoplayDetection/autoplay.js +++ b/libraries/autoplayDetection/autoplay.js @@ -22,6 +22,12 @@ const autoplayVideoUrl = 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAADxtZGF0AAAAMGWIhAAV//73ye/Apuvb3rW/k89I/Cy3PsIqP39atohOSV14BYa1heKCYgALQC5K4QAAAwZtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAD6AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACMHRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAD6AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAoAAAAFoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAA+gAAAAAAAEAAAAAAahtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAEAAAABAAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAFTbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABE3N0YmwAAACvc3RzZAAAAAAAAAABAAAAn2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAoABaAEgAAABIAAAAAAAAAAEVTGF2YzYwLjMxLjEwMiBsaWJ4MjY0AAAAAAAAAAAAAAAY//8AAAA1YXZjQwFkAAr/4QAYZ2QACqzZQo35IQAAAwABAAADAAIPEiWWAQAGaOvjyyLA/fj4AAAAABRidHJ0AAAAAAAAAaAAAAGgAAAAGHN0dHMAAAAAAAAAAQAAAAEAAEAAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAADQAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjYwLjE2LjEwMA=='; function startDetection() { + const version = navigator.userAgent.match(/iPhone OS (\d+)_(\d+)/) + if (version !== null && parseInt(version[1]) < 17 && !navigator.userAgent.includes('Safari')) { + // skip autodetection on iOS 16 WebView + return + } + // we create an HTMLVideoElement muted and not displayed in which we try to play a one frame video const videoElement = document.createElement('video'); videoElement.src = autoplayVideoUrl; @@ -32,7 +38,8 @@ function startDetection() { .play() .then(() => { autoplayEnabled = true; - videoElement.pause(); + // if the video is played on a WebView with playsinline = false, this stops the video, to prevent it from being displayed fullscreen + videoElement.src = ''; }) .catch(() => { autoplayEnabled = false; diff --git a/modules/.submodules.json b/modules/.submodules.json index 39f3969c4fd..38cf5f71cc6 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -60,6 +60,7 @@ "1plusXRtdProvider", "a1MediaRtdProvider", "aaxBlockmeterRtdProvider", + "adagioRtdProvider", "adlooxRtdProvider", "adnuntiusRtdProvider", "airgridRtdProvider", diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 3cc31336827..8d945beafa1 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -7,13 +7,11 @@ import { generateUUID, getDNT, getWindowSelf, - getWindowTop, isArray, isArrayOfNums, isFn, isInteger, isNumber, - isSafeFrameWindow, isStr, logError, logInfo, @@ -26,6 +24,7 @@ import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { find } from '../src/polyfill.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { _ADAGIO } from '../libraries/adagioUtils/adagioUtils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { userSync } from '../src/userSync.js'; @@ -78,21 +77,6 @@ export const ORTB_VIDEO_PARAMS = { * Returns the window.ADAGIO global object used to store Adagio data. * This object is created in window.top if possible, otherwise in window.self. */ -const _ADAGIO = (function() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); - w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; - w.ADAGIO.queue = w.ADAGIO.queue || []; - w.ADAGIO.versions = w.ADAGIO.versions || {}; - w.ADAGIO.versions.pbjs = '$prebid.version$'; - w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); - - return w.ADAGIO; -})(); - function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index a901c2c489d..16bd8fc2ae2 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -15,6 +15,8 @@ import { deepAccess, deepSetValue, generateUUID, + getDomLoadingDuration, + getSafeframeGeometry, getUniqueIdentifierStr, getWindowSelf, getWindowTop, @@ -24,6 +26,7 @@ import { isStr, prefixLog } from '../src/utils.js'; +import { _ADAGIO, getBestWindowForAdagio } from '../libraries/adagioUtils/adagioUtils.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; /** @@ -35,6 +38,11 @@ const ADAGIO_BIDDER_CODE = 'adagio'; const GVLID = 617; const SCRIPT_URL = 'https://script.4dex.io/a/latest/adagio.js'; const SESS_DURATION = 30 * 60 * 1000; +export const PLACEMENT_SOURCES = { + ORTB: 'ortb', // implicit default, not used atm. + ADUNITCODE: 'code', + GPID: 'gpid' +}; export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); const { logError, logWarn } = prefixLog('AdagioRtdProvider:'); @@ -42,23 +50,6 @@ const { logError, logWarn } = prefixLog('AdagioRtdProvider:'); // Guard to avoid storing the same bid data several times. const guard = new Set(); -/** - * Returns the window.ADAGIO global object used to store Adagio data. - * This object is created in window.top if possible, otherwise in window.self. - */ -const _ADAGIO = (function() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); - w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; - w.ADAGIO.queue = w.ADAGIO.queue || []; - w.ADAGIO.windows = w.ADAGIO.windows || []; - - return w.ADAGIO; -})(); - /** * Store the sampling data. * This data is used to determine if beacons should be sent to adagio. @@ -131,12 +122,14 @@ const _FEATURES = (function() { features.data = {}; }, get: function() { + const w = getBestWindowForAdagio(); + if (!features.initialized) { features.data = { page_dimensions: getPageDimensions().toString(), viewport_dimensions: getViewPortDimensions().toString(), user_timestamp: getTimestampUTC().toString(), - dom_loading: getDomLoadingDuration().toString(), + dom_loading: getDomLoadingDuration(w).toString(), }; features.initialized = true; } @@ -271,10 +264,12 @@ function onBidRequest(bidderRequest, config, _userConsent) { * @param {*} config */ function onGetBidRequestData(bidReqConfig, callback, config) { + const configParams = deepAccess(config, 'params', {}); const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const features = _internal.getFeatures().get(); const ext = { uid: generateUUID(), + pageviewId: _ADAGIO.pageviewId, features: { ...features }, session: { ..._SESSION.get() } }; @@ -294,8 +289,26 @@ function onGetBidRequestData(bidReqConfig, callback, config) { const slotPosition = getSlotPosition(adUnit); deepSetValue(ortb2Imp, `ext.data.adg_rtd.adunit_position`, slotPosition); - // We expect `pagetype` `category` are defined in FPD `ortb2.site.ext.data` object. - // `placement` is expected in FPD `adUnits[].ortb2Imp.ext.data` object. (Please note that this `placement` is not related to the oRTB video property.) + // It is expected that the publisher set a `adUnits[].ortb2Imp.ext.data.placement` value. + // Btw, We allow fallback sources to programmatically set this value. + // The source is defined in the `config.params.placementSource` and the possible values are `code` or `gpid`. + // (Please note that this `placement` is not related to the oRTB video property.) + if (!deepAccess(ortb2Imp, 'ext.data.placement')) { + const { placementSource = '' } = configParams; + + switch (placementSource.toLowerCase()) { + case PLACEMENT_SOURCES.ADUNITCODE: + deepSetValue(ortb2Imp, 'ext.data.placement', adUnit.code); + break; + case PLACEMENT_SOURCES.GPID: + deepSetValue(ortb2Imp, 'ext.data.placement', deepAccess(ortb2Imp, 'ext.gpid')); + break; + default: + logWarn('`ortb2Imp.ext.data.placement` is missing and `params.definePlacement` is not set in the config.'); + } + } + + // We expect that `pagetype`, `category`, `placement` are defined in FPD `ortb2.site.ext.data` and `adUnits[].ortb2Imp.ext.data` objects. // Btw, we have to ensure compatibility with publishers that use the "legacy" adagio params at the adUnit.params level. const adagioBid = adUnit.bids.find(bid => _internal.isAdagioBidder(bid.bidder)); if (adagioBid) { @@ -316,9 +329,6 @@ function onGetBidRequestData(bidReqConfig, callback, config) { if (adagioBid.params.placement) { deepSetValue(ortb2Imp, 'ext.data.placement', adagioBid.params.placement); mustWarnOrtb2Imp = true; - } else { - // If the placement is not defined, we fallback to the adUnit code. - deepSetValue(ortb2Imp, 'ext.data.placement', adUnit.code); } } @@ -428,16 +438,14 @@ function getSlotPosition(adUnit) { const position = { x: 0, y: 0 }; if (isSafeFrameWindow()) { - const ws = getWindowSelf(); - - const sfGeom = (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : null; + const { self } = getSafeframeGeometry() || {}; - if (!sfGeom || !sfGeom.self) { + if (!self) { return ''; } - position.x = Math.round(sfGeom.self.t); - position.y = Math.round(sfGeom.self.l); + position.x = Math.round(self.t); + position.y = Math.round(self.l); } else { try { // window.top based computing @@ -513,16 +521,14 @@ function getViewPortDimensions() { const viewportDims = { w: 0, h: 0 }; if (isSafeFrameWindow()) { - const ws = getWindowSelf(); + const { win } = getSafeframeGeometry() || {}; - const sfGeom = (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : null; - - if (!sfGeom || !sfGeom.win) { + if (!win) { return ''; } - viewportDims.w = Math.round(sfGeom.win.w); - viewportDims.h = Math.round(sfGeom.win.h); + viewportDims.w = Math.round(win.w); + viewportDims.h = Math.round(win.h); } else { // window.top based computing const wt = getWindowTop(); @@ -538,22 +544,6 @@ function getTimestampUTC() { return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; } -function getDomLoadingDuration() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - const performance = w.performance; - - let domLoadingDuration = -1; - - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) { - domLoadingDuration = val; - } - } - - return domLoadingDuration; -} - /** * registerEventsForAdServers bind adagio listeners to ad-server events. * Theses events are used to track the viewability and attention. diff --git a/modules/adagioRtdProvider.md b/modules/adagioRtdProvider.md index f05521ec54a..a51137d571f 100644 --- a/modules/adagioRtdProvider.md +++ b/modules/adagioRtdProvider.md @@ -30,6 +30,7 @@ pbjs.setConfig({ params: { organizationId: '1000' // Required. Provided by Adagio site: 'my-site' // Required. Provided by Adagio + placementSource: 'ortb' // Optional. Where to find the "placement" value. Possible values: 'ortb' | 'code' | 'gpid' } }] } diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 0fb56949539..b66923fd476 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' import { config } from '../src/config.js' -import { _each, deepAccess, deepSetValue, getWindowSelf, getWindowTop } from '../src/utils.js' +import { _each, canAccessWindowTop, deepAccess, deepSetValue, getDomLoadingDuration, getWindowSelf, getWindowTop } from '../src/utils.js' export const BIDDER_CODE = 'bliink' export const GVL_ID = 658 export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/prebid' @@ -121,35 +121,6 @@ export function getKeywords() { return []; } -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; - } - } catch (error) { - return false; - } -} - -/** - * domLoading feature is computed on window.top if reachable. - */ -export function getDomLoadingDuration() { - let domLoadingDuration = -1; - let performance; - - performance = (canAccessTopWindow()) ? getWindowTop().performance : getWindowSelf().performance; - - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) { - domLoadingDuration = val; - } - } - - return domLoadingDuration; -} - /** * @param bidResponse * @return {({cpm, netRevenue: boolean, requestId, width: number, currency, ttl: number, creativeId, height: number}&{mediaType: string, vastXml})|null} @@ -208,7 +179,8 @@ export const isBidRequestValid = (bid) => { */ export const buildRequests = (validBidRequests, bidderRequest) => { if (!validBidRequests || !bidderRequest || !bidderRequest.bids) return null - const domLoadingDuration = getDomLoadingDuration().toString(); + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + const domLoadingDuration = getDomLoadingDuration(w).toString(); const tags = bidderRequest.bids.map((bid) => { let bidFloor; const sizes = bid.sizes.map((size) => ({ w: size[0], h: size[1] })); diff --git a/modules/ceeIdSystem.js b/modules/ceeIdSystem.js index d1534ddada2..30240e3f75f 100644 --- a/modules/ceeIdSystem.js +++ b/modules/ceeIdSystem.js @@ -43,7 +43,7 @@ export const ceeIdSubmodule = { * performs action to obtain id and return a value * @function * @returns {(IdResponse|undefined)} - */ + */ getId(config) { const { params = {} } = config; const { tokenName, value } = params diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index bf8eaaebb55..b268d497723 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -67,7 +67,7 @@ function getVideoMetadata(bidRequest, bidderRequest) { ? videoParams.isCreatedForKids : null, context: { - siteOrAppCat: deepAccess(contentObj, 'cat', ''), + siteOrAppCat: deepAccess(contentObj, 'cat', []), videoViewsInSession: ( typeof videoParams.videoViewsInSession === 'number' && videoParams.videoViewsInSession >= 0 diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index e7a7ec7f037..1f369f5a7a1 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -41,6 +41,7 @@ const LOCAL_STORAGE = 'html5'; const LOG_PREFIX = 'User ID - ID5 submodule: '; const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; const ID5_DOMAIN = 'id5-sync.com'; +const TRUE_LINK_SOURCE = 'true-link-id5-sync.com'; // order the legacy cookie names in reverse priority order so the last // cookie in the array is the most preferred to use @@ -134,12 +135,13 @@ export const id5IdSubmodule = { * @returns {(Object|undefined)} */ decode(value, config) { - let universalUid; + let universalUid, publisherTrueLinkId; let ext = {}; if (value && typeof value.universal_uid === 'string') { universalUid = value.universal_uid; ext = value.ext || ext; + publisherTrueLinkId = value.publisherTrueLinkId; } else { return undefined; } @@ -159,6 +161,12 @@ export const id5IdSubmodule = { }; } + if (publisherTrueLinkId) { + responseObj.trueLinkId = { + uid: publisherTrueLinkId, + }; + } + const abTestingResult = deepAccess(value, 'ab_testing.result'); switch (abTestingResult) { case 'control': @@ -263,7 +271,22 @@ export const id5IdSubmodule = { return data.ext; } } + }, + 'trueLinkId': { + getValue: function (data) { + return data.uid; + }, + getSource: function (data) { + return TRUE_LINK_SOURCE; + }, + atype: 1, + getUidExt: function (data) { + if (data.ext) { + return data.ext; + } + } } + } }; @@ -380,6 +403,8 @@ export class IdFetchFlow { const referer = getRefererInfo(); const signature = (this.cacheIdObj && this.cacheIdObj.signature) ? this.cacheIdObj.signature : getLegacyCookieSignature(); const nbPage = incrementAndResetNb(params.partner); + const trueLinkInfo = window.id5Bootstrap ? window.id5Bootstrap.getTrueLinkInfo() : {booted: false}; + const data = { 'partner': params.partner, 'gdpr': hasGdpr, @@ -392,7 +417,8 @@ export class IdFetchFlow { 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', 'storage': this.submoduleConfig.storage, - 'localStorage': storage.localStorageIsEnabled() ? 1 : 0 + 'localStorage': storage.localStorageIsEnabled() ? 1 : 0, + 'true_link': trueLinkInfo }; // pass in optional data, but only if populated @@ -431,6 +457,9 @@ export class IdFetchFlow { try { if (fetchCallResponse.privacy) { storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(fetchCallResponse.privacy), NB_EXP_DAYS); + if (window.id5Bootstrap && window.id5Bootstrap.setPrivacy) { + window.id5Bootstrap.setPrivacy(fetchCallResponse.privacy); + } } } catch (error) { logError(LOG_PREFIX + 'Error while writing privacy info into local storage.', error); diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 592c69056fa..a2782b70559 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -37,7 +37,7 @@ pbjs.setConfig({ type: 'html5', // "html5" is the required storage type name: 'id5id', // "id5id" is the required storage name expires: 90, // storage lasts for 90 days - refreshInSeconds: 8*3600 // refresh ID every 8 hours to ensure it's fresh + refreshInSeconds: 7200 // refresh ID every 2 hours to ensure it's fresh } }], auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules @@ -61,7 +61,7 @@ pbjs.setConfig({ | storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | | storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | | storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | -| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 2 hours between refreshes | `7200` | **ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). @@ -73,3 +73,68 @@ To turn on A/B Testing, simply edit the configuration (see above table) to enabl ### A Note on Using Multiple Wrappers If you or your monetization partners are deploying multiple Prebid wrappers on your websites, you should make sure you add the ID5 ID User ID module to *every* wrapper. Only the bidders configured in the Prebid wrapper where the ID5 ID User ID module is installed and configured will be able to pick up the ID5 ID. Bidders from other Prebid instances will not be able to pick up the ID5 ID. + +### Provided eids +The module provides following eids: + +``` +[ + { + source: 'id5-sync.com', + uids: [ + { + id: 'some-random-id-value', + atype: 1, + ext: { + linkType: 2, + abTestingControlGroup: false + } + } + ] + }, + { + source: 'true-link-id5-sync.com', + uids: [ + { + id: 'some-publisher-true-link-id', + atype: 1 + } + ] + }, + { + source: 'uidapi.com', + uids: [ + { + id: 'some-uid2', + atype: 3, + ext: { + provider: 'id5-sync.com' + } + } + ] + } +] +``` + +The id from `id5-sync.com` should be always present (though the id provided will be '0' in case of no consent or optout) + +The id from `true-link-id5-sync.com` will be available if the page is integrated with TrueLink (if you are an ID5 partner you can learn more at https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/true-link-integration) + +The id from `uidapi.com` will be available if the partner that is used in ID5 user module has the EUID2 integration enabled (it has to be enabled on the ID5 side) + + +### Providing TrueLinkId as a Google PPID + +TrueLinkId can be provided as a PPID - to use, it the `true-link-id5-sync.com` needs to be provided as a ppid source in prebid userSync configuration: + +```javascript +pbjs.setConfig({ + userSync: { + ppid: 'true-link-id5-sync.com', + userIds: [], //userIds modules should be configured here + } +}); +``` + + + diff --git a/modules/loyalBidAdapter.js b/modules/loyalBidAdapter.js index ffa88529b2f..80524f1e8ed 100644 --- a/modules/loyalBidAdapter.js +++ b/modules/loyalBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, logError } from '../src/utils.js'; +import { logMessage } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -84,7 +84,6 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (err) { - logError(err); return 0; } } diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index f43dde20681..09a338c9bb0 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -1,6 +1,6 @@ import { submodule } from '../src/hook.js'; import { ajaxBuilder } from '../src/ajax.js'; -export const MOBIAN_URL = 'http://impact-analytics-staging.themobian.com'; +export const MOBIAN_URL = 'https://impact-api-prod.themobian.com/brand_safety'; export const mobianBrandSafetySubmodule = { name: 'mobianBrandSafety', @@ -15,7 +15,7 @@ function init() { function getBidRequestData(bidReqConfig, callback, config) { const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const pageUrl = encodeURIComponent(getPageUrl()); - const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`; + const requestUrl = `${MOBIAN_URL}/by_url?url=${pageUrl}`; const ajax = ajaxBuilder(); diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index b5de3df4626..afa980b05c9 100644 --- a/modules/nobidAnalyticsAdapter.js +++ b/modules/nobidAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import {deepClone, logError, getParameterByName} from '../src/utils.js'; +import {deepClone, logError, getParameterByName, logMessage} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -26,8 +26,7 @@ const { AD_RENDER_SUCCEEDED } = EVENTS; function log (msg) { - // eslint-disable-next-line no-console - console.log(`%cNoBid Analytics ${VERSION}`, 'padding: 2px 8px 2px 8px; background-color:#f50057; color: white', msg); + logMessage(`%cNoBid Analytics ${VERSION}: ${msg}`); } function isJson (str) { return str && str.startsWith('{') && str.endsWith('}'); diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md index f33fc700311..a78363e8fe3 100644 --- a/modules/novatiqIdSystem.md +++ b/modules/novatiqIdSystem.md @@ -19,12 +19,11 @@ pbjs.setConfig({ name: 'novatiq', params: { // change to the Partner Number you received from Novatiq - sourceid '1a3' - } + sourceid: '1a3' } }], // 50ms maximum auction delay, applies to all userId modules - auctionDelay: 50 + auctionDelay: 50 } }); ``` diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 1e5b2ae8ca5..9968072eff4 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -1,5 +1,4 @@ import { - deepClone, logInfo, logError, deepAccess, @@ -9,7 +8,7 @@ import { contains, mergeDeep, parseUrl, - generateUUID + generateUUID, isInteger, deepClone } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -23,7 +22,7 @@ const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.9.1'; +const OZONEVERSION = '2.9.2'; export const spec = { gvlid: 524, aliases: [{code: 'lmc', gvlid: 524}, {code: 'venatus', gvlid: 524}], @@ -39,11 +38,11 @@ export const spec = { 'auctionUrl': ORIGIN + AUCTIONURI, 'cookieSyncUrl': ORIGIN + OZONECOOKIESYNC, 'rendererUrl': OZONE_RENDERER_URL, - 'batchRequests': false /* you can change this to true OR override it in the config: config.ozone.batchRequests */ + 'batchRequests': false /* you can change this to true OR numeric OR override it in the config: config.ozone.batchRequests = true/false/number */ }, loadWhitelabelData(bid) { if (this.propertyBag.whitelabel) { return; } - this.propertyBag.whitelabel = deepClone(this.whitelabel_defaults); + this.propertyBag.whitelabel = JSON.parse(JSON.stringify(this.whitelabel_defaults)); let bidder = bid.bidder || 'ozone'; // eg. ozone this.propertyBag.whitelabel.logId = bidder.toUpperCase(); this.propertyBag.whitelabel.bidder = bidder; @@ -77,10 +76,22 @@ export const spec = { } } if (bidderConfig.hasOwnProperty('batchRequests')) { - this.propertyBag.whitelabel.batchRequests = bidderConfig.batchRequests; + if (this.batchValueIsValid(bidderConfig.batchRequests)) { + this.propertyBag.whitelabel.batchRequests = bidderConfig.batchRequests; + } else { + logError('bidderConfig.batchRequests must be boolean or a number. Found & ignored data type: ' + typeof bidderConfig.batchRequests); + } + } + if (bidderConfig.hasOwnProperty('videoParams')) { + this.propertyBag.whitelabel.videoParams = bidderConfig.videoParams; } if (arr.hasOwnProperty('batchRequests')) { - this.propertyBag.whitelabel.batchRequests = true; + let getBatch = parseInt(arr.batchRequests); + if (this.batchValueIsValid(getBatch)) { + this.propertyBag.whitelabel.batchRequests = getBatch; + } else { + logError('Ignoring query param: batchRequests - this must be a positive number'); + } } try { if (arr.hasOwnProperty('auction') && arr.auction === 'dev') { @@ -94,6 +105,9 @@ export const spec = { } catch (e) {} logInfo('set propertyBag.whitelabel to', this.propertyBag.whitelabel); }, + batchValueIsValid(batch) { + return typeof batch === 'boolean' || (typeof batch === 'number' && batch > 0); + }, getAuctionUrl() { return this.propertyBag.whitelabel.auctionUrl; }, @@ -103,9 +117,17 @@ export const spec = { getRendererUrl() { return this.propertyBag.whitelabel.rendererUrl; }, - isBatchRequests() { - logInfo('isBatchRequests going to return ', this.propertyBag.whitelabel.batchRequests); - return this.propertyBag.whitelabel.batchRequests; + getVideoPlacementValue: function(context) { + if (['instream', 'outstream'].indexOf(context) < 0) return null; + return deepAccess(this.propertyBag, `whitelabel.videoParams.${context}`, null); + }, + getBatchRequests() { + logInfo('getBatchRequests going to return ', this.propertyBag.whitelabel.batchRequests); + if (this.propertyBag.whitelabel.batchRequests === true) { return 10; } + if (typeof this.propertyBag.whitelabel.batchRequests === 'number' && this.propertyBag.whitelabel.batchRequests > 0) { + return this.propertyBag.whitelabel.batchRequests; + } + return false; }, isBidRequestValid(bid) { this.loadWhitelabelData(bid); @@ -182,9 +204,10 @@ export const spec = { if (this.blockTheRequest()) { return []; } + let fledgeEnabled = !!bidderRequest.fledgeEnabled; // IF true then this is added as each bid[].ext.ae=1 let htmlParams = {'publisherId': '', 'siteId': ''}; if (validBidRequests.length > 0) { - this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); + this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIdsFromEids(validBidRequests[0])); this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); htmlParams = validBidRequests[0].params; @@ -306,6 +329,14 @@ export const spec = { if (gpid) { deepSetValue(obj, 'ext.gpid', gpid); } + if (fledgeEnabled) { // fledge is enabled at some config level - pbjs.setBidderConfig or pbjs.setConfig + const auctionEnvironment = deepAccess(ozoneBidRequest, 'ortb2Imp.ext.ae'); // this will be set for one of 3 reasons; adunit, setBidderConfig, setConfig + if (isInteger(auctionEnvironment)) { + deepSetValue(obj, 'ext.ae', auctionEnvironment); + } else { + logError('ortb2Imp.ext.ae is not an integer - ignoring it for obj.id=' + obj.id); + } + } return obj; }); let extObj = {}; @@ -314,8 +345,8 @@ export const spec = { extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info - if (userIds.hasOwnProperty('pubcid')) { - extObj[whitelabelBidder].pubcid = userIds.pubcid; + if (userIds.hasOwnProperty('pubcid.org')) { + extObj[whitelabelBidder].pubcid = userIds['pubcid.org']; } } extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auction calls for this page if refresh() is called @@ -363,6 +394,10 @@ export const spec = { } else { logInfo('WILL NOT ADD USP consent info; no bidderRequest.uspConsent.'); } + if (bidderRequest?.ortb2?.regs?.gpp) { + deepSetValue(ozoneRequest, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(ozoneRequest, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } if (schain) { // we set this while iterating over the bids logInfo('schain found'); deepSetValue(ozoneRequest, 'source.ext.schain', schain); @@ -370,15 +405,26 @@ export const spec = { if (config.getConfig('coppa') === true) { deepSetValue(ozoneRequest, 'regs.coppa', 1); } + extObj[whitelabelBidder].cookieDeprecationLabel = deepAccess(bidderRequest, 'ortb2.device.ext.cdep', 'none'); + logInfo('cookieDeprecationLabel from bidderRequest object = ' + extObj[whitelabelBidder].cookieDeprecationLabel); let ozUuid = generateUUID(); - if (this.isBatchRequests()) { + let batchRequestsVal = this.getBatchRequests(); // false|numeric + if (typeof batchRequestsVal === 'number') { logInfo('going to batch the requests'); let arrRet = []; // return an array of objects containing data describing max 10 bids - for (let i = 0; i < tosendtags.length; i += 10) { - ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) - ozoneRequest.imp = tosendtags.slice(i, i + 10); - ozoneRequest.ext = extObj; + for (let i = 0; i < tosendtags.length; i += batchRequestsVal) { + if (bidderRequest.auctionId) { + logInfo('Found bidderRequest.auctionId - will pass these values through & not generate our own id'); + ozoneRequest.id = bidderRequest.auctionId; + ozoneRequest.auctionId = bidderRequest.auctionId; + deepSetValue(ozoneRequest, 'source.tid', deepAccess(bidderRequest, 'ortb2.source.tid')); + } else { + logInfo('Did not find bidderRequest.auctionId - will generate our own id'); + ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) + } deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); + ozoneRequest.imp = tosendtags.slice(i, i + batchRequestsVal); + ozoneRequest.ext = extObj; if (ozoneRequest.imp.length > 0) { arrRet.push({ method: 'POST', @@ -394,7 +440,15 @@ export const spec = { logInfo('requests will not be batched.'); if (singleRequest) { logInfo('buildRequests starting to generate response for a single request'); - ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) + if (bidderRequest.auctionId) { + logInfo('Found bidderRequest.auctionId - will pass these values through & not generate our own id'); + ozoneRequest.id = bidderRequest.auctionId; + ozoneRequest.auctionId = bidderRequest.auctionId; + deepSetValue(ozoneRequest, 'source.tid', deepAccess(bidderRequest, 'ortb2.source.tid')); + } else { + logInfo('Did not find bidderRequest.auctionId - will generate our own id'); + ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) + } ozoneRequest.imp = tosendtags; ozoneRequest.ext = extObj; deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); @@ -522,6 +576,7 @@ export const spec = { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); adserverTargeting[whitelabelPrefix + '_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_size'] = String(allBidsForThisBidid[bidderName].width) + 'x' + String(allBidsForThisBidid[bidderName].height); if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); } @@ -572,10 +627,28 @@ export const spec = { arrAllBids.push(thisBid); } } + let ret = arrAllBids; + let fledgeAuctionConfigs = deepAccess(serverResponse, 'ext.igi') || []; // 20240606 standardising + if (Array.isArray(fledgeAuctionConfigs) && fledgeAuctionConfigs.length > 0) { + fledgeAuctionConfigs = fledgeAuctionConfigs.filter(config => { + if (!this.isValidAuctionConfig(config)) { + logWarn('Malformed auction config detected:', config); + return false; + } + return true; + }); + ret = { + bids: arrAllBids, + fledgeAuctionConfigs, + }; + } let endTime = new Date().getTime(); logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`); - logInfo('interpretResponse arrAllBids (serialised): ', deepClone(arrAllBids)); // this is ok to log because the renderer has not been attached yet - return arrAllBids; + logInfo('interpretResponse arrAllBids (serialised): ', deepClone(ret)); // this is ok to log because the renderer has not been attached yet + return ret; + }, + isValidAuctionConfig(config) { + return typeof config === 'object' && config !== null; }, setBidMediaTypeIfNotExist(thisBid, mediaType) { if (!thisBid.hasOwnProperty('mediaType')) { @@ -614,11 +687,12 @@ export const spec = { } return ret; }, - getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { + getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy, gppConsent = {}) { logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'gdprConsent', gdprConsent, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { return []; } + let { gppString = '', applicableSections = [] } = gppConsent; if (optionsType.iframeEnabled) { var arrQueryString = []; if (config.getConfig('debug')) { @@ -627,6 +701,10 @@ export const spec = { arrQueryString.push('gdpr=' + (deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); arrQueryString.push('gdpr_consent=' + deepAccess(gdprConsent, 'consentString', '')); arrQueryString.push('usp_consent=' + (usPrivacy || '')); + arrQueryString.push('gpp=' + gppString); + if (Array.isArray(applicableSections)) { + arrQueryString.push(`gpp_sid=${applicableSections.join()}`); + } for (let keyname in this.cookieSyncBag.userIdObject) { arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); } @@ -660,43 +738,26 @@ export const spec = { } return null; }, - findAllUserIds(bidRequest) { - var ret = {}; - let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; - if (bidRequest.hasOwnProperty('userId')) { - for (let arrayId in searchKeysSingle) { - let key = searchKeysSingle[arrayId]; - if (bidRequest.userId.hasOwnProperty(key)) { - if (typeof (bidRequest.userId[key]) == 'string') { - ret[key] = bidRequest.userId[key]; - } else if (typeof (bidRequest.userId[key]) == 'object') { - logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); - ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values - } else { - logError(`failed to get string key value for userId : ${key}`); - } - } - } - let lipbid = deepAccess(bidRequest.userId, 'lipb.lipbid'); - if (lipbid) { - ret['lipb'] = {'lipbid': lipbid}; - } - let id5id = deepAccess(bidRequest.userId, 'id5id.uid'); - if (id5id) { - ret['id5id'] = id5id; - } - let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); - if (sharedid) { - ret['sharedid'] = sharedid; - } + findAllUserIdsFromEids(bidRequest) { + let ret = {}; + if (!bidRequest.hasOwnProperty('userIdAsEids')) { + logInfo('findAllUserIdsFromEids - no bidRequest.userIdAsEids object - will quit'); + this.tryGetPubCidFromOldLocation(ret, bidRequest); // legacy + return ret; + } + for (let obj of bidRequest.userIdAsEids) { + ret[obj.source] = deepAccess(obj, 'uids.0.id'); } + this.tryGetPubCidFromOldLocation(ret, bidRequest); // legacy + return ret; + }, + tryGetPubCidFromOldLocation(ret, bidRequest) { if (!ret.hasOwnProperty('pubcid')) { let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); if (pubcid) { - ret['pubcid'] = pubcid; // if built with old pubCommonId module + ret['pubcid.org'] = pubcid; // if built with old pubCommonId module (use the new eid key) } } - return ret; }, getPlacementId(bidRequest) { return (bidRequest.params.placementId).toString(); @@ -792,11 +853,9 @@ export const spec = { return objRet; }, _addVideoDefaults(objRet, objConfig, addIfMissing) { - let context = deepAccess(objConfig, 'context'); - if (context === 'outstream') { - objRet.placement = 3; - } else if (context === 'instream') { - objRet.placement = 1; + let placementValue = this.getVideoPlacementValue(deepAccess(objConfig, 'context')); + if (placementValue) { + objRet.placement = placementValue; } let skippable = deepAccess(objConfig, 'skippable', null); if (skippable == null) { diff --git a/modules/paapi.js b/modules/paapi.js index 310974b31fe..8ddd1912c29 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -158,8 +158,9 @@ export function addPaapiConfigHook(next, request, paapiConfig) { const {config, igb} = paapiConfig; if (config) { config.auctionSignals = setFPD(config.auctionSignals || {}, request); + const pbs = config.perBuyerSignals = config.perBuyerSignals ?? {}; (config.interestGroupBuyers || []).forEach(buyer => { - deepSetValue(config, `perBuyerSignals.${buyer}`, setFPD(config.perBuyerSignals?.[buyer] || {}, request)); + pbs[buyer] = setFPD(pbs[buyer] ?? {}, request); }) storePendingData(pendingConfigsForAuction, config); } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 168758763e8..0ecb6365430 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -550,6 +550,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); + const customHeaders = deepAccess(s2sBidRequest, 's2sConfig.customHeaders', {}); if (request && requestJson && endpointUrl) { const networkDone = s2sBidRequest.metrics.startTiming('net'); ajax( @@ -584,7 +585,8 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques { contentType: 'text/plain', withCredentials: true, - browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)) + browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)), + customHeaders } ); } else { diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 9e1fa49fef2..e54db34d401 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -281,7 +281,7 @@ function isOWPubmaticBid(adapterName) { }) } -function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { +function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { adUnit.bids[bidId].forEach(function(bid) { @@ -290,6 +290,16 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { return; } const pg = window.parseFloat(Number(bid.bidResponse?.adserverTargeting?.hb_pb || bid.bidResponse?.adserverTargeting?.pwtpb).toFixed(BID_PRECISION)); + + const prebidBidsReceived = e?.bidsReceived; + if (isArray(prebidBidsReceived) && prebidBidsReceived.length > 0) { + prebidBidsReceived.forEach(function(iBid) { + if (iBid.adId === bid.adId) { + bid.bidderCode = iBid.bidderCode; + } + }); + } + partnerBids.push({ 'pn': adapterName, 'bc': bid.bidderCode || bid.bidder, @@ -391,7 +401,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['pdvid'] = '' + profileVersionId; outputObj['dvc'] = {'plt': getDevicePlatform()}; outputObj['tgid'] = getTgId(); - outputObj['pbv'] = getGlobal()?.version || '-1'; + outputObj['pbv'] = '$prebid.version$' || '-1'; if (floorData && floorFetchStatus) { outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; @@ -407,7 +417,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'au': origAdUnit.owAdUnitId || getGptSlotInfoForAdUnitCode(adUnitId)?.gptSlot || adUnitId, 'mt': getAdUnitAdFormats(origAdUnit), 'sz': getSizesForAdUnit(adUnit, adUnitId), - 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), + 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId), e), 'fskp': floorData && floorFetchStatus ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, 'sid': generateUUID() }; @@ -559,7 +569,8 @@ function bidResponseHandler(args) { logWarn(LOG_PRE_FIX + 'Got null requestId in bidResponseHandler'); return; } - let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId][0]; + let requestId = args.originalRequestId || args.requestId; + let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[requestId][0]; if (!bid) { logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); return; @@ -567,7 +578,9 @@ function bidResponseHandler(args) { if ((bid.bidder && args.bidderCode && bid.bidder !== args.bidderCode) || (bid.bidder === args.bidderCode && bid.status === SUCCESS)) { bid = copyRequiredBidDetails(args); - cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId].push(bid); + cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[requestId].push(bid); + } else if (args.originalRequestId) { + bid.bidId = args.requestId; } if (args.floorData) { @@ -598,7 +611,7 @@ function bidRejectedHandler(args) { function bidderDoneHandler(args) { cache.auctions[args.auctionId].bidderDonePendingCount--; args.bids.forEach(bid => { - let cachedBid = cache.auctions[bid.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId || bid.requestId]; + let cachedBid = cache.auctions[bid.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId || bid.originalRequestId || bid.requestId]; if (typeof bid.serverResponseTimeMs !== 'undefined') { cachedBid.serverLatencyTimeMs = bid.serverResponseTimeMs; } @@ -613,7 +626,7 @@ function bidderDoneHandler(args) { function bidWonHandler(args) { let auctionCache = cache.auctions[args.auctionId]; - auctionCache.adUnitCodes[args.adUnitCode].bidWon = args.requestId; + auctionCache.adUnitCodes[args.adUnitCode].bidWon = args.originalRequestId || args.requestId; auctionCache.adUnitCodes[args.adUnitCode].bidWonAdId = args.adId; executeBidWonLoggerCall(args.auctionId, args.adUnitCode); } @@ -631,7 +644,7 @@ function bidTimeoutHandler(args) { // db = 0 and t = 1 means bidder did respond with a bid but post timeout args.forEach(badBid => { let auctionCache = cache.auctions[badBid.auctionId]; - let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.requestId ][0]; + let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.originalRequestId || badBid.requestId ][0]; if (bid) { bid.status = ERROR; bid.error = { diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index d4a7ec70a70..3b9aee3ecb7 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -24,11 +24,7 @@ let events = { }; function getStorage() { - try { - return window.top['sessionStorage']; - } catch (e) { - return null; - } + return null; } var pubxaiAnalyticsAdapter = Object.assign(adapter( diff --git a/modules/raynRtdProvider.js b/modules/raynRtdProvider.js index d558c360c4a..ee3d18be381 100644 --- a/modules/raynRtdProvider.js +++ b/modules/raynRtdProvider.js @@ -14,6 +14,7 @@ import { deepAccess, deepSetValue, logError, logMessage, mergeDeep } from '../sr const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'rayn'; const RAYN_TCF_ID = 1220; +const RAYN_PERSONA_TAXONOMY_ID = 103015; const LOG_PREFIX = 'RaynJS: '; export const SEGMENTS_RESOLVER = 'rayn.io'; export const RAYN_LOCAL_STORAGE_KEY = 'rayn-segtax'; @@ -77,6 +78,32 @@ export function generateOrtbDataObject(segtax, segment, maxTier) { }; } +/** + * Create and return ORTB2 object with segtax and personaIds + * @param {number} segtax + * @param {Array} personaIds + * @return {Array} + */ +export function generatePersonaOrtbDataObject(segtax, personaIds) { + const segmentIds = []; + + try { + segmentIds.push(...personaIds.map((id) => { + return { id }; + })) + } catch (error) { + logError(LOG_PREFIX, error); + } + + return { + name: SEGMENTS_RESOLVER, + ext: { + segtax, + }, + segment: segmentIds, + }; +} + /** * Generates checksum * @param {string} url @@ -127,8 +154,14 @@ export function setSegmentsAsBidderOrtb2(bidConfig, bidders, integrationConfig, deepSetValue(raynOrtb2, 'site.content.data', raynContentData); } + const raynUserData = []; if (integrationConfig.iabAudienceCategories.v1_1.enabled && segments[4]) { - const raynUserData = [generateOrtbDataObject(4, segments[4], integrationConfig.iabAudienceCategories.v1_1.tier)]; + raynUserData.push(generateOrtbDataObject(4, segments[4], integrationConfig.iabAudienceCategories.v1_1.tier)); + } + if (segments[RAYN_PERSONA_TAXONOMY_ID]) { + raynUserData.push(generatePersonaOrtbDataObject(RAYN_PERSONA_TAXONOMY_ID, segments[RAYN_PERSONA_TAXONOMY_ID])); + } + if (raynUserData.length > 0) { deepSetValue(raynOrtb2, 'user.data', raynUserData); } @@ -163,8 +196,8 @@ function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { segments[checksum] || (segments[4] && integrationConfig.iabAudienceCategories.v1_1.enabled && !integrationConfig.iabContentCategories.v2_2.enabled && - !integrationConfig.iabContentCategories.v3_0.enabled - ) + !integrationConfig.iabContentCategories.v3_0.enabled) || + segments[RAYN_PERSONA_TAXONOMY_ID] )) { logMessage(LOG_PREFIX, `Segtax data from localStorage: ${JSON.stringify(segments)}`); setSegmentsAsBidderOrtb2(reqBidsConfigObj, bidders, integrationConfig, segments, checksum); diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js new file mode 100644 index 00000000000..98d628df181 --- /dev/null +++ b/modules/richaudienceBidAdapter.js @@ -0,0 +1,375 @@ +import {deepAccess, isStr, triggerPixel} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; + +const BIDDER_CODE = 'richaudience'; +let REFERER = ''; + +export const spec = { + code: BIDDER_CODE, + gvlid: 108, + aliases: ['ra'], + supportedMediaTypes: [BANNER, VIDEO], + + /*** + * Determines whether or not the given bid request is valid + * + * @param {bidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.pid && bid.params.supplyType); + }, + /*** + * Build a server request from the list of valid BidRequests + * @param {validBidRequests} is an array of the valid bids + * @param {bidderRequest} bidder request object + * @returns {ServerRequest} Info describing the request to the server + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + var payload = { + bidfloor: raiGetFloor(bid, config), + ifa: bid.params.ifa, + pid: bid.params.pid, + supplyType: bid.params.supplyType, + currencyCode: config.getConfig('currency.adServerCurrency'), + auctionId: bid.auctionId, + bidId: bid.bidId, + BidRequestsCount: bid.bidRequestsCount, + bidder: bid.bidder, + bidderRequestId: bid.bidderRequestId, + tagId: bid.adUnitCode, + sizes: raiGetSizes(bid), + referer: (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null), + numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), + transactionId: bid.ortb2Imp?.ext?.tid, + timeout: bidderRequest.timeout || 600, + user: raiSetEids(bid), + demand: raiGetDemandType(bid), + videoData: raiGetVideoInfo(bid), + scr_rsl: raiGetResolution(), + cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), + kws: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords).join(','), + schain: bid.schain, + gpid: raiSetPbAdSlot(bid) + }; + + REFERER = (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null) + + payload.gdpr_consent = ''; + payload.gdpr = false; + + if (bidderRequest && bidderRequest.gdprConsent) { + if (typeof bidderRequest.gdprConsent.gdprApplies != 'undefined') { + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + } + if (typeof bidderRequest.gdprConsent.consentString != 'undefined') { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + } + + if (bidderRequest?.gppConsent) { + payload.privacy = { + gpp: bidderRequest.gppConsent.gppString, + gpp_sid: bidderRequest.gppConsent.applicableSections + } + } else if (bidderRequest?.ortb2?.regs?.gpp) { + payload.privacy = { + gpp: bidderRequest.ortb2.regs.gpp, + gpp_sid: bidderRequest.ortb2.regs.gpp_sid + } + } + + var payloadString = JSON.stringify(payload); + + var endpoint = 'https://shb.richaudience.com/hb/'; + + return { + method: 'POST', + url: endpoint, + data: payloadString, + }; + }); + }, + /*** + * Read the response from the server and build a list of bids + * @param {serverResponse} Response from the server. + * @param {bidRequest} Bid request object + * @returns {bidResponses} Array of bids which were nested inside the server + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + // try catch + var response = serverResponse.body; + if (response) { + var bidResponse = { + requestId: JSON.parse(bidRequest.data).bidId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creative_id, + mediaType: response.media_type, + netRevenue: response.netRevenue, + currency: response.currency, + ttl: response.ttl, + meta: response.adomain, + dealId: response.dealId + }; + + if (response.media_type === 'video') { + bidResponse.vastXml = response.vastXML; + try { + if (bidResponse.vastXml != null) { + if (JSON.parse(bidRequest.data).videoData.format == 'outstream' || JSON.parse(bidRequest.data).videoData.format == 'banner') { + bidResponse.renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: response.media_type, + url: 'https://cdn3.richaudience.com/prebidVideo/player.js' + }); + } + bidResponse.renderer.setRender(renderer); + } + } catch (e) { + bidResponse.ad = response.adm; + } + } else { + bidResponse.ad = response.adm; + } + + bidResponses.push(bidResponse); + } + return bidResponses; + }, + /*** + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @param {gdprConsent} GPDR consent object + * @returns {Array} + */ + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + + var rand = Math.floor(Math.random() * 9999999999); + var syncUrl = ''; + var consent = ''; + var consentGPP = ''; + + var raiSync = {}; + + raiSync = raiGetSyncInclude(config); + + if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') { + consent = `consentString=${gdprConsent.consentString}` + } + + // GPP Consent + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + consentGPP = 'gpp=' + encodeURIComponent(gppConsent.gppString); + consentGPP += '&gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(',')); + } + + if (syncOptions.iframeEnabled && raiSync.raiIframe != 'exclude') { + syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand + if (consent != '') { + syncUrl += `&${consent}` + } + if (consentGPP != '') { + syncUrl += `&${consentGPP}` + } + syncs.push({ + type: 'iframe', + url: syncUrl + }); + } + + if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0 && raiSync.raiImage != 'exclude') { + syncUrl = `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=${REFERER}`; + if (consent != '') { + syncUrl += `&${consent}` + } + if (consentGPP != '') { + syncUrl += `&${consentGPP}` + } + syncs.push({ + type: 'image', + url: syncUrl + }); + } + return syncs + }, + + onTimeout: function (data) { + let url = raiGetTimeoutURL(data); + if (url) { + triggerPixel(url); + } + } +}; + +registerBidder(spec); + +function raiGetSizes(bid) { + let raiNewSizes; + if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) { + raiNewSizes = bid.mediaTypes.banner.sizes + } + if (raiNewSizes != null) { + return raiNewSizes.map(size => ({ + w: size[0], + h: size[1] + })); + } +} + +function raiGetDemandType(bid) { + let raiFormat = 'display'; + if (typeof bid.sizes != 'undefined') { + bid.sizes.forEach(function (sz) { + if ((sz[0] == '1800' && sz[1] == '1000') || (sz[0] == '1' && sz[1] == '1')) { + raiFormat = 'skin' + } + }) + } + if (bid.mediaTypes != undefined) { + if (bid.mediaTypes.video != undefined) { + raiFormat = 'video'; + } + } + return raiFormat; +} + +function raiGetVideoInfo(bid) { + let videoData; + if (raiGetDemandType(bid) == 'video') { + videoData = { + format: bid.mediaTypes.video.context, + playerSize: bid.mediaTypes.video.playerSize, + mimes: bid.mediaTypes.video.mimes + }; + } else { + videoData = { + format: 'banner' + } + } + return videoData; +} + +function raiSetEids(bid) { + let eids = []; + + if (bid && bid.userId) { + raiSetUserId(bid, eids, 'id5-sync.com', deepAccess(bid, `userId.id5id.uid`)); + raiSetUserId(bid, eids, 'pubcommon', deepAccess(bid, `userId.pubcid`)); + raiSetUserId(bid, eids, 'criteo.com', deepAccess(bid, `userId.criteoId`)); + raiSetUserId(bid, eids, 'liveramp.com', deepAccess(bid, `userId.idl_env`)); + raiSetUserId(bid, eids, 'liveintent.com', deepAccess(bid, `userId.lipb.lipbid`)); + raiSetUserId(bid, eids, 'adserver.org', deepAccess(bid, `userId.tdid`)); + } + + return eids; +} + +function raiSetUserId(bid, eids, source, value) { + if (isStr(value)) { + eids.push({ + userId: value, + source: source + }); + } +} + +function renderer(bid) { + bid.renderer.push(() => { + renderAd(bid) + }); +} + +function renderAd(bid) { + let raOutstreamHBPassback = `${bid.vastXml}`; + let raPlayerHB = { + config: bid.params[0].player != undefined ? { + end: bid.params[0].player.end != null ? bid.params[0].player.end : 'close', + init: bid.params[0].player.init != null ? bid.params[0].player.init : 'close', + skin: bid.params[0].player.skin != null ? bid.params[0].player.skin : 'light', + } : {end: 'close', init: 'close', skin: 'light'}, + pid: bid.params[0].pid, + adUnit: bid.adUnitCode + }; + + window.raParams(raPlayerHB, raOutstreamHBPassback, true); +} + +function raiGetResolution() { + let resolution = ''; + if (typeof window.screen != 'undefined') { + resolution = window.screen.width + 'x' + window.screen.height; + } + return resolution; +} + +function raiSetPbAdSlot(bid) { + let pbAdSlot = ''; + if (deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') != null) { + pbAdSlot = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') + } + return pbAdSlot +} + +function raiGetSyncInclude(config) { + try { + let raConfig = null; + let raiSync = {}; + if (config.getConfig('userSync').filterSettings != null && typeof config.getConfig('userSync').filterSettings != 'undefined') { + raConfig = config.getConfig('userSync').filterSettings + if (raConfig.iframe != null && typeof raConfig.iframe != 'undefined') { + raiSync.raiIframe = raConfig.iframe.bidders == 'richaudience' || raConfig.iframe.bidders == '*' ? raConfig.iframe.filter : 'exclude'; + } + if (raConfig.image != null && typeof raConfig.image != 'undefined') { + raiSync.raiImage = raConfig.image.bidders == 'richaudience' || raConfig.image.bidders == '*' ? raConfig.image.filter : 'exclude'; + } + } + return raiSync; + } catch (e) { + return null; + } +} + +function raiGetFloor(bid, config) { + try { + let raiFloor; + if (bid.params.bidfloor != null) { + raiFloor = bid.params.bidfloor; + } else if (typeof bid.getFloor == 'function') { + let floorSpec = bid.getFloor({ + currency: config.getConfig('floors.data.currency') != null ? config.getConfig('floors.data.currency') : 'USD', + mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', + size: '*' + }) + + raiFloor = floorSpec.floor; + } + return raiFloor + } catch (e) { + return 0 + } +} + +function raiGetTimeoutURL(data) { + let {params, timeout} = data[0] + let url = 'https://s.richaudience.com/err/?ec=6&ev=[timeout_publisher]&pla=[placement_hash]&int=PREBID&pltfm=&node=&dm=[domain]'; + + url = url.replace('[timeout_publisher]', timeout) + url = url.replace('[placement_hash]', params[0].pid) + if (document.location.host != null) { + url = url.replace('[domain]', document.location.host) + } + return url +} diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index f888117b166..35298b8421d 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Rich Audience Bidder Adapter Module Type: Bidder Adapter -Maintainer: cert@richaudience.com +Maintainer: integrations@richaudience.com ``` # Description diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 284e62e70fe..7170f8b04b7 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -119,6 +119,7 @@ function buildBidRequest(validBidRequest) { const bidRequest = { id: validBidRequest.bidId, transactionId: validBidRequest.ortb2Imp?.ext?.tid, + gpid: validBidRequest.ortb2Imp?.ext?.gpid, sizes: validBidRequest.sizes, supplyTypes: mediaTypes, adUnitId: params.adUnitId, diff --git a/modules/setupadBidAdapter.js b/modules/setupadBidAdapter.js index c6fb9097122..4ee6dd7c085 100644 --- a/modules/setupadBidAdapter.js +++ b/modules/setupadBidAdapter.js @@ -1,14 +1,14 @@ import { _each, - createTrackPixelHtml, - deepAccess, isStr, getBidIdParameter, triggerPixel, logWarn, + deepSetValue, } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'setupad'; const ENDPOINT = 'https://prebid.setupad.io/openrtb2/auction'; @@ -16,11 +16,39 @@ const SYNC_ENDPOINT = 'https://cookie.stpd.cloud/sync?'; const REPORT_ENDPOINT = 'https://adapter-analytics.setupad.io/api/adapter-analytics?'; const GVLID = 1241; const TIME_TO_LIVE = 360; -const biddersCreativeIds = {}; - -function getEids(bidRequest) { - if (deepAccess(bidRequest, 'userIdAsEids')) return bidRequest.userIdAsEids; -} +export const biddersCreativeIds = {}; // export only for tests +const NET_REVENUE = true; +const TEST_REQUEST = 0; // used only for testing + +const converter = ortbConverter({ + context: { + netRevenue: NET_REVENUE, + ttl: TIME_TO_LIVE, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue( + imp, + 'ext.prebid.storedrequest.id', + getBidIdParameter('placement_id', bidRequest.params) + ); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'test', TEST_REQUEST); + deepSetValue( + request, + 'ext.prebid.storedrequest.id', + getBidIdParameter( + 'account_id', + bidderRequest.bids.find((bid) => bid.hasOwnProperty('params')).params + ) + ); + deepSetValue(request, 'setupad', 'adapter'); + return request; + }, +}); export const spec = { code: BIDDER_CODE, @@ -37,97 +65,17 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - const requests = []; - - _each(validBidRequests, function (bid) { - const id = getBidIdParameter('placement_id', bid.params); - const accountId = getBidIdParameter('account_id', bid.params); - const auctionId = bid.auctionId; - const bidId = bid.bidId; - const eids = getEids(bid) || undefined; - let sizes = bid.sizes; - if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; - - const site = { - page: bidderRequest?.refererInfo?.page, - ref: bidderRequest?.refererInfo?.ref, - domain: bidderRequest?.refererInfo?.domain, - }; - const device = { - w: bidderRequest?.ortb2?.device?.w, - h: bidderRequest?.ortb2?.device?.h, - }; - - const payload = { - id: bid?.bidderRequestId, - ext: { - prebid: { - storedrequest: { - id: accountId || 'default', - }, - }, - }, - user: { ext: { eids } }, - device, - site, - imp: [], - }; - - const imp = { - id: bid.adUnitCode, - ext: { - prebid: { - storedrequest: { id }, - }, - }, - }; - - if (deepAccess(bid, 'mediaTypes.banner')) { - imp.banner = { - format: (sizes || []).map((s) => { - return { w: s[0], h: s[1] }; - }), - }; - } - - payload.imp.push(imp); - - const gdprConsent = bidderRequest && bidderRequest.gdprConsent; - const uspConsent = bidderRequest && bidderRequest.uspConsent; - - if (gdprConsent || uspConsent) { - payload.regs = { ext: {} }; - - if (uspConsent) payload.regs.ext.us_privacy = uspConsent; - - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies !== 'undefined') { - payload.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; - } - - if (typeof gdprConsent.consentString !== 'undefined') { - payload.user.ext.consent = gdprConsent.consentString; - } - } - } - const params = bid.params; - - requests.push({ - method: 'POST', - url: ENDPOINT, - data: JSON.stringify(payload), - options: { - contentType: 'text/plain', - withCredentials: true, - }, - - bidId, - params, - auctionId, - }); - }); - - return requests; + const data = converter.toORTB({ validBidRequests, bidderRequest }); + + return { + method: 'POST', + url: ENDPOINT, + data, + options: { + contentType: 'text/plain', + withCredentials: true, + }, + }; }, interpretResponse: function (serverResponse, bidRequest) { @@ -141,40 +89,22 @@ export const spec = { return []; } - const serverBody = serverResponse.body; - const bidResponses = []; - - _each(serverBody.seatbid, (res) => { + // set a seat for creativeId for triggerPixel url + _each(serverResponse.body.seatbid, (res) => { _each(res.bid, (bid) => { - const requestId = bidRequest.bidId; - const params = bidRequest.params; - const { ad, adUrl } = getAd(bid); - - const bidResponse = { - requestId, - params, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.id, - currency: serverBody.cur, - netRevenue: true, - ttl: TIME_TO_LIVE, - meta: { - advertiserDomains: bid.adomain || [], - }, - }; - - // set a seat for creativeId for triggerPixel url - biddersCreativeIds[bidResponse.creativeId] = res.seat; - - bidResponse.ad = ad; - bidResponse.adUrl = adUrl; - bidResponses.push(bidResponse); + biddersCreativeIds[bid.crid] = res.seat; }); }); - return bidResponses; + // used for a test case "should update biddersCreativeIds correctly" to return early and not throw ORTB error + if (serverResponse.testCase === 1) return; + + const bids = converter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; + + return bids; }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { @@ -261,16 +191,4 @@ function getBidders(serverResponse) { } } -function getAd(bid) { - const { adm, nurl } = bid; - let ad = adm; - - if (nurl) { - const trackingPixel = createTrackPixelHtml(decodeURIComponent(nurl)); - ad += trackingPixel; - } - - return { ad }; -} - registerBidder(spec); diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index af1442bd301..c081b49c2e6 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -127,14 +127,19 @@ export const spec = { creativeId: response.creativeId, netRevenue: true, currency: response.currency, - mediaType: BANNER - } + mediaType: BANNER, + meta: {} + }; if (response.mediaType === VIDEO) { bidObject.vastXml = response.ad; bidObject.mediaType = VIDEO; } + if (response.meta) { + bidObject.meta = response.meta; + } + return bidObject; }, diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index d99696152ba..8df01fcd599 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -130,7 +130,7 @@ export function processFpd(config, {global}, {data = topicsData} = {}) { export function getCachedTopics() { let cachedTopicData = []; const topics = config.getConfig('userSync.topics'); - const bidderList = topics.bidders || []; + const bidderList = topics?.bidders || []; let storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); storedSegments && storedSegments.forEach((value, cachedBidder) => { // Check bidder exist in config for cached bidder data and then only retrieve the cached data diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 1ce9b0f5a09..afdde5f0a7f 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ /** * This module adds uid2 ID support to the User ID module * The {@link module:modules/userId} module is required. diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index 917e305f3fb..36e5d414941 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { ajax } from '../src/ajax.js'; import { cyrb53Hash } from '../src/utils.js'; diff --git a/package-lock.json b/package-lock.json index de68505b648..04572fd3c8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.1.0-pre", + "version": "9.2.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.1.0-pre", + "version": "9.2.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index a4d6e3a61dd..ab91c6e006a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.1.0-pre", + "version": "9.2.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { diff --git a/src/auction.js b/src/auction.js index 881dee9f2de..b422ffa7333 100644 --- a/src/auction.js +++ b/src/auction.js @@ -82,7 +82,7 @@ import { } from './utils.js'; import {getPriceBucketString} from './cpmBucketManager.js'; import {getNativeTargeting, isNativeResponse, setNativeResponseProperties} from './native.js'; -import {getCacheUrl, store} from './videoCache.js'; +import {batchAndStore} from './videoCache.js'; import {Renderer} from './Renderer.js'; import {config} from './config.js'; import {userSync} from './userSync.js'; @@ -94,7 +94,7 @@ import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; import adapterManager from './adapterManager.js'; -import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS } from './constants.js'; +import {EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS} from './constants.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {adjustCpm} from './utils/cpm.js'; @@ -580,68 +580,10 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au } } -const _storeInCache = (batch) => { - store(batch.map(entry => entry.bidResponse), function (error, cacheIds) { - cacheIds.forEach((cacheId, i) => { - const { auctionInstance, bidResponse, afterBidAdded } = batch[i]; - if (error) { - logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); - } else { - if (cacheId.uuid === '') { - logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); - } else { - bidResponse.videoCacheKey = cacheId.uuid; - if (!bidResponse.vastUrl) { - bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); - } - addBidToAuction(auctionInstance, bidResponse); - afterBidAdded(); - } - } - }); - }); -}; - -const storeInCache = FEATURES.VIDEO ? _storeInCache : () => {}; - -let batchSize, batchTimeout; -config.getConfig('cache', (cacheConfig) => { - batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 - ? cacheConfig.cache.batchSize - : 1; - batchTimeout = typeof cacheConfig.cache.batchTimeout === 'number' && cacheConfig.cache.batchTimeout > 0 - ? cacheConfig.cache.batchTimeout - : 0; -}); - -export const batchingCache = (timeout = setTimeout, cache = storeInCache) => { - let batches = [[]]; - let debouncing = false; - const noTimeout = cb => cb(); - - return function(auctionInstance, bidResponse, afterBidAdded) { - const batchFunc = batchTimeout > 0 ? timeout : noTimeout; - if (batches[batches.length - 1].length >= batchSize) { - batches.push([]); - } - - batches[batches.length - 1].push({auctionInstance, bidResponse, afterBidAdded}); - - if (!debouncing) { - debouncing = true; - batchFunc(() => { - batches.forEach(cache); - batches = [[]]; - debouncing = false; - }, batchTimeout); - } - } -}; - -const batchAndStore = batchingCache(); - export const callPrebidCache = hook('async', function(auctionInstance, bidResponse, afterBidAdded, videoMediaType) { - batchAndStore(auctionInstance, bidResponse, afterBidAdded); + if (FEATURES.VIDEO) { + batchAndStore(auctionInstance, bidResponse, afterBidAdded); + } }, 'callPrebidCache'); /** diff --git a/src/utils.js b/src/utils.js index 1f05b7b37c1..35596cb6442 100644 --- a/src/utils.js +++ b/src/utils.js @@ -661,6 +661,21 @@ export function isSafeFrameWindow() { return !!(ws.$sf && ws.$sf.ext); } +/** + * Returns the result of calling the function $sf.ext.geom() if it exists + * @see https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf — 5.4 Function $sf.ext.geom + * @returns {Object | undefined} geometric information about the container + */ +export function getSafeframeGeometry() { + try { + const ws = getWindowSelf(); + return (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : undefined; + } catch (e) { + logError('Error getting SafeFrame geometry', e); + return undefined; + } +} + export function isSafariBrowser() { return /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); } @@ -693,6 +708,33 @@ export function getPerformanceNow() { return (window.performance && window.performance.now && window.performance.now()) || 0; } +/** + * Retuns the difference between `timing.domLoading` and `timing.navigationStart`. + * This function uses the deprecated `Performance.timing` API and should be removed in future. + * It has not been updated yet because it is still used in some modules. + * @deprecated + * @param {Window} w The window object used to perform the api call. default to window.self + * @returns {number} + */ +export function getDomLoadingDuration(w) { + let domLoadingDuration = -1; + + w = w || getWindowSelf(); + + const performance = w.performance; + + if (w.performance?.timing) { + if (w.performance.timing.navigationStart > 0) { + const val = performance.timing.domLoading - performance.timing.navigationStart; + if (val > 0) { + domLoadingDuration = val; + } + } + } + + return domLoadingDuration; +} + /** * When the deviceAccess flag config option is false, no cookies should be read or set * @returns {boolean} diff --git a/src/videoCache.js b/src/videoCache.js index 6cba77de308..cf39c1c9452 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -12,6 +12,8 @@ import {ajaxBuilder} from './ajax.js'; import {config} from './config.js'; import {auctionManager} from './auctionManager.js'; +import {logError, logWarn} from './utils.js'; +import {addBidToAuction} from './auction.js'; /** * Might be useful to be configurable in the future @@ -159,3 +161,73 @@ export function store(bids, done, getAjax = ajaxBuilder) { export function getCacheUrl(id) { return `${config.getConfig('cache.url')}?uuid=${id}`; } + +export const _internal = { + store +} + +export function storeBatch(batch) { + const bids = batch.map(entry => entry.bidResponse) + function err(msg) { + logError(`Failed to save to the video cache: ${msg}. Video bids will be discarded:`, bids) + } + _internal.store(bids, function (error, cacheIds) { + if (error) { + err(error) + } else if (batch.length !== cacheIds.length) { + logError(`expected ${batch.length} cache IDs, got ${cacheIds.length} instead`) + } else { + cacheIds.forEach((cacheId, i) => { + const {auctionInstance, bidResponse, afterBidAdded} = batch[i]; + if (cacheId.uuid === '') { + logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); + } else { + bidResponse.videoCacheKey = cacheId.uuid; + if (!bidResponse.vastUrl) { + bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); + } + addBidToAuction(auctionInstance, bidResponse); + afterBidAdded(); + } + }); + } + }); +}; + +let batchSize, batchTimeout; +if (FEATURES.VIDEO) { + config.getConfig('cache', (cacheConfig) => { + batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 + ? cacheConfig.cache.batchSize + : 1; + batchTimeout = typeof cacheConfig.cache.batchTimeout === 'number' && cacheConfig.cache.batchTimeout > 0 + ? cacheConfig.cache.batchTimeout + : 0; + }); +} + +export const batchingCache = (timeout = setTimeout, cache = storeBatch) => { + let batches = [[]]; + let debouncing = false; + const noTimeout = cb => cb(); + + return function (auctionInstance, bidResponse, afterBidAdded) { + const batchFunc = batchTimeout > 0 ? timeout : noTimeout; + if (batches[batches.length - 1].length >= batchSize) { + batches.push([]); + } + + batches[batches.length - 1].push({auctionInstance, bidResponse, afterBidAdded}); + + if (!debouncing) { + debouncing = true; + batchFunc(() => { + batches.forEach(cache); + batches = [[]]; + debouncing = false; + }, batchTimeout); + } + }; +}; + +export const batchAndStore = batchingCache(); diff --git a/test/mocks/videoCacheStub.js b/test/mocks/videoCacheStub.js index 7ce899cae35..acae5cd6a32 100644 --- a/test/mocks/videoCacheStub.js +++ b/test/mocks/videoCacheStub.js @@ -1,4 +1,4 @@ -import * as videoCache from 'src/videoCache.js'; +import {_internal as videoCache} from 'src/videoCache.js'; /** * Function which can be called from unit tests to stub out the video cache. diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index e5cdb66e75f..26f641a10e7 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -12,7 +12,7 @@ import * as auctionModule from 'src/auction.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; import { createBid } from 'src/bidfactory.js'; import { config } from 'src/config.js'; -import * as store from 'src/videoCache.js'; +import {_internal as store} from 'src/videoCache.js'; import * as ajaxLib from 'src/ajax.js'; import {find} from 'src/polyfill.js'; import { server } from 'test/mocks/xhr.js'; diff --git a/test/spec/modules/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js index 2c1612f2e83..ad469d29b37 100644 --- a/test/spec/modules/adagioRtdProvider_spec.js +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -1,4 +1,9 @@ -import { adagioRtdSubmodule, _internal, storage } from 'modules/adagioRtdProvider.js'; +import { + PLACEMENT_SOURCES, + _internal, + adagioRtdSubmodule, + storage, +} from 'modules/adagioRtdProvider.js'; import * as utils from 'src/utils.js'; import { loadExternalScript } from '../../../src/adloader.js'; import { expect } from 'chai'; @@ -376,6 +381,62 @@ describe('Adagio Rtd Provider', function () { const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; expect(ortb2ImpExt.adunit_position).equal(''); }); + + describe('update the ortb2Imp.ext.data.placement if not present', function() { + const config = { + name: SUBMODULE_NAME, + params: { + organizationId: '1000', + site: 'mysite' + } + }; + + it('update the placement value with the adUnit.code value', function() { + const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.ADUNITCODE; + + const bidRequest = utils.deepClone(bidReqConfig); + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal('div-gpt-ad-1460505748561-0'); + }); + + it('update the placement value with the gpid value', function() { + const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.GPID; + + const bidRequest = utils.deepClone(bidReqConfig); + const gpid = '/19968336/header-bid-tag-0' + utils.deepSetValue(bidRequest.adUnits[0], 'ortb2Imp.ext.gpid', gpid) + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal(gpid); + }); + + it('update the placement value the legacy adUnit[].bids adagio.params.placement value', function() { + const placement = 'placement-value'; + + const configCopy = utils.deepClone(config); + + const bidRequest = utils.deepClone(bidReqConfig); + bidRequest.adUnits[0].bids[0].params.placement = placement; + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal(placement); + }); + + it('it does not populate `ortb2Imp.ext.data.placement` if no fallback', function() { + const configCopy = utils.deepClone(config); + const bidRequest = utils.deepClone(bidReqConfig); + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.not.exist; + }); + }); }); describe('submodule `onBidRequestEvent`', function() { diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index 3db97a17d88..ff48d8579a7 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -7,9 +7,14 @@ import { BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME, getEffectiveConnectionType, getUserIds, - getDomLoadingDuration, GVL_ID, } from 'modules/bliinkBidAdapter.js'; +import { + canAccessWindowTop, + getDomLoadingDuration, + getWindowSelf, + getWindowTop +} from 'src/utils.js'; import { config } from 'src/config.js'; /** @@ -32,8 +37,9 @@ import { config } from 'src/config.js'; * ortb2Imp: {ext: {data: {pbadslot: string}}}}} */ +const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); const connectionType = getEffectiveConnectionType(); -const domLoadingDuration = getDomLoadingDuration().toString(); +const domLoadingDuration = getDomLoadingDuration(w).toString(); const getConfigBid = (placement) => { return { adUnitCode: '/19968336/test', diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index 0fa199ed034..4f8862982ad 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -196,7 +196,7 @@ describe('dailymotionBidAdapterTests', () => { livestream: !!bidRequestData[0].params.video.livestream, isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, context: { - siteOrAppCat: '', + siteOrAppCat: [], videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, autoplay: bidRequestData[0].params.video.autoplay, playerVolume: bidRequestData[0].params.video.playerVolume, @@ -342,7 +342,7 @@ describe('dailymotionBidAdapterTests', () => { livestream: !!bidRequestData[0].params.video.livestream, isCreatedForKids: null, context: { - siteOrAppCat: '', + siteOrAppCat: [], videoViewsInSession: null, autoplay: null, playerVolume: null, @@ -588,7 +588,7 @@ describe('dailymotionBidAdapterTests', () => { livestream: false, isCreatedForKids: null, context: { - siteOrAppCat: '', + siteOrAppCat: [], videoViewsInSession: null, autoplay: null, playerVolume: null, diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 7a2756dff9e..eae5fd21310 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -26,6 +26,7 @@ describe('ID5 ID System', function () { const ID5_MODULE_NAME = 'id5Id'; const ID5_EIDS_NAME = ID5_MODULE_NAME.toLowerCase(); const ID5_SOURCE = 'id5-sync.com'; + const TRUE_LINK_SOURCE = 'true-link-id5-sync.com'; const ID5_TEST_PARTNER_ID = 173; const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_TEST_PARTNER_ID}.json`; const ID5_API_CONFIG_URL = `https://id5-sync.com/api/config/prebid`; @@ -48,10 +49,8 @@ describe('ID5 ID System', function () { const EUID_STORED_ID = 'EUID_1'; const EUID_SOURCE = 'uidapi.com'; const ID5_STORED_OBJ_WITH_EUID = { - 'universal_uid': ID5_STORED_ID, - 'signature': ID5_STORED_SIGNATURE, + ...ID5_STORED_OBJ, 'ext': { - 'linkType': ID5_STORED_LINK_TYPE, 'euid': { 'source': EUID_SOURCE, 'uids': [{ @@ -61,6 +60,11 @@ describe('ID5 ID System', function () { } } }; + const TRUE_LINK_STORED_ID = 'TRUE_LINK_1'; + const ID5_STORED_OBJ_WITH_TRUE_LINK = { + ...ID5_STORED_OBJ, + publisherTrueLinkId: TRUE_LINK_STORED_ID + }; const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; const ID5_RESPONSE_LINK_TYPE = 2; @@ -148,6 +152,16 @@ describe('ID5 ID System', function () { }); } + function wrapAsyncExpects(done, expectsFn) { + return function () { + try { + expectsFn(); + } catch (err) { + done(err); + } + } + } + class XhrServerMock { currentRequestIdx = 0; server; @@ -837,6 +851,38 @@ describe('ID5 ID System', function () { id5System.id5IdSubmodule.getId(getId5FetchConfig()); }); }); + + it('should pass true link info to ID5 server even when true link is not booted', function () { + let xhrServerMock = new XhrServerMock(server); + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.true_link).is.deep.equal({booted: false}); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }); + }); + + it('should pass full true link info to ID5 server when true link is booted', function () { + let xhrServerMock = new XhrServerMock(server); + let trueLinkResponse = {booted: true, redirected: true, id: 'TRUE_LINK_ID'}; + window.id5Bootstrap = { + getTrueLinkInfo: function () { + return trueLinkResponse; + } + }; + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.true_link).is.deep.equal(trueLinkResponse); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }); + }); }); describe('Local storage', () => { @@ -950,6 +996,31 @@ describe('ID5 ID System', function () { }, {adUnits}); }); + it('should add stored TRUE_LINK_ID from cache to bids', function (done) { + id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_TRUE_LINK), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + requestBidsHook(wrapAsyncExpects(done, function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.trueLinkId`); + expect(bid.userId.trueLinkId.uid).is.equal(TRUE_LINK_STORED_ID); + expect(bid.userIdAsEids[1]).is.deep.equal({ + source: TRUE_LINK_SOURCE, + uids: [{ + id: TRUE_LINK_STORED_ID, + atype: 1, + }] + }); + }); + }); + done(); + }), {adUnits}); + }); + it('should add config value ID to bids', function (done) { init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); @@ -1056,6 +1127,11 @@ describe('ID5 ID System', function () { 'ext': {'provider': ID5_SOURCE} }); }); + it('should decode trueLinkId from a stored object with trueLinkId', function () { + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_TRUE_LINK, getId5FetchConfig()).trueLinkId).is.deep.equal({ + 'uid': TRUE_LINK_STORED_ID + }); + }); }); describe('A/B Testing', function () { diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index 73df2fba8fd..b48943da266 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import {Renderer} from '../../../src/Renderer.js'; import {getGranularityKeyName, getGranularityObject} from '../../../modules/ozoneBidAdapter.js'; import * as utils from '../../../src/utils.js'; +import {deepSetValue} from '../../../src/utils.js'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const BIDDER_CODE = 'ozone'; var validBidRequests = [ @@ -401,6 +402,66 @@ var validBidderRequest = { start: 1536838908987, timeout: 3000 }; +var validBidderRequestWithCookieDeprecation = { + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + auctionStart: 1536838908986, + bidderCode: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + bids: [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000, + ortb2: { + 'device': { + 'w': 1617, + 'h': 317, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '125' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '125' + ] + }, + { + 'brand': 'Not.A/Brand', + 'version': [ + '24' + ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'fake_control_2' + } + } + } +}; var bidderRequestWithFullGdpr = { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', auctionStart: 1536838908986, @@ -1814,7 +1875,7 @@ describe('ozone Adapter', function () { }); it('should add gdpr consent information to the request when ozone is true', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: true, @@ -1832,7 +1893,7 @@ describe('ozone Adapter', function () { }); it('should add gdpr consent information to the request when vendorData is missing vendorConsents (Mirror)', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: true, @@ -1848,7 +1909,7 @@ describe('ozone Adapter', function () { }); it('should set regs.ext.gdpr flag to 0 when gdprApplies is false', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: false, @@ -1863,9 +1924,24 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); expect(payload.regs.ext.gdpr).to.equal(0); }); + it('should set gpp and gpp_sid when available', function() { + let gppString = 'gppConsentString'; + let gppSections = [7, 8, 9]; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + bidderRequest.ortb2 = {regs: {gpp: gppString, gpp_sid: gppSections}}; + const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs.gpp).to.equal(gppString); + expect(payload.regs.gpp_sid).to.have.same.members(gppSections); + }); + it('should not set gpp and gpp_sid keys when not available', function() { + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload).to.not.contain.keys(['gpp', 'gpp_sid', 'ext', 'regs']); + }); it('should not have imp[N].ext.ozone.userId', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: false, @@ -1876,7 +1952,7 @@ describe('ozone Adapter', function () { purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} } }; - let bidRequests = validBidRequests; + let bidRequests = JSON.parse(JSON.stringify(validBidRequests)); bidRequests[0]['userId'] = { 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, @@ -1891,23 +1967,12 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); let firstBid = payload.imp[0].ext.ozone; expect(firstBid).to.not.have.property('userId'); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests }); it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { let bidRequests = validBidRequests; - bidRequests[0]['userId'] = { - 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'idl_env': '3333', - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - 'tdid': '6666', - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} - }; - bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload.ext.ozone.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests }); it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019) Updated Aug 2020', function() { const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest); @@ -2056,7 +2121,7 @@ describe('ozone Adapter', function () { const data = JSON.parse(request.data); expect(data.imp[0].ext.gpid).to.equal('/22037345/projectozone'); }); - it('should batch into 10s if config is set', function () { + it('should batch into 10s if config is set to true', function () { config.setConfig({ozone: {'batchRequests': true}}); var specMock = utils.deepClone(spec); let arrReq = []; @@ -2069,7 +2134,20 @@ describe('ozone Adapter', function () { expect(request.length).to.equal(3); config.resetConfig(); }); - it('should not batch into 10s if config is set to false and singleRequest is true', function () { + it('should batch into 7 if config is set to 7', function () { + config.setConfig({ozone: {'batchRequests': 7}}); + var specMock = utils.deepClone(spec); + let arrReq = []; + for (let i = 0; i < 25; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + const request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(4); + config.resetConfig(); + }); + it('should not batch if config is set to false and singleRequest is true', function () { config.setConfig({ozone: {'batchRequests': false, 'singleRequest': true}}); var specMock = utils.deepClone(spec); let arrReq = []; @@ -2082,6 +2160,57 @@ describe('ozone Adapter', function () { expect(request.method).to.equal('POST'); config.resetConfig(); }); + it('should not batch if config is set to invalid value -10 and singleRequest is true', function () { + config.setConfig({ozone: {'batchRequests': -10, 'singleRequest': true}}); + var specMock = utils.deepClone(spec); + let arrReq = []; + for (let i = 0; i < 15; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + const request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); + config.resetConfig(); + }); + it('should use GET values for batchRequests if found', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': '5'}; + }; + let arrReq = []; + for (let i = 0; i < 25; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + let request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(5); // 5 x 5 = 25 + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': '10'}; // the built in function will return '10' (string) + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(3); // 10, 10, 5 + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': true}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching - GET param must be numeric + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': 'true'}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching - GET param must be numeric + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': -5}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching + }); it('should use GET values auction=dev & cookiesync=dev if set', function() { var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { @@ -2295,6 +2424,27 @@ describe('ozone Adapter', function () { expect(data.source.ext).to.haveOwnProperty('schain'); expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` }); + it('should find ortb2 cookieDeprecation values', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequestWithCookieDeprecation)); + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.cookieDeprecationLabel).to.equal('fake_control_2'); + }); + it('should set ortb2 cookieDeprecation to "none" if there is none', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.cookieDeprecationLabel).to.equal('none'); + }); + it('should handle fledge requests', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + let bidRequests = JSON.parse(JSON.stringify(validBidRequests)); + deepSetValue(bidRequests[0], 'ortb2Imp.ext.ae', 1); + bidderRequest.fledgeEnabled = true; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].ext.ae).to.equal(1); + }); }); describe('interpretResponse', function () { beforeEach(function () { @@ -2509,6 +2659,20 @@ describe('ozone Adapter', function () { const bid = result[0]; expect(bid.mediaType).to.equal('video'); }); + it('should handle fledge response', function () { + const req = spec.buildRequests(validBidRequests, validBidderRequest); + let objResp = JSON.parse(JSON.stringify(validResponse)); + objResp.body.ext = {igi: [{ + 'impid': '1', + 'igb': [{ + 'origin': 'https://paapi.dsp.com', + 'pbs': '{"key": "value"}' + }] + }]}; + const result = spec.interpretResponse(objResp, req); + expect(result).to.be.an('object'); + expect(result.fledgeAuctionConfigs[0]['impid']).to.equal('1'); + }); }); describe('userSyncs', function () { it('should fail gracefully if no server response', function () { @@ -2540,6 +2704,10 @@ describe('ozone Adapter', function () { expect(result).to.be.an('array'); expect(result[0].url).to.include('usp_consent=&'); }); + it('should add gpp if its present', function () { + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1, '1---', { gppString: 'gppStringHere', applicableSections: [7, 8, 9] }); + expect(result[0].url).to.include('gpp=gppStringHere&gpp_sid=7,8,9'); + }); }); describe('video object utils', function () { it('should find width & height from video object', function () { @@ -2698,7 +2866,7 @@ describe('ozone Adapter', function () { }); }); describe('addVideoDefaults', function() { - it('should correctly add video defaults', function () { + it('should not add video defaults if there is no videoParams config', function () { let mediaTypes = { playerSize: [640, 480], mimes: ['video/mp4'], @@ -2715,12 +2883,14 @@ describe('ozone Adapter', function () { testKey: 'child value' }; let result = spec.addVideoDefaults({}, mediaTypes, mediaTypes); - expect(result.placement).to.equal(3); + expect(result.placement).to.be.undefined; expect(result.skip).to.equal(0); result = spec.addVideoDefaults({}, mediaTypes, bid_params_video); expect(result.skip).to.equal(1); }); - it('should correctly add video defaults including skippable in parent', function () { + it('should correctly add video defaults if page config videoParams is defined, also check skip in the parent', function () { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel.videoParams = {outstream: 3, instream: 1}; let mediaTypes = { playerSize: [640, 480], mimes: ['video/mp4'], @@ -2736,7 +2906,7 @@ describe('ozone Adapter', function () { skipafter: 5, testKey: 'child value' }; - let result = spec.addVideoDefaults({}, mediaTypes, bid_params_video); + let result = specMock.addVideoDefaults({}, mediaTypes, bid_params_video); expect(result.placement).to.equal(3); expect(result.skip).to.equal(1); }); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index 7814c09ea61..77c8a6d7dda 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -91,10 +91,10 @@ describe('paapi module', () => { let igb1, igb2, buyerAuctionConfig; beforeEach(() => { igb1 = { - origin: 'buyer1' + origin: 'buyer.1' }; igb2 = { - origin: 'buyer2' + origin: 'buyer.2' }; buyerAuctionConfig = { seller: 'seller', @@ -263,11 +263,11 @@ describe('paapi module', () => { }); it('should be added to perBuyerSignals', () => { - auctionConfig.interestGroupBuyers = ['buyer1', 'buyer2']; + auctionConfig.interestGroupBuyers = ['buyer.1', 'buyer.2']; const pbs = getComponentAuctionConfig().perBuyerSignals; sinon.assert.match(pbs, { - buyer1: {prebid: {ortb2, ortb2Imp}}, - buyer2: {prebid: {ortb2, ortb2Imp}} + 'buyer.1': {prebid: {ortb2, ortb2Imp}}, + 'buyer.2': {prebid: {ortb2, ortb2Imp}} }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 2b4cd025515..d44a67d2acc 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -825,6 +825,20 @@ describe('S2S Adapter', function () { }) }) }) + + it('should set customHeaders correctly when publisher has provided it', () => { + let configWithCustomHeaders = utils.deepClone(CONFIG); + configWithCustomHeaders.customHeaders = { customHeader1: 'customHeader1Value' }; + config.setConfig({ s2sConfig: configWithCustomHeaders }); + + let reqWithNewConfig = utils.deepClone(REQUEST); + reqWithNewConfig.s2sConfig = configWithCustomHeaders; + + adapter.callBids(reqWithNewConfig, BID_REQUESTS, addBidResponse, done, ajax); + const reqHeaders = server.requests[0].requestHeaders + expect(reqHeaders.customHeader1).to.exist; + expect(reqHeaders.customHeader1).to.equal('customHeader1Value'); + }); it('should block request if config did not define p1Consent URL in endpoint object config', function () { let badConfig = utils.deepClone(CONFIG); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 002b7fb3063..f77b167a3e9 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -568,7 +568,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -786,7 +786,7 @@ describe('pubmatic analytics adapter', function () { expect(data.pid).to.equal('1111'); expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); expect(data.tgid).to.equal(0); @@ -866,7 +866,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(0);// test group id should be between 0-15 else set to 0 expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -1434,7 +1434,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -1566,7 +1566,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -1657,8 +1657,55 @@ describe('pubmatic analytics adapter', function () { expect(data.origbidid).to.equal('partnerImpressionID-1'); }); + it('Logger: should use originalRequestId to find the bid', function() { + MOCK.BID_RESPONSE[1]['originalRequestId'] = '3bd4ebb1c900e2'; + MOCK.BID_RESPONSE[1]['requestId'] = '54d4ebb1c9003e'; + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] + }); + + config.setConfig({ + testGroupId: 15 + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + + // slot 1 + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].origbidid).to.equal('partnerImpressionID-1'); + + // slot 2 + expect(data.s[1].ps[0].bidid).to.equal('54d4ebb1c9003e'); + expect(data.s[1].ps[0].origbidid).to.equal('partnerImpressionID-2'); + + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.bidid).to.equal('2ecff0db240757'); + expect(data.origbidid).to.equal('partnerImpressionID-1'); + }); + it('Logger: best case + win tracker. Log bidId when partnerimpressionid is missing', function() { delete MOCK.BID_RESPONSE[1]['partnerImpId']; + MOCK.BID_RESPONSE[1]['requestId'] = '3bd4ebb1c900e2'; MOCK.BID_RESPONSE[1]['prebidBidId'] = 'Prebid-bid-id-1'; sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] diff --git a/test/spec/modules/raynRtdProvider_spec.js b/test/spec/modules/raynRtdProvider_spec.js index 69ea316e8b5..3920d090550 100644 --- a/test/spec/modules/raynRtdProvider_spec.js +++ b/test/spec/modules/raynRtdProvider_spec.js @@ -152,6 +152,7 @@ describe('rayn RTD Submodule', function () { 2: ['71', '313'], 4: ['33', '145', '712'] }; + TEST_SEGMENTS['103015'] = ['agdv23', 'avscg3']; const bidderOrtb2 = {}; const bidders = RTD_CONFIG.dataProviders[0].params.bidders; @@ -174,6 +175,9 @@ describe('rayn RTD Submodule', function () { TEST_SEGMENTS['4']['3'].forEach((id) => { expect(ortb2.user.data[0].segment.find(segment => segment.id === id)).to.exist; }); + TEST_SEGMENTS['103015'].forEach((id) => { + expect(ortb2.user.data[1].segment.find(segment => segment.id === id)).to.exist; + }); }); }); }); @@ -229,6 +233,27 @@ describe('rayn RTD Submodule', function () { logMessageSpy.restore(); }); + it('should update reqBidsConfigObj and execute callback using persona segment from localStorage', function () { + const callbackSpy = sinon.spy(); + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const testSegments = { + 103015: ['agdv23', 'avscg3'] + }; + + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(JSON.stringify(testSegments)); + + const reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + + raynRTD.raynSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, RTD_CONFIG.dataProviders[0]); + + expect(callbackSpy.calledOnce).to.be.true; + expect(logMessageSpy.lastCall.lastArg).to.equal(`Segtax data from localStorage: ${JSON.stringify(testSegments)}`); + + logMessageSpy.restore(); + }); + it('should update reqBidsConfigObj and execute callback using segments from raynJS', function () { const callbackSpy = sinon.spy(); const logMessageSpy = sinon.spy(utils, 'logMessage'); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js new file mode 100644 index 00000000000..d2b173f53df --- /dev/null +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -0,0 +1,1304 @@ +// import or require modules necessary for the test, e.g.: +import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' +import { + spec +} from 'modules/richaudienceBidAdapter.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import sinon from 'sinon'; + +describe('Richaudience adapter tests', function () { + var DEFAULT_PARAMS_NEW_SIZES = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + keywords: 'key1=value1;key2=value2' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + ortb2Imp: { + ext: { + tid: '29df2112-348b-4961-8863-1b33684d95e6', + } + }, + user: {} + }]; + + var DEFAULT_PARAMS_NEW_SIZES_GPID = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1#example-2', + data: { + pbadslot: '/19968336/header-bid-tag-1#example-2' + } + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + keywords: 'key1=value1;key2=value2' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_VIDEO_TIMEOUT = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: [{ + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }], + timeout: 3000, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }] + + var DEFAULT_PARAMS_VIDEO_IN = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_VIDEO_OUT = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [[300, 250], [600, 300]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_APP = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'richaudience', + params: { + bidfloor: 0.5, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + pid: 'ADb1f40rmi', + supplyType: 'app', + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var DEFAULT_PARAMS_WO_OPTIONAL = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'richaudience', + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + }, + auctionId: '851adee7-d843-48f9-a7e9-9ff00573fcbf', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var BID_RESPONSE = { + body: { + cpm: 1.50, + adm: '', + media_type: 'js', + width: 300, + height: 250, + creative_id: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + dealId: 'dealId', + adomain: 'richaudience.com' + } + }; + + var BID_RESPONSE_VIDEO = { + body: { + cpm: 1.50, + media_type: 'video', + width: 1, + height: 1, + creative_id: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + vastXML: '', + dealId: 'dealId', + adomain: 'richaudience.com' + } + }; + + var DEFAULT_PARAMS_GDPR = { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'http://domain.com', + numIframes: 0 + } + } + + it('Referer undefined', function() { + config.setConfig({ + 'currency': {'adServerCurrency': 'USD'} + }) + + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('referer').and.to.equal(null); + expect(requestContent).to.have.property('referer').and.to.equal(null); + }) + + it('Verify build request to prebid 3.0 display test', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.5); + expect(requestContent).to.have.property('pid').and.to.equal('ADb1f40rmi'); + expect(requestContent).to.have.property('supplyType').and.to.equal('site'); + expect(requestContent).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); + expect(requestContent).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); + expect(requestContent).to.have.property('BidRequestsCount').and.to.equal(1); + expect(requestContent).to.have.property('bidder').and.to.equal('richaudience'); + expect(requestContent).to.have.property('bidderRequestId').and.to.equal('1858b7382993ca'); + expect(requestContent).to.have.property('tagId').and.to.equal('test-div'); + expect(requestContent).to.have.property('referer').and.to.equal('https%3A%2F%2Fdomain.com'); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(600); + expect(requestContent.sizes[2]).to.have.property('w').and.to.equal(728); + expect(requestContent.sizes[2]).to.have.property('h').and.to.equal(90); + expect(requestContent.sizes[3]).to.have.property('w').and.to.equal(970); + expect(requestContent.sizes[3]).to.have.property('h').and.to.equal(250); + expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); + expect(requestContent).to.have.property('timeout').and.to.equal(600); + expect(requestContent).to.have.property('numIframes').and.to.equal(0); + expect(typeof requestContent.scr_rsl === 'string') + expect(typeof requestContent.cpuc === 'number') + expect(typeof requestContent.gpid === 'string') + expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); + }) + + it('Verify build request to prebid video inestream', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('demand').and.to.equal('video'); + expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); + }) + + it('Verify build request to prebid video outstream', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('demand').and.to.equal('video'); + expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); + }) + + describe('gdpr test', function () { + it('Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'USD' + }, + consentManagement: { + cmpApi: 'iab', + timeout: 8000, + allowAuctionWithoutConsent: true + } + }); + + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); + }); + + it('Verify adding ifa when supplyType equal to app', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_APP, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + }); + + it('Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 8000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA' + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); + }); + }); + + describe('UID test', function () { + config.setConfig({ + consentManagement: { + cmpApi: 'iab', + timeout: 5000, + allowAuctionWithoutConsent: true + }, + userSync: { + userIds: [{ + name: 'id5Id', + params: { + partner: 173, // change to the Partner Number you received from ID5 + pd: 'MT1iNTBjY...' // optional, see table below for a link to how to generate this + }, + storage: { + type: 'html5', // "html5" is the required storage type + name: 'id5id', // "id5id" is the required storage name + expires: 90, // storage lasts for 90 days + refreshInSeconds: 8 * 3600 // refresh ID every 8 hours to ensure it's fresh + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } + }); + it('Verify build id5', function () { + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 1 }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: [] }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: null }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: {} }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build pubCommonId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 'pub_common_user_id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'pub_common_user_id', + 'source': 'pubcommon' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build criteoId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 'criteo-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'criteo-user-id', + 'source': 'criteo.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build identityLink', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'identity-link-user-id', + 'source': 'liveramp.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + it('Verify build liveIntentId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data) + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'identity-link-user-id', + 'source': 'liveramp.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + it('Verify build TradeDesk', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.tdid = 'tdid-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data) + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'tdid-user-id', + 'source': 'adserver.org' + }]); + + request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + }); + + it('Verify interprete response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.ad).to.equal(''); + expect(bid.mediaType).to.equal('js'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + expect(bid.meta).to.equal('richaudience.com'); + }); + + it('no banner media response inestream', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + expect(bid.meta).to.equal('richaudience.com'); + }); + + it('no banner media response outstream', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('banner media and response VAST', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + const bid = bids[0]; + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + }); + + it('Verifies bidder_code', function () { + expect(spec.code).to.equal('richaudience'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('ra'); + }); + + it('Verifies bidder gvlid', function () { + expect(spec.gvlid).to.equal(108); + }); + + it('Verifies bidder supportedMediaTypes', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); + + it('Verifies if bid request is valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'app' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'app', + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + bidfloor: 0.50, + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); + }); + + it('should pass schain', function() { + let schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + DEFAULT_PARAMS_NEW_SIZES[0].schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('schain').to.deep.equal(schain); + }) + + it('should pass gpid', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gpid').and.to.equal('/19968336/header-bid-tag-1#example-2'); + }) + + describe('onTimeout', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('onTimeout exist as a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should send timeouts', function () { + spec.onTimeout(DEFAULT_PARAMS_VIDEO_TIMEOUT); + expect(utils.triggerPixel.called).to.equal(true); + expect(utils.triggerPixel.firstCall.args[0]).to.equal('https://s.richaudience.com/err/?ec=6&ev=3000&pla=ADb1f40rmi&int=PREBID&pltfm=&node=&dm=localhost:9876'); + }); + }); + + describe('userSync', function () { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }); + it('Verifies user syncs iframe include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + it('Verifies user syncs iframe exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: false + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('Verifies user syncs image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: false + }) + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe exclude / image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe include / image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image include with GPP', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({iframeEnabled: true}, [BID_RESPONSE], { + gppString: 'DBABL~BVVqAAEABgA.QA', + applicableSections: [7]}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({pixelEnabled: true}, [BID_RESPONSE], { + gppString: 'DBABL~BVVqAAEABgA.QA', + applicableSections: [7, 5]}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('Verifies user syncs URL image include with GPP', function () { + const gppConsent = { gppString: 'DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA', applicableSections: [0] }; + const result = spec.getUserSyncs({pixelEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'image', url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=http%3A%2F%2Fdomain.com&gpp=DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA&gpp_sid=0` + }]); + }); + }) +}); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index ed3e2c9be0b..1e0141b5107 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -37,6 +37,7 @@ function getSlotConfigs(mediaTypes, params) { ortb2Imp: { ext: { tid: 'd704d006-0d6e-4a09-ad6c-179e7e758096', + gpid: 'some-gpid' } }, adUnitCode: adUnitCode, @@ -299,6 +300,7 @@ describe('Seedtag Adapter', function () { expect(data.ttfb).to.be.greaterThanOrEqual(0); expect(data.bidRequests[0].adUnitCode).to.equal(adUnitCode); + expect(data.bidRequests[0].gpid).to.equal('some-gpid'); }); describe('GDPR params', function () { diff --git a/test/spec/modules/setupadBidAdapter_spec.js b/test/spec/modules/setupadBidAdapter_spec.js index bc1527df603..3a184c50922 100644 --- a/test/spec/modules/setupadBidAdapter_spec.js +++ b/test/spec/modules/setupadBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { spec } from 'modules/setupadBidAdapter.js'; +import { spec, biddersCreativeIds } from 'modules/setupadBidAdapter.js'; describe('SetupadAdapter', function () { const userIdAsEids = [ @@ -42,9 +42,104 @@ describe('SetupadAdapter', function () { }, userIdAsEids, }, + { + adUnitCode: 'test-div-2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, ]; const bidderRequest = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: 1579746300522, + bidderCode: 'setupad', + bidderRequestId: '15246a574e859f', + bids: [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, + { + adUnitCode: 'test-div-2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, + ], + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + vendorData: {}, + gdprApplies: true, + }, ortb2: { device: { w: 1500, @@ -52,39 +147,27 @@ describe('SetupadAdapter', function () { }, }, refererInfo: { + canonicalUrl: null, domain: 'test.com', page: 'http://test.com', - ref: '', + referer: null, }, }; const serverResponse = { body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', seatbid: [ { - bid: [ - { - id: 'test-bid-id', - price: 0.8, - adm: 'this is an ad', - adid: 'test-ad-id', - adomain: ['test.addomain.com'], - w: 300, - h: 250, - }, - ], - seat: 'testBidder', + bid: [{ crid: 123 }, { crid: 1234 }], + seat: 'pubmatic', }, - ], - cur: 'USD', - ext: { - sync: { - image: ['urlA?gdpr={{.GDPR}}'], - iframe: ['urlB'], + { + bid: [{ crid: 12345 }], + seat: 'setupad', }, - }, + ], }, + testCase: 1, }; describe('isBidRequestValid', function () { @@ -119,77 +202,25 @@ describe('SetupadAdapter', function () { }); describe('buildRequests', function () { - it('check request params with GDPR and USP', function () { - const request = spec.buildRequests(bidRequests, bidRequests[0]); - expect(JSON.parse(request[0].data).user.ext.consent).to.equal( - 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA' - ); - expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); - expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('usp-context-string'); - }); - - it('check request params without GDPR', function () { - let bidRequestsWithoutGDPR = Object.assign({}, bidRequests[0]); - delete bidRequestsWithoutGDPR.gdprConsent; - const request = spec.buildRequests([bidRequestsWithoutGDPR], bidRequestsWithoutGDPR); - expect(JSON.parse(request[0].data).regs.ext.gdpr).to.be.undefined; - expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('usp-context-string'); + it('should return correct storedrequest id for bids if placement_id is provided', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.prebid.storedrequest.id).to.equal('123'); }); it('should return correct storedrequest id if account_id is provided', function () { - const request = spec.buildRequests(bidRequests, bidRequests[0]); - expect(JSON.parse(request[0].data).ext.prebid.storedrequest.id).to.equal('test-account-id'); - }); - - it('should return correct storedrequest id if account_id is not provided', function () { - let bidRequestsWithoutAccountId = Object.assign({}, bidRequests[0]); - delete bidRequestsWithoutAccountId.params.account_id; - const request = spec.buildRequests( - [bidRequestsWithoutAccountId], - bidRequestsWithoutAccountId - ); - expect(JSON.parse(request[0].data).ext.prebid.storedrequest.id).to.equal('default'); - }); - - it('validate generated params', function () { - const request = spec.buildRequests(bidRequests); - expect(request[0].bidId).to.equal('22c4871113f461'); - expect(JSON.parse(request[0].data).id).to.equal('15246a574e859f'); - }); - - it('check if correct site object was added', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - const siteObj = JSON.parse(request[0].data).site; - - expect(siteObj.domain).to.equal('test.com'); - expect(siteObj.page).to.equal('http://test.com'); - expect(siteObj.ref).to.equal(''); + expect(request.data.ext.prebid.storedrequest.id).to.equal('test-account-id'); }); - it('check if correct device object was added', function () { + it('should return setupad custom adapter param', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - const deviceObj = JSON.parse(request[0].data).device; - - expect(deviceObj.w).to.equal(1500); - expect(deviceObj.h).to.equal(1000); - }); - - it('check if imp object was added', function () { - const request = spec.buildRequests(bidRequests); - expect(JSON.parse(request[0].data).imp).to.be.an('array'); - }); - - it('should send "user.ext.eids" in the request for Prebid.js supported modules only', function () { - const request = spec.buildRequests(bidRequests); - expect(JSON.parse(request[0].data).user.ext.eids).to.deep.equal(userIdAsEids); + expect(request.data.setupad).to.equal('adapter'); }); - it('should send an undefined "user.ext.eids" in the request if userId module is unsupported', function () { - let bidRequestsUnsupportedUserIdModule = Object.assign({}, bidRequests[0]); - delete bidRequestsUnsupportedUserIdModule.userIdAsEids; - const request = spec.buildRequests(bidRequestsUnsupportedUserIdModule); - - expect(JSON.parse(request[0].data).user.ext.eids).to.be.undefined; + // Change this to 1 whenever TEST_REQUEST = 1. This is allowed only for testing requests locally + it('should return correct test attribute value from global value', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.test).to.equal(0); }); }); @@ -256,25 +287,21 @@ describe('SetupadAdapter', function () { describe('interpretResponse', function () { it('should return empty array if error during parsing', () => { const wrongServerResponse = 'wrong data'; - let request = spec.buildRequests(bidRequests, bidRequests[0]); + let request = spec.buildRequests(bidRequests, bidderRequest); let result = spec.interpretResponse(wrongServerResponse, request); expect(result).to.be.instanceof(Array); expect(result.length).to.equal(0); }); - it('should get correct bid response', function () { - const result = spec.interpretResponse(serverResponse, bidRequests[0]); - expect(result).to.be.an('array').with.lengthOf(1); - expect(result[0].requestId).to.equal('22c4871113f461'); - expect(result[0].cpm).to.equal(0.8); - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].creativeId).to.equal('test-bid-id'); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(360); - expect(result[0].ad).to.equal('this is an ad'); + it('should update biddersCreativeIds correctly', function () { + spec.interpretResponse(serverResponse, bidderRequest); + + expect(biddersCreativeIds).to.deep.equal({ + 123: 'pubmatic', + 1234: 'pubmatic', + 12345: 'setupad', + }); }); }); diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index 6b3147859bf..3b6d5d0c5fc 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -204,7 +204,11 @@ function mockResponseData(requestData) { creativeId: `creative-id-${index}`, cpm: Math.floor(Math.random() * 100), currency: `UAH-${rndIndex}`, - mediaType: mediaType + mediaType: mediaType, + meta: { + primaryCatId: 'IAB2-2', + secondaryCatIds: ['IAB2-14', 'IAB2-6'] + } }; }); return { diff --git a/test/spec/modules/topicsFpdModule_spec.js b/test/spec/modules/topicsFpdModule_spec.js index 6bc8eb1e7c3..0f8e379e95f 100644 --- a/test/spec/modules/topicsFpdModule_spec.js +++ b/test/spec/modules/topicsFpdModule_spec.js @@ -308,33 +308,6 @@ describe('topics', () => { }], name: 'ads.pubmatic.com' }]; - const consentString = 'CPi8wgAPi8wgAADABBENCrCsAP_AAH_AAAAAISNB7D=='; - const consentConfig = { - consentString: consentString, - gdprApplies: true, - vendorData: { - metadata: consentString, - gdprApplies: true, - purpose: { - consents: { - 1: true, - 2: true, - 3: true, - 4: true - } - } - } - }; - const mockData = [ - { - name: 'domain', - segment: [{id: 123}] - }, - { - name: 'domain', - segment: [{id: 321}], - } - ]; const evt = { data: '{"segment":{"domain":"ads.pubmatic.com","topics":[{"configVersion":"chrome.1","modelVersion":"2206021246","taxonomyVersion":"1","topic":165,"version":"chrome.1:1:2206021246"}],"bidder":"pubmatic"},"date":1669743901858}', @@ -345,49 +318,60 @@ describe('topics', () => { storage.removeDataFromLocalStorage(topicStorageName); }); - describe('when cached data is available and not expired', () => { + describe('caching', () => { let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); - const storedSegments = JSON.stringify( - [['pubmatic', { - '2206021246': { - 'ext': {'segtax': 600, 'segclass': '2206021246'}, - 'segment': [{'id': '243'}, {'id': '265'}], - 'name': 'ads.pubmatic.com' - }, - 'lastUpdated': new Date().getTime() - }]] - ); - storage.setDataInLocalStorage(topicStorageName, storedSegments); - config.setConfig({ - userSync: { - topics: { - maxTopicCaller: 4, - bidders: [{ - bidder: 'pubmatic', - iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' - }] - } - } - }) - }); + }) + afterEach(() => { sandbox.restore(); config.resetConfig(); }); - it('should return segments for bidder if transmitUfpd is allowed', () => { - assert.deepEqual(getCachedTopics(), expected); - }); + it('should return no segments when not configured', () => { + config.setConfig({userSync: {}}); + expect(getCachedTopics()).to.eql([]); + }) + + describe('when cached data is available and not expired', () => { + beforeEach(() => { + const storedSegments = JSON.stringify( + [['pubmatic', { + '2206021246': { + 'ext': {'segtax': 600, 'segclass': '2206021246'}, + 'segment': [{'id': '243'}, {'id': '265'}], + 'name': 'ads.pubmatic.com' + }, + 'lastUpdated': new Date().getTime() + }]] + ); + storage.setDataInLocalStorage(topicStorageName, storedSegments); + config.setConfig({ + userSync: { + topics: { + maxTopicCaller: 4, + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' + }] + } + } + }) + }); - it('should NOT return segments for bidder if enrichUfpd is NOT allowed', () => { - sandbox.stub(activities, 'isActivityAllowed').callsFake((activity, params) => { - return !(activity === ACTIVITY_ENRICH_UFPD && params.component === 'bidder.pubmatic'); + it('should return segments for bidder if transmitUfpd is allowed', () => { + assert.deepEqual(getCachedTopics(), expected); + }); + + it('should NOT return segments for bidder if enrichUfpd is NOT allowed', () => { + sandbox.stub(activities, 'isActivityAllowed').callsFake((activity, params) => { + return !(activity === ACTIVITY_ENRICH_UFPD && params.component === 'bidder.pubmatic'); + }); + expect(getCachedTopics()).to.eql([]); }); - expect(getCachedTopics()).to.eql([]); }); - }) + }); it('should return empty segments for bidder if there is cached segments stored which is expired', () => { let storedSegments = '[["pubmatic",{"2206021246":{"ext":{"segtax":600,"segclass":"2206021246"},"segment":[{"id":"243"},{"id":"265"}],"name":"ads.pubmatic.com"},"lastUpdated":10}]]'; diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index fc6e71779cb..7d07da9de90 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,39 +1,13 @@ import chai from 'chai'; -import {getCacheUrl, store} from 'src/videoCache.js'; +import {batchingCache, getCacheUrl, store, _internal, storeBatch} from 'src/videoCache.js'; import {config} from 'src/config.js'; import {server} from 'test/mocks/xhr.js'; import {auctionManager} from '../../src/auctionManager.js'; import {AuctionIndex} from '../../src/auctionIndex.js'; -import {batchingCache} from '../../src/auction.js'; +import * as utils from 'src/utils.js'; const should = chai.should(); -function getMockBid(bidder, auctionId, bidderRequestId) { - return { - 'bidder': bidder, - 'params': { - 'placementId': '10433394', - 'member': 123, - 'keywords': { - 'foo': ['bar', 'baz'], - 'fizz': ['buzz'] - } - }, - 'bid_id': '12345abc', - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', - 'sizes': [300, 250], - 'bidId': '123', - 'bidderRequestId': bidderRequestId, - 'auctionId': auctionId - }; -} - describe('The video cache', function () { function assertError(callbackSpy) { callbackSpy.calledOnce.should.equal(true); @@ -126,9 +100,7 @@ describe('The video cache', function () { prebid.org wrapper - - - + \n \n `; @@ -335,34 +307,36 @@ describe('The video cache', function () { JSON.parse(request.requestBody).should.deep.equal(payload); }); - it('should wait the duration of the batchTimeout and pass the correct batchSize if batched requests are enabled in the config', () => { - const mockAfterBidAdded = function() {}; - let callback = null; - let mockTimeout = sinon.stub().callsFake((cb) => { callback = cb }); + if (FEATURES.VIDEO) { + it('should wait the duration of the batchTimeout and pass the correct batchSize if batched requests are enabled in the config', () => { + const mockAfterBidAdded = function() {}; + let callback = null; + let mockTimeout = sinon.stub().callsFake((cb) => { callback = cb }); - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache', - batchSize: 3, - batchTimeout: 20 - } - }); + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache', + batchSize: 3, + batchTimeout: 20 + } + }); - let stubCache = sinon.stub(); - const batchAndStore = batchingCache(mockTimeout, stubCache); - for (let i = 0; i < 3; i++) { - batchAndStore({}, {}, mockAfterBidAdded); - } + let stubCache = sinon.stub(); + const batchAndStore = batchingCache(mockTimeout, stubCache); + for (let i = 0; i < 3; i++) { + batchAndStore({}, {}, mockAfterBidAdded); + } - sinon.assert.calledOnce(mockTimeout); - sinon.assert.calledWith(mockTimeout, sinon.match.any, 20); + sinon.assert.calledOnce(mockTimeout); + sinon.assert.calledWith(mockTimeout, sinon.match.any, 20); - const expectedBatch = [{ afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }]; + const expectedBatch = [{ afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }]; - callback(); + callback(); - sinon.assert.calledWith(stubCache, expectedBatch); - }); + sinon.assert.calledWith(stubCache, expectedBatch); + }); + } function assertRequestMade(bid, expectedValue) { store([bid], function () { }); @@ -393,6 +367,35 @@ describe('The video cache', function () { return callback; } }); + + describe('storeBatch', () => { + let sandbox; + let err, cacheIds + beforeEach(() => { + err = null; + cacheIds = []; + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'logError'); + sandbox.stub(_internal, 'store').callsFake((_, cb) => cb(err, cacheIds)); + }); + afterEach(() => { + sandbox.restore(); + }) + it('should log an error when store replies with an error', () => { + err = new Error('err'); + storeBatch([]); + sinon.assert.called(utils.logError); + }); + it('should not process returned uuids if they do not match the batch size', () => { + const el = {auctionInstance: {}, bidResponse: {}, afterBidAdded: sinon.stub()} + const batch = [el, el]; + cacheIds = [{uuid: 'mock-id'}] + storeBatch(batch); + expect(el.bidResponse.videoCacheKey).to.not.exist; + sinon.assert.notCalled(batch[0].afterBidAdded); + sinon.assert.called(utils.logError); + }) + }) }); describe('The getCache function', function () {