diff --git a/allowedModules.js b/allowedModules.js index bc9ada39571..dbcae2db2cc 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -1,7 +1,6 @@ module.exports = { 'modules': [ - 'criteo-direct-rsa-validate', 'crypto-js', 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/ ], diff --git a/integrationExamples/gpt/fledge_example.html b/integrationExamples/gpt/paapi_example.html similarity index 97% rename from integrationExamples/gpt/fledge_example.html rename to integrationExamples/gpt/paapi_example.html index 5a6ab7a5fef..860d7c22edf 100644 --- a/integrationExamples/gpt/fledge_example.html +++ b/integrationExamples/gpt/paapi_example.html @@ -3,7 +3,7 @@ diff --git a/integrationExamples/gpt/prebidServer_fledge_example.html b/integrationExamples/gpt/prebidServer_paapi_example.html similarity index 91% rename from integrationExamples/gpt/prebidServer_fledge_example.html rename to integrationExamples/gpt/prebidServer_paapi_example.html index eb2fc438997..d138d2b7753 100644 --- a/integrationExamples/gpt/prebidServer_fledge_example.html +++ b/integrationExamples/gpt/prebidServer_paapi_example.html @@ -3,7 +3,7 @@ @@ -44,8 +44,8 @@ pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { - enabled: true + paapi: { + enabled: true, }, s2sConfig: [{ accountId : '1', @@ -57,13 +57,6 @@ }] }); - pbjs.setBidderConfig({ - bidders: ['openx'], - config: { - fledgeEnabled: true - } - }); - pbjs.addAdUnits(adUnits); pbjs.requestBids({ diff --git a/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html b/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html deleted file mode 100644 index 9a4991d2711..00000000000 --- a/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - -

Standalone PAAPI Prebid.js Example

-

Start local server with:

-gulp serve-fast --https -

Chrome flags:

---enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true - --privacy-sandbox-enrollment-overrides=https://localhost:9999 -

Join interest group at https://privacysandbox.openx.net/fledge/advertiser -

-
Div-1
-
- -
- - - diff --git a/integrationExamples/realTimeData/jwplayerRtdProvider_example.html b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html index f3f0c64fb1a..c6170b565b5 100644 --- a/integrationExamples/realTimeData/jwplayerRtdProvider_example.html +++ b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html @@ -65,7 +65,11 @@ waitForIt: true, params: { // Note: the following media Ids are placeholders and should be replaced with your Ids. - mediaIDs: ['abc', 'def', 'ghi', 'jkl'] + mediaIDs: ['abc', 'def', 'ghi', 'jkl'], + overrideContentId: 'always', + overrideContentUrl: 'always', + overrideContentTitle: 'always', + overrideContentDescription: 'always' } }] } diff --git a/integrationExamples/top-level-paapi/gam-contextual.html b/integrationExamples/top-level-paapi/gam-contextual.html new file mode 100644 index 00000000000..b51b512e0ca --- /dev/null +++ b/integrationExamples/top-level-paapi/gam-contextual.html @@ -0,0 +1,134 @@ + + + + + + + + + + + +

GAM contextual + Publisher as top level PAAPI seller example

+ +

+ This example starts PAAPI auctions at the same time as GAM targeting. The flow is + similar to a typical GAM auction, but if Prebid wins, and got a + PAAPI bid, it is rendered instead of the contextual bid. +

+
+ +
+
Div-1
+
+ +
+
+

Instructions

+

Start local server with:

+gulp serve-fast --https +

Chrome flags:

+--enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true + --privacy-sandbox-enrollment-overrides=https://localhost:9999 +

Join interest group at https://www.optable.co/ +

+
+ + diff --git a/integrationExamples/top-level-paapi/no_adserver.html b/integrationExamples/top-level-paapi/no_adserver.html new file mode 100644 index 00000000000..0b37f80f27c --- /dev/null +++ b/integrationExamples/top-level-paapi/no_adserver.html @@ -0,0 +1,113 @@ + + + + + + + + + +

No ad server, publisher as top level PAAPI seller example

+ +

+ +

+
+ +
+
Div-1
+
+ +
+
+

Instructions

+

Start local server with:

+ gulp serve-fast --https +

Chrome flags:

+ --enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true + --privacy-sandbox-enrollment-overrides=https://localhost:9999 +

Join interest group at https://www.optable.co/ +

+
+ + diff --git a/integrationExamples/gpt/top-level-paapi/decisionLogic.js b/integrationExamples/top-level-paapi/shared/decisionLogic.js similarity index 100% rename from integrationExamples/gpt/top-level-paapi/decisionLogic.js rename to integrationExamples/top-level-paapi/shared/decisionLogic.js diff --git a/integrationExamples/top-level-paapi/shared/example-setup.js b/integrationExamples/top-level-paapi/shared/example-setup.js new file mode 100644 index 00000000000..1c52abf02c9 --- /dev/null +++ b/integrationExamples/top-level-paapi/shared/example-setup.js @@ -0,0 +1,95 @@ +// intercept navigator.runAdAuction and print parameters to console +(() => { + var originalRunAdAuction = navigator.runAdAuction; + navigator.runAdAuction = function (...args) { + console.log('%c runAdAuction', 'background: cyan; border: 2px; border-radius: 3px', ...args); + return originalRunAdAuction.apply(navigator, args); + }; +})(); +init(); +setupContextualResponse(); + +function addExampleControls(requestBids) { + const ctl = document.createElement('div'); + ctl.innerHTML = ` + + Simulate contextual bid: + + CPM + + + `; + ctl.style = 'margin-top: 30px'; + document.body.appendChild(ctl); + ctl.querySelector('.bid').addEventListener('click', function (ev) { + const cpm = ctl.querySelector('.cpm').value; + if (cpm) { + setupContextualResponse(parseInt(cpm, 10)); + } + requestBids(); + }); +} + +function init() { + window.pbjs = window.pbjs || {que: []}; + window.pbjs.que.push(() => { + pbjs.aliasBidder('optable', 'contextual'); + [ + 'auctionInit', + 'auctionTimeout', + 'auctionEnd', + 'bidAdjustment', + 'bidTimeout', + 'bidRequested', + 'bidResponse', + 'bidRejected', + 'noBid', + 'seatNonBid', + 'bidWon', + 'bidderDone', + 'bidderError', + 'setTargeting', + 'beforeRequestBids', + 'beforeBidderHttp', + 'requestBids', + 'addAdUnits', + 'adRenderFailed', + 'adRenderSucceeded', + 'tcf2Enforcement', + 'auctionDebug', + 'bidViewable', + 'staleRender', + 'billableEvent', + 'bidAccepted', + 'paapiRunAuction', + 'paapiBid', + 'paapiNoBid', + 'paapiError', + ].forEach(evt => { + pbjs.onEvent(evt, (arg) => { + console.log('Event:', evt, arg); + }) + }); + }); +} + +function setupContextualResponse(cpm = 1) { + pbjs.que.push(() => { + pbjs.setConfig({ + debugging: { + enabled: true, + intercept: [ + { + when: { + bidder: 'contextual' + }, + then: { + cpm, + currency: 'USD' + } + } + ] + } + }); + }); +} diff --git a/libraries/ortbConverter/lib/composer.js b/libraries/ortbConverter/lib/composer.js index 0ceff7f9edb..477d4e10890 100644 --- a/libraries/ortbConverter/lib/composer.js +++ b/libraries/ortbConverter/lib/composer.js @@ -11,13 +11,13 @@ const SORTED = new WeakMap(); /** * - * @param {Object[string, Component]} components to compose - * @param {Object[string, function|boolean]} overrides a map from component name, to a function that should override that component. + * @param {Object.} components - An object where keys are component names and values are components to compose. + * @param {Object.} overrides - A map from component names to functions that should override those components. * Override functions are replacements, except that they get the original function they are overriding as their first argument. If the override * is `false`, the component is disabled. * - * @return a function that will run all components in order of priority, with functions from `overrides` taking - * precedence over components that match names + * @return {function} - A function that will run all components in order of priority, with functions from `overrides` taking + * precedence over components that match names. */ export function compose(components, overrides = {}) { if (!SORTED.has(components)) { diff --git a/libraries/ortbConverter/lib/sizes.js b/libraries/ortbConverter/lib/sizes.js deleted file mode 100644 index 16b75048203..00000000000 --- a/libraries/ortbConverter/lib/sizes.js +++ /dev/null @@ -1,14 +0,0 @@ -import {parseSizesInput} from '../../../src/utils.js'; - -export function sizesToFormat(sizes) { - sizes = parseSizesInput(sizes); - - // get sizes in form [{ w: , h: }, ...] - return sizes.map(size => { - const [width, height] = size.split('x'); - return { - w: parseInt(width, 10), - h: parseInt(height, 10) - }; - }); -} diff --git a/libraries/ortbConverter/processors/banner.js b/libraries/ortbConverter/processors/banner.js index 2d0136c84b2..fca9598022b 100644 --- a/libraries/ortbConverter/processors/banner.js +++ b/libraries/ortbConverter/processors/banner.js @@ -1,6 +1,13 @@ -import {createTrackPixelHtml, deepAccess, encodeMacroURI, inIframe, mergeDeep} from '../../../src/utils.js'; +import { + createTrackPixelHtml, + deepAccess, + inIframe, + mergeDeep, + sizesToSizeTuples, + sizeTupleToRtbSize, + encodeMacroURI +} from '../../../src/utils.js'; import {BANNER} from '../../../src/mediaTypes.js'; -import {sizesToFormat} from '../lib/sizes.js'; /** * fill in a request `imp` with banner parameters from `bidRequest`. @@ -14,7 +21,7 @@ export function fillBannerImp(imp, bidRequest, context) { topframe: inIframe() === true ? 0 : 1 }; if (bannerParams.sizes) { - banner.format = sizesToFormat(bannerParams.sizes); + banner.format = sizesToSizeTuples(bannerParams.sizes).map(sizeTupleToRtbSize); } if (bannerParams.hasOwnProperty('pos')) { banner.pos = bannerParams.pos; diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js index c38231d9002..caa855566eb 100644 --- a/libraries/ortbConverter/processors/video.js +++ b/libraries/ortbConverter/processors/video.js @@ -1,6 +1,5 @@ -import {deepAccess, isEmpty, logWarn, mergeDeep} from '../../../src/utils.js'; +import {deepAccess, isEmpty, logWarn, mergeDeep, sizesToSizeTuples, sizeTupleToRtbSize} from '../../../src/utils.js'; import {VIDEO} from '../../../src/mediaTypes.js'; -import {sizesToFormat} from '../lib/sizes.js'; // parameters that share the same name & semantics between pbjs adUnits and imp.video const ORTB_VIDEO_PARAMS = new Set([ @@ -27,10 +26,6 @@ const ORTB_VIDEO_PARAMS = new Set([ 'playbackend' ]); -const PLACEMENT = { - 'instream': 1, -} - export function fillVideoImp(imp, bidRequest, context) { if (context.mediaType && context.mediaType !== VIDEO) return; @@ -41,16 +36,13 @@ export function fillVideoImp(imp, bidRequest, context) { .filter(([name]) => ORTB_VIDEO_PARAMS.has(name)) ); if (videoParams.playerSize) { - const format = sizesToFormat(videoParams.playerSize); + const format = sizesToSizeTuples(videoParams.playerSize).map(sizeTupleToRtbSize); if (format.length > 1) { logWarn('video request specifies more than one playerSize; all but the first will be ignored') } Object.assign(video, format[0]); } - const placement = PLACEMENT[videoParams.context]; - if (placement != null) { - video.placement = placement; - } + imp.video = mergeDeep(video, imp.video); } } diff --git a/libraries/video/constants/ortb.js b/libraries/video/constants/ortb.js index d67c8a5f393..86e7b499774 100644 --- a/libraries/video/constants/ortb.js +++ b/libraries/video/constants/ortb.js @@ -1,6 +1,6 @@ /** * @typedef {Object} OrtbParams - * @property {OrtbVideoParamst} video + * @property {OrtbVideoParams} video * @property {OrtbContentParams} content */ @@ -13,7 +13,8 @@ * @property {number} w - Width of the video player in device independent pixels (DIPS). * @property {number} h - Height of the video player in device independent pixels (DIPS). * @property {number|undefined} startdelay - Indicates the offset of the ad placement. - * @property {number|undefined} placement - Placement type for the impression. + * @property {number|undefined} placement - Legacy Placement type for the impression. + * @property {number|undefined} plcmt - Modern placement type for the impression. * @property {number|undefined} linearity - Indicates if the impression must be linear, nonlinear, etc. If omitted, assume all are allowed. * @property {number} skip - Indicates if the player can allow the video to be skipped, where 0 is no, 1 is yes. * @property {number|undefined} skipmin - Only ad creatives with a duration greater than this value can be skippable; only applicable if the ad is skippable. @@ -97,6 +98,18 @@ export const PLACEMENT = { INTERSTITIAL_SLIDER_FLOATING: 5 }; +/** + * ADCOM - https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/develop/AdCOM%20v1.0%20FINAL.md#list_plcmtsubtypesvideo + * @enum OrtbVideoParams.plcmt + */ +export const PLCMT = { + INSTREAM: 1, + ACCOMPANYING_CONTENT: 2, + INTERSTITIAL: 3, + OUTSTREAM: 4, + NO_CONTENT: 4 +}; + /** * ORTB 2.5 section 5.4 - Ad Position * @enum OrtbVideoParams.pos diff --git a/libraries/weakStore/weakStore.js b/libraries/weakStore/weakStore.js new file mode 100644 index 00000000000..09606354dae --- /dev/null +++ b/libraries/weakStore/weakStore.js @@ -0,0 +1,15 @@ +import {auctionManager} from '../../src/auctionManager.js'; + +export function weakStore(get) { + const store = new WeakMap(); + return function (id, init = {}) { + const obj = get(id); + if (obj == null) return; + if (!store.has(obj)) { + store.set(obj, init); + } + return store.get(obj); + }; +} + +export const auctionStore = () => weakStore((auctionId) => auctionManager.index.getAuction({auctionId})); diff --git a/modules/.submodules.json b/modules/.submodules.json index 7eae631f695..9811db70205 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -107,7 +107,8 @@ "videojsVideoProvider" ], "paapi": [ - "fledgeForGpt" + "paapiForGpt", + "topLevelPaapi" ] } } diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index d8a32d75afc..19f38467d46 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -492,7 +492,6 @@ function _buildVideoORTB(bidRequest) { // Placement Inference Rules: // - If no placement is defined then default to 2 (In Banner) // - If the old deprecated field is defined, use its value for the recent placement field - // - If product is instream (for instream context) then override placement to 1 const calculatePlacementValue = () => { const IN_BANNER_PLACEMENT_VALUE = 2; @@ -510,8 +509,6 @@ function _buildVideoORTB(bidRequest) { if (product === PRODUCT.INSTREAM) { video.startdelay = video.startdelay || 0; - video.plcmt = 1; - video.placement &&= 1; } // bidfloors diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 975a95c3656..8f99846017a 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -11,6 +11,7 @@ import { submodule } from '../src/hook.js'; import { uspDataHandler, coppaDataHandler, gppDataHandler } from '../src/adapterManager.js'; import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { domainOverrideToRootDomain } from '../libraries/domainOverrideToRootDomain/index.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -31,6 +32,10 @@ const DEFAULT_TPID_SUPPORT = true; export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); +export const domainUtils = { + domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME) +}; + function calculateResponseObj(response) { if (!response.succeeded) { if (response.error == 'Cookied User') { @@ -54,7 +59,7 @@ function calculateResponseObj(response) { }; } -function calculateQueryStringParams(pid, gdprConsentData, storageConfig) { +function calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes) { const uspString = uspDataHandler.getConsentData(); const coppaValue = coppaDataHandler.getCoppa(); const gppConsent = gppDataHandler.getConsentData(); @@ -82,12 +87,12 @@ function calculateQueryStringParams(pid, gdprConsentData, storageConfig) { params.gdpr_consent = gdprConsentData.consentString; } - const fp = getStoredValue(STORAGE_FPID_KEY, storageConfig); + const fp = getStoredValue(STORAGE_FPID_KEY, enabledStorageTypes); if (fp) { params.fp = encodeURIComponent(fp); } - const tp = getStoredValue(STORAGE_TPID_KEY, storageConfig); + const tp = getStoredValue(STORAGE_TPID_KEY, enabledStorageTypes); if (tp) { params.tp = encodeURIComponent(tp); } @@ -99,32 +104,42 @@ function deleteFromStorage(key) { if (storage.cookiesAreEnabled()) { const expiredDate = new Date(0).toUTCString(); - storage.setCookie(key, '', expiredDate, 'Lax'); + storage.setCookie(key, '', expiredDate, 'Lax', domainUtils.domainOverride()); } storage.removeDataFromLocalStorage(key); } -function storeValue(key, value, storageConfig = {}) { - if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) { - const expirationInMs = 60 * 60 * 24 * 1000 * storageConfig.expires; - const expirationTime = new Date(Date.now() + expirationInMs); +function storeValue(key, value, { enabledStorageTypes, expires }) { + enabledStorageTypes.forEach(storageType => { + if (storageType === STORAGE_TYPE_COOKIES) { + const expirationInMs = 60 * 60 * 24 * 1000 * expires; + const expirationTime = new Date(Date.now() + expirationInMs); - storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax'); - } else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) { - storage.setDataInLocalStorage(key, value); - } + storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax', domainUtils.domainOverride()); + } else if (storageType === STORAGE_TYPE_LOCALSTORAGE) { + storage.setDataInLocalStorage(key, value); + } + }); } -function getStoredValue(key, storageConfig = {}) { - if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) { - return storage.getCookie(key); - } else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) { - return storage.getDataFromLocalStorage(key); - } +function getStoredValue(key, enabledStorageTypes) { + let storedValue; + + enabledStorageTypes.find(storageType => { + if (storageType === STORAGE_TYPE_COOKIES) { + storedValue = storage.getCookie(key); + } else if (storageType === STORAGE_TYPE_LOCALSTORAGE) { + storedValue = storage.getDataFromLocalStorage(key); + } + + return !!storedValue; + }); + + return storedValue; } -function handleSupplementalId(key, id, storageConfig = {}) { +function handleSupplementalId(key, id, storageConfig) { id ? storeValue(key, id, storageConfig) : deleteFromStorage(key); @@ -160,7 +175,7 @@ export const thirthyThreeAcrossIdSubmodule = { * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId({ params = { }, storage: storageConfig }, gdprConsentData) { + getId({ params = { }, enabledStorageTypes = [], storage: storageConfig = {} }, gdprConsentData) { if (typeof params.pid !== 'string') { logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`); @@ -192,11 +207,17 @@ export const thirthyThreeAcrossIdSubmodule = { } if (storeFpid) { - handleSupplementalId(STORAGE_FPID_KEY, responseObj.fp, storageConfig); + handleSupplementalId(STORAGE_FPID_KEY, responseObj.fp, { + enabledStorageTypes, + expires: storageConfig.expires + }); } if (storeTpid) { - handleSupplementalId(STORAGE_TPID_KEY, responseObj.tp, storageConfig); + handleSupplementalId(STORAGE_TPID_KEY, responseObj.tp, { + enabledStorageTypes, + expires: storageConfig.expires + }); } cb(responseObj.envelope); @@ -206,10 +227,14 @@ export const thirthyThreeAcrossIdSubmodule = { cb(); } - }, calculateQueryStringParams(pid, gdprConsentData, storageConfig), { method: 'GET', withCredentials: true }); + }, calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes), { + method: 'GET', + withCredentials: true + }); } }; }, + domainOverride: domainUtils.domainOverride, eids: { '33acrossId': { source: '33across.com', diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index f6733d6cc8c..e983c8ab871 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -14,7 +14,7 @@ pbjs.setConfig({ name: "33acrossId", storage: { name: "33acrossId", - type: "html5", + type: "cookie&html5", expires: 30, refreshInSeconds: 8*3600 }, @@ -40,7 +40,7 @@ The following settings are available for the `storage` property in the `userSync | Param name | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"33acrossId"` | -| type | Required | String | `"html5"` (preferred) or `"cookie"` | `"html5"` | +| type | Required | String | `"cookie&html5"` (preferred) or `"cookie"` or `"html5"` | `"cookie&html5"` | | expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `30`. | `30` | | refreshInSeconds | Strongly Recommended | Number | The interval (in seconds) for refreshing the user ID. 33Across recommends no more than 8 hours between refreshes. | `8*3600` | diff --git a/modules/acuityadsBidAdapter.js b/modules/acuityadsBidAdapter.js index 5b12eb2133b..4000230b1e0 100644 --- a/modules/acuityadsBidAdapter.js +++ b/modules/acuityadsBidAdapter.js @@ -53,6 +53,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js deleted file mode 100644 index cb03f2ffc17..00000000000 --- a/modules/adbookpspBidAdapter.js +++ /dev/null @@ -1,830 +0,0 @@ -import {find, includes} from '../src/polyfill.js'; -import {config} from '../src/config.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {getStorageManager} from '../src/storageManager.js'; -import { - deepAccess, - deepSetValue, - flatten, - generateUUID, - inIframe, - isArray, - isEmptyStr, - isNumber, - isPlainObject, - isStr, - logError, - logWarn, - triggerPixel, - uniques -} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - -/** - * CONSTANTS - */ - -export const VERSION = '1.0.0'; -const EXCHANGE_URL = 'https://ex.fattail.com/openrtb2'; -const WIN_TRACKING_URL = 'https://ev.fattail.com/wins'; -const BIDDER_CODE = 'adbookpsp'; -const USER_ID_KEY = 'hb_adbookpsp_uid'; -const USER_ID_COOKIE_EXP = 2592000000; // lasts 30 days -const BID_TTL = 300; -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; -const DEFAULT_CURRENCY = 'USD'; -const VIDEO_PARAMS = [ - 'mimes', - 'minduration', - 'maxduration', - 'protocols', - 'w', - 'h', - 'startdelay', - 'placement', - 'linearity', - 'skip', - 'skipmin', - 'skipafter', - 'sequence', - 'battr', - 'maxextended', - 'minbitrate', - 'maxbitrate', - 'boxingallowed', - 'playbackmethod', - 'playbackend', - 'delivery', - 'pos', - 'companionad', - 'api', - 'companiontype', - 'ext', -]; -const TARGETING_VALUE_SEPARATOR = ','; - -export const DEFAULT_BIDDER_CONFIG = { - bidTTL: BID_TTL, - defaultCurrency: DEFAULT_CURRENCY, - exchangeUrl: EXCHANGE_URL, - winTrackingEnabled: true, - winTrackingUrl: WIN_TRACKING_URL, - orgId: null, -}; - -config.setDefaults({ - adbookpsp: DEFAULT_BIDDER_CONFIG, -}); - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - - buildRequests, - getUserSyncs, - interpretResponse, - isBidRequestValid, - onBidWon, -}; - -registerBidder(spec); - -/** - * BID REQUEST - */ - -function isBidRequestValid(bidRequest) { - return ( - hasRequiredParams(bidRequest) && - (isValidBannerRequest(bidRequest) || isValidVideoRequest(bidRequest)) - ); -} - -function buildRequests(validBidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - const requests = []; - - if (validBidRequests.length > 0) { - requests.push({ - method: 'POST', - url: getBidderConfig('exchangeUrl'), - options: { - contentType: 'application/json', - withCredentials: true, - }, - data: buildRequest(validBidRequests, bidderRequest), - }); - } - - return requests; -} - -function buildRequest(validBidRequests, bidderRequest) { - const request = { - id: bidderRequest.bidderRequestId, - tmax: bidderRequest.timeout, - site: { - domain: bidderRequest.refererInfo.domain, - page: bidderRequest.refererInfo.page, - ref: bidderRequest.refererInfo.ref, - }, - source: buildSource(validBidRequests, bidderRequest), - device: buildDevice(), - regs: buildRegs(bidderRequest), - user: buildUser(bidderRequest), - imp: validBidRequests.map(buildImp), - ext: { - adbook: { - config: getBidderConfig(), - version: { - prebid: '$prebid.version$', - adapter: VERSION, - }, - }, - }, - }; - - return JSON.stringify(request); -} - -function buildDevice() { - const { innerWidth, innerHeight } = common.getWindowDimensions(); - - const device = { - w: innerWidth, - h: innerHeight, - js: true, - ua: navigator.userAgent, - dnt: - navigator.doNotTrack === 'yes' || - navigator.doNotTrack == '1' || - navigator.msDoNotTrack == '1' - ? 1 - : 0, - }; - - const deviceConfig = common.getConfig('device'); - - if (isPlainObject(deviceConfig)) { - return { ...device, ...deviceConfig }; - } - - return device; -} - -function buildRegs(bidderRequest) { - const regs = { - coppa: common.getConfig('coppa') === true ? 1 : 0, - }; - - if (bidderRequest.gdprConsent) { - deepSetValue( - regs, - 'ext.gdpr', - bidderRequest.gdprConsent.gdprApplies ? 1 : 0 - ); - deepSetValue( - regs, - 'ext.gdprConsentString', - bidderRequest.gdprConsent.consentString || '' - ); - } - - if (bidderRequest.uspConsent) { - deepSetValue(regs, 'ext.us_privacy', bidderRequest.uspConsent); - } - - return regs; -} - -function buildSource(bidRequests, bidderRequest) { - const source = { - fd: 1, - tid: bidderRequest.ortb2.source.tid, - }; - const schain = deepAccess(bidRequests, '0.schain'); - - if (schain) { - deepSetValue(source, 'ext.schain', schain); - } - - return source; -} - -function buildUser(bidderRequest) { - const user = { - id: getUserId(), - }; - - if (bidderRequest.gdprConsent) { - user.gdprConsentString = bidderRequest.gdprConsent.consentString || ''; - } - - return user; -} - -function buildImp(bidRequest) { - let impBase = { - id: bidRequest.bidId, - tagid: bidRequest.adUnitCode, - ext: buildImpExt(bidRequest), - }; - - return Object.keys(bidRequest.mediaTypes) - .filter((mediaType) => includes(SUPPORTED_MEDIA_TYPES, mediaType)) - .reduce((imp, mediaType) => { - return { - ...imp, - [mediaType]: buildMediaTypeObject(mediaType, bidRequest), - }; - }, impBase); -} - -function buildMediaTypeObject(mediaType, bidRequest) { - switch (mediaType) { - case BANNER: - return buildBannerObject(bidRequest); - case VIDEO: - return buildVideoObject(bidRequest); - default: - logWarn(`${BIDDER_CODE}: Unsupported media type ${mediaType}!`); - } -} - -function buildBannerObject(bidRequest) { - const format = bidRequest.mediaTypes.banner.sizes.map((size) => { - const [w, h] = size; - - return { w, h }; - }); - const { w, h } = format[0]; - - return { - pos: 0, - topframe: inIframe() ? 0 : 1, - format, - w, - h, - }; -} - -function buildVideoObject(bidRequest) { - const { w, h } = getVideoSize(bidRequest); - let videoObj = { - w, - h, - }; - - for (const param of VIDEO_PARAMS) { - const paramsValue = deepAccess(bidRequest, `params.video.${param}`); - const mediaTypeValue = deepAccess( - bidRequest, - `mediaTypes.video.${param}` - ); - - if (paramsValue || mediaTypeValue) { - videoObj[param] = paramsValue || mediaTypeValue; - } - } - - return videoObj; -} - -function getVideoSize(bidRequest) { - const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize', [[]]); - const { w, h } = deepAccess(bidRequest, 'mediaTypes.video', {}); - - if (isNumber(w) && isNumber(h)) { - return { w, h }; - } - - return { - w: playerSize[0][0], - h: playerSize[0][1], - } -} - -function buildImpExt(validBidRequest) { - const defaultOrgId = getBidderConfig('orgId'); - const { orgId, placementId } = validBidRequest.params || {}; - const effectiverOrgId = orgId || defaultOrgId; - const ext = {}; - - if (placementId) { - deepSetValue(ext, 'adbook.placementId', placementId); - } - - if (effectiverOrgId) { - deepSetValue(ext, 'adbook.orgId', effectiverOrgId); - } - - return ext; -} - -/** - * BID RESPONSE - */ - -function interpretResponse(bidResponse, bidderRequest) { - const bidderRequestBody = safeJSONparse(bidderRequest.data); - - if ( - deepAccess(bidderRequestBody, 'id') != - deepAccess(bidResponse, 'body.id') - ) { - logError( - `${BIDDER_CODE}: Bid response id does not match bidder request id` - ); - - return []; - } - - const referrer = deepAccess(bidderRequestBody, 'site.ref', ''); - const incomingBids = deepAccess(bidResponse, 'body.seatbid', []) - .filter((seat) => isArray(seat.bid)) - .reduce((bids, seat) => bids.concat(seat.bid), []) - .filter(validateBid(bidderRequestBody)); - const targetingMap = buildTargetingMap(incomingBids); - - return impBidsToPrebidBids( - incomingBids, - bidderRequestBody, - bidResponse.body.cur, - referrer, - targetingMap - ); -} - -function impBidsToPrebidBids( - incomingBids, - bidderRequestBody, - bidResponseCurrency, - referrer, - targetingMap -) { - return incomingBids - .map( - impToPrebidBid( - bidderRequestBody, - bidResponseCurrency, - referrer, - targetingMap - ) - ) - .filter((i) => i !== null); -} - -const impToPrebidBid = - (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid, bidIndex) => { - try { - const bidRequest = findBidRequest(bidderRequestBody, bid); - - if (!bidRequest) { - logError(`${BIDDER_CODE}: Could not match bid to bid request`); - - return null; - } - const categories = deepAccess(bid, 'cat', []); - const mediaType = getMediaType(bid.adm); - let prebidBid = { - ad: bid.adm, - adId: bid.adid, - adserverTargeting: targetingMap[bidIndex], - adUnitCode: bidRequest.tagid, - bidderRequestId: bidderRequestBody.id, - bidId: bid.id, - cpm: bid.price, - creativeId: bid.crid || bid.id, - currency: bidResponseCurrency || getBidderConfig('defaultCurrency'), - height: bid.h, - lineItemId: deepAccess(bid, 'ext.liid'), - mediaType, - meta: { - advertiserDomains: bid.adomain, - mediaType, - primaryCatId: categories[0], - secondaryCatIds: categories.slice(1), - }, - netRevenue: true, - nurl: bid.nurl, - referrer: referrer, - requestId: bid.impid, - ttl: getBidderConfig('bidTTL'), - width: bid.w, - }; - - if (mediaType === VIDEO) { - prebidBid = { - ...prebidBid, - ...getVideoSpecificParams(bidRequest, bid), - }; - } - - if (deepAccess(bid, 'ext.pa_win') === true) { - prebidBid.auctionWinner = true; - } - return prebidBid; - } catch (error) { - logError(`${BIDDER_CODE}: Error while building bid`, error); - - return null; - } - }; - -function getVideoSpecificParams(bidRequest, bid) { - return { - height: bid.h || bidRequest.video.h, - vastXml: bid.adm, - width: bid.w || bidRequest.video.w, - }; -} - -function buildTargetingMap(bids) { - const impIds = bids.map(({ impid }) => impid).filter(uniques); - const values = impIds.reduce((result, id) => { - result[id] = { - lineItemIds: [], - orderIds: [], - dealIds: [], - adIds: [], - adAndOrderIndexes: [] - }; - - return result; - }, {}); - - bids.forEach((bid, bidIndex) => { - let impId = bid.impid; - values[impId].lineItemIds.push(bid.ext.liid); - values[impId].dealIds.push(bid.dealid); - values[impId].adIds.push(bid.adid); - - if (deepAccess(bid, 'ext.ordid')) { - values[impId].orderIds.push(bid.ext.ordid); - bid.ext.ordid.split(TARGETING_VALUE_SEPARATOR).forEach((ordid, ordIndex) => { - let adIdIndex = values[impId].adIds.indexOf(bid.adid); - values[impId].adAndOrderIndexes.push(adIdIndex + '_' + ordIndex) - }) - } - }); - - const targetingMap = {}; - - bids.forEach((bid, bidIndex) => { - let id = bid.impid; - - targetingMap[bidIndex] = { - hb_liid_adbookpsp: values[id].lineItemIds.join(TARGETING_VALUE_SEPARATOR), - hb_deal_adbookpsp: values[id].dealIds.join(TARGETING_VALUE_SEPARATOR), - hb_ad_ord_adbookpsp: values[id].adAndOrderIndexes.join(TARGETING_VALUE_SEPARATOR), - hb_adid_c_adbookpsp: values[id].adIds.join(TARGETING_VALUE_SEPARATOR), - hb_ordid_adbookpsp: values[id].orderIds.join(TARGETING_VALUE_SEPARATOR), - }; - }) - return targetingMap; -} - -/** - * VALIDATION - */ - -function hasRequiredParams(bidRequest) { - const value = - deepAccess(bidRequest, 'params.placementId') != null || - deepAccess(bidRequest, 'params.orgId') != null || - getBidderConfig('orgId') != null; - - if (!value) { - logError(`${BIDDER_CODE}: missing orgId and placementId parameter`); - } - - return value; -} - -function isValidBannerRequest(bidRequest) { - const value = validateSizes( - deepAccess(bidRequest, 'mediaTypes.banner.sizes', []) - ); - - return value; -} - -function isValidVideoRequest(bidRequest) { - const value = - isArray(deepAccess(bidRequest, 'mediaTypes.video.mimes')) && - validateVideoSizes(bidRequest); - - return value; -} - -function validateSize(size) { - return isArray(size) && size.length === 2 && size.every(isNumber); -} - -function validateSizes(sizes) { - return isArray(sizes) && sizes.length > 0 && sizes.every(validateSize); -} - -function validateVideoSizes(bidRequest) { - const { w, h } = deepAccess(bidRequest, 'mediaTypes.video', {}); - - return ( - validateSizes( - deepAccess(bidRequest, 'mediaTypes.video.playerSize') - ) || - (isNumber(w) && isNumber(h)) - ); -} - -function validateBid(bidderRequestBody) { - return function (bid) { - const mediaType = getMediaType(bid.adm); - const bidRequest = findBidRequest(bidderRequestBody, bid); - let validators = commonBidValidators; - - if (mediaType === BANNER) { - validators = [...commonBidValidators, ...bannerBidValidators]; - } - - const value = validators.every((validator) => validator(bid, bidRequest)); - - if (!value) { - logWarn(`${BIDDER_CODE}: Invalid bid`, bid); - } - - return value; - }; -} - -const commonBidValidators = [ - (bid) => isPlainObject(bid), - (bid) => isNonEmptyStr(bid.adid), - (bid) => isNonEmptyStr(bid.adm), - (bid) => isNonEmptyStr(bid.id), - (bid) => isNonEmptyStr(bid.impid), - (bid) => isNonEmptyStr(deepAccess(bid, 'ext.liid')), - (bid) => isNumber(bid.price), -]; - -const bannerBidValidators = [ - validateBannerDimension('w'), - validateBannerDimension('h'), -]; - -function validateBannerDimension(dimension) { - return function (bid, bidRequest) { - if (bid[dimension] == null) { - return bannerHasSingleSize(bidRequest); - } - - return isNumber(bid[dimension]); - }; -} - -function bannerHasSingleSize(bidRequest) { - return deepAccess(bidRequest, 'banner.format', []).length === 1; -} - -/** - * USER SYNC - */ - -export const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { - return responses - .map((response) => deepAccess(response, 'body.ext.sync')) - .filter(isArray) - .reduce(flatten, []) - .filter(validateSync(syncOptions)) - .map(applyConsents(gdprConsent, uspConsent)); -} - -const validateSync = (syncOptions) => (sync) => { - return ( - ((sync.type === 'image' && syncOptions.pixelEnabled) || - (sync.type === 'iframe' && syncOptions.iframeEnabled)) && - sync.url - ); -}; - -const applyConsents = (gdprConsent, uspConsent) => (sync) => { - const url = getUrlBuilder(sync.url); - - if (gdprConsent) { - url.set('gdpr', gdprConsent.gdprApplies ? 1 : 0); - url.set('consentString', gdprConsent.consentString || ''); - } - if (uspConsent) { - url.set('us_privacy', encodeURIComponent(uspConsent)); - } - if (common.getConfig('coppa') === true) { - url.set('coppa', 1); - } - - return { ...sync, url: url.toString() }; -}; - -function getUserId() { - const id = getUserIdFromStorage() || common.generateUUID(); - - setUserId(id); - - return id; -} - -function getUserIdFromStorage() { - const id = storage.localStorageIsEnabled() - ? storage.getDataFromLocalStorage(USER_ID_KEY) - : storage.getCookie(USER_ID_KEY); - - if (!validateUUID(id)) { - return; - } - - return id; -} - -function setUserId(userId) { - if (storage.localStorageIsEnabled()) { - storage.setDataInLocalStorage(USER_ID_KEY, userId); - } - - if (storage.cookiesAreEnabled()) { - const expires = new Date(Date.now() + USER_ID_COOKIE_EXP).toISOString(); - - storage.setCookie(USER_ID_KEY, userId, expires); - } -} - -function validateUUID(uuid) { - return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( - uuid - ); -} - -/** - * EVENT TRACKING - */ - -function onBidWon(bid) { - if (!getBidderConfig('winTrackingEnabled')) { - return; - } - - const wurl = buildWinUrl(bid); - - if (wurl !== null) { - triggerPixel(wurl); - } - - if (isStr(bid.nurl)) { - triggerPixel(bid.nurl); - } -} - -function buildWinUrl(bid) { - try { - const url = getUrlBuilder(getBidderConfig('winTrackingUrl')); - - url.set('impId', bid.requestId); - url.set('reqId', bid.bidderRequestId); - url.set('bidId', bid.bidId); - - return url.toString(); - } catch (_) { - logError( - `${BIDDER_CODE}: Could not build win tracking URL with %s`, - getBidderConfig('winTrackingUrl') - ); - - return null; - } -} - -/** - * COMMON - */ - -const VAST_REGEXP = /VAST\s+version/; - -function getMediaType(adm) { - const videoRegex = new RegExp(VAST_REGEXP); - - if (videoRegex.test(adm)) { - return VIDEO; - } - - const markup = safeJSONparse(adm.replace(/\\/g, '')); - - if (markup && isPlainObject(markup.native)) { - return NATIVE; - } - - return BANNER; -} - -function safeJSONparse(...args) { - try { - return JSON.parse(...args); - } catch (_) { - return undefined; - } -} - -function isNonEmptyStr(value) { - return isStr(value) && !isEmptyStr(value); -} - -function findBidRequest(bidderRequest, bid) { - return find(bidderRequest.imp, (imp) => imp.id === bid.impid); -} - -function getBidderConfig(property) { - if (!property) { - return common.getConfig(`${BIDDER_CODE}`); - } - - return common.getConfig(`${BIDDER_CODE}.${property}`); -} - -const getUrlBase = function (url) { - return url.split('?')[0]; -}; - -const getUrlQuery = function (url) { - const query = url.split('?')[1]; - - if (!query) { - return; - } - - return '?' + query.split('#')[0]; -}; - -const getUrlHash = function (url) { - const hash = url.split('#')[1]; - - if (!hash) { - return; - } - - return '#' + hash; -}; - -const getUrlBuilder = function (url) { - const hash = getUrlHash(url); - const base = getUrlBase(url); - const query = getUrlQuery(url); - const pairs = []; - - function set(key, value) { - pairs.push([key, value]); - - return { - set, - toString, - }; - } - - function toString() { - if (!pairs.length) { - return url; - } - - const queryString = pairs - .map(function (pair) { - return pair.join('='); - }) - .join('&'); - - if (!query) { - return base + '?' + queryString + (hash || ''); - } - - return base + query + '&' + queryString + (hash || ''); - } - - return { - set, - toString, - }; -}; - -export const common = { - generateUUID: function () { - return generateUUID(); - }, - getConfig: function (property) { - return config.getConfig(property); - }, - getWindowDimensions: function () { - return { - innerWidth: window.innerWidth, - innerHeight: window.innerHeight, - }; - }, -}; diff --git a/modules/adbookpspBidAdapter.md b/modules/adbookpspBidAdapter.md deleted file mode 100644 index e258b1fd7c3..00000000000 --- a/modules/adbookpspBidAdapter.md +++ /dev/null @@ -1,191 +0,0 @@ -### Overview - -``` -Module Name: AdbookPSP Bid Adapter -Module Type: Bidder Adapter -Maintainer: hbsupport@fattail.com -``` - -### Description - -Prebid.JS adapter that connects to the AdbookPSP demand sources. - -*NOTE*: The AdBookPSP Bidder Adapter requires setup and approval before use. The adapter uses custom targeting keys that require a dedicated Google Ad Manager setup to work. Please reach out to your AdbookPSP representative for more details. - -### Bidder parameters - -Each adUnit with `adbookpsp` adapter has to have either `placementId` or `orgId` set. - -```js -var adUnits = [ - { - bids: [ - { - bidder: 'adbookpsp', - params: { - placementId: 'example-placement-id', - orgId: 'example-org-id', - }, - }, - ], - }, -]; -``` - -Alternatively, `orgId` can be set globally while configuring prebid.js: - -```js -pbjs.setConfig({ - adbookpsp: { - orgId: 'example-org-id', - }, -}); -``` - -*NOTE*: adUnit orgId will take precedence over the globally set orgId. - -#### Banner parameters - -Required: - -- sizes - -Example configuration: - -```js -var adUnits = [ - { - code: 'div-1', - mediaTypes: { - banner: { - sizes: [[300, 250]], - }, - } - }, -]; -``` - -#### Video parameters - -Required: - -- context -- mimes -- playerSize - -Additionaly, all `Video` object parameters described in chapter `3.2.7` of the [OpenRTB 2.5 specification](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf) can be passed as bidder params. - -Example configuration: - -```js -var adUnits = [ - { - code: 'div-1', - mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4', 'video/x-flv'], - playerSize: [400, 300], - protocols: [2, 3], - }, - }, - bids: [ - { - bidder: 'adbookpsp', - params: { - placementId: 'example-placement-id', - video: { - placement: 2, - }, - }, - }, - ], - }, -]; -``` - -*NOTE*: Supporting outstream video requires the publisher to set up a renderer as described [in the Prebid docs](https://docs.prebid.org/dev-docs/show-outstream-video-ads.html). - -#### Testing params - -To test the adapter, either `placementId: 'example-placement-id'` or `orgId: 'example-org-id'` can be used. - -*NOTE*: If any adUnit uses the testing params, all adUnits will receive testing responses. - -Example adUnit configuration: - -```js -var adUnits = [ - { - code: 'div-1', - mediaTypes: { - banner: { - sizes: [[300, 250]], - }, - }, - bids: [ - { - bidder: 'adbookpsp', - params: { - placementId: 'example-placement-id', - }, - }, - ], - }, -]; -``` - -Example google publisher tag configuration: - -```js -googletag - .defineSlot('/22094606581/example-adbookPSP', sizes, 'div-1') - .addService(googletag.pubads()); -``` - -### Configuration - -Setting of the `orgId` can be done in the `pbjs.setConfig()` call. If this is the case, both `orgId` and `placementId` become optional. Remember to only call `pbjs.setConfig()` once as each call overwrites anything set in previous calls. - -Enabling iframe based user syncs is also encouraged. - -```javascript -pbjs.setConfig({ - adbookpsp: { - orgId: 'example-org-id', - winTrackingEnabled: true, - }, - userSync: { - filterSettings: { - iframe: { - bidders: '*', - filter: 'include', - }, - }, - }, -}); -``` - -### Privacy - -GDPR and US Privacy are both supported by default. - -#### Event tracking - -This adapter tracks win events for it’s bids. This functionality can be disabled by adding `winTrackingEnabled: false` to the adapter configuration: - -```js -pbjs.setConfig({ - adbookpsp: { - winTrackingEnabled: false, - }, -}); -``` - -#### COPPA support - -COPPA support can be enabled for all the visitors by changing the config value: - -```js -config.setConfig({ coppa: true }); -``` diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index b78737722bd..fd59ba74944 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -144,6 +144,7 @@ export const spec = { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 6c268b2d382..34a6ec788bd 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -40,7 +40,8 @@ export const spec = { code: BIDDER_CODE, gvlid: 1281, aliases: [ - {code: 'pixad', gvlid: 1281} + {code: 'pixad', gvlid: 1281}, + {code: 'monetixads', gvlid: 1281} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -140,11 +141,14 @@ export const spec = { if (payload) { switch (bidderName) { + case 'monetixads': + SYNC_URL = 'https://static.cdn.monetixads.com/sync.html'; + break; case 'pixad': SYNC_URL = 'https://static.cdn.pixad.com.tr/sync.html'; break; default: - SYNC_URL = 'https://cdn.serve.admatic.com.tr/showad/sync.html'; + SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; break; } diff --git a/modules/adpod.js b/modules/adpod.js index b6d13673178..35a78766979 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -451,7 +451,6 @@ export function callPrebidCacheAfterAuction(bids, callback) { /** * Compare function to be used in sorting long-form bids. This will compare bids on price per second. * @param {Object} bid - * @param {Object} bid */ export function sortByPricePerSecond(a, b) { if (a.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket < b.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { @@ -465,10 +464,10 @@ export function sortByPricePerSecond(a, b) { /** * This function returns targeting keyvalue pairs for long-form adserver modules. Freewheel and GAM are currently supporting Prebid long-form - * @param {Object} options - * @param {Array[string]} codes - * @param {function} callback - * @returns targeting kvs for adUnitCodes + * @param {Object} options - Options for targeting. + * @param {Array} options.codes - Array of ad unit codes. + * @param {function} options.callback - Callback function to handle the targeting key-value pairs. + * @returns {Object} Targeting key-value pairs for ad unit codes. */ export function getTargeting({ codes, callback } = {}) { if (!callback) { diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 389986eb586..a6186d6129f 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -43,6 +43,7 @@ const VIDEO_CUSTOM_PARAMS = { 'battr': DATA_TYPES.ARRAY, 'linearity': DATA_TYPES.NUMBER, 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER }; diff --git a/modules/ampliffyBidAdapter.js b/modules/ampliffyBidAdapter.js index bcd28e5bcf1..e79b04ab4c4 100644 --- a/modules/ampliffyBidAdapter.js +++ b/modules/ampliffyBidAdapter.js @@ -278,7 +278,6 @@ function extractTrackingURL(htmlContent, ret) { const trackingUrlDiv = htmlContent.querySelectorAll('[bidder-tracking-url]')[0]; if (trackingUrlDiv) { const trackingUrl = trackingUrlDiv.getAttribute('bidder-tracking-url'); - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'parseXML: trackingUrl: ', trackingUrl) ret.trackingUrl = trackingUrl; } @@ -304,10 +303,8 @@ export function parseXML(xml, bid) { ret.userSyncs = extractSyncs(htmlContent); } } catch (e) { - // eslint-disable-next-line no-console logError(LOG_PREFIX + 'Error parsing XML', e); } - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'parseXML RET:', ret); return ret; @@ -341,7 +338,6 @@ export function isAllowedToBidUp(html, currentURL) { } } } catch (e) { - // eslint-disable-next-line no-console logError(LOG_PREFIX + 'isAllowedToBidUp', e); } return allowedToPush; @@ -399,7 +395,6 @@ function onBidWon(bid) { } } function onTimeOut() { - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'TIMEOUT'); } diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js index 97772b65e45..67557aed10c 100644 --- a/modules/appushBidAdapter.js +++ b/modules/appushBidAdapter.js @@ -57,6 +57,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/axisBidAdapter.js b/modules/axisBidAdapter.js index 8d7f2dd04fd..da167fae062 100644 --- a/modules/axisBidAdapter.js +++ b/modules/axisBidAdapter.js @@ -53,6 +53,7 @@ function getPlacementReqData(bid) { placement.mimes = mediaTypes[VIDEO].mimes; placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.placement = mediaTypes[VIDEO].placement; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; diff --git a/modules/beyondmediaBidAdapter.js b/modules/beyondmediaBidAdapter.js index bbcd972470c..d3c7d185058 100644 --- a/modules/beyondmediaBidAdapter.js +++ b/modules/beyondmediaBidAdapter.js @@ -52,6 +52,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/bidmaticBidAdapter.js b/modules/bidmaticBidAdapter.js new file mode 100644 index 00000000000..8c22d70c632 --- /dev/null +++ b/modules/bidmaticBidAdapter.js @@ -0,0 +1,110 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { replaceAuctionPrice, isNumber, deepAccess, isFn } from '../src/utils.js'; + +export const END_POINT = 'https://adapter.bidmatic.io/ortb-client'; +const BIDDER_CODE = 'bidmatic'; +const DEFAULT_CURRENCY = 'USD'; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 290, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floorInfo = isFn(bidRequest.getFloor) ? bidRequest.getFloor({ + currency: context.currency || 'USD', + size: '*', + mediaType: '*' + }) : { + floor: imp.bidfloor || deepAccess(bidRequest, 'params.bidfloor') || 0, + currency: DEFAULT_CURRENCY + }; + + if (floorInfo) { + imp.bidfloor = floorInfo.floor; + imp.bidfloorcur = floorInfo.currency; + } + imp.tagid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || bidRequest.adUnitCode; + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + if (!request.cur) { + request.cur = [DEFAULT_CURRENCY]; + } + return request; + }, + bidResponse(buildBidResponse, bid, context) { + const { bidRequest } = context; + + let resMediaType; + const reqMediaTypes = Object.keys(bidRequest.mediaTypes); + if (reqMediaTypes.length === 1) { + resMediaType = reqMediaTypes[0]; + } else { + if (bid.adm.search(/^(<\?xml| { + acc[bidRequest.params.source] = acc[bidRequest.params.source] || []; + acc[bidRequest.params.source].push(bidRequest); + return acc; + }, {}); + + return Object.entries(requestsBySource).map(([source, bidRequests]) => { + const data = converter.toORTB({ bidRequests, bidderRequest }); + const url = new URL(END_POINT); + url.searchParams.append('source', source); + return { + method: 'POST', + url: url.toString(), + data: data, + options: { + withCredentials: true, + } + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + if (!serverResponse || !serverResponse.body) return []; + const parsedSeatbid = serverResponse.body.seatbid.map(seatbidItem => { + const parsedBid = seatbidItem.bid.map((bidItem) => ({ + ...bidItem, + adm: replaceAuctionPrice(bidItem.adm, bidItem.price), + nurl: replaceAuctionPrice(bidItem.nurl, bidItem.price) + })); + return { ...seatbidItem, bid: parsedBid }; + }); + const responseBody = { ...serverResponse.body, seatbid: parsedSeatbid }; + return converter.fromORTB({ + response: responseBody, + request: bidRequest.data, + }).bids; + }, + +}; +registerBidder(spec); diff --git a/modules/bidmaticBidAdapter.md b/modules/bidmaticBidAdapter.md new file mode 100644 index 00000000000..d248b386ea3 --- /dev/null +++ b/modules/bidmaticBidAdapter.md @@ -0,0 +1,26 @@ +# Overview + +``` +Module Name: Bidmatic Bid Adapter +Module Type: Bidder Adapter +Maintainer: mg@bidmatic.io +``` + +# Description + +Adds access to Bidmatic SSP oRTB service. + +# Sample Ad Unit: For Publishers +``` +var adUnits = [{ + code: 'bg-test-rectangle', + sizes: [[300, 250]], + bids: [{ + bidder: 'bidmatic', + params: { + source: 886409, + bidfloor: 0.1 + } + }] +}] +``` diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js index c7def383b5e..6d49c814486 100644 --- a/modules/boldwinBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -110,6 +110,7 @@ export const spec = { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index 1ba0c8d2ec3..bd40713924e 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -161,8 +161,10 @@ export class GPPClient { * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP - * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). + * @param {function(): Object} [mkCmp=cmpClient] - A function to create the CMP client. Defaults to `cmpClient`. */ export function lookupIabConsent({onSuccess, onError}, mkCmp = cmpClient) { pipeCallbacks(() => GPPClient.get(mkCmp).refresh(), {onSuccess, onError}); @@ -291,7 +293,6 @@ function processCmpData(consentData) { /** * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction * @param {{}} gppData the result of calling a CMP's `getGPPData` (or equivalent) - * @param {{}} sectionData map from GPP section name to the result of calling a CMP's `getSection` (or equivalent) */ export function storeConsentData(gppData = {}) { consentData = { diff --git a/modules/consentManagementTcf.js b/modules/consentManagementTcf.js index 346b241fc1f..c7c618635ba 100644 --- a/modules/consentManagementTcf.js +++ b/modules/consentManagementTcf.js @@ -35,7 +35,9 @@ const cmpCallMap = { /** * This function reads the consent string from the config to obtain the consent information of the user. - * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when the value is read from config; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} [options.onError] - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). Optional. */ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) @@ -45,8 +47,10 @@ function lookupStaticConsentData({onSuccess, onError}) { * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP - * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). + * @param {function(Object): void} options.onEvent - Acts as an event callback for processing TCF data events from CMP. */ function lookupIabConsent({onSuccess, onError, onEvent}) { function cmpResponseCallback(tcfData, success) { diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 78ec13cb891..29a67af0631 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -163,10 +163,12 @@ export const requestBidsHook = timedAuctionHook('usp', function requestBidsHook( /** * This function checks the consent data provided by USPAPI to ensure it's in an expected state. * If it's bad, we exit the module depending on config settings. - * If it's good, then we store the value and exits the module. - * @param {object} consentObject required; object returned by USPAPI that contains user's consent choices - * @param {function(string)} onSuccess callback accepting the resolved consent USP consent string - * @param {function(string, ...{}?)} onError callback accepting error message and any extra error arguments (used purely for logging) + * If it's good, then we store the value and exit the module. + * + * @param {Object} consentObject - The object returned by USPAPI that contains the user's consent choices. + * @param {Object} callbacks - An object containing the callback functions. + * @param {function(string): void} callbacks.onSuccess - Callback accepting the resolved USP consent string. + * @param {function(string, ...Object?): void} callbacks.onError - Callback accepting an error message and any extra error arguments (used purely for logging). */ function processUspData(consentObject, {onSuccess, onError}) { const valid = !!(consentObject && consentObject.usPrivacy); diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 5343322d658..55879089530 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -21,7 +21,7 @@ import {ortb25Translator} from '../libraries/ortb2.5Translator/translator.js'; const GVLID = 91; export const ADAPTER_VERSION = 37; const BIDDER_CODE = 'criteo'; -const CDB_ENDPOINT = 'https://bidder.criteo.com/openrtb_2_5/pbjs/auction/request'; +const CDB_ENDPOINT = 'https://grid-bidder.criteo.com/openrtb_2_5/pbjs/auction/request'; const PROFILE_ID_INLINE = 207; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const LOG_PREFIX = 'Criteo: '; @@ -392,7 +392,7 @@ export const spec = { if (fledgeAuctionConfigs?.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } @@ -449,7 +449,7 @@ function buildContext(bidRequests, bidderRequest) { url: bidderRequest?.refererInfo?.page || '', debug: queryString['pbt_debug'] === '1', noLog: queryString['pbt_nolog'] === '1', - fledgeEnabled: bidderRequest.fledgeEnabled, + fledgeEnabled: bidderRequest.paapi?.enabled, amp: bidRequests.some(bidRequest => bidRequest.params.integrationMode === 'amp'), networkId: bidRequests.find(bidRequest => bidRequest.params?.networkId)?.params.networkId, publisherId: bidRequests.find(bidRequest => bidRequest.params?.pubid)?.params.pubid, diff --git a/modules/currency.js b/modules/currency.js index e2da8b519fa..aa464b81f9a 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -28,6 +28,7 @@ export let responseReady = defer(); /** * Configuration function for currency + * @param {object} config * @param {string} [config.adServerCurrency = 'USD'] * ISO 4217 3-letter currency code that represents the target currency. (e.g. 'EUR'). If this value is present, * the currency conversion feature is activated. diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index afded538fd0..bf8eaaebb55 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -54,14 +54,37 @@ function getVideoMetadata(bidRequest, bidderRequest) { : Object.keys(parsedContentData.iabcat2), id: videoParams.id || deepAccess(contentObj, 'id', ''), lang: videoParams.lang || deepAccess(contentObj, 'language', ''), + livestream: typeof videoParams.livestream === 'number' + ? !!videoParams.livestream + : !!deepAccess(contentObj, 'livestream', 0), private: videoParams.private || false, tags: videoParams.tags || deepAccess(contentObj, 'keywords', ''), title: videoParams.title || deepAccess(contentObj, 'title', ''), + url: videoParams.url || deepAccess(contentObj, 'url', ''), topics: videoParams.topics || '', xid: videoParams.xid || '', - livestream: typeof videoParams.livestream === 'number' - ? !!videoParams.livestream - : !!deepAccess(contentObj, 'livestream', 0), + isCreatedForKids: typeof videoParams.isCreatedForKids === 'boolean' + ? videoParams.isCreatedForKids + : null, + context: { + siteOrAppCat: deepAccess(contentObj, 'cat', ''), + videoViewsInSession: ( + typeof videoParams.videoViewsInSession === 'number' && + videoParams.videoViewsInSession >= 0 + ) + ? videoParams.videoViewsInSession + : null, + autoplay: typeof videoParams.autoplay === 'boolean' + ? videoParams.autoplay + : null, + playerVolume: ( + typeof videoParams.playerVolume === 'number' && + videoParams.playerVolume >= 0 && + videoParams.playerVolume <= 10 + ) + ? videoParams.playerVolume + : null, + }, }; return videoMetadata; @@ -111,6 +134,7 @@ export const spec = { method: 'POST', url: 'https://pb.dmxleo.com', data: { + pbv: '$prebid.version$', bidder_request: { gdprConsent: { apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), @@ -139,6 +163,13 @@ export const spec = { appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), } : {}), + ...(deepAccess(bidderRequest, 'ortb2.device') ? { + device: { + lmt: deepAccess(bidderRequest, 'ortb2.device.lmt', null), + ifa: deepAccess(bidderRequest, 'ortb2.device.ifa', ''), + atts: deepAccess(bidderRequest, 'ortb2.device.ext.atts', 0), + }, + } : {}), request: { adUnitCode: deepAccess(bid, 'adUnitCode', ''), auctionId: deepAccess(bid, 'auctionId', ''), @@ -149,6 +180,8 @@ export const spec = { mimes: bid.mediaTypes?.[VIDEO]?.mimes || [], minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0, maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0, + playbackmethod: bid.mediaTypes?.[VIDEO]?.playbackmethod || [], + plcmt: bid.mediaTypes?.[VIDEO]?.plcmt || 1, // Fallback to instream considering logic of `isBidRequestValid` protocols: bid.mediaTypes?.[VIDEO]?.protocols || [], skip: bid.mediaTypes?.[VIDEO]?.skip || 0, skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0, @@ -177,6 +210,37 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: serverResponse => serverResponse?.body ? [serverResponse.body] : [], + + /** + * Retrieves user synchronization URLs based on provided options and consents. + * + * @param {object} syncOptions - Options for synchronization. + * @param {object[]} serverResponses - Array of server responses. + * @returns {object[]} - Array of synchronization URLs. + */ + getUserSyncs: (syncOptions, serverResponses) => { + if (!!serverResponses?.length && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + const iframeSyncs = []; + const pixelSyncs = []; + + serverResponses.forEach((response) => { + (response.user_syncs || []).forEach((syncUrl) => { + if (syncUrl.type === 'image') { + pixelSyncs.push({ url: syncUrl.url, type: 'image' }); + } + + if (syncUrl.type === 'iframe') { + iframeSyncs.push({ url: syncUrl.url, type: 'iframe' }); + } + }); + }); + + if (syncOptions.iframeEnabled) return iframeSyncs; + return pixelSyncs; + } + + return []; + }, }; registerBidder(spec); diff --git a/modules/dailymotionBidAdapter.md b/modules/dailymotionBidAdapter.md index 7c871b0d536..7ff3fd47d74 100644 --- a/modules/dailymotionBidAdapter.md +++ b/modules/dailymotionBidAdapter.md @@ -1,4 +1,4 @@ -# Overview +### Overview ``` Module Name: Dailymotion Bid Adapter @@ -6,12 +6,12 @@ Module Type: Bidder Adapter Maintainer: ad-leo-engineering@dailymotion.com ``` -# Description +### Description Dailymotion prebid adapter. Supports video ad units in instream context. -# Configuration options +### Configuration options Before calling this adapter, you need to at least set a video adUnit in an instream context and the API key in the bid parameters: @@ -36,7 +36,29 @@ const adUnits = [ `apiKey` is your publisher API key. For testing purpose, you can use "dailymotion-testing". -# Test Parameters +#### User Sync + +To enable user synchronization, add the following code. Dailymotion highly recommends using iframes and/or pixels for user syncing. This feature enhances DSP user match rates, resulting in higher bid rates and bid prices. Ensure that `pbjs.setConfig()` is called only once. + +```javascript +pbjs.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: '*', // Or add dailymotion to your list included bidders + filter: 'include' + }, + image: { + bidders: '*', // Or add dailymotion to your list of included bidders + filter: 'include' + }, + }, + }, +}); +``` + +### Test Parameters By setting the following bid parameters, you'll get a constant response to any request, to validate your adapter integration: @@ -61,7 +83,7 @@ const adUnits = [ Please note that failing to set these will result in the adapter not bidding at all. -# Sample video AdUnit +### Sample video AdUnit To allow better targeting, you should provide as much context about the video as possible. There are three ways of doing this depending on if you're using Dailymotion player or a third party one. @@ -118,6 +140,10 @@ const adUnits = [ tags: 'tag_1,tag_2,tag_3', title: 'test video', topics: 'topic_1, topic_2', + isCreatedForKids: false, + videoViewsInSession: 1, + autoplay: false, + playerVolume: 8 } } }], @@ -126,6 +152,12 @@ const adUnits = [ video: { api: [2, 7], context: 'instream', + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [7, 8, 11, 12, 13, 14] startdelay: 0, w: 1280, h: 720, @@ -147,10 +179,18 @@ Each of the following video metadata fields can be added in bids.params.video. * `private` - True if video is not publicly available * `tags` - Tags for the video, comma separated * `title` - Video title +* `url` - URL of the content * `topics` - Main topics for the video, comma separated * `xid` - Dailymotion video identifier (only applicable if using the Dailymotion player) +* `isCreatedForKids` - [The content is created for children as primary audience](https://faq.dailymotion.com/hc/en-us/articles/360020920159-Content-created-for-kids) + +The following contextual informations can also be added in bids.params.video. -If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will fallback to those values when possible. See the mapping below. +* `videoViewsInSession` - Number of videos viewed within the current user session +* `autoplay` - Playback was launched without user interaction +* `playerVolume` - Player volume between 0 (muted, 0%) and 10 (100%) + +If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will collect the following values and fallback to bids.params.video values when applicable. See the mapping below. | From ortb2 | Metadata fields | |---------------------------------------------------------------------------------|-----------------| @@ -161,8 +201,14 @@ If you already specify [First-Party data](https://docs.prebid.org/features/first | `ortb2.site.content.livestream` | `livestream` | | `ortb2.site.content.keywords` | `tags` | | `ortb2.site.content.title` | `title` | - -# Integrating the adapter +| `ortb2.site.content.url` | `url` | +| `ortb2.app.bundle` | N/A | +| `ortb2.app.storeurl` | N/A | +| `ortb2.device.lmt` | N/A | +| `ortb2.device.ifa` | N/A | +| `ortb2.device.ext.atts` | N/A | + +### Integrating the adapter To use the adapter with any non-test request, you first need to ask an API key from Dailymotion. Please contact us through **DailymotionPrebid.js@dailymotion.com**. diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 395706994fe..088dfd32979 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -417,7 +417,7 @@ export const spec = { // INITIATE USER SYNCING getUserSyncs: function(options, rtbResponse, gdprConsent) { const syncs = []; - let bidResponse = rtbResponse[0].body; + let bidResponse = rtbResponse?.[0]?.body ?? null; let scope = this; // LISTEN FOR SYNC DATA FROM IFRAME TYPE SYNC diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 109269e623c..8325af56b20 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -205,7 +205,7 @@ if (config.getConfig('brandCategoryTranslation.translationFile')) { getHook('reg * Builds a video url from a base dfp video url and a winning bid, appending * Prebid-specific key-values. * @param {Object} components base video adserver url parsed into components object - * @param {AdapterBidResponse} bid winning bid object to append parameters from + * @param {Object} bid winning bid object to append parameters from * @param {Object} options Options which should be used to construct the URL (used for custom params). * @return {string} video url */ @@ -222,7 +222,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { /** * Returns the encoded vast url if it exists on a bid object, only if prebid-cache * is disabled, and description_url is not already set on a given input - * @param {AdapterBidResponse} bid object to check for vast url + * @param {Object} bid object to check for vast url * @param {Object} components the object to check that description_url is NOT set on * @param {string} prop the property of components that would contain description_url * @return {string | undefined} The encoded vast url if it exists, or undefined @@ -233,7 +233,7 @@ function getDescriptionUrl(bid, components, prop) { /** * Returns the encoded `cust_params` from the bid.adserverTargeting and adds the `hb_uuid`, and `hb_cache_id`. Optionally the options.params.cust_params - * @param {AdapterBidResponse} bid + * @param {Object} bid * @param {Object} options this is the options passed in from the `buildDfpVideoUrl` function * @return {Object} Encoded key value pairs for cust_params */ diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index ea47c64094d..2b819789ec1 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -12,7 +12,7 @@ const BIDDER_CODE = 'dspx'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; const GVLID = 602; -const VIDEO_ORTB_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', +const VIDEO_ORTB_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'plcmt', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext']; diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 2ce6cda16d1..5c4627cfe1b 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -1,36 +1,80 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { deepAccess, logMessage } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'e_volution'; const GVLID = 957; const AD_URL = 'https://service.e-volution.ai/?c=o&m=multi'; -const URL_SYNC = 'https://service.e-volution.ai/?c=o&m=sync'; -const NO_SYNC = true; function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { return false; } + switch (bid.mediaType) { case BANNER: return Boolean(bid.width && bid.height && bid.ad); case VIDEO: - return Boolean(bid.vastUrl); + return Boolean(bid.vastUrl || bid.vastXml); case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); default: return false; } } -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; + const schain = bid.schain || {}; + const { placementId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + placementId, + bidId, + schain, + bidfloor + }; + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.plcmt = mediaTypes[VIDEO].plcmt; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + if (transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = transactionId; + } + + if (userIdAsEids && userIdAsEids.length) { + placement.eids = userIdAsEids; } + return placement; +} + +function getBidFloor(bid) { try { const bidFloor = bid.getFloor({ currency: 'USD', @@ -38,21 +82,8 @@ function getBidFloor(bid) { size: '*', }); return bidFloor.floor; - } catch (_) { - return 0 - } -} - -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); + } catch (err) { + return 0; } } @@ -60,92 +91,91 @@ export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - noSync: NO_SYNC, - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && params.placementId); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; }, - buildRequests: (validBidRequests = [], bidderRequest) => { + buildRequests: (validBidRequests = [], bidderRequest = {}) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; try { - location = new URL(bidderRequest.refererInfo.page); - winTop = window.top; + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; } catch (e) { - location = winTop.location; logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } + winLocation = window.location; } - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } - const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - bidfloor: getBidFloor(bid), - eids: [] - }; + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, + tmax: bidderRequest.timeout + }; - if (bid.userId) { - getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); - } + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } - if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { - placement.traffic = BANNER; - placement.sizes = bid.mediaTypes[BANNER].sizes; - } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { - placement.traffic = VIDEO; - placement.wPlayer = bid.mediaTypes[VIDEO].playerSize[0]; - placement.hPlayer = bid.mediaTypes[VIDEO].playerSize[1]; - placement.minduration = bid.mediaTypes[VIDEO].minduration; - placement.maxduration = bid.mediaTypes[VIDEO].maxduration; - placement.mimes = bid.mediaTypes[VIDEO].mimes; - placement.protocols = bid.mediaTypes[VIDEO].protocols; - placement.startdelay = bid.mediaTypes[VIDEO].startdelay; - placement.placement = bid.mediaTypes[VIDEO].placement; - placement.skip = bid.mediaTypes[VIDEO].skip; - placement.skipafter = bid.mediaTypes[VIDEO].skipafter; - placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = bid.mediaTypes[VIDEO].maxbitrate; - placement.delivery = bid.mediaTypes[VIDEO].delivery; - placement.playbackmethod = bid.mediaTypes[VIDEO].playbackmethod; - placement.api = bid.mediaTypes[VIDEO].api; - placement.linearity = bid.mediaTypes[VIDEO].linearity; - } else if (bid.mediaTypes && bid.mediaTypes[NATIVE]) { - placement.traffic = NATIVE; - placement.native = bid.mediaTypes[NATIVE]; - } + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } - if (bid.schain) { - placements.schain = bid.schain; - } + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } - placements.push(placement); + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); } + return { method: 'POST', url: AD_URL, @@ -165,19 +195,7 @@ export const spec = { } } return response; - }, - - getUserSyncs: (syncOptions, serverResponses) => { - if (NO_SYNC) { - return false - } else { - return [{ - type: 'image', - url: URL_SYNC - }]; - } } - }; registerBidder(spec); diff --git a/modules/edge226BidAdapter.js b/modules/edge226BidAdapter.js index 6d1e2466abe..f0b91183a3e 100644 --- a/modules/edge226BidAdapter.js +++ b/modules/edge226BidAdapter.js @@ -57,6 +57,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/emtvBidAdapter.js b/modules/emtvBidAdapter.js index 7a2fdae8adf..30a63ea5942 100644 --- a/modules/emtvBidAdapter.js +++ b/modules/emtvBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/genericAnalyticsAdapter.js b/modules/genericAnalyticsAdapter.js index 7f721863912..ce37e5c02fe 100644 --- a/modules/genericAnalyticsAdapter.js +++ b/modules/genericAnalyticsAdapter.js @@ -142,7 +142,7 @@ export function defaultHandler({url, method, batchSize, ajax = ajaxBuilder()}) { const serialize = method === 'GET' ? (data) => ({data: JSON.stringify(data)}) : (data) => JSON.stringify(data); return function (events) { - ajax(url, callbacks, serialize(extract(events)), {method}) + ajax(url, callbacks, serialize(extract(events)), {method, keepalive: true}) } } diff --git a/modules/globalsunBidAdapter.js b/modules/globalsunBidAdapter.js index 5b5d97c2cac..eeecf152869 100644 --- a/modules/globalsunBidAdapter.js +++ b/modules/globalsunBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js index ab59c6febec..bcd382e507a 100644 --- a/modules/gothamadsBidAdapter.js +++ b/modules/gothamadsBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, deepSetValue, deepAccess, _map, logWarn } from '../src/utils.js'; +import { deepSetValue, deepAccess, _map, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -80,18 +80,9 @@ export const spec = { if (validBidRequests && validBidRequests.length === 0) return [] let accuontId = validBidRequests[0].params.accountId; const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); - let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - + location = bidderRequest?.refererInfo ?? null; let bids = []; for (let bidRequest of validBidRequests) { let impObject = prepareImpObject(bidRequest); @@ -105,8 +96,8 @@ export const spec = { language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', }, site: { - page: location.pathname, - host: location.host + page: location?.page, + host: location?.domain }, source: { tid: bidderRequest?.ortb2?.source?.tid, @@ -332,7 +323,7 @@ const parseSizes = (bid, mediaType) => { const addVideoParameters = (bidRequest) => { let videoObj = {}; - let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] for (let param of supportParamsList) { if (bidRequest.mediaTypes.video[param] !== undefined) { diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 4b12431302f..774b4b2f6f7 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -315,7 +315,7 @@ function buildRequests(validBidRequests, bidderRequest) { } = bidRequest; const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); const eids = getEids(userId); - const gpid = deepAccess(ortb2Imp, 'ext.data.pbadslot') || deepAccess(ortb2Imp, 'ext.data.adserver.adslot'); + const gpid = deepAccess(ortb2Imp, 'ext.gpid') || deepAccess(ortb2Imp, 'ext.data.pbadslot'); let sizes = [1, 1]; let data = {}; data.displaymanager = 'Prebid.js - gumgum'; @@ -373,15 +373,12 @@ function buildRequests(validBidRequests, bidderRequest) { data.fp = floor; data.fpc = currency; } - + if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.site) { + setIrisId(data, bidderRequest.ortb2.site, params); + } if (params.iriscat && typeof params.iriscat === 'string') { data.iriscat = params.iriscat; } - - if (params.irisid && typeof params.irisid === 'string') { - data.irisid = params.irisid; - } - if (params.zone || params.pubId) { params.zone ? (data.t = params.zone) : (data.pubId = params.pubId); @@ -447,6 +444,27 @@ function buildRequests(validBidRequests, bidderRequest) { }); return bids; } +export function getCids(site) { + if (site.content && Array.isArray(site.content.data)) { + for (const dataItem of site.content.data) { + if (dataItem.name.includes('iris.com') || dataItem.name.includes('iris.tv')) { + return dataItem.ext.cids.join(','); + } + } + } + return null; +} +export function setIrisId(data, site, params) { + let irisID = getCids(site); + if (irisID) { + data.irisid = irisID; + } else { + // Just adding this chechk for safty and if needed we can remove + if (params.irisid && typeof params.irisid === 'string') { + data.irisid = params.irisid; + } + } +} function handleLegacyParams(params, sizes) { const data = {}; diff --git a/modules/instreamTracking.js b/modules/instreamTracking.js index 2686feab679..909c21b29bd 100644 --- a/modules/instreamTracking.js +++ b/modules/instreamTracking.js @@ -41,9 +41,10 @@ const whitelistedResources = /video|fetch|xmlhttprequest|other/; * * Note: this is a workaround till a better approach is engineered. * - * @param {Array} adUnits - * @param {Array} bidsReceived - * @param {Array} bidderRequests + * @param {object} config + * @param {Array} config.adUnits + * @param {Array} config.bidsReceived + * @param {Array} config.bidderRequests * * @return {boolean} returns TRUE if tracking started */ diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js deleted file mode 100644 index 233dec57f21..00000000000 --- a/modules/iqmBidAdapter.js +++ /dev/null @@ -1,276 +0,0 @@ -import {_each, deepAccess, getBidIdParameter, isArray} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {INSTREAM} from '../src/video.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - */ - -const BIDDER_CODE = 'iqm'; -const VERSION = 'v.1.0.0'; -const VIDEO_ORTB_PARAMS = [ - 'mimes', - 'minduration', - 'maxduration', - 'placement', - 'protocols', - 'startdelay' -]; -var ENDPOINT_URL = 'https://pbd.bids.iqm.com'; - -export const spec = { - supportedMediaTypes: [BANNER, VIDEO], - code: BIDDER_CODE, - aliases: ['iqm'], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - const banner = deepAccess(bid, 'mediaTypes.banner'); - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - if ((videoMediaType && context === INSTREAM)) { - const videoBidderParams = deepAccess(bid, 'params.video', {}); - - if (!Array.isArray(videoMediaType.playerSize)) { - return false; - } - - if (!videoMediaType.context) { - return false; - } - - const videoParams = { - ...videoMediaType, - ...videoBidderParams - }; - - if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { - return false; - } - - if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { - return false; - } - - if ( - typeof videoParams.placement !== 'undefined' && - typeof videoParams.placement !== 'number' - ) { - return false; - } - if ( - videoMediaType.context === INSTREAM && - typeof videoParams.startdelay !== 'undefined' && - typeof videoParams.startdelay !== 'number' - ) { - return false; - } - - return !!(bid && bid.params && bid.params.publisherId && bid.params.placementId); - } else { - if (banner === 'undefined') { - return false; - } - return !!(bid && bid.params && bid.params.publisherId && bid.params.placementId); - } - }, - /** - * Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. - *It prepares a bid request with the required information for the DSP side and sends this request to alloted endpoint - * parameter{validBidRequests, bidderRequest} bidderRequest object is useful because it carries a couple of bid parameters that are global to all the bids. - */ - buildRequests: function (validBidRequests, bidderRequest) { - return validBidRequests.map(bid => { - var finalRequest = {}; - - const imp = { - id: bid.bidId, - secure: 1, - bidfloor: 0, - displaymanager: 'Prebid.js', - displaymanagerver: VERSION, - - } - if (deepAccess(bid, 'mediaTypes.banner')) { - imp.banner = getSize(bid.sizes); - imp.mediatype = 'banner'; - } else if (deepAccess(bid, 'mediaTypes.video')) { - imp.video = _buildVideoORTB(bid); - imp.mediatype = 'video'; - } - const site = getSite(bidderRequest); - let device = getDevice(bid.params); - finalRequest = { - sizes: bid.sizes, - id: bid.bidId, - publisherId: getBidIdParameter('publisherId', bid.params), - placementId: getBidIdParameter('placementId', bid.params), - device: device, - site: site, - imp: imp, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bid.auctionId, - adUnitCode: bid.adUnitCode, - bidderRequestId: bid.bidderRequestId, - uuid: bid.bidId, - // TODO: please do not send internal data structures over the network - // I am not going to attempt to accommodate this, no way this is usable on their end, it changes way too frequently - bidderRequest - } - const request = { - method: 'POST', - url: ENDPOINT_URL, - data: finalRequest, - options: { - withCredentials: false - }, - - } - return request; - }); - }, - /** - * Takes Response from server as input and request. - *It parses the response from server side and generates bidresponses for with required rendering paramteres - * parameter{serverResponse, bidRequest} serverReponse: Response from the server side with ad creative. - */ - interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = []; - serverResponse = serverResponse.body; - if (serverResponse && isArray(serverResponse.seatbid)) { - _each(serverResponse.seatbid, function (bidList) { - _each(bidList.bid, function (bid) { - const responseCPM = parseFloat(bid.price); - if (responseCPM > 0.0 && bid.impid) { - const bidResponse = { - requestId: bidRequest.data.id, - currency: serverResponse.cur || 'USD', - cpm: responseCPM, - netRevenue: true, - creativeId: bid.crid || '', - adUnitCode: bidRequest.data.adUnitCode, - auctionId: bidRequest.data.auctionId, - mediaType: bidRequest.data.imp.mediatype, - - ttl: bid.ttl || 60 - }; - - if (bidRequest.data.imp.mediatype === VIDEO) { - bidResponse.width = bid.w || bidRequest.data.imp.video.w; - bidResponse.height = bid.h || bidRequest.data.imp.video.h; - bidResponse.adResponse = { - content: bid.adm, - height: bidRequest.data.imp.video.h, - width: bidRequest.data.imp.video.w - }; - - if (bidRequest.data.imp.video.context === INSTREAM) { - bidResponse.vastUrl = bid.adm; - } - } else if (bidRequest.data.imp.mediatype === BANNER) { - bidResponse.ad = bid.adm; - bidResponse.width = bid.w || bidRequest.data.imp.banner.w; - bidResponse.height = bid.h || bidRequest.data.imp.banner.h; - } - bidResponses.push(bidResponse); - } - }) - }); - } - return bidResponses; - }, - -}; - -let getDevice = function (bidparams) { - const language = navigator.language ? 'language' : 'userLanguage'; - return { - geo: bidparams.geo, - h: screen.height, - w: screen.width, - dnt: _getDNT() ? 1 : 0, - language: navigator[language].split('-')[0], - make: navigator.vendor ? navigator.vendor : '', - ua: navigator.userAgent, - devicetype: _isMobile() ? 1 : _isConnectedTV() ? 3 : 2 - }; -}; - -let _getDNT = function () { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes'; -}; - -let getSize = function (sizes) { - let sizeMap; - if (sizes.length === 2 && typeof sizes[0] === 'number' && typeof sizes[1] === 'number') { - sizeMap = {w: sizes[0], h: sizes[1]}; - } else { - sizeMap = {w: sizes[0][0], h: sizes[0][1]}; - } - return sizeMap; -}; - -function _isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(global.navigator.userAgent); -} - -function _isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent); -} - -function getSite(bidderRequest) { - let domain = ''; - let page = ''; - let referrer = ''; - const Id = 1; - - const {refererInfo} = bidderRequest; - - // TODO: are these the right refererInfo values? - domain = refererInfo.domain; - page = refererInfo.page; - referrer = refererInfo.ref; - - return { - domain, - page, - Id, - referrer - }; -}; - -function _buildVideoORTB(bidRequest) { - const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); - const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); - const video = {}; - - const videoParams = { - ...videoAdUnit, - ...videoBidderParams // Bidder Specific overrides - }; - video.context = 1; - const {w, h} = getSize(videoParams.playerSize[0]); - video.w = w; - video.h = h; - - VIDEO_ORTB_PARAMS.forEach((param) => { - if (videoParams.hasOwnProperty(param)) { - video[param] = videoParams[param]; - } - }); - - video.placement = video.placement || 2; - - video.startdelay = video.startdelay || 0; - video.placement = 1; - video.context = INSTREAM; - - return video; -} -registerBidder(spec); diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 3ce622dba10..0af26ccf6e7 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, logError, deepAccess } from '../src/utils.js'; +import { logMessage, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -91,7 +91,6 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (err) { - logError(err); return 0; } } diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 7062a13cee5..e182634b52a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -26,7 +26,6 @@ import { Renderer } from '../src/Renderer.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'ix'; -const ALIAS_BIDDER_CODE = 'roundel'; const GLOBAL_VENDOR_ID = 10; const SECURE_BID_URL = 'https://htlb.casalemedia.com/openrtb/pbjs'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]; @@ -695,11 +694,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { addRTI(userEids, eidInfo); } - // If `roundel` alias bidder, only send requests if liveramp ids exist. - if (bidderRequest && bidderRequest.bidderCode === ALIAS_BIDDER_CODE && !eidInfo.seenSources['liveramp.com']) { - return []; - } - const requests = []; let r = createRequest(validBidRequests); @@ -707,7 +701,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = addRequestedFeatureToggles(r, FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES) // getting ixdiags for adunits of the video, outstream & multi format (MF) style - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') let ixdiag = buildIXDiag(validBidRequests, fledgeEnabled); for (let key in ixdiag) { r.ext.ixdiag[key] = ixdiag[key]; @@ -973,6 +967,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { const tid = impressions[impKeys[adUnitIndex]].tid; const sid = impressions[impKeys[adUnitIndex]].sid; const auctionEnvironment = impressions[impKeys[adUnitIndex]].ae; + const paapi = impressions[impKeys[adUnitIndex]].paapi; const bannerImpressions = impressionObjects.filter(impression => BANNER in impression); const otherImpressions = impressionObjects.filter(impression => !(BANNER in impression)); @@ -1022,7 +1017,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { _bannerImpression.banner.pos = position; } - if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID) { + if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID || paapi) { _bannerImpression.ext = {}; _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; @@ -1034,6 +1029,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { // enable fledge auction if (auctionEnvironment == 1) { _bannerImpression.ext.ae = 1; + _bannerImpression.ext.paapi = paapi; } } @@ -1436,17 +1432,19 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidde bannerImps[validBidRequest.adUnitCode].pos = deepAccess(validBidRequest, 'mediaTypes.banner.pos'); // Add Fledge flag if enabled - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') if (fledgeEnabled) { const auctionEnvironment = deepAccess(validBidRequest, 'ortb2Imp.ext.ae') + const paapi = deepAccess(validBidRequest, 'ortb2Imp.ext.paapi') + if (paapi) { + bannerImps[validBidRequest.adUnitCode].paapi = paapi + } if (auctionEnvironment) { if (isInteger(auctionEnvironment)) { bannerImps[validBidRequest.adUnitCode].ae = auctionEnvironment; } else { logWarn('error setting auction environment flag - must be an integer') } - } else if (deepAccess(bidderRequest, 'defaultForSlots') == 1) { - bannerImps[validBidRequest.adUnitCode].ae = 1 } } @@ -1602,11 +1600,6 @@ export const spec = { code: BIDDER_CODE, gvlid: GLOBAL_VENDOR_ID, - aliases: [{ - code: ALIAS_BIDDER_CODE, - gvlid: GLOBAL_VENDOR_ID, - skipPbsAliasing: false - }], supportedMediaTypes: SUPPORTED_AD_TYPES, /** @@ -1858,7 +1851,7 @@ export const spec = { try { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } catch (error) { logWarn('Error attaching AuctionConfigs', error); diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index 0705c5932cf..f2f6d97daf9 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -472,7 +472,7 @@ The timeout value must be a positive whole number in milliseconds. Protected Audience API (FLEDGE) =========================== -In order to enable receiving [Protected Audience API](https://developer.chrome.com/en/docs/privacy-sandbox/fledge/) traffic, follow Prebid's documentation on [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) module to build and enable Fledge. +In order to enable receiving [Protected Audience API](https://developer.chrome.com/en/docs/privacy-sandbox/fledge/) traffic, follow Prebid's documentation on [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module to build and enable Fledge. Additional Information ====================== diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 29ce0da5317..a06404e52f2 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -31,9 +31,7 @@ const playlistItemCache = {}; const pendingRequests = {}; let activeRequestCount = 0; let resumeBidRequest; -// defaults to 'always' for backwards compatibility -// TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY -let overrideContentId = ENRICH_ALWAYS; +let overrideContentId = ENRICH_WHEN_EMPTY; let overrideContentUrl = ENRICH_WHEN_EMPTY; let overrideContentTitle = ENRICH_WHEN_EMPTY; let overrideContentDescription = ENRICH_WHEN_EMPTY; @@ -83,9 +81,7 @@ export function fetchTargetingInformation(jwTargeting) { } export function setOverrides(params) { - // For backwards compatibility, default to always unless overridden by Publisher. - // TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY - overrideContentId = sanitizeOverrideParam(params.overrideContentId, ENRICH_ALWAYS); + overrideContentId = sanitizeOverrideParam(params.overrideContentId, ENRICH_WHEN_EMPTY); overrideContentUrl = sanitizeOverrideParam(params.overrideContentUrl, ENRICH_WHEN_EMPTY); overrideContentTitle = sanitizeOverrideParam(params.overrideContentTitle, ENRICH_WHEN_EMPTY); overrideContentDescription = sanitizeOverrideParam(params.overrideContentDescription, ENRICH_WHEN_EMPTY); diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 936cd1d10a2..44d696eea6d 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -12,16 +12,20 @@ Publishers must register JW Player as a real time data provider by setting up a following structure: ```javascript -const jwplayerDataProvider = { - name: "jwplayer" -}; - pbjs.setConfig({ ..., realTimeData: { - dataProviders: [ - jwplayerDataProvider - ] + dataProviders: [{ + name: 'jwplayer', + waitForIt: true, + params: { + mediaIDs: ['abc', 'def', 'ghi', 'jkl'], + overrideContentId: 'always', + overrideContentUrl: 'always', + overrideContentTitle: 'always', + overrideContentDescription: 'always' + } + }] } }); ``` @@ -86,7 +90,7 @@ realTimeData = { | waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | | params | Object | | | | params.mediaIDs | Array of Strings | Media Ids for prefetching | Optional | -| params.overrideContentId | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.id | Defaults to 'always' | +| params.overrideContentId | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.id | Defaults to 'whenEmpty' | | params.overrideContentUrl | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.url | Defaults to 'whenEmpty' | | params.overrideContentTitle | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.title | Defaults to 'whenEmpty' | | params.overrideContentDescription | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.ext.description | Defaults to 'whenEmpty' | @@ -155,7 +159,7 @@ To view an example: - in your browser, navigate to: -`http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` +`http://localhost:9999/integrationExamples/realTimeData/jwplayerRtdProvider_example.html` **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index cafbbd982fa..73e10f1dd39 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -250,7 +250,7 @@ function interpretResponse(response, bidRequest) { if (fledgeAuctionConfigs.length > 0) { return { bids: bidResponses, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs } } else { return bidResponses; diff --git a/modules/kiviadsBidAdapter.js b/modules/kiviadsBidAdapter.js index 13739d57cb2..cc1e319c348 100644 --- a/modules/kiviadsBidAdapter.js +++ b/modules/kiviadsBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js index c4507201064..b2c1548a02d 100644 --- a/modules/krushmediaBidAdapter.js +++ b/modules/krushmediaBidAdapter.js @@ -1,4 +1,4 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { isFn, deepAccess } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -55,23 +55,15 @@ export const spec = { let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page); - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - + location = bidderRequest?.refererInfo ?? null; const placements = []; const request = { deviceWidth: winTop.screen.width, deviceHeight: winTop.screen.height, language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', secure: 1, - host: location.host, - page: location.pathname, + host: location?.domain ?? '', + page: location?.page ?? '', placements: placements }; @@ -107,6 +99,7 @@ export const spec = { placement.protocols = bid.mediaTypes[VIDEO].protocols; placement.startdelay = bid.mediaTypes[VIDEO].startdelay; placement.placement = bid.mediaTypes[VIDEO].placement; + placement.plcmt = bid.mediaTypes[VIDEO].plcmt; placement.skip = bid.mediaTypes[VIDEO].skip; placement.skipafter = bid.mediaTypes[VIDEO].skipafter; placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate; diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js index 5a5536e0c1a..63a01bfea02 100644 --- a/modules/kueezBidAdapter.js +++ b/modules/kueezBidAdapter.js @@ -417,6 +417,7 @@ function populateVideoParams(params, bid) { const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); const placement = deepAccess(bid, `mediaTypes.video.placement`); + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); const playbackMethod = getPlaybackMethod(bid); const skip = deepAccess(bid, `mediaTypes.video.skip`); @@ -435,7 +436,9 @@ function populateVideoParams(params, bid) { if (placement) { params.placement = placement; } - + if (plcmt) { + params.plcmt = plcmt; + } if (playbackMethod) { params.playbackMethod = playbackMethod; } diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index 814e5a84df1..6c97f64e6a8 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -157,7 +157,6 @@ export const spec = { h: sizes[1], skip: VIDEO_BID.skip || 0, playbackmethod: VIDEO_BID.playbackmethod || [1], - placement: (bid.params.execution === 'outstream' || VIDEO_BID.context === 'outstream') ? 5 : 1, ext: { lkqdcustomparameters: {} }, diff --git a/modules/loganBidAdapter.js b/modules/loganBidAdapter.js index 7aa82e3046c..bec23cddc2d 100644 --- a/modules/loganBidAdapter.js +++ b/modules/loganBidAdapter.js @@ -99,6 +99,7 @@ export const spec = { placement.protocols = mediaType[VIDEO].protocols; placement.startdelay = mediaType[VIDEO].startdelay; placement.placement = mediaType[VIDEO].placement; + placement.plcmt = mediaType[VIDEO].plcmt; placement.skip = mediaType[VIDEO].skip; placement.skipafter = mediaType[VIDEO].skipafter; placement.minbitrate = mediaType[VIDEO].minbitrate; diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index fe4dd83c9e2..e7c5300d072 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { if (fledgeAuctionConfigs.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } @@ -74,7 +74,7 @@ function newBidRequest(bidRequest, bidderRequest) { mediaTypes: bidRequest.mediaTypes, } - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') if (fledgeEnabled) { const ae = deepAccess(bidRequest, 'ortb2Imp.ext.ae'); if (ae) { diff --git a/modules/loyalBidAdapter.js b/modules/loyalBidAdapter.js index 30fdeb44233..ffa88529b2f 100644 --- a/modules/loyalBidAdapter.js +++ b/modules/loyalBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js index b95dfc08732..ffc2307bcb8 100755 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -134,7 +134,7 @@ function interpretResponse(serverResponse, bidRequest) { } })); - return {bids, fledgeAuctionConfigs}; + return {bids, paapi: fledgeAuctionConfigs}; } function report(type, data) { diff --git a/modules/lunamediahbBidAdapter.js b/modules/lunamediahbBidAdapter.js index 66838014e18..470a11510c5 100644 --- a/modules/lunamediahbBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -1,4 +1,3 @@ -import { logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -39,14 +38,7 @@ export const spec = { let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; + location = bidderRequest?.refererInfo ?? null; const placements = []; const request = { @@ -54,8 +46,8 @@ export const spec = { 'deviceHeight': winTop.screen.height, 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', 'secure': 1, - 'host': location.host, - 'page': location.pathname, + 'host': location?.domain ?? '', + 'page': location?.page ?? '', 'placements': placements }; diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index f4967fed170..987b2689f6b 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -66,6 +66,7 @@ const ORTB_VIDEO_PARAMS = { h: value => isInteger(value), startdelay: value => isInteger(value), placement: value => [1, 2, 3, 4, 5].indexOf(value) !== -1, + plcmt: value => [1, 2, 3, 4].indexOf(value) !== -1, linearity: value => [1, 2].indexOf(value) !== -1, skip: value => [0, 1].indexOf(value) !== -1, skipmin: value => isInteger(value), diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 4d4cf0d80ed..5949e198fd1 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -261,7 +261,7 @@ function slotParams(bidRequest, bidderRequests) { if (floorInfo && floorInfo.length > 0) { params.bidfloors = floorInfo; } - if (bidderRequests.fledgeEnabled) { + if (bidderRequests.paapi?.enabled) { params.ext.ae = bidRequest?.ortb2Imp?.ext?.ae; } return params; @@ -508,7 +508,7 @@ export const spec = { } return { bids: validBids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } }, getUserSyncs: function(syncOptions, serverResponses) { diff --git a/modules/medianetBidAdapter.md b/modules/medianetBidAdapter.md index d401a72f1f6..500c9f3f12b 100644 --- a/modules/medianetBidAdapter.md +++ b/modules/medianetBidAdapter.md @@ -186,12 +186,12 @@ var adUnits = [{ In order to enable PAAPI auctions follow the instructions below: -1. Add the fledgeForGpt and paapi modules to your prebid bundle. +1. Add the paapiForGpt and paapi modules to your prebid bundle. 2. Add the following configuration for the module ``` pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { + paapi: { enabled: true, bidders: ['medianet'], defaultForSlots: 1 @@ -200,4 +200,4 @@ pbjs.que.push(function() { }); ``` -For a detailed guide to enabling PAAPI auctions follow Prebid's documentation on [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) +For a detailed guide to enabling PAAPI auctions follow Prebid's documentation on [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index f073fb4c576..6bd1f2e79db 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -70,6 +70,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js index 35e9b03c031..7fb585ada06 100644 --- a/modules/mobfoxpbBidAdapter.js +++ b/modules/mobfoxpbBidAdapter.js @@ -107,6 +107,7 @@ export const spec = { placement.protocols = mediaType[VIDEO].protocols; placement.startdelay = mediaType[VIDEO].startdelay; placement.placement = mediaType[VIDEO].placement; + placement.plcmt = mediaType[VIDEO].plcmt; placement.skip = mediaType[VIDEO].skip; placement.skipafter = mediaType[VIDEO].skipafter; placement.minbitrate = mediaType[VIDEO].minbitrate; diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 62bee5c2aeb..8ddcb2c3980 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -93,7 +93,7 @@ function buildRequests(validBidRequests, bidderRequest) { const connection = navigator.connection || navigator.webkitConnection; payload.networkConnectionType = (connection && connection.type) ? connection.type : null; payload.networkEffectiveConnectionType = (connection && connection.effectiveType) ? connection.effectiveType : null; - payload.fledgeEnabled = Boolean(bidderRequest && bidderRequest.fledgeEnabled) + payload.fledgeEnabled = Boolean(bidderRequest?.paapi?.enabled) return { method: 'POST', url: ENDPOINT, @@ -156,7 +156,7 @@ function interpretResponse(serverResponse, bidderRequest) { const fledgeAuctionConfigs = body.fledgeAuctionConfigs return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { return bids; diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 2237dced58f..8b16aa1a84e 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -114,7 +114,7 @@ const converter = ortbConverter({ }); return { bids: response.bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { return response.bids diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index 957192d1bec..486d5ac726b 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -525,7 +525,6 @@ function createImp(bidRequest) { playbackmethod: videoReq.playbackmethod || VIDEO_DEFAULTS.PLAYBACK_METHODS, delivery: videoReq.delivery || VIDEO_DEFAULTS.DELIVERY, api: videoReq.api || VIDEO_DEFAULTS.API, - placement: videoReq.context === OUTSTREAM ? 3 : 1, }; mediaType = VIDEO; diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js index 87d00f14de0..2ad14227804 100644 --- a/modules/opscoBidAdapter.js +++ b/modules/opscoBidAdapter.js @@ -19,7 +19,11 @@ export const spec = { Array.isArray(bid.mediaTypes?.banner?.sizes)), buildRequests: (validBidRequests, bidderRequest) => { - const {publisherId, placementId, siteId} = validBidRequests[0].params; + if (!validBidRequests || !bidderRequest) { + return; + } + + const {publisherId, siteId} = validBidRequests[0].params; const payload = { id: bidderRequest.bidderRequestId, @@ -28,7 +32,7 @@ export const spec = { banner: {format: extractSizes(bidRequest)}, ext: { opsco: { - placementId: placementId, + placementId: bidRequest.params.placementId, publisherId: publisherId, } } diff --git a/modules/optableBidAdapter.js b/modules/optableBidAdapter.js index f6c7cf00a35..4e639fb88ee 100644 --- a/modules/optableBidAdapter.js +++ b/modules/optableBidAdapter.js @@ -35,7 +35,7 @@ export const spec = { return { bidId: impid, config } }) - return { bids, fledgeAuctionConfigs: auctionConfigs } + return { bids, paapi: auctionConfigs } }, supportedMediaTypes: [BANNER] } diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index e9a983d5be2..0bbbfef028e 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -763,7 +763,7 @@ export const spec = { return ret; }, _unpackVideoConfigIntoIABformat(ret, objConfig) { - let arrVideoKeysAllowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; + let arrVideoKeysAllowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'plcmt', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; for (const key in objConfig) { var found = false; arrVideoKeysAllowed.forEach(function(arg) { diff --git a/modules/paapi.js b/modules/paapi.js index 28252d0bb7a..310974b31fe 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -2,15 +2,15 @@ * Collect PAAPI component auction configs from bid adapters and make them available through `pbjs.getPAAPIConfig()` */ import {config} from '../src/config.js'; -import {getHook, module} from '../src/hook.js'; -import {deepSetValue, logInfo, logWarn, mergeDeep, deepEqual, parseSizesInput, deepAccess} from '../src/utils.js'; +import {getHook, hook, module} from '../src/hook.js'; +import {deepSetValue, logInfo, logWarn, mergeDeep, sizesToSizeTuples, deepAccess, deepEqual} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; import * as events from '../src/events.js'; import {EVENTS} from '../src/constants.js'; import {currencyCompare} from '../libraries/currencyUtils/currency.js'; -import {maximum, minimum} from '../src/utils/reducers.js'; -import {auctionManager} from '../src/auctionManager.js'; +import {keyCompare, maximum, minimum} from '../src/utils/reducers.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {auctionStore} from '../libraries/weakStore/weakStore.js'; const MODULE = 'PAAPI'; @@ -19,23 +19,14 @@ const USED = new WeakSet(); export function registerSubmodule(submod) { submodules.push(submod); - submod.init && submod.init({getPAAPIConfig}); + submod.init && submod.init({ + getPAAPIConfig, + expandFilters + }); } module('paapi', registerSubmodule); -function auctionStore() { - const store = new WeakMap(); - return function (auctionId, init = {}) { - const auction = auctionManager.index.getAuction({auctionId}); - if (auction == null) return; - if (!store.has(auction)) { - store.set(auction, init); - } - return store.get(auction); - }; -} - const pendingConfigsForAuction = auctionStore(); const configsForAuction = auctionStore(); const pendingBuyersForAuction = auctionStore(); @@ -43,10 +34,8 @@ const pendingBuyersForAuction = auctionStore(); let latestAuctionForAdUnit = {}; let moduleConfig = {}; -['paapi', 'fledgeForGpt'].forEach(ns => { - config.getConfig(ns, config => { - init(config[ns], ns); - }); +config.getConfig('paapi', config => { + init(config.paapi); }); export function reset() { @@ -54,10 +43,7 @@ export function reset() { latestAuctionForAdUnit = {}; } -export function init(cfg, configNamespace) { - if (configNamespace !== 'paapi') { - logWarn(`'${configNamespace}' configuration options will be renamed to 'paapi'; consider using setConfig({paapi: [...]}) instead`); - } +export function init(cfg) { if (cfg && cfg.enabled === true) { moduleConfig = cfg; logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} runAdAuction)`, cfg); @@ -71,7 +57,7 @@ getHook('addPaapiConfig').before(addPaapiConfigHook); getHook('makeBidRequests').after(markForFledge); events.on(EVENTS.AUCTION_END, onAuctionEnd); -function getSlotSignals(bidsReceived = [], bidRequests = []) { +function getSlotSignals(adUnit = {}, bidsReceived = [], bidRequests = []) { let bidfloor, bidfloorcur; if (bidsReceived.length > 0) { const bestBid = bidsReceived.reduce(maximum(currencyCompare(bid => [bid.cpm, bid.currency]))); @@ -88,6 +74,10 @@ function getSlotSignals(bidsReceived = [], bidRequests = []) { deepSetValue(cfg, 'auctionSignals.prebid.bidfloor', bidfloor); bidfloorcur && deepSetValue(cfg, 'auctionSignals.prebid.bidfloorcur', bidfloorcur); } + const requestedSize = getRequestedSize(adUnit); + if (requestedSize) { + cfg.requestedSize = requestedSize; + } return cfg; } @@ -109,7 +99,7 @@ export function buyersToAuctionConfigs(igbRequests, merge = mergeBuyers, config } function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adUnits}) { - const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []) + const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []); const allReqs = bidderRequests?.flatMap(br => br.bids); const paapiConfigs = {}; (adUnitCodes || []).forEach(au => { @@ -125,23 +115,11 @@ function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adU } Object.entries(pendingConfigs || {}).forEach(([adUnitCode, auctionConfigs]) => { const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; - const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); + const slotSignals = getSlotSignals(adUnitsByCode[adUnitCode], bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); paapiConfigs[adUnitCode] = { ...slotSignals, componentAuctions: auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg)) }; - // TODO: need to flesh out size treatment: - // - which size should the paapi auction pick? (this uses the first one defined) - // - should we signal it to SSPs, and how? - // - what should we do if adapters pick a different one? - // - what does size mean for video and native? - const size = parseSizesInput(adUnitsByCode[adUnitCode]?.mediaTypes?.banner?.sizes)?.[0]?.split('x'); - if (size) { - paapiConfigs[adUnitCode].requestedSize = { - width: size[0], - height: size[1], - }; - } latestAuctionForAdUnit[adUnitCode] = auctionId; }); configsForAuction(auctionId, paapiConfigs); @@ -164,7 +142,7 @@ function setFPD(target, {ortb2, ortb2Imp}) { } export function addPaapiConfigHook(next, request, paapiConfig) { - if (getFledgeConfig().enabled) { + if (getFledgeConfig(config.getCurrentBidder()).enabled) { const {adUnitCode, auctionId} = request; // eslint-disable-next-line no-inner-declarations @@ -263,33 +241,54 @@ export function partitionBuyersByBidder(igbRequests) { }) return Object.entries(igbs).map(([bidder, igbs]) => [requests[bidder], igbs]) } + +/** + * Expand PAAPI api filters into a map from ad unit code to auctionId. + * + * @param auctionId when specified, the result will have this as the value for each entry. + * when not specified, each ad unit will map to the latest auction that involved that ad unit. + * @param adUnitCode when specified, the result will contain only one entry (for this ad unit) or be empty (if this ad + * unit was never involved in an auction). + * when not specified, the result will contain an entry for every ad unit that was involved in any auction. + * @return {{[adUnitCode: string]: string}} + */ +function expandFilters({auctionId, adUnitCode} = {}) { + let adUnitCodes = []; + if (adUnitCode == null) { + adUnitCodes = Object.keys(latestAuctionForAdUnit); + } else if (latestAuctionForAdUnit.hasOwnProperty(adUnitCode)) { + adUnitCodes = [adUnitCode]; + } + return Object.fromEntries( + adUnitCodes.map(au => [au, auctionId ?? latestAuctionForAdUnit[au]]) + ); +} + /** * Get PAAPI auction configuration. * - * @param auctionId? optional auction filter; if omitted, the latest auction for each ad unit is used - * @param adUnitCode? optional ad unit filter - * @param includeBlanks if true, include null entries for ad units that match the given filters but do not have any available auction configs. - * @returns {{}} a map from ad unit code to auction config for the ad unit. + * @param {Object} [filters] - Filters object + * @param {string} [filters.auctionId] optional auction filter; if omitted, the latest auction for each ad unit is used + * @param {string} [filters.adUnitCode] optional ad unit filter + * @param {boolean} [includeBlanks=false] if true, include null entries for ad units that match the given filters but do not have any available auction configs. + * @returns {Object} a map from ad unit code to auction config for the ad unit. */ -export function getPAAPIConfig({auctionId, adUnitCode} = {}, includeBlanks = false) { +export function getPAAPIConfig(filters = {}, includeBlanks = false) { const output = {}; - const targetedAuctionConfigs = auctionId && configsForAuction(auctionId); - Object.keys((auctionId != null ? targetedAuctionConfigs : latestAuctionForAdUnit) ?? []).forEach(au => { - const latestAuctionId = latestAuctionForAdUnit[au]; - const auctionConfigs = targetedAuctionConfigs ?? (latestAuctionId && configsForAuction(latestAuctionId)); - if ((adUnitCode ?? au) === au) { - let candidate; - if (targetedAuctionConfigs?.hasOwnProperty(au)) { - candidate = targetedAuctionConfigs[au]; - } else if (auctionId == null && auctionConfigs?.hasOwnProperty(au)) { - candidate = auctionConfigs[au]; - } + Object.entries(expandFilters(filters)).forEach(([au, auctionId]) => { + const auctionConfigs = configsForAuction(auctionId); + if (auctionConfigs?.hasOwnProperty(au)) { + // ad unit was involved in a PAAPI auction + const candidate = auctionConfigs[au]; if (candidate && !USED.has(candidate)) { output[au] = candidate; USED.add(candidate); } else if (includeBlanks) { output[au] = null; } + } else if (auctionId == null && includeBlanks) { + // ad unit was involved in a non-PAAPI auction + output[au] = null; } }); return output; @@ -301,45 +300,68 @@ function isFledgeSupported() { return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator; } -function getFledgeConfig() { - const bidder = config.getCurrentBidder(); - const useGlobalConfig = moduleConfig.enabled && (bidder == null || !moduleConfig.bidders?.length || moduleConfig.bidders?.includes(bidder)); +function getFledgeConfig(bidder) { + const enabled = moduleConfig.enabled && (bidder == null || !moduleConfig.bidders?.length || moduleConfig.bidders?.includes(bidder)); return { - enabled: config.getConfig('fledgeEnabled') ?? useGlobalConfig, - ae: config.getConfig('defaultForSlots') ?? (useGlobalConfig ? moduleConfig.defaultForSlots : undefined) + enabled, + ae: enabled ? moduleConfig.defaultForSlots : undefined }; } +/** + * Given an array of size tuples, return the one that should be used for PAAPI. + */ +export const getPAAPISize = hook('sync', function (sizes) { + if (sizes?.length) { + return sizes + .filter(([w, h]) => !(w === h && w <= 5)) + .reduce(maximum(keyCompare(([w, h]) => w * h))); + } +}, 'getPAAPISize'); + +function getRequestedSize(adUnit) { + return adUnit.ortb2Imp?.ext?.paapi?.requestedSize || (() => { + const size = getPAAPISize(sizesToSizeTuples(adUnit.mediaTypes?.banner?.sizes)); + if (size) { + return { + width: size[0], + height: size[1] + }; + } + })(); +} + export function markForFledge(next, bidderRequests) { if (isFledgeSupported()) { bidderRequests.forEach((bidderReq) => { - config.runWithBidder(bidderReq.bidderCode, () => { - const {enabled, ae} = getFledgeConfig(); - Object.assign(bidderReq, { - fledgeEnabled: enabled, - paapi: { - enabled, - componentSeller: !!moduleConfig.componentSeller?.auctionConfig - } - }); - bidderReq.bids.forEach(bidReq => { - // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md - const igsAe = bidReq.ortb2Imp?.ext?.igs != null - ? bidReq.ortb2Imp.ext.igs.ae || 1 - : null - const extAe = bidReq.ortb2Imp?.ext?.ae; - if (igsAe !== extAe && igsAe != null && extAe != null) { - logWarn(MODULE, `Bid request defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, bidReq); - } - const bidAe = igsAe ?? extAe ?? ae; - if (bidAe) { - deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidAe); - bidReq.ortb2Imp.ext.igs = Object.assign({ - ae: bidAe, - biddable: 1 - }, bidReq.ortb2Imp.ext.igs) + const {enabled, ae} = getFledgeConfig(bidderReq.bidderCode); + Object.assign(bidderReq, { + paapi: { + enabled, + componentSeller: !!moduleConfig.componentSeller?.auctionConfig + } + }); + bidderReq.bids.forEach(bidReq => { + // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md + const igsAe = bidReq.ortb2Imp?.ext?.igs != null + ? bidReq.ortb2Imp.ext.igs.ae || 1 + : null; + const extAe = bidReq.ortb2Imp?.ext?.ae; + if (igsAe !== extAe && igsAe != null && extAe != null) { + logWarn(MODULE, `Bid request defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, bidReq); + } + const bidAe = igsAe ?? extAe ?? ae; + if (bidAe) { + deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidAe); + bidReq.ortb2Imp.ext.igs = Object.assign({ + ae: bidAe, + biddable: 1 + }, bidReq.ortb2Imp.ext.igs); + const requestedSize = getRequestedSize(bidReq); + if (requestedSize) { + deepSetValue(bidReq, 'ortb2Imp.ext.paapi.requestedSize', requestedSize); } - }); + } }); }); } @@ -347,7 +369,7 @@ export function markForFledge(next, bidderRequests) { } export function setImpExtAe(imp, bidRequest, context) { - if (!context.bidderRequest.fledgeEnabled) { + if (!context.bidderRequest.paapi?.enabled) { delete imp.ext?.ae; delete imp.ext?.igs; } @@ -418,7 +440,7 @@ export function setResponsePaapiConfigs(response, ortbResponse, context) { registerOrtbProcessor({ type: RESPONSE, - name: 'fledgeAuctionConfigs', + name: 'paapiConfigs', priority: -1, fn: setResponsePaapiConfigs, }); diff --git a/modules/fledgeForGpt.js b/modules/paapiForGpt.js similarity index 57% rename from modules/fledgeForGpt.js rename to modules/paapiForGpt.js index bda4494faaf..8196425ba0a 100644 --- a/modules/fledgeForGpt.js +++ b/modules/paapiForGpt.js @@ -1,34 +1,21 @@ /** * GPT-specific slot configuration logic for PAAPI. */ -import {submodule} from '../src/hook.js'; -import {deepAccess, logInfo, logWarn} from '../src/utils.js'; +import {getHook, submodule} from '../src/hook.js'; +import {deepAccess, logInfo, logWarn, sizeTupleToSizeString} from '../src/utils.js'; import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; -// import parent module to keep backwards-compat for NPM consumers after paapi was split from fledgeForGpt -// there's a special case in webpack.conf.js to avoid duplicating build output on non-npm builds -// TODO: remove this in prebid 9 -// eslint-disable-next-line prebid/validate-imports -import './paapi.js'; -const MODULE = 'fledgeForGpt'; +import {keyCompare} from '../src/utils/reducers.js'; +const MODULE = 'paapiForGpt'; let getPAAPIConfig; -// for backwards compat, we attempt to automatically set GPT configuration as soon as we -// have the auction configs available. Disabling this allows one to call pbjs.setPAAPIConfigForGPT at their -// own pace. -let autoconfig = true; +let autoconfig = false; -Object.entries({ - [MODULE]: MODULE, - 'paapi': 'paapi.gpt' -}).forEach(([topic, ns]) => { - const configKey = `${ns}.autoconfig`; - config.getConfig(topic, (cfg) => { - autoconfig = deepAccess(cfg, configKey, true); - }); +config.getConfig('paapi', (cfg) => { + autoconfig = deepAccess(cfg, 'paapi.gpt.autoconfig', false); }); export function slotConfigurator() { @@ -73,6 +60,68 @@ export function onAuctionConfigFactory(setGptConfig = setComponentAuction) { } } +export const getPAAPISizeHook = (() => { + /* + https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing#faq + https://support.google.com/admanager/answer/1100453?hl=en + + Ignore any placeholder sizes, where placeholder is defined as a square creative with a side of <= 5 pixels + Look if there are any sizes that are part of the set of supported ad sizes defined here. If there are, choose the largest supported size by area (width * height) + For clarity, the set of supported ad sizes includes all of the ad sizes listed under “Top-performing ad sizes”, “Other supported ad sizes”, and “Regional ad sizes”. + If not, choose the largest remaining size (i.e. that isn’t in the list of supported ad sizes) by area (width * height) + */ + const SUPPORTED_SIZES = [ + [728, 90], + [336, 280], + [300, 250], + [300, 50], + [160, 600], + [1024, 768], + [970, 250], + [970, 90], + [768, 1024], + [480, 320], + [468, 60], + [320, 480], + [320, 100], + [320, 50], + [300, 600], + [300, 100], + [250, 250], + [234, 60], + [200, 200], + [180, 150], + [125, 125], + [120, 600], + [120, 240], + [120, 60], + [88, 31], + [980, 120], + [980, 90], + [950, 90], + [930, 180], + [750, 300], + [750, 200], + [750, 100], + [580, 400], + [250, 360], + [240, 400], + ].sort(keyCompare(([w, h]) => -(w * h))) + .map(size => [size, sizeTupleToSizeString(size)]); + + return function(next, sizes) { + if (sizes?.length) { + const sizeStrings = new Set(sizes.map(sizeTupleToSizeString)); + const preferredSize = SUPPORTED_SIZES.find(([_, sizeStr]) => sizeStrings.has(sizeStr)); + if (preferredSize) { + next.bail(preferredSize[0]); + return; + } + } + next(sizes); + } +})(); + export function setPAAPIConfigFactory( getConfig = (filters) => getPAAPIConfig(filters, true), setGptConfig = setComponentAuction) { @@ -105,5 +154,6 @@ submodule('paapi', { onAuctionConfig: onAuctionConfigFactory(), init(params) { getPAAPIConfig = params.getPAAPIConfig; + getHook('getPAAPISize').before(getPAAPISizeHook); } }); diff --git a/modules/fledgeForGpt.md b/modules/paapiForGpt.md similarity index 55% rename from modules/fledgeForGpt.md rename to modules/paapiForGpt.md index 28f44da6459..31cde2e268d 100644 --- a/modules/fledgeForGpt.md +++ b/modules/paapiForGpt.md @@ -1,22 +1,22 @@ # Overview -This module allows Prebid.js to support FLEDGE by integrating it with GPT's [experimental FLEDGE +This module allows Prebid.js to support PAAPI by integrating it with GPT's [experimental PAAPI support](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing). -To learn more about FLEDGE in general, go [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md). +To learn more about PAAPI in general, go [here](https://github.com/WICG/turtledove/blob/main/PAAPI.md). -This document covers the steps necessary for publishers to enable FLEDGE on their inventory. It also describes -the changes Bid Adapters need to implement in order to support FLEDGE. +This document covers the steps necessary for publishers to enable PAAPI on their inventory. It also describes +the changes Bid Adapters need to implement in order to support PAAPI. ## Publisher Integration -Publishers wishing to enable FLEDGE support must do two things. First, they must compile Prebid.js with support for this module. -This is accomplished by adding the `fledgeForGpt` module to the list of modules they are already using: +Publishers wishing to enable PAAPI support must do two things. First, they must compile Prebid.js with support for this module. +This is accomplished by adding the `paapiForGpt` module to the list of modules they are already using: ``` -gulp build --modules=fledgeForGpt,... +gulp build --modules=paapiForGpt,... ``` -Second, they must enable FLEDGE in their Prebid.js configuration. -This is done through module level configuration, but to provide a high degree of flexiblity for testing, FLEDGE settings also exist at the bidder level and slot level. +Second, they must enable PAAPI in their Prebid.js configuration. +This is done through module level configuration, but to provide a high degree of flexiblity for testing, PAAPI settings also exist the slot level. ### Module Configuration This module exposes the following settings: @@ -27,14 +27,13 @@ This module exposes the following settings: |bidders | Array[String] |Optional list of bidders |Defaults to all bidders | |defaultForSlots | Number |Default value for `imp.ext.ae` in requests for specified bidders |Should be 1 | -As noted above, FLEDGE support is disabled by default. To enable it, set the `enabled` value to `true` for this module and configure `defaultForSlots` to be `1` (meaning _Client-side auction_). -using the `setConfig` method of Prebid.js. Optionally, a list of -bidders to apply these settings to may be provided: +As noted above, PAAPI support is disabled by default. To enable it, set the `enabled` value to `true` for this module and configure `defaultForSlots` to be `1` (meaning _Client-side auction_). +using the `setConfig` method of Prebid.js. Optionally, a list of bidders to apply these settings to may be provided: ```js pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { + paapi: { enabled: true, bidders: ['openx', 'rtbhouse'], defaultForSlots: 1 @@ -43,35 +42,14 @@ pbjs.que.push(function() { }); ``` -### Bidder Configuration -This module adds the following setting for bidders: - -|Name |Type |Description |Notes | -| :------------ | :------------ | :------------ |:------------ | -| fledgeEnabled | Boolean | Enable/disable a bidder to participate in FLEDGE | Defaults to `false` | -|defaultForSlots | Number |Default value for `imp.ext.ae` in requests for specified bidders |Should be 1| - -Individual bidders may be further included or excluded here using the `setBidderConfig` method -of Prebid.js: - -```js -pbjs.setBidderConfig({ - bidders: ["openx"], - config: { - fledgeEnabled: true, - defaultForSlots: 1 - } -}); -``` - ### AdUnit Configuration -All adunits can be opted-in to FLEDGE in the global config via the `defaultForSlots` parameter. +All adunits can be opted-in to PAAPI in the global config via the `defaultForSlots` parameter. If needed, adunits can be configured individually by setting an attribute of the `ortb2Imp` object for that adunit. This attribute will take precedence over `defaultForSlots` setting. |Name |Type |Description |Notes | | :------------ | :------------ | :------------ |:------------ | -| ortb2Imp.ext.ae | Integer | Auction Environment: 1 indicates FLEDGE eligible, 0 indicates it is not | Absence indicates this is not FLEDGE eligible | +| ortb2Imp.ext.ae | Integer | Auction Environment: 1 indicates PAAPI eligible, 0 indicates it is not | Absence indicates this is not PAAPI eligible | The `ae` field stands for Auction Environment and was chosen to be consistent with the field that GAM passes to bidders in their Open Bidding and Exchange Bidding APIs. More details on that can be found @@ -91,31 +69,31 @@ pbjs.addAdUnits({ ``` ## Bid Adapter Integration -Chrome has enabled a two-tier auction in FLEDGE. This allows multiple sellers (frequently SSPs) to act on behalf of the publisher with +Chrome has enabled a two-tier auction in PAAPI. This allows multiple sellers (frequently SSPs) to act on behalf of the publisher with a single entity serving as the final decision maker. In their [current approach](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing), GPT has opted to run the final auction layer while allowing other SSPs/sellers to participate as -[Component Auctions](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#21-initiating-an-on-device-auction) which feed their -bids to the final layer. To learn more about Component Auctions, go [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#24-scoring-bids-in-component-auctions). +[Component Auctions](https://github.com/WICG/turtledove/blob/main/PAAPI.md#21-initiating-an-on-device-auction) which feed their +bids to the final layer. To learn more about Component Auctions, go [here](https://github.com/WICG/turtledove/blob/main/PAAPI.md#24-scoring-bids-in-component-auctions). -The FLEDGE auction, including Component Auctions, are configured via an `AuctionConfig` object that defines the parameters of the auction for a given -seller. This module enables FLEDGE support by allowing bid adaptors to return `AuctionConfig` objects in addition to bids. If a bid adaptor returns an +The PAAPI auction, including Component Auctions, are configured via an `AuctionConfig` object that defines the parameters of the auction for a given +seller. This module enables PAAPI support by allowing bid adaptors to return `AuctionConfig` objects in addition to bids. If a bid adaptor returns an `AuctionConfig` object, Prebid.js will register it with the appropriate GPT ad slot so the bidder can participate as a Component Auction in the overall -FLEDGE auction for that slot. More details on the GPT API can be found [here](https://developers.google.com/publisher-tag/reference#googletag.config.componentauctionconfig). +PAAPI auction for that slot. More details on the GPT API can be found [here](https://developers.google.com/publisher-tag/reference#googletag.config.componentauctionconfig). -Modifying a bid adapter to support FLEDGE is a straightforward process and consists of the following steps: -1. Detecting when a bid request is FLEDGE eligible +Modifying a bid adapter to support PAAPI is a straightforward process and consists of the following steps: +1. Detecting when a bid request is PAAPI eligible 2. Responding with AuctionConfig -FLEDGE eligibility is made available to bid adapters through the `bidderRequest.fledgeEnabled` field. +PAAPI eligibility is made available to bid adapters through the `bidderRequest.paapi.enabled` field. The [`bidderRequest`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#bidderrequest-parameters) object is passed to the [`buildRequests`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#building-the-request) method of an adapter. Bid adapters -who wish to participate should read this flag and pass it to their server. FLEDGE eligibility depends on a number of parameters: +who wish to participate should read this flag and pass it to their server. PAAPI eligibility depends on a number of parameters: 1. Chrome enablement 2. Publisher participatipon in the [Origin Trial](https://developer.chrome.com/docs/privacy-sandbox/unified-origin-trial/#configure) 3. Publisher Prebid.js configuration (detailed above) -When a bid request is FLEDGE enabled, a bid adapter can return a tuple consisting of bids and AuctionConfig objects rather than just a list of bids: +When a bid request is PAAPI enabled, a bid adapter can return a tuple consisting of bids and AuctionConfig objects rather than just a list of bids: ```js function interpretResponse(resp, req) { @@ -138,8 +116,8 @@ An AuctionConfig must be associated with an adunit and auction, and this is acco `validBidRequests` array passed to the `buildRequests` function - see [here](https://docs.prebid.org/dev-docs/bidder-adaptor.html#ad-unit-params-in-the-validbidrequests-array) for more details. This means that the AuctionConfig objects returned from `interpretResponse` must contain a `bidId` field whose value corresponds to the request it should be associated with. This may raise the question: why isn't the AuctionConfig object returned as part of the bid? The -answer is that it's possible to participate in the FLEDGE auction without returning a contextual bid. +answer is that it's possible to participate in the PAAPI auction without returning a contextual bid. An example of this can be seen in the OpenX OpenRTB bid adapter [here](https://github.com/prebid/Prebid.js/blob/master/modules/openxOrtbBidAdapter.js#L327). -Other than the addition of the `bidId` field, the AuctionConfig object should adhere to the requirements set forth in FLEDGE. The details of creating an AuctionConfig object are beyond the scope of this document. +Other than the addition of the `bidId` field, the AuctionConfig object should adhere to the requirements set forth in PAAPI. The details of creating an AuctionConfig object are beyond the scope of this document. diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js index fdc6bcf302f..0304c325c33 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -64,6 +64,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index e2154a077c0..168758763e8 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -560,10 +560,10 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let result; try { result = JSON.parse(response); - const {bids, fledgeAuctionConfigs} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); + const {bids, paapi} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); bids.forEach(onBid); - if (fledgeAuctionConfigs) { - fledgeAuctionConfigs.forEach(onFledge); + if (paapi) { + paapi.forEach(onFledge); } } catch (error) { logError(error); diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 59d79fbcf4b..bb033271b3c 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -232,7 +232,7 @@ const PBS_CONVERTER = ortbConverter({ // override to process each request context.actualBidderRequests.forEach(req => orig(response, ortbResponse, {...context, bidderRequest: req, bidRequests: req.bids})); }, - fledgeAuctionConfigs(orig, response, ortbResponse, context) { + paapiConfigs(orig, response, ortbResponse, context) { const configs = Object.values(context.impContext) .flatMap((impCtx) => (impCtx.paapiConfigs || []).map(cfg => { const bidderReq = impCtx.actualBidderRequests.find(br => br.bidderCode === cfg.bidder); @@ -245,7 +245,7 @@ const PBS_CONVERTER = ortbConverter({ }; })); if (configs.length > 0) { - response.fledgeAuctionConfigs = configs; + response.paapi = configs; } } } @@ -301,7 +301,9 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste const proxyBidderRequest = { ...Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))), - fledgeEnabled: bidderRequests.some(req => req.fledgeEnabled) + paapi: { + enabled: bidderRequests.some(br => br.paapi?.enabled) + } } return PBS_CONVERTER.toORTB({ diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index 9125f6f3911..b4f1b665d91 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, isFn, deepAccess, logInfo } from '../src/utils.js'; +import { isFn, deepAccess, logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -34,14 +34,7 @@ export const spec = { const countryCode = getCountryCodeByTimezone(city); logInfo(`The country code for ${city} is ${countryCode}`); - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; + location = bidderRequest?.refererInfo ?? null; let request = { id: validBidRequests[0].bidderRequestId, @@ -87,8 +80,8 @@ export const spec = { // Show a map centered at latitude / longitude. }) || { utcoffset: new Date().getTimezoneOffset() }, city: city, - 'host': location.host, - 'page': location.pathname, + 'host': location?.domain ?? '', + 'page': location?.page ?? '', 'coppa': config.getConfig('coppa') === true ? 1 : 0 // userId: validBidRequests[0].userId }; diff --git a/modules/pstudioBidAdapter.js b/modules/pstudioBidAdapter.js index 77a11ac58c6..1265d5c546f 100644 --- a/modules/pstudioBidAdapter.js +++ b/modules/pstudioBidAdapter.js @@ -14,8 +14,8 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'pstudio'; const ENDPOINT = 'https://exchange.pstudio.tadex.id/prebid-bid' const TIME_TO_LIVE = 300; -// in case that the publisher limits number of user syncs, thisse syncs will be discarded from the end of the list -// so more improtant syncing calls should be at the start of the list +// in case that the publisher limits number of user syncs, these syncs will be discarded from the end of the list +// so more important syncing calls should be at the start of the list const USER_SYNCS = [ // PARTNER_UID is a partner user id { @@ -40,6 +40,7 @@ const VIDEO_PARAMS = [ 'protocols', 'startdelay', 'placement', + 'plcmt', 'skip', 'skipafter', 'minbitrate', diff --git a/modules/pubCircleBidAdapter.js b/modules/pubCircleBidAdapter.js index 54224fd0403..db435d5fa4f 100644 --- a/modules/pubCircleBidAdapter.js +++ b/modules/pubCircleBidAdapter.js @@ -60,6 +60,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index d92a9352cee..617123746e5 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -155,7 +155,7 @@ function buildVideoParams(videoMediaType, videoParams) { 'maxduration', 'protocols', 'startdelay', - 'placement', + 'plcmt', 'skip', 'skipafter', 'minbitrate', @@ -166,17 +166,6 @@ function buildVideoParams(videoMediaType, videoParams) { 'linearity', ]); - switch (videoMediaType.context) { - case 'instream': - params.placement = 1; - break; - case 'outstream': - params.placement = 2; - break; - default: - break; - } - if (videoMediaType.playerSize) { params.w = videoMediaType.playerSize[0][0]; params.h = videoMediaType.playerSize[0][1]; @@ -301,8 +290,7 @@ function isValidBanner(banner) { function isValidVideo(videoMediaType, videoParams) { const params = buildVideoParams(videoMediaType, videoParams); - return !!(params.placement && - isValidSize([params.w, params.h]) && + return !!(isValidSize([params.w, params.h]) && params.mimes && params.mimes.length && isArrayOfNums(params.protocols) && params.protocols.length); } diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 5b470fdc34a..5add3fb9be1 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -663,7 +663,7 @@ function _createImpressionObject(bid, bidderRequest) { var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; var mediaTypes = ''; var format = []; - var isFledgeEnabled = bidderRequest?.fledgeEnabled; + var isFledgeEnabled = bidderRequest?.paapi?.enabled; impObj = { id: bid.bidId, @@ -1094,7 +1094,6 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { @@ -1419,7 +1418,7 @@ export const spec = { }); return { bids: bidResponses, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } } catch (error) { diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index eca0c971050..639a39d4636 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -63,6 +63,7 @@ const VIDEO_CUSTOM_PARAMS = { 'battr': DATA_TYPES.ARRAY, 'linearity': DATA_TYPES.NUMBER, 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER, 'skip': DATA_TYPES.NUMBER diff --git a/modules/qtBidAdapter.js b/modules/qtBidAdapter.js index e26aad8f9ec..7616b990ff8 100644 --- a/modules/qtBidAdapter.js +++ b/modules/qtBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, logError, deepAccess } from '../src/utils.js'; +import { logMessage, deepAccess } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -91,7 +91,6 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (err) { - logError(err); return 0; } } diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 7449c4af090..ea907f0429c 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -49,7 +49,6 @@ function makeVideoImp(bid) { maxbitrate: video.maxbitrate, playbackmethod: video.playbackmethod, delivery: video.delivery, - placement: video.placement, api: video.api, w: video.w, h: video.h diff --git a/modules/ringieraxelspringerBidAdapter.js b/modules/ringieraxelspringerBidAdapter.js index 4fcf07088e0..1fd6e327b9b 100644 --- a/modules/ringieraxelspringerBidAdapter.js +++ b/modules/ringieraxelspringerBidAdapter.js @@ -364,7 +364,7 @@ export const spec = { const slotsQuery = getSlots(bidRequests); const contextQuery = getContextParams(bidRequests, bidderRequest); const gdprQuery = getGdprParams(bidderRequest); - const fledgeEligible = Boolean(bidderRequest && bidderRequest.fledgeEnabled); + const fledgeEligible = Boolean(bidderRequest?.paapi?.enabled); const network = bidRequests[0].params.network; const bidIds = bidRequests.map((bid) => ({ slot: bid.params.slot, @@ -392,7 +392,7 @@ export const spec = { if (fledgeAuctionConfigs) { // Return a tuple of bids and auctionConfigs. It is possible that bids could be null. - return {bids, fledgeAuctionConfigs}; + return {bids, paapi: fledgeAuctionConfigs}; } else { return bids; } diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 1cd97696770..7e2a7da3b61 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -114,7 +114,7 @@ export const spec = { let computedEndpointUrl = ENDPOINT_URL; - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { const fledgeConfig = config.getConfig('fledgeConfig') || { seller: FLEDGE_SELLER_URL, decisionLogicUrl: FLEDGE_DECISION_LOGIC_URL, @@ -209,7 +209,7 @@ export const spec = { logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } return bids; @@ -250,7 +250,7 @@ function mapImpression(slot, bidderRequest) { imp.bidfloor = bidfloor; } - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { imp.ext = imp.ext || {}; imp.ext.ae = slot?.ortb2Imp?.ext?.ae } else { diff --git a/modules/rtbhouseBidAdapter.md b/modules/rtbhouseBidAdapter.md index 338ba6b4df4..7fcae1299b2 100644 --- a/modules/rtbhouseBidAdapter.md +++ b/modules/rtbhouseBidAdapter.md @@ -69,7 +69,7 @@ Please reach out to pmp@rtbhouse.com to receive your own # Protected Audience API (FLEDGE) support There’s an option to receive demand for Protected Audience API (FLEDGE/PAAPI) ads using RTB House bid adapter. -Prebid’s [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) +Prebid’s [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module and Google Ad Manager is currently required. The following steps should be taken to setup Protected Audience for RTB House: @@ -77,15 +77,15 @@ The following steps should be taken to setup Protected Audience for RTB House: 1. Reach out to your RTB House representative for setup coordination. 2. Build and enable FLEDGE module as described in -[fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) +[paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module documentation. a. Make sure to enable RTB House bidder to participate in FLEDGE. If there are any other bidders to be allowed for that, add them to the **bidders** array: ```javascript - pbjs.setBidderConfig({ - bidders: ["rtbhouse"], - config: { - fledgeEnabled: true + pbjs.setConfig({ + paapi: { + bidders: ["rtbhouse"], + enabled: true } }); ``` @@ -93,15 +93,15 @@ module documentation. b. If you as a publisher have your own [decisionLogicUrl](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#21-initiating-an-on-device-auction) you may utilize it by setting up a dedicated `fledgeConfig` object: ```javascript - pbjs.setBidderConfig({ - bidders: ["rtbhouse"], - config: { - fledgeEnabled: true, - fledgeConfig: { - seller: 'https://seller.domain', - decisionLogicUrl: 'https://seller.domain/decisionLogicFile.js', - sellerTimeout: 100 - } + pbjs.setConfig({ + paapi: { + bidders: ["rtbhouse"], + enabled: true + }, + fledgeConfig: { + seller: 'https://seller.domain', + decisionLogicUrl: 'https://seller.domain/decisionLogicFile.js', + sellerTimeout: 100 } }); ``` diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 9e47807bdc0..64bcdf78399 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -736,7 +736,7 @@ export const spec = { }); if (fledgeAuctionConfigs) { - return { bids, fledgeAuctionConfigs }; + return { bids, paapi: fledgeAuctionConfigs }; } else { return bids; } diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js index 8e9628c8810..4c78b62d710 100644 --- a/modules/s2sTesting.js +++ b/modules/s2sTesting.js @@ -44,7 +44,7 @@ s2sTesting.getSourceBidderMap = function(adUnits = [], allS2SBidders = []) { /** * @function calculateBidSources determines the source for each s2s bidder based on bidderControl weightings. these can be overridden at the adUnit level - * @param s2sConfigs server-to-server configuration + * @param s2sConfig server-to-server configuration */ s2sTesting.calculateBidSources = function(s2sConfig = {}) { // calculate bid source (server/client) for each s2s bidder diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 590fddca079..92d36b0b699 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -108,7 +108,7 @@ export const sharethroughAdapterSpec = { const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); - if (bidderRequest.fledgeEnabled && bidReq.mediaTypes.banner) { + if (bidderRequest.paapi?.enabled && bidReq.mediaTypes.banner) { mergeDeep(impression, { ext: { ae: 1 } }); // ae = auction environment; if this is 1, ad server knows we have a fledge auction } @@ -242,7 +242,7 @@ export const sharethroughAdapterSpec = { if (fledgeAuctionEnabled) { return { bids: bidsFromExchange, - fledgeAuctionConfigs: body.ext?.auctionConfigs || {}, + paapi: body.ext?.auctionConfigs || {}, }; } else { return bidsFromExchange; diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 47fca317de2..993c069ded0 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -336,7 +336,10 @@ function generateBidParameters(bid, bidderRequest) { if (placement) { bidObject.placement = placement; } - + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); + if (plcmt) { + bidObject.plcmt = plcmt; + } const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); if (minDuration) { bidObject.minDuration = minDuration; diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js index 5403f3bd88c..1d5662f88eb 100644 --- a/modules/silverpushBidAdapter.js +++ b/modules/silverpushBidAdapter.js @@ -128,7 +128,7 @@ export const CONVERTER = ortbConverter({ }); return { bids: response.bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { return response.bids diff --git a/modules/sizeMapping.js b/modules/sizeMapping.js index fcd0b0963f2..eab85aa3d93 100644 --- a/modules/sizeMapping.js +++ b/modules/sizeMapping.js @@ -35,7 +35,7 @@ config.getConfig('sizeConfig', config => setSizeConfig(config.sizeConfig)); * Returns object describing the status of labels on the adUnit or bidder along with labels passed into requestBids * @param bidOrAdUnit the bidder or adUnit to get label info on * @param activeLabels the labels passed to requestBids - * @returns {LabelDescriptor} + * @returns {object} */ export function getLabels(bidOrAdUnit, activeLabels) { if (bidOrAdUnit.labelAll) { @@ -66,14 +66,18 @@ if (FEATURES.VIDEO) { } /** - * Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match - * @param {Array} labels Labels specified on adUnit or bidder - * @param {boolean} labelAll if true, all labels must match to be enabled - * @param {Array} activeLabels Labels passed in through requestBids - * @param {object} mediaTypes A mediaTypes object describing the various media types (banner, video, native) - * @param {Array>} sizes Sizes specified on adUnit (deprecated) - * @param {Array} configs - * @returns {{labels: Array, sizes: Array>}} + * Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match. + * + * @param {Object} options - The options object. + * @param {Array} [options.labels=[]] - Labels specified on adUnit or bidder. + * @param {boolean} [options.labelAll=false] - If true, all labels must match to be enabled. + * @param {Array} [options.activeLabels=[]] - Labels passed in through requestBids. + * @param {Object} mediaTypes - A mediaTypes object describing the various media types (banner, video, native). + * @param {Array} configs - An array of SizeConfig objects. + * @returns {Object} - An object containing the active status, media types, and filter results. + * @returns {boolean} return.active - Whether the media types are active. + * @returns {Object} return.mediaTypes - The media types object. + * @returns {Object} [return.filterResults] - The filter results before and after applying size filtering. */ export function resolveStatus({labels = [], labelAll = false, activeLabels = []} = {}, mediaTypes, configs = sizeConfig) { let maps = evaluateSizeConfig(configs); diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index ea2b62c95c9..b5970fbb9ee 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -48,7 +48,7 @@ function getPlacementReqData(bid) { const { partnerName, seat, token, iabCat, minBidfloor, pos } = params; const bidfloor = getBidFloor(bid); - const placement = { + const plcmt = { partnerName: String(partnerName || bidder).toLowerCase(), seat, token, @@ -61,31 +61,32 @@ function getPlacementReqData(bid) { }; if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; + plcmt.adFormat = BANNER; + plcmt.sizes = mediaTypes[BANNER].sizes; } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; + plcmt.adFormat = VIDEO; + plcmt.playerSize = mediaTypes[VIDEO].playerSize; + plcmt.minduration = mediaTypes[VIDEO].minduration; + plcmt.maxduration = mediaTypes[VIDEO].maxduration; + plcmt.mimes = mediaTypes[VIDEO].mimes; + plcmt.protocols = mediaTypes[VIDEO].protocols; + plcmt.startdelay = mediaTypes[VIDEO].startdelay; + plcmt.placement = mediaTypes[VIDEO].plcmt; + plcmt.plcmt = mediaTypes[VIDEO].plcmt; // https://github.com/prebid/Prebid.js/issues/10452 + plcmt.skip = mediaTypes[VIDEO].skip; + plcmt.skipafter = mediaTypes[VIDEO].skipafter; + plcmt.minbitrate = mediaTypes[VIDEO].minbitrate; + plcmt.maxbitrate = mediaTypes[VIDEO].maxbitrate; + plcmt.delivery = mediaTypes[VIDEO].delivery; + plcmt.playbackmethod = mediaTypes[VIDEO].playbackmethod; + plcmt.api = mediaTypes[VIDEO].api; + plcmt.linearity = mediaTypes[VIDEO].linearity; } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; + plcmt.native = mediaTypes[NATIVE]; + plcmt.adFormat = NATIVE; } - return placement; + return plcmt; } function getBidFloor(bid) { diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 8394814365c..483a7a86d73 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -119,12 +119,6 @@ export const spec = { const pos = getBidIdParameter('pos', bid.params) || 1; const api = getBidIdParameter('api', bid.params) || [2]; const protocols = getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6]; - var contextcustom = deepAccess(bid, 'mediaTypes.video.context'); - var placement = 1; - - if (contextcustom === 'outstream') { - placement = 3; - } let smartxReq = [{ id: bid.bidId, @@ -144,7 +138,6 @@ export const spec = { maxbitrate: maxbitrate, delivery: delivery, pos: pos, - placement: placement, api: api, ext: ext }, diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 6920983e50d..9098bb8f862 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,4 +1,3 @@ -import { logMessage } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -74,23 +73,15 @@ export const spec = { let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - + location = bidderRequest?.refererInfo ?? null; let placements = []; let request = { 'deviceWidth': winTop.screen.width, 'deviceHeight': winTop.screen.height, 'language': (navigator && navigator.language) ? navigator.language : '', 'secure': 1, - 'host': location.host, - 'page': location.pathname, + 'host': location?.domain ?? '', + 'page': location?.page ?? '', 'coppa': config.getConfig('coppa') === true ? 1 : 0, 'placements': placements, 'eeid': validBidRequests[0]?.userIdAsEids, diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index b6563cac4c5..53f6fb2f40d 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -28,6 +28,7 @@ const ORTB_VIDEO_PARAMS = { 'h': (value) => isInteger(value), 'startdelay': (value) => isInteger(value), 'placement': (value) => isInteger(value) && value >= 1 && value <= 5, + 'plcmt': (value) => isInteger(value) && value >= 1 && value <= 4, 'linearity': (value) => [1, 2].indexOf(value) !== -1, 'skip': (value) => [0, 1].indexOf(value) !== -1, 'skipmin': (value) => isInteger(value), @@ -139,7 +140,7 @@ export const spec = { } const auctionEnvironment = bid?.ortb2Imp?.ext?.ae - if (bidderRequest.fledgeEnabled && isInteger(auctionEnvironment)) { + if (bidderRequest.paapi?.enabled && isInteger(auctionEnvironment)) { imp.ext = imp.ext || {} imp.ext.ae = auctionEnvironment } else { @@ -288,7 +289,7 @@ export const spec = { }) return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } return bids diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index e67941ed3a1..35f40953b1f 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -52,7 +52,6 @@ export const spec = { const basePayload = { id: generateUUID(), ref: refererInfo.ref, - ssl: isSecureWindow(), mpa: isMainPageAccessible(), timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart), url: refererInfo.page, @@ -147,8 +146,6 @@ export const spec = { } }; -const isSecureWindow = () => getWindowSelf().location.protocol === 'https:'; - const isMainPageAccessible = () => { try { return !!getWindowTop().location.href; diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index ab5d5fef139..5fa7f2c8b8e 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -206,7 +206,7 @@ export const spec = { if (fledgeAuctionConfigs.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } return bids; diff --git a/modules/topLevelPaapi.js b/modules/topLevelPaapi.js new file mode 100644 index 00000000000..040c0125b3a --- /dev/null +++ b/modules/topLevelPaapi.js @@ -0,0 +1,215 @@ +import {submodule} from '../src/hook.js'; +import {config} from '../src/config.js'; +import {logError, logInfo, logWarn, mergeDeep} from '../src/utils.js'; +import {auctionStore} from '../libraries/weakStore/weakStore.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {emit} from '../src/events.js'; +import {BID_STATUS, EVENTS} from '../src/constants.js'; +import {GreedyPromise} from '../src/utils/promise.js'; +import {getBidToRender, getRenderingData, markWinningBid} from '../src/adRendering.js'; + +let getPAAPIConfig, expandFilters, moduleConfig; + +const paapiBids = auctionStore(); +const MODULE_NAME = 'topLevelPaapi'; + +config.getConfig('paapi', (cfg) => { + moduleConfig = cfg.paapi?.topLevelSeller; + if (moduleConfig) { + getBidToRender.before(renderPaapiHook); + getBidToRender.after(renderOverrideHook); + getRenderingData.before(getRenderingDataHook); + markWinningBid.before(markWinningBidHook); + } else { + getBidToRender.getHooks({hook: renderPaapiHook}).remove(); + getBidToRender.getHooks({hook: renderOverrideHook}).remove(); + getRenderingData.getHooks({hook: getRenderingDataHook}).remove(); + markWinningBid.getHooks({hook: markWinningBidHook}).remove(); + } +}); + +function isPaapiBid(bid) { + return bid?.source === 'paapi'; +} + +function bidIfRenderable(bid) { + if (bid && !bid.urn) { + logWarn(MODULE_NAME, 'rendering in fenced frames is not supported. Consider using resolveToConfig: false', bid); + return; + } + return bid; +} + +const forRenderStack = []; + +function renderPaapiHook(next, adId, forRender = true, override = GreedyPromise.resolve()) { + forRenderStack.push(forRender); + const ids = parsePaapiAdId(adId); + if (ids) { + override = override.then((bid) => { + if (bid) return bid; + const [auctionId, adUnitCode] = ids; + return paapiBids(auctionId)?.[adUnitCode]?.then(bid => { + if (!bid) { + logWarn(MODULE_NAME, `No PAAPI bid found for auctionId: "${auctionId}", adUnit: "${adUnitCode}"`); + } + return bidIfRenderable(bid); + }); + }); + } + next(adId, forRender, override); +} + +function renderOverrideHook(next, bidPm) { + const forRender = forRenderStack.pop(); + if (moduleConfig?.overrideWinner) { + bidPm = bidPm.then((bid) => { + if (isPaapiBid(bid) || bid?.status === BID_STATUS.RENDERED) return bid; + return getPAAPIBids({adUnitCode: bid.adUnitCode}).then(res => { + let paapiBid = bidIfRenderable(res[bid.adUnitCode]); + if (paapiBid) { + if (!forRender) return paapiBid; + if (forRender && paapiBid.status !== BID_STATUS.RENDERED) { + paapiBid.overriddenAdId = bid.adId; + logInfo(MODULE_NAME, 'overriding contextual bid with PAAPI bid', bid, paapiBid) + return paapiBid; + } + } + return bid; + }); + }); + } + next(bidPm); +} + +export function getRenderingDataHook(next, bid, options) { + if (isPaapiBid(bid)) { + next.bail({ + width: bid.width, + height: bid.height, + adUrl: bid.urn + }); + } else { + next(bid, options); + } +} + +export function markWinningBidHook(next, bid) { + if (isPaapiBid(bid)) { + bid.status = BID_STATUS.RENDERED; + emit(EVENTS.BID_WON, bid); + next.bail(); + } else { + next(bid); + } +} + +function getBaseAuctionConfig() { + if (moduleConfig?.auctionConfig) { + return Object.assign({ + resolveToConfig: false + }, moduleConfig.auctionConfig); + } +} + +function onAuctionConfig(auctionId, auctionConfigs) { + const base = getBaseAuctionConfig(); + if (base) { + Object.entries(auctionConfigs).forEach(([adUnitCode, auctionConfig]) => { + mergeDeep(auctionConfig, base); + if (moduleConfig.autorun ?? true) { + getPAAPIBids({adUnitCode, auctionId}); + } + }); + } +} + +export function parsePaapiSize(size) { + /* From https://github.com/WICG/turtledove/blob/main/FLEDGE.md#12-interest-group-attributes: + * Each size has the format {width: widthVal, height: heightVal}, + * where the values can have either pixel units (e.g. 100 or '100px') or screen dimension coordinates (e.g. 100sw or 100sh). + */ + if (typeof size === 'number') return size; + if (typeof size === 'string') { + const px = /^(\d+)(px)?$/.exec(size)?.[1]; + if (px) { + return parseInt(px, 10); + } + } + return null; +} + +export function getPaapiAdId(auctionId, adUnitCode) { + return `paapi:/${auctionId.replace(/:/g, '::')}/:/${adUnitCode.replace(/:/g, '::')}`; +} + +export function parsePaapiAdId(adId) { + const match = /^paapi:\/(.*)\/:\/(.*)$/.exec(adId); + if (match) { + return [match[1], match[2]].map(s => s.replace(/::/g, ':')); + } +} + +/** + * Returns the PAAPI runAdAuction result for the given filters, as a map from ad unit code to auction result + * (an object with `width`, `height`, and one of `urn` or `frameConfig`). + * + * @param filters + * @param raa + * @return {Promise<{[p: string]: any}>} + */ +export function getPAAPIBids(filters, raa = (...args) => navigator.runAdAuction(...args)) { + return Promise.all( + Object.entries(expandFilters(filters)) + .map(([adUnitCode, auctionId]) => { + const bids = paapiBids(auctionId); + if (bids && !bids.hasOwnProperty(adUnitCode)) { + const auctionConfig = getPAAPIConfig({adUnitCode, auctionId})[adUnitCode]; + if (auctionConfig) { + emit(EVENTS.RUN_PAAPI_AUCTION, { + auctionId, + adUnitCode, + auctionConfig + }); + bids[adUnitCode] = new Promise((resolve, reject) => raa(auctionConfig).then(resolve, reject)) + .then(result => { + if (result) { + const bid = { + source: 'paapi', + adId: getPaapiAdId(auctionId, adUnitCode), + width: parsePaapiSize(auctionConfig.requestedSize?.width), + height: parsePaapiSize(auctionConfig.requestedSize?.height), + adUnitCode, + auctionId, + [typeof result === 'string' ? 'urn' : 'frameConfig']: result, + auctionConfig, + }; + emit(EVENTS.PAAPI_BID, bid); + return bid; + } else { + emit(EVENTS.PAAPI_NO_BID, {auctionId, adUnitCode, auctionConfig}); + return null; + } + }).catch(error => { + logError(MODULE_NAME, `error (auction "${auctionId}", adUnit "${adUnitCode}"):`, error); + emit(EVENTS.PAAPI_ERROR, {auctionId, adUnitCode, error, auctionConfig}); + return null; + }); + } + } + return bids?.[adUnitCode]?.then(res => [adUnitCode, res]); + }).filter(e => e) + ).then(result => Object.fromEntries(result)); +} + +getGlobal().getPAAPIBids = (filters) => getPAAPIBids(filters); + +export const topLevelPAAPI = { + name: MODULE_NAME, + init(params) { + getPAAPIConfig = params.getPAAPIConfig; + expandFilters = params.expandFilters; + }, + onAuctionConfig +}; +submodule('paapi', topLevelPAAPI); diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index 7b9dfe2a89a..d99696152ba 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -169,7 +169,8 @@ export function receiveMessage(evt) { /** Function to store Topics data received from iframe in storage(name: "prebid:topics") - * @param {Topics} topics + * @param {string} bidder + * @param {object} topics */ export function storeInLocalStorage(bidder, topics) { const storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 056ab2b9d19..a665de6140f 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -58,8 +58,8 @@ export const tripleliftAdapterSpec = { tlCall = tryAppendQueryString(tlCall, 'us_privacy', bidderRequest.uspConsent); } - if (bidderRequest && bidderRequest.fledgeEnabled) { - tlCall = tryAppendQueryString(tlCall, 'fledge', bidderRequest.fledgeEnabled); + if (bidderRequest?.paapi?.enabled) { + tlCall = tryAppendQueryString(tlCall, 'fledge', bidderRequest.paapi.enabled); } if (config.getConfig('coppa') === true) { @@ -96,7 +96,7 @@ export const tripleliftAdapterSpec = { logMessage('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { bids, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs }; } else { return bids; diff --git a/modules/twistDigitalBidAdapter.js b/modules/twistDigitalBidAdapter.js index f509e68f9a2..bee32a19870 100644 --- a/modules/twistDigitalBidAdapter.js +++ b/modules/twistDigitalBidAdapter.js @@ -155,7 +155,7 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout data.gppSid = bidderRequest.ortb2.regs.gpp_sid; } - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); if (fledge) { data.fledge = fledge; diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 061f0f981e5..1ce9b0f5a09 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -69,7 +69,7 @@ export const uid2IdSubmodule = { /** * performs action to obtain id and return a value. * @function - * @param {SubmoduleConfig} [configparams] + * @param {SubmoduleConfig} config * @param {ConsentData|undefined} consentData * @returns {uid2Id} */ diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index b825003f36f..39d77c81b57 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -226,7 +226,7 @@ export const adapter = { 'options': { 'contentType': 'application/json' }, - 'protectedAudienceEnabled': bidderRequest.fledgeEnabled + 'protectedAudienceEnabled': bidderRequest.paapi?.enabled }, validBidRequests, bidderRequest); }, @@ -261,7 +261,7 @@ export const adapter = { return { bids, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs }; } }; diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 0c2b03164be..fd53b684ec0 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -163,7 +163,7 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout data.gppSid = bidderRequest.ortb2.regs.gpp_sid; } - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); if (fledge) { data.fledge = fledge; diff --git a/modules/videoModule/coreVideo.js b/modules/videoModule/coreVideo.js index fc54d0d0b98..4ac5f090334 100644 --- a/modules/videoModule/coreVideo.js +++ b/modules/videoModule/coreVideo.js @@ -104,7 +104,6 @@ import { ParentModule, SubmoduleBuilder } from '../../libraries/video/shared/par /** * @summary Maps a Video Provider factory to the video player's vendor code. - * @type {vendorSubmoduleDirectory} */ const videoVendorDirectory = {}; diff --git a/modules/videoModule/gamAdServerSubmodule.js b/modules/videoModule/gamAdServerSubmodule.js index 87db71ae38b..728ee4d060e 100644 --- a/modules/videoModule/gamAdServerSubmodule.js +++ b/modules/videoModule/gamAdServerSubmodule.js @@ -4,7 +4,6 @@ import { getGlobal } from '../../src/prebidGlobal.js'; /** * @constructor * @param {Object} dfpModule_ - the DFP ad server module - * @returns {AdServerProvider} */ function GamAdServerProvider(dfpModule_) { const dfp = dfpModule_; diff --git a/modules/videobyteBidAdapter.js b/modules/videobyteBidAdapter.js index 8cedf9ac16a..b62474d0c25 100644 --- a/modules/videobyteBidAdapter.js +++ b/modules/videobyteBidAdapter.js @@ -19,6 +19,7 @@ const VIDEO_ORTB_PARAMS = [ 'minduration', 'maxduration', 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -191,16 +192,6 @@ function buildRequestData(bidRequest, bidderRequest) { } }); - // Placement Inference Rules: - // - If no placement is defined then default to 1 (In Stream) - video.placement = video.placement || 2; - - // - If product is instream (for instream context) then override placement to 1 - if (params.context === 'instream') { - video.startdelay = video.startdelay || 0; - video.placement = 1; - } - // bid floor const bidFloorRequest = { currency: bidRequest.params.cur || 'USD', diff --git a/modules/videojsVideoProvider.js b/modules/videojsVideoProvider.js index 7764e8af995..efe518ea495 100644 --- a/modules/videojsVideoProvider.js +++ b/modules/videojsVideoProvider.js @@ -6,7 +6,7 @@ import { } from '../libraries/video/constants/events.js'; // missing events: , AD_BREAK_START, , AD_BREAK_END, VIEWABLE, BUFFER, CAST, PLAYLIST_COMPLETE, RENDITION_UPDATE, PLAY_ATTEMPT_FAILED, AUTOSTART_BLOCKED import { - PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION, PLAYBACK_END + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLCMT, VPAID_MIME_TYPE, AD_POSITION, PLAYBACK_END } from '../libraries/video/constants/ortb.js'; import { VIDEO_JS_VENDOR } from '../libraries/video/constants/vendorCodes.js'; import { submodule } from '../src/hook.js'; @@ -146,8 +146,9 @@ export function VideojsProvider(providerConfig, vjs_, adState_, timeState_, call // ~ Sort of resolved check if the player has a source to tell if the placement is instream // Still cannot reliably check what type of placement the player is if its outstream // i.e. we can't tell if its interstitial, in article, etc. + // update: cannot infer instream ever, always need declarations if (player.src()) { - video.placement = PLACEMENT.INSTREAM; + video.plcmt = PLCMT.ACCOMPANYING_CONTENT; } // Placement according to IQG Guidelines 4.2.8 diff --git a/modules/viouslyBidAdapter.js b/modules/viouslyBidAdapter.js index 5ccca7590dd..e474a8de93c 100644 --- a/modules/viouslyBidAdapter.js +++ b/modules/viouslyBidAdapter.js @@ -2,7 +2,7 @@ import { deepAccess, logError, parseUrl, parseSizesInput, triggerPixel } from '. import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; // eslint-disable-line prebid/validate-imports +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'viously'; const GVLID = 1028; diff --git a/modules/visiblemeasuresBidAdapter.js b/modules/visiblemeasuresBidAdapter.js index e77477c812b..fa54f27e4c0 100644 --- a/modules/visiblemeasuresBidAdapter.js +++ b/modules/visiblemeasuresBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index beffcf5da95..c7f415e4dac 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -59,11 +59,16 @@ export const spec = { config.getConfig('currency.adServerCurrency') || DEFAULT_CUR; + let request; let reqId; let payloadSchain; let payloadUserId; let payloadUserEids; let timeout; + let payloadDevice; + let payloadSite; + let payloadRegs; + let payloadContent; if (currencyWhiteList.indexOf(currency) === -1) { logError(LOG_ERROR_MESS.notAllowedCurrency + currency); @@ -80,9 +85,7 @@ export const spec = { imp.push(impObj); bidsMap[bid.bidId] = bid; } - const { params: { uid }, schain, userId, userIdAsEids } = bid; - if (!payloadSchain && schain) { payloadSchain = schain; } @@ -93,6 +96,7 @@ export const spec = { if (!payloadUserId && userId) { payloadUserId = userId; } + auids.push(uid); }); @@ -100,10 +104,7 @@ export const spec = { if (bidderRequest) { timeout = bidderRequest.timeout; - if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { - // TODO: is 'page' the right value here? - payload.u = bidderRequest.refererInfo.page; - } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; @@ -112,6 +113,21 @@ export const spec = { (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; } + + const { ortb2 } = bidderRequest; + const { device, site, regs, content } = ortb2; + if (device) { + payloadDevice = device; + } + if (site) { + payloadSite = site; + } + if (regs) { + payloadRegs = regs; + } + if (content) { + payloadContent = content; + } } const tmax = timeout; @@ -131,21 +147,25 @@ export const spec = { ...(vads && { vads }) } }; - const regs = ('gdpr_applies' in payload) && { - ext: { - gdpr: payload.gdpr_applies - } - }; + if (payloadRegs === undefined) { + payloadRegs = ('gdpr_applies' in payload) && { + ext: { + gdpr: payload.gdpr_applies + } + }; + } - const request = { + request = { id: reqId, imp, tmax, cur: [currency], source, - site: { page: payload.u }, ...(Object.keys(user.ext).length && { user }), - ...(regs && { regs }) + ...(payloadRegs && {regs: payloadRegs}), + ...(payloadDevice && { device: payloadDevice }), + ...(payloadSite && { site: payloadSite }), + ...(payloadContent && { content: payloadContent }), }; return { diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index 92b7fc26e4c..c4ca5d299bc 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -147,7 +147,6 @@ const createVideoObject = (videoMediaTypes, videoParams) => { maxduration: getBidIdParameter('maxduration', videoParams) || 500, protocols: getBidIdParameter('protocols', videoParams) || [2, 3, 5, 6], startdelay: getBidIdParameter('startdelay', videoParams) || 0, - placement: getBidIdParameter('placement', videoParams) || videoMediaTypes.context === 'outstream' ? 3 : 1, skip: getBidIdParameter('skip', videoParams) || 1, skipafter: getBidIdParameter('skipafter', videoParams) || 0, minbitrate: getBidIdParameter('minbitrate', videoParams) || 0, diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 2ba119b4d35..31bcffcd7b1 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -42,6 +42,9 @@ function adRenderSucceededHandler(args) { adomain: args.bid?.adserverTargeting?.hb_adomain, timeToRespond: args.bid?.timeToRespond, cpm: args.bid?.cpm + }, + device: { + ua: navigator.userAgent } } sendEvent(EVENTS.AD_RENDER_SUCCEEDED, event); @@ -59,7 +62,8 @@ function auctionEndHandler(args) { auctionId: b?.auctionId, bidder: b?.bidder, mediaType: b?.mediaTypes?.video ? 'VIDEO' : (b?.mediaTypes?.banner ? 'BANNER' : undefined), - size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s) + size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), + device: b?.ortb2?.device })) })), bidsReceived: args.bidsReceived?.map(br => ({ @@ -80,6 +84,8 @@ function auctionEndHandler(args) { function bidTimeoutHandler(args) { const event = { zetaParams: zetaParams, + domain: args.find(t => t?.ortb2?.site?.domain), + page: args.find(t => t?.ortb2?.site?.page), timeouts: args.map(t => ({ bidId: t?.bidId, auctionId: t?.auctionId, diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 918d03861ae..a273927e1ec 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -147,6 +147,18 @@ export const spec = { payload.device.w = screen.width; payload.device.h = screen.height; + if (bidderRequest.ortb2?.user?.geo && bidderRequest.ortb2?.device?.geo) { + payload.device.geo = { ...payload.device.geo, ...bidderRequest.ortb2?.device.geo }; + payload.user.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.user.geo }; + } else { + if (bidderRequest.ortb2?.user?.geo) { + payload.user.geo = payload.device.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.user.geo }; + } + if (bidderRequest.ortb2?.device?.geo) { + payload.user.geo = payload.device.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.device.geo }; + } + } + if (bidderRequest?.ortb2?.device?.sua) { payload.device.sua = bidderRequest.ortb2.device.sua; } diff --git a/package.json b/package.json index cf62e2e5519..1c660505e2c 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,6 @@ "@babel/runtime": "^7.18.9", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", - "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^4.2.0", "dlv": "1.1.3", "dset": "3.1.2", diff --git a/src/activities/params.js b/src/activities/params.js index 036a6657cf8..859f5d5beed 100644 --- a/src/activities/params.js +++ b/src/activities/params.js @@ -40,8 +40,10 @@ export const ACTIVITY_PARAM_SYNC_TYPE = 'syncType' export const ACTIVITY_PARAM_SYNC_URL = 'syncUrl'; /** * @private - * configuration options for analytics adapter - the argument passed to `enableAnalytics`. - * relevant for: reportAnalytics + * Configuration options for analytics adapter - the argument passed to `enableAnalytics`. + * Relevant for: reportAnalytics. + * @constant + * @type {string} */ export const ACTIVITY_PARAM_ANL_CONFIG = '_config'; diff --git a/src/activities/rules.js b/src/activities/rules.js index f84f1080843..7b4f4634f07 100644 --- a/src/activities/rules.js +++ b/src/activities/rules.js @@ -40,19 +40,19 @@ export function ruleRegistry(logger = prefixLog('Activity control:')) { /** * Register an activity control rule. * - * @param {string} activity activity name - set is defined in `activities.js` - * @param {string} ruleName a name for this rule; used for logging. - * @param {function({}): {allow: boolean, reason?: string}} rule definition function. Takes in activity + * @param {string} activity - Activity name, as defined in `activities.js`. + * @param {string} ruleName - A name for this rule, used for logging. + * @param {function(Object): {allow: boolean, reason?: string}} rule - Rule definition function. Takes in activity * parameters as a single map; MAY return an object {allow, reason}, where allow is true/false, * and reason is an optional message used for logging. * - * {allow: true} will allow this activity AS LONG AS no other rules with same or higher priority return {allow: false}; + * {allow: true} will allow this activity AS LONG AS no other rules with the same or higher priority return {allow: false}; * {allow: false} will deny this activity AS LONG AS no other rules with higher priority return {allow: true}; - * returning null/undefined has no effect - the decision is left to other rules. + * Returning null/undefined has no effect - the decision is left to other rules. * If no rule returns an allow value, the default is to allow the activity. * - * @param {number} priority rule priority; lower number means higher priority - * @returns {function(void): void} a function that unregisters the rule when called. + * @param {number} [priority=10] - Rule priority; lower number means higher priority. + * @returns {function(): void} - A function that unregisters the rule when called. */ function registerActivityControl(activity, ruleName, rule, priority = 10) { const rules = getRules(activity); diff --git a/src/adRendering.js b/src/adRendering.js index 7d306adc9cc..33f7fe9252c 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -8,10 +8,22 @@ import {auctionManager} from './auctionManager.js'; import {getCreativeRenderer} from './creativeRenderers.js'; import {hook} from './hook.js'; import {fireNativeTrackers} from './native.js'; +import {GreedyPromise} from './utils/promise.js'; const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON } = EVENTS; const { EXCEPTION } = AD_RENDER_FAILED_REASON; +export const getBidToRender = hook('sync', function (adId, forRender = true, override = GreedyPromise.resolve()) { + return override + .then(bid => bid ?? auctionManager.findBidByAdId(adId)) + .catch(() => {}) +}) + +export const markWinningBid = hook('sync', function (bid) { + events.emit(BID_WON, bid); + auctionManager.addWinningBid(bid); +}) + /** * Emit the AD_RENDER_FAILED event. * @@ -168,8 +180,7 @@ export function handleRender({renderFn, resizeFn, adId, options, bidResponse, do bid: bidResponse }); } - auctionManager.addWinningBid(bidResponse); - events.emit(BID_WON, bidResponse); + markWinningBid(bidResponse); } export function renderAdDirect(doc, adId, options) { @@ -211,12 +222,13 @@ export function renderAdDirect(doc, adId, options) { if (!adId || !doc) { fail(AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`); } else { - bid = auctionManager.findBidByAdId(adId); - if ((doc === document && !inIframe())) { fail(AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, `renderAd was prevented from writing to the main document.`); } else { - handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid, doc}); + getBidToRender(adId).then(bidResponse => { + bid = bidResponse; + handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse, doc}); + }); } } } catch (e) { diff --git a/src/adapterManager.js b/src/adapterManager.js index 8228001892d..2d4bd7a5f6b 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -230,9 +230,11 @@ export function getS2SBidderSet(s2sConfigs) { } /** - * @returns {{[PARTITIONS.CLIENT]: Array, [PARTITIONS.SERVER]: Array}} - * All the bidder codes in the given `adUnits`, divided in two arrays - - * those that should be routed to client, and server adapters (according to the configuration in `s2sConfigs`). + * @param {Array} adUnits - The ad units to be processed. + * @param {Object} s2sConfigs - The server-to-server configurations. + * @returns {Object} - An object containing arrays of bidder codes for client and server. + * @returns {Object} return.client - Array of bidder codes that should be routed to client adapters. + * @returns {Object} return.server - Array of bidder codes that should be routed to server adapters. */ export function _partitionBidders (adUnits, s2sConfigs, {getS2SBidders = getS2SBidderSet} = {}) { const serverBidders = getS2SBidders(s2sConfigs); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index dae0902d4cc..ac26883ae99 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -361,18 +361,7 @@ export function newBidder(spec) { } } -// Transition from 'fledge' to 'paapi' -// TODO: remove this in prebid 9 -const PAAPI_PROPS = ['paapi', 'fledgeAuctionConfigs']; -const RESPONSE_PROPS = ['bids'].concat(PAAPI_PROPS); - -function getPaapiConfigs(adapterResponse) { - const [paapi, fledge] = PAAPI_PROPS.map(prop => adapterResponse[prop]); - if (paapi != null && fledge != null) { - throw new Error(`Adapter response should use ${PAAPI_PROPS[0]} over ${PAAPI_PROPS[1]}, not both`); - } - return paapi ?? fledge; -} +const RESPONSE_PROPS = ['bids', 'paapi'] /** * Run a set of bid requests - that entails converting them to HTTP requests, sending @@ -443,7 +432,7 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe let bids, paapiConfigs; if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) { bids = response.bids; - paapiConfigs = getPaapiConfigs(response); + paapiConfigs = response.paapi; } else { bids = response; } diff --git a/src/ajax.js b/src/ajax.js index ef4c2e4bcb4..92bff6dd527 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -52,6 +52,9 @@ export function toFetchRequest(url, data, options = {}) { // but we're not in a secure context rqOpts.browsingTopics = true; } + if (options.keepalive) { + rqOpts.keepalive = true; + } return dep.makeRequest(url, rqOpts); } diff --git a/src/consentHandler.js b/src/consentHandler.js index 004d195ea8d..87d1e1a6e23 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -154,14 +154,19 @@ export function gvlidRegistry() { } } }, + /** + * @typedef {Object} GvlIdResult + * @property {Object.} modules - A map from module type to that module's GVL ID. + * @property {number} [gvlid] - The single GVL ID for this family of modules (only defined if all modules with this name declared the same ID). + */ + /** * Get a module's GVL ID(s). * - * @param {string} moduleName - * @return {{modules: {[moduleType]: number}, gvlid?: number}} an object where: + * @param {string} moduleName - The name of the module. + * @return {GvlIdResult} An object where: * `modules` is a map from module type to that module's GVL ID; - * `gvlid` is the single GVL ID for this family of modules (only defined - * if all modules with this name declared the same ID). + * `gvlid` is the single GVL ID for this family of modules (only defined if all modules with this name declare the same ID). */ get(moduleName) { const result = {modules: registry[moduleName] || {}}; diff --git a/src/constants.js b/src/constants.js index bb76083862b..4ca5f6a1b12 100644 --- a/src/constants.js +++ b/src/constants.js @@ -41,7 +41,11 @@ export const EVENTS = { BID_VIEWABLE: 'bidViewable', STALE_RENDER: 'staleRender', BILLABLE_EVENT: 'billableEvent', - BID_ACCEPTED: 'bidAccepted' + BID_ACCEPTED: 'bidAccepted', + RUN_PAAPI_AUCTION: 'paapiRunAuction', + PAAPI_BID: 'paapiBid', + PAAPI_NO_BID: 'paapiNoBid', + PAAPI_ERROR: 'paapiError', }; export const AD_RENDER_FAILED_REASON = { diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index e4f519772c9..5024a0ee184 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -23,9 +23,9 @@ export const dep = { const oneClient = clientSectionChecker('FPD') /** - * Enrich an ortb2 object with first party data. - * @param {Promise[{}]} fpd: a promise to an ortb2 object. - * @returns: {Promise[{}]}: a promise to an enriched ortb2 object. + * Enrich an ortb2 object with first-party data. + * @param {Promise} fpd - A promise that resolves to an ortb2 object. + * @returns {Promise} - A promise that resolves to an enriched ortb2 object. */ export const enrichFPD = hook('sync', (fpd) => { const promArr = [fpd, getSUA().catch(() => null), tryToGetCdepLabel().catch(() => null)]; diff --git a/src/prebid.js b/src/prebid.js index 8e95c294709..02c8293c31b 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -304,7 +304,7 @@ pbjsInstance.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param adUnitCode {string} adUnitCode to get the bid responses for + * @param adunitCode {string} adUnitCode to get the bid responses for * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode * @returns {Object} returnObj return bid */ @@ -414,7 +414,7 @@ pbjsInstance.getBidResponsesForAdUnitCode = function (adUnitCode) { /** * Set query string targeting on one or more GPT ad units. * @param {(string|string[])} adUnit a single `adUnit.code` or multiple. - * @param {function(object)} customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; + * @param {function(object): function(string): boolean} customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; * @alias module:pbjs.setTargetingForGPTAsync */ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { @@ -447,7 +447,7 @@ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { /** * Set query string targeting on all AST (AppNexus Seller Tag) ad units. Note that this function has to be called after all ad units on page are defined. For working example code, see [Using Prebid.js with AppNexus Publisher Ad Server](http://prebid.org/dev-docs/examples/use-prebid-with-appnexus-ad-server.html). - * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes + * @param {(string|string[])} adUnitCodes adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ pbjsInstance.setTargetingForAst = function (adUnitCodes) { diff --git a/src/refererDetection.js b/src/refererDetection.js index 93ebf085dd5..bfe7fb02671 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -34,9 +34,11 @@ export function ensureProtocol(url, win = window) { /** * Extract the domain portion from a URL. - * @param url - * @param noLeadingWww: if true, remove 'www.' appearing at the beginning of the domain. - * @param noPort: if true, do not include the ':[port]' portion + * @param {string} url - The URL to extract the domain from. + * @param {Object} options - Options for parsing the domain. + * @param {boolean} options.noLeadingWww - If true, remove 'www.' appearing at the beginning of the domain. + * @param {boolean} options.noPort - If true, do not include the ':[port]' portion. + * @return {string|undefined} - The extracted domain or undefined if the URL is invalid. */ export function parseDomain(url, {noLeadingWww = false, noPort = false} = {}) { try { @@ -108,13 +110,13 @@ export function detectReferer(win) { * @property {string|null} ref the referrer (document.referrer) to the current page, or null if not available (due to cross-origin restrictions) * @property {string} topmostLocation of the top-most frame for which we could guess the location. Outside of cross-origin scenarios, this is equivalent to `location`. * @property {number} numIframes number of steps between window.self and window.top - * @property {Array[string|null]} stack our best guess at the location for each frame, in the direction top -> self. + * @property {Array} stack our best guess at the location for each frame, in the direction top -> self. */ /** * Walk up the windows to get the origin stack and best available referrer, canonical URL, etc. * - * @returns {refererInfo} + * @returns {refererInfo} An object containing referer information. */ function refererInfo() { const stack = []; diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 96ace0792e4..a33f742b738 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -3,19 +3,15 @@ access to a publisher page from creative payloads. */ -import * as events from './events.js'; import {getAllAssetsMessage, getAssetMessage} from './native.js'; -import { BID_STATUS, EVENTS, MESSAGES } from './constants.js'; +import {BID_STATUS, MESSAGES} from './constants.js'; import {isApnGetTagDefined, isGptPubadsDefined, logError, logWarn} from './utils.js'; -import {auctionManager} from './auctionManager.js'; import {find, includes} from './polyfill.js'; -import {handleCreativeEvent, handleNativeMessage, handleRender} from './adRendering.js'; +import {getBidToRender, handleCreativeEvent, handleNativeMessage, handleRender, markWinningBid} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; const { REQUEST, RESPONSE, NATIVE, EVENT } = MESSAGES; -const BID_WON = EVENTS.BID_WON; - const HANDLER_MAP = { [REQUEST]: handleRenderRequest, [EVENT]: handleEventRequest, @@ -28,7 +24,9 @@ if (FEATURES.NATIVE) { } export function listenMessagesFromCreative() { - window.addEventListener('message', receiveMessage, false); + window.addEventListener('message', function (ev) { + receiveMessage(ev); + }, false); } export function getReplier(ev) { @@ -49,6 +47,12 @@ export function getReplier(ev) { } } +function ensureAdId(adId, reply) { + return function (data, ...args) { + return reply(Object.assign({}, data, {adId}), ...args); + } +} + export function receiveMessage(ev) { var key = ev.message ? 'message' : 'data'; var data = {}; @@ -58,19 +62,19 @@ export function receiveMessage(ev) { return; } - if (data && data.adId && data.message) { - const adObject = find(auctionManager.getBidsReceived(), function (bid) { - return bid.adId === data.adId; - }); - if (HANDLER_MAP.hasOwnProperty(data.message)) { - HANDLER_MAP[data.message](getReplier(ev), data, adObject); - } + if (data && data.adId && data.message && HANDLER_MAP.hasOwnProperty(data.message)) { + return getBidToRender(data.adId, data.message === MESSAGES.REQUEST).then(adObject => { + HANDLER_MAP[data.message](ensureAdId(data.adId, getReplier(ev)), data, adObject); + }) } } -function getResizer(bidResponse) { +function getResizer(adId, bidResponse) { + // in some situations adId !== bidResponse.adId + // the first is the one that was requested and is tied to the element + // the second is the one that is being rendered (sometimes different, e.g. in some paapi setups) return function (width, height) { - resizeRemoteCreative({...bidResponse, width, height}); + resizeRemoteCreative({...bidResponse, width, height, adId}); } } function handleRenderRequest(reply, message, bidResponse) { @@ -81,7 +85,7 @@ function handleRenderRequest(reply, message, bidResponse) { renderer: getCreativeRendererSource(bidResponse) }, adData)); }, - resizeFn: getResizer(bidResponse), + resizeFn: getResizer(message.adId, bidResponse), options: message.options, adId: message.adId, bidResponse @@ -100,8 +104,7 @@ function handleNativeRequest(reply, data, adObject) { } if (adObject.status !== BID_STATUS.RENDERED) { - auctionManager.addWinningBid(adObject); - events.emit(BID_WON, adObject); + markWinningBid(adObject); } switch (data.action) { diff --git a/src/targeting.js b/src/targeting.js index acb3ddb09ff..d3fb3878248 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -482,7 +482,8 @@ export function newTargeting(auctionManager) { /** * Returns top bids for a given adUnit or set of adUnits. * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes - * @return {[type]} [description] + * @param {Array} [bidsReceived=getBidsReceived()] - The received bids, defaulting to the result of getBidsReceived(). + * @return {Array} - An array of winning bids. */ targeting.getWinningBids = function(adUnitCode, bidsReceived = getBidsReceived()) { const adUnitCodes = getAdUnitCodes(adUnitCode); diff --git a/src/utils.js b/src/utils.js index 46dd06a6a41..2affc52ab8c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -130,37 +130,55 @@ export function transformAdServerTargetingObj(targeting) { } /** - * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' - * @param {(Array.|Array.)} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] - * @return {Array.} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of width, height tuples `[[300, 250]]` or '[[300,250], [970,90]]' */ -export function parseSizesInput(sizeObj) { - if (typeof sizeObj === 'string') { +export function sizesToSizeTuples(sizes) { + if (typeof sizes === 'string') { // multiple sizes will be comma-separated - return sizeObj.split(',').filter(sz => sz.match(/^(\d)+x(\d)+$/i)) - } else if (typeof sizeObj === 'object') { - if (sizeObj.length === 2 && typeof sizeObj[0] === 'number' && typeof sizeObj[1] === 'number') { - return [parseGPTSingleSizeArray(sizeObj)]; - } else { - return sizeObj.map(parseGPTSingleSizeArray) + return sizes + .split(/\s*,\s*/) + .map(sz => sz.match(/^(\d+)x(\d+)$/i)) + .filter(match => match) + .map(([_, w, h]) => [parseInt(w, 10), parseInt(h, 10)]) + } else if (Array.isArray(sizes)) { + if (isValidGPTSingleSize(sizes)) { + return [sizes] } + return sizes.filter(isValidGPTSingleSize); } return []; } +/** + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' + * @param {(Array.|Array.)} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] + * @return {Array.} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + */ +export function parseSizesInput(sizeObj) { + return sizesToSizeTuples(sizeObj).map(sizeTupleToSizeString); +} + +export function sizeTupleToSizeString(size) { + return size[0] + 'x' + size[1] +} + // Parse a GPT style single size array, (i.e [300, 250]) // into an AppNexus style string, (i.e. 300x250) export function parseGPTSingleSizeArray(singleSize) { if (isValidGPTSingleSize(singleSize)) { - return singleSize[0] + 'x' + singleSize[1]; + return sizeTupleToSizeString(singleSize); } } +export function sizeTupleToRtbSize(size) { + return {w: size[0], h: size[1]}; +} + // Parse a GPT style single size array, (i.e [300, 250]) // into OpenRTB-compatible (imp.banner.w/h, imp.banner.format.w/h, imp.video.w/h) object(i.e. {w:300, h:250}) export function parseGPTSingleSizeArrayToRtbSize(singleSize) { if (isValidGPTSingleSize(singleSize)) { - return {w: singleSize[0], h: singleSize[1]}; + return sizeTupleToRtbSize(singleSize) } } @@ -364,7 +382,8 @@ export function isEmptyStr(str) { * Iterate object with the function * falls back to es5 `forEach` * @param {Array|Object} object - * @param {Function(value, key, object)} fn + * @param {Function} fn - The function to execute for each element. It receives three arguments: value, key, and the original object. + * @returns {void} */ export function _each(object, fn) { if (isFn(object?.forEach)) return object.forEach(fn, this); @@ -379,7 +398,7 @@ export function contains(a, obj) { * Map an array or object into another array * given a function * @param {Array|Object} object - * @param {Function(value, key, object)} callback + * @param {Function} callback - The function to execute for each element. It receives three arguments: value, key, and the original object. * @return {Array} */ export function _map(object, callback) { @@ -482,7 +501,6 @@ export function insertHtmlIntoIframe(htmlCode) { /** * Inserts empty iframe with the specified `url` for cookie sync * @param {string} url URL to be requested - * @param {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true * @param {function} [done] an optional exit callback, used when this usersync pixel is added during an async process * @param {Number} [timeout] an optional timeout in milliseconds for the iframe to load before calling `done` */ @@ -719,7 +737,6 @@ export function delayExecution(func, numRequiredCalls) { /** * https://stackoverflow.com/a/34890276/428704 - * @export * @param {Array} xs * @param {string} key * @returns {Object} {${key_value}: ${groupByArray}, key_value: {groupByArray}} @@ -935,9 +952,9 @@ export function buildUrl(obj) { * This function deeply compares two objects checking for their equivalence. * @param {Object} obj1 * @param {Object} obj2 - * @param checkTypes {boolean} if set, two objects with identical properties but different constructors will *not* - * be considered equivalent. - * @returns {boolean} + * @param {Object} [options] - Options for comparison. + * @param {boolean} [options.checkTypes=false] - If set, two objects with identical properties but different constructors will *not* be considered equivalent. + * @returns {boolean} - Returns `true` if the objects are equivalent, `false` otherwise. */ export function deepEqual(obj1, obj2, {checkTypes = false} = {}) { if (obj1 === obj2) return true; @@ -1072,7 +1089,7 @@ export function memoize(fn, key = function (arg) { return arg; }) { /** * Sets dataset attributes on a script - * @param {Script} script + * @param {HTMLScriptElement} script * @param {object} attributes */ export function setScriptAttributes(script, attributes) { diff --git a/src/utils/perfMetrics.js b/src/utils/perfMetrics.js index b1fdb38effe..d0736b71554 100644 --- a/src/utils/perfMetrics.js +++ b/src/utils/perfMetrics.js @@ -63,9 +63,9 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make /** * Get the tame passed since `checkpoint`, and optionally save it as a metric. * - * @param checkpoint checkpoint name - * @param metric? metric name - * @return {number} time between now and `checkpoint` + * @param {string} checkpoint checkpoint name + * @param {string} [metric] - The name of the metric to save. Optional. + * @returns {number|null} - The time in milliseconds between now and the checkpoint, or `null` if the checkpoint is not found. */ function timeSince(checkpoint, metric) { const ts = getTimestamp(checkpoint); @@ -79,10 +79,10 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make /** * Get the time passed between `startCheckpoint` and `endCheckpoint`, optionally saving it as a metric. * - * @param startCheckpoint begin checkpoint - * @param endCheckpoint end checkpoint - * @param metric? metric name - * @return {number} time passed between `startCheckpoint` and `endCheckpoint` + * @param {string} startCheckpoint - The name of the starting checkpoint. + * @param {string} endCheckpoint - The name of the ending checkpoint. + * @param {string} [metric] - The name of the metric to save. Optional. + * @returns {number|null} - The time in milliseconds between `startCheckpoint` and `endCheckpoint`, or `null` if either checkpoint is not found. */ function timeBetween(startCheckpoint, endCheckpoint, metric) { const start = getTimestamp(startCheckpoint); @@ -128,12 +128,12 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make } /** - * @typedef {function: T} HookFn - * @property {function(T): void} bail + * @typedef {Function} HookFn + * @property {Function(T): void} bail * * @template T - * @typedef {T: HookFn} TimedHookFn - * @property {function(): void} stopTiming + * @typedef {HookFn} TimedHookFn + * @property {Function(): void} stopTiming * @property {T} untimed */ @@ -141,12 +141,12 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * Convenience method for measuring time spent in a `.before` or `.after` hook. * * @template T - * @param name metric name - * @param {HookFn} next the hook's `next` (first) argument - * @param {function(TimedHookFn): T} fn a function that will be run immediately; it takes `next`, + * @param {string} name - The metric name. + * @param {HookFn} next - The hook's `next` (first) argument. + * @param {function(TimedHookFn): T} fn - A function that will be run immediately; it takes `next`, * where both `next` and `next.bail` automatically * call `stopTiming` before continuing with the original hook. - * @return {T} fn's return value + * @return {T} - The return value of `fn`. */ function measureHookTime(name, next, fn) { const stopTiming = startTiming(name); @@ -208,10 +208,11 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * ``` * * - * @param propagate if false, the forked metrics will not be propagated here - * @param stopPropagation if true, propagation from the new metrics is stopped here - instead of - * continuing up the chain (if for example these metrics were themselves created through `.fork()`) - * @param includeGroups if true, the forked metrics will also replicate metrics that were propagated + * @param {Object} [options={}] - Options for forking the metrics. + * @param {boolean} [options.propagate=true] - If false, the forked metrics will not be propagated here. + * @param {boolean} [options.stopPropagation=false] - If true, propagation from the new metrics is stopped here, instead of + * continuing up the chain (if for example these metrics were themselves created through `.fork()`). + * @param {boolean} [options.includeGroups=false] - If true, the forked metrics will also replicate metrics that were propagated * here from elsewhere. For example: * ``` * const metrics = newMetrics(); @@ -222,6 +223,7 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * withoutGroups.getMetrics() // {} * withGroups.getMetrics() // {foo: ['bar']} * ``` + * @returns {Object} - The new metrics object. */ function fork({propagate = true, stopPropagation = false, includeGroups = false} = {}) { return makeMetrics(mkNode([[self, {propagate, stopPropagation, includeGroups}]]), rename); diff --git a/src/utils/ttlCollection.js b/src/utils/ttlCollection.js index 0972d175848..2294c072108 100644 --- a/src/utils/ttlCollection.js +++ b/src/utils/ttlCollection.js @@ -4,19 +4,26 @@ import {binarySearch, logError, timestamp} from '../utils.js'; /** * Create a set-like collection that automatically forgets items after a certain time. * - * @param {({}) => Number|Promise} startTime? a function taking an item added to this collection, + * @param {function(*): (number|Promise)} [startTime=timestamp] - A function taking an item added to this collection, * and returning (a promise to) a timestamp to be used as the starting time for the item * (the item will be dropped after `ttl(item)` milliseconds have elapsed since this timestamp). * Defaults to the time the item was added to the collection. - * @param {({}) => Number|void|Promise} ttl a function taking an item added to this collection, + * @param {function(*): (number|void|Promise)} [ttl=() => null] - A function taking an item added to this collection, * and returning (a promise to) the duration (in milliseconds) the item should be kept in it. * May return null to indicate that the item should be persisted indefinitely. - * @param {boolean} monotonic? set to true for better performance, but only if, given any two items A and B in this collection: + * @param {boolean} [monotonic=false] - Set to true for better performance, but only if, given any two items A and B in this collection: * if A was added before B, then: * - startTime(A) + ttl(A) <= startTime(B) + ttl(B) * - Promise.all([startTime(A), ttl(A)]) never resolves later than Promise.all([startTime(B), ttl(B)]) - * @param {number} slack? maximum duration (in milliseconds) that an item is allowed to persist + * @param {number} [slack=5000] - Maximum duration (in milliseconds) that an item is allowed to persist * once past its TTL. This is also roughly the interval between "garbage collection" sweeps. + * @returns {Object} A set-like collection with automatic TTL expiration. + * @returns {function(*): void} return.add - Add an item to the collection. + * @returns {function(): void} return.clear - Clear the collection. + * @returns {function(): Array<*>} return.toArray - Get all the items in the collection, in insertion order. + * @returns {function(): void} return.refresh - Refresh the TTL for each item in the collection. + * @returns {function(function(*)): function(): void} return.onExpiry - Register a callback to be run when an item has expired and is about to be + * removed from the collection. Returns an un-registration function */ export function ttlCollection( { diff --git a/src/video.js b/src/video.js index ff137892a2b..f8de2b98861 100644 --- a/src/video.js +++ b/src/video.js @@ -25,7 +25,8 @@ export function fillVideoDefaults(adUnit) { /** * Validate that the assets required for video context are present on the bid * @param {VideoBid} bid Video bid to validate - * @param index + * @param {Object} [options] - Options object + * @param {Object} [options.index=auctionManager.index] - Index object, defaulting to `auctionManager.index` * @return {Boolean} If object is valid */ export function isValidVideoBid(bid, {index = auctionManager.index} = {}) { diff --git a/src/videoCache.js b/src/videoCache.js index ce03f2f624e..6cba77de308 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -39,7 +39,7 @@ const ttlBufferInSeconds = 15; * Function which wraps a URI that serves VAST XML, so that it can be loaded. * * @param {string} uri The URI where the VAST content can be found. - * @param {string} impUrl An impression tracker URL for the delivery of the video ad + * @param {(string|string[])} impTrackerURLs An impression tracker URL for the delivery of the video ad * @return A VAST URL which loads XML from the given URI. */ function wrapURI(uri, impTrackerURLs) { @@ -65,7 +65,9 @@ function wrapURI(uri, impTrackerURLs) { * the bid can't be converted cleanly. * * @param {CacheableBid} bid - * @param index + * @param {Object} [options] - Options object. + * @param {Object} [options.index=auctionManager.index] - Index object, defaulting to `auctionManager.index`. + * @return {Object|null} - The payload to be sent to the prebid-server endpoints, or null if the bid can't be converted cleanly. */ function toStorageRequest(bid, {index = auctionManager.index} = {}) { const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); diff --git a/test/mocks/ortbConverter.js b/test/mocks/ortbConverter.js new file mode 100644 index 00000000000..446fac4629a --- /dev/null +++ b/test/mocks/ortbConverter.js @@ -0,0 +1,8 @@ +import {defaultProcessors} from '../../libraries/ortbConverter/converter.js'; +import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js'; + +beforeEach(() => { + // disable caching of default processors so that tests do not freeze a subset for other tests + defaultProcessors.clear(); + pbsExtensions.clear(); +}); diff --git a/test/spec/libraries/weakStore_spec.js b/test/spec/libraries/weakStore_spec.js new file mode 100644 index 00000000000..407b83391ef --- /dev/null +++ b/test/spec/libraries/weakStore_spec.js @@ -0,0 +1,32 @@ +import {weakStore} from '../../../libraries/weakStore/weakStore.js'; + +describe('weakStore', () => { + let targets, store; + beforeEach(() => { + targets = { + id: {} + }; + store = weakStore((id) => targets[id]); + }); + + it('returns undef if getter returns undef', () => { + expect(store('missing')).to.not.exist; + }); + + it('inits to empty object by default', () => { + expect(store('id')).to.eql({}); + }); + + it('inits to given value', () => { + expect(store('id', {initial: 'value'})).to.eql({'initial': 'value'}); + }); + + it('returns the same object as long as the target does not change', () => { + expect(store('id')).to.equal(store('id')); + }); + + it('ignores init value if already initialized', () => { + store('id', {initial: 'value'}); + expect(store('id', {second: 'value'})).to.eql({initial: 'value'}); + }) +}); diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 936e7cee074..35c8e31ecfe 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -1539,7 +1539,6 @@ describe('33acrossBidAdapter:', function () { .withProduct('instream') .build(); - ttxRequest.imp[0].video.plcmt = 1; ttxRequest.imp[0].video.startdelay = 0; const serverRequest = new ServerRequestBuilder() @@ -1558,7 +1557,7 @@ describe('33acrossBidAdapter:', function () { ); const ttxRequest = new TtxRequestBuilder() - .withVideo({startdelay: -2, plcmt: 1}) + .withVideo({startdelay: -2}) .withProduct('instream') .build(); @@ -1567,36 +1566,8 @@ describe('33acrossBidAdapter:', function () { expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); - it('overrides the placement value', function() { - const bidRequests = ( - new BidRequestsBuilder() - .withVideo({ - plcmt: 2, // Incorrect placement value for an instream video - placement: 2, // Placement specified in the DEPRECATED field. - context: 'instream' - }) - .build() - ); - - const ttxRequest = new TtxRequestBuilder() - .withVideo() - .withProduct('instream') - .build(); - - ttxRequest.imp[0].video.plcmt = 1; - ttxRequest.imp[0].video.placement = 1; - ttxRequest.imp[0].video.startdelay = 0; - - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - - expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); - }); - context('when the placement is still specified in the DEPRECATED `placement` field', function() { - it('overwrites its value and sets it in the recent `plcmt` field as well', function() { + it('does not overwrite its value and does not set it in the recent `plcmt` field as well', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({ @@ -1611,8 +1582,7 @@ describe('33acrossBidAdapter:', function () { .withProduct('instream') .build(); - ttxRequest.imp[0].video.plcmt = 1; - ttxRequest.imp[0].video.placement = 1; + ttxRequest.imp[0].video.placement = 2; ttxRequest.imp[0].video.startdelay = 0; const serverRequest = new ServerRequestBuilder() diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 5086fc6570e..6ec3554d353 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -1,4 +1,4 @@ -import { thirthyThreeAcrossIdSubmodule, storage } from 'modules/33acrossIdSystem.js'; +import { thirthyThreeAcrossIdSubmodule, storage, domainUtils } from 'modules/33acrossIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; @@ -61,7 +61,7 @@ describe('33acrossIdSystem', () => { Object.entries(additionalOptions).forEach(([caseTitle, opts]) => { context(`if the use of a first-party ID has been enabled ${caseTitle}`, () => { context('and the response includes a first-party ID', () => { - context('and the storage type is "cookie"', () => { + context('and the enabled storage types include "cookie"', () => { it('should store the provided first-party ID in a cookie', () => { const completeCallback = () => {}; @@ -70,10 +70,8 @@ describe('33acrossIdSystem', () => { pid: '12345', ...opts }, - storage: { - type: 'cookie', - expires: 30 - } + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } }); callback(completeCallback); @@ -81,7 +79,7 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -94,31 +92,68 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); - expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); - context('and the storage type is "html5"', () => { + context('and the enabled storage types include "html5"', () => { it('should store the provided first-party ID in local storage', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', - storeFpid: true + ...opts + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdFp', 'bar')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + + context('and the enabled storage types are "cookie" and "html5"', () => { + it('should store the provided first-party ID in each storage type', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} }); callback(completeCallback); const [request] = server.requests; + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); request.respond(200, { @@ -132,8 +167,11 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; expect(setDataInLocalStorage.calledWithExactly('33acrossIdFp', 'bar')).to.be.true; + setCookie.restore(); + domainUtils.domainOverride.restore(); setDataInLocalStorage.restore(); }); }); @@ -148,9 +186,8 @@ describe('33acrossIdSystem', () => { pid: '12345', ...opts }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -159,7 +196,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -172,18 +209,18 @@ describe('33acrossIdSystem', () => { })); expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdFp')).to.be.true; - expect(setCookie.calledWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); }); context(`if the use of a supplemental third-party ID has been enabled ${caseTitle}`, () => { context('and the response includes a third-party ID', () => { - context('and the storage type is "cookie"', () => { + context('and the enabled storage type include "cookie"', () => { it('should store the provided third-party ID in a cookie', () => { const completeCallback = () => {}; @@ -192,10 +229,8 @@ describe('33acrossIdSystem', () => { pid: '12345', ...opts }, - storage: { - type: 'cookie', - expires: 30 - } + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } }); callback(completeCallback); @@ -203,7 +238,7 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -216,14 +251,14 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); - expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); - context('and the storage type is "html5"', () => { + context('and the enabled storage types include "html5"', () => { it('should store the provided third-party ID in local storage', () => { const completeCallback = () => {}; @@ -232,9 +267,8 @@ describe('33acrossIdSystem', () => { pid: '12345', ...opts }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -259,6 +293,47 @@ describe('33acrossIdSystem', () => { setDataInLocalStorage.restore(); }); }); + + context('and the enabled storage types are "cookie" and "html5"', () => { + it('should store the provided third-party ID in each storage type', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + tp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(setDataInLocalStorage.calledWithExactly('33acrossIdTp', 'bar')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + setDataInLocalStorage.restore(); + }); + }); }); context('and the response lacks a third-party ID', () => { @@ -268,11 +343,10 @@ describe('33acrossIdSystem', () => { const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', - storeTpid: true + ...opts }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -281,7 +355,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -294,11 +368,11 @@ describe('33acrossIdSystem', () => { })); expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdTp')).to.be.true; - expect(setCookie.calledWithExactly('33acrossIdTp', '', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdTp', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); }); @@ -314,8 +388,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeFpid: false }, + enabledStorageTypes: [ 'cookie' ], storage: { - type: 'cookie', expires: 30 } }); @@ -325,7 +399,7 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -338,10 +412,10 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); - expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax')).to.be.false; + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.false; setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); it('should not store the provided first-party ID in local storage', () => { @@ -352,9 +426,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeFpid: false }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -391,8 +464,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeTpid: false }, + enabledStorageTypes: [ 'cookie' ], storage: { - type: 'cookie', expires: 30 } }); @@ -402,7 +475,6 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); request.respond(200, { 'Content-Type': 'application/json' @@ -418,7 +490,6 @@ describe('33acrossIdSystem', () => { expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax')).to.be.false; setCookie.restore(); - cookiesAreEnabled.restore(); }); it('should not store the provided third-party ID in local storage', () => { @@ -429,9 +500,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeTpid: false }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -466,9 +536,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -477,7 +546,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -490,11 +559,11 @@ describe('33acrossIdSystem', () => { })); expect(removeDataFromLocalStorage.calledWith('33acrossId')).to.be.true; - expect(setCookie.calledWith('33acrossId', '', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossId', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); @@ -708,9 +777,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); sinon.stub(storage, 'getDataFromLocalStorage') @@ -734,9 +802,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'cookie' - } + enabledStorageTypes: [ 'cookie' ], + storage: {} }); sinon.stub(storage, 'getCookie') @@ -777,9 +844,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); sinon.stub(storage, 'getDataFromLocalStorage') @@ -803,9 +869,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'cookie' - } + enabledStorageTypes: [ 'cookie' ], + storage: {} }); sinon.stub(storage, 'getCookie') diff --git a/test/spec/modules/adbookpspBidAdapter_spec.js b/test/spec/modules/adbookpspBidAdapter_spec.js deleted file mode 100755 index 3f26cd7749f..00000000000 --- a/test/spec/modules/adbookpspBidAdapter_spec.js +++ /dev/null @@ -1,1344 +0,0 @@ -import { expect } from 'chai'; -import * as utils from '../../../src/utils.js'; -import { - spec, - storage, - DEFAULT_BIDDER_CONFIG, - VERSION, - common, -} from '../../../modules/adbookpspBidAdapter.js'; - -describe('adbookpsp bid adapter', () => { - let sandbox; - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - - sandbox - .stub(common, 'generateUUID') - .returns('54444444-5444-4444-9444-544444444444'); - sandbox.stub(common, 'getWindowDimensions').returns({ - innerWidth: 100, - innerHeight: 100, - }); - }); - - afterEach(function () { - sandbox.restore(); - }); - - describe('isBidRequestValid()', () => { - it('should return false when there is no banner in mediaTypes', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when orgId and placementId is not defined', () => { - const bid = utils.deepClone(bannerBid); - delete bid.params.placementId; - delete bid.params.orgId; - - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - - it('should return true when orgId is set in config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params.placementId; - delete bid.params.orgId; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('129576'); - - expect(spec.isBidRequestValid(bid)).to.be.true; - }); - - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(bannerBid)).to.equal(true); - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - expect(spec.isBidRequestValid(mixedBid)).to.equal(true); - }); - - it('should return false when sizes for banner are not specified', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner.sizes; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when sizes for banner are invalid', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner.sizes; - - bid.mediaTypes.banner.sizes = [['123', 'foo']]; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return true if player size is set via playerSize', () => { - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); - - it('should return true if player size is set via w and h', () => { - const bid = utils.deepClone(videoBid); - delete bid.mediaTypes.video.playerSize; - - bid.mediaTypes.video.w = 400; - bid.mediaTypes.video.h = 300; - - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should reutrn false if player size is not set', () => { - const bid = utils.deepClone(videoBid); - delete bid.mediaTypes.video.playerSize; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests()', () => { - it('should build correct request for banner bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns(undefined) - .withArgs('adbookpsp.exchangeUrl') - .returns('https://ex.fattail.com/openrtb2'); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.equal(bannerExchangeRequest); - }); - - it('should build correct request for video bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp') - .returns(DEFAULT_BIDDER_CONFIG) - .withArgs('adbookpsp.exchangeUrl') - .returns(DEFAULT_BIDDER_CONFIG.exchangeUrl) - .withArgs('adbookpsp.orgId') - .returns(undefined); - - const requests = spec.buildRequests([videoBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.include({ - ...videoExchangeRequest, - ext: { - adbook: { - config: DEFAULT_BIDDER_CONFIG, - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, - }); - }); - - it('should build correct request for video bid with w and h', () => { - const bid = utils.deepClone(videoBid); - - delete bid.mediaTypes.video.playerSize; - - bid.mediaTypes.video.w = 400; - bid.mediaTypes.video.h = 300; - - const [request] = spec.buildRequests([bid], bidderRequest); - const requestData = JSON.parse(request.data); - - expect(requestData.imp[0].video.w).to.equal(400); - expect(requestData.imp[0].video.h).to.equal(300); - }); - - it('should build correct request for video bid with both w, h and playerSize', () => { - const bid = utils.deepClone(videoBid); - - bid.mediaTypes.video.w = 640; - bid.mediaTypes.video.h = 480; - - const [request] = spec.buildRequests([bid], bidderRequest); - const requestData = JSON.parse(request.data); - - expect(requestData.imp[0].video.w).to.equal(640); - expect(requestData.imp[0].video.h).to.equal(480); - }); - - it('should build correct request for mixed bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns(undefined) - .withArgs('adbookpsp.exchangeUrl') - .returns('https://ex.fattail.com/openrtb2'); - - const requests = spec.buildRequests([mixedBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.include( - mixedExchangeRequest - ); - }); - - it('should use orgId from config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('129576'); - - const requests = spec.buildRequests([bid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0].ext).to.deep.include({ - adbook: { - orgId: '129576', - }, - }); - }); - - it('should use orgId from adUnit when orgId is also set in config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params.placementId; - - bid.params.orgId = 'adUnitOrgId'; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('configOrgId'); - - const requests = spec.buildRequests([bid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0].ext).to.deep.include({ - adbook: { - orgId: 'adUnitOrgId', - }, - }); - }); - - it('should include in request GDPR options if available', () => { - const request = utils.deepClone(bidderRequest); - - delete request.uspConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - }, - }, - }); - }); - - it('should include in request USP (CPPA) options if available', () => { - const request = utils.deepClone(bidderRequest); - - delete request.gdprConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 0, - ext: { - us_privacy: 'uspConsentString', - }, - }, - }); - }); - - it('should pass valid coppa flag based on config', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const request = utils.deepClone(bidderRequest); - - delete request.gdprConsent; - delete request.uspConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 1, - }, - }); - }); - - it('should pass GDPR, USP (CCPA) and COPPA options', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 1, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - }); - }); - - it('should generate and pass user id when is not present in cookie and local storage is not enabled', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(false); - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - - expect(rtbRequest.user.id).to.have.lengthOf(36); - }); - - it('should pass user id when is present in cookie', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(false); - sandbox - .stub(storage, 'getCookie') - .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d'); - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - - expect(rtbRequest.user.id).to.equal( - 'e35da6bb-f2f8-443b-aeff-3375bef45c9d' - ); - }); - - it('should pass user id if is present in local storage', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - sandbox - .stub(storage, 'getDataFromLocalStorage') - .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d'); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - expect(rtbRequest.user.id).to.equal( - 'e35da6bb-f2f8-443b-aeff-3375bef45c9d' - ); - }); - - it('should regenerate user id if it is invalid', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - sandbox.stub(storage, 'getDataFromLocalStorage').returns('foo'); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - expect(rtbRequest.user.id).to.have.lengthOf(36); - }); - - it('should pass schain if available', () => { - const bid = utils.deepClone(bannerBid); - const schain = { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'exchange1.com', - sid: '1234', - hp: 1, - rid: 'bid-request-1', - name: 'publisher', - domain: 'publisher.com', - }, - ], - }; - - bid.schain = schain; - - const requests = spec.buildRequests([bid], bidderRequest); - - expect(JSON.parse(requests[0].data).source).to.deep.include({ - ext: { - schain, - }, - }); - }); - - it('return empty array if there are no valid bid requests', () => { - const requests = spec.buildRequests([], bidderRequest); - - expect(requests).to.deep.equal([]); - }); - - it('should prioritize device information set in config', () => { - const ua = - 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1'; - - sandbox.stub(common, 'getConfig').withArgs('device').returns({ - ua, - }); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - - expect(JSON.parse(requests[0].data).device.ua).to.equal(ua); - }); - - it('should include bidder config', () => { - const bidderConfig = { - bidTTL: 500, - defaultCurrency: 'USD', - exchangeUrl: 'https://exsb.fattail.com/openrtb2', - winTrackingEnabled: true, - winTrackingUrl: 'https://evsb.fattail.com/wins', - orgId: '129576', - }; - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp') - .returns(bidderConfig); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.ext).to.deep.include({ - adbook: { - config: bidderConfig, - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }); - }); - - it('should use bidder video params if they are set', () => { - const videoBidWithParams = utils.deepClone(videoBid); - const bidderVideoParams = { - api: [1, 2], - mimes: ['video/mp4', 'video/x-flv'], - playbackmethod: [3, 4], - protocols: [5, 6], - minduration: 10, - maxduration: 30, - }; - videoBidWithParams.params.video = bidderVideoParams; - - const requests = spec.buildRequests([videoBidWithParams], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0]).to.deep.include({ - video: { - ...bidderVideoParams, - w: videoBidWithParams.mediaTypes.video.playerSize[0][0], - h: videoBidWithParams.mediaTypes.video.playerSize[0][1], - }, - }); - }); - }); - - describe('interpretResponse()', () => { - it('should correctly interpret valid response', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.defaultCurrency') - .returns(DEFAULT_BIDDER_CONFIG.defaultCurrency) - .withArgs('adbookpsp.bidTTL') - .returns(DEFAULT_BIDDER_CONFIG.bidTTL); - - const response = utils.deepClone(exchangeResponse); - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([ - { - bidderRequestId: '999ccceeee11', - requestId: '9873kfse', - bidId: 'bid123456', - width: 300, - height: 250, - ttl: 300, - cpm: 0.5, - currency: 'USD', - creativeId: '123456789', - mediaType: 'banner', - meta: { - advertiserDomains: ['advertiser.com'], - mediaType: 'banner', - primaryCatId: 'IAB2-1', - secondaryCatIds: ['IAB2-2', 'IAB2-3'], - }, - netRevenue: true, - nurl: 'http://win.example.url', - adUnitCode: 'div-gpt-ad-837465923534-0', - ad: '
ad
', - adId: '5', - adserverTargeting: { - hb_ad_ord_adbookpsp: '0_0', // the value to the left of the underscore represents the index of the ad id and the number to the right represents the order index - hb_adid_c_adbookpsp: '5', - hb_deal_adbookpsp: 'werwetwerw', - hb_liid_adbookpsp: '2342345', - hb_ordid_adbookpsp: '567843', - }, - referrer: 'http://prebid-test-page.io:8080/banner.html', - lineItemId: '2342345', - }, - { - ad: '', - adId: '10', - adUnitCode: 'div-gpt-ad-837465923534-0', - adserverTargeting: { - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }, - bidId: 'bid4321', - bidderRequestId: '999ccceeee11', - cpm: 0.45, - creativeId: '543123', - currency: 'USD', - height: 250, - lineItemId: '2121221', - mediaType: 'video', - meta: { - advertiserDomains: ['advertiser.com', 'campaign.advertiser.com'], - mediaType: 'video', - primaryCatId: 'IAB2-3', - secondaryCatIds: [], - }, - netRevenue: true, - nurl: 'http://win.example.url', - referrer: 'http://prebid-test-page.io:8080/banner.html', - requestId: '120kfeske', - ttl: 300, - vastXml: - '', - width: 300, - }, - ]); - }); - - it('should place valid GAM targeting for all bids when multiple bids are present for multiple impressions', () => { - const response = utils.deepClone(exchangeResponse); - - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.have.length(2); - expect(bids[0].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '5', - hb_deal_adbookpsp: 'werwetwerw', - hb_liid_adbookpsp: '2342345', - hb_ordid_adbookpsp: '567843', - }); - expect(bids[1].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }); - }); - - it('should place valid GAM targeting for all bids when multiple bids are present for single impression', () => { - const response = utils.deepClone(exchangeResponse); - - response.seatbid[1].bid[0].impid = '9873kfse'; - - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.have.length(2); - for (const bid of bids) { - expect(bid.adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0,1_0', - hb_adid_c_adbookpsp: '5,10', - hb_deal_adbookpsp: 'werwetwerw,dsfxcxcvxc', - hb_liid_adbookpsp: '2342345,2121221', - hb_ordid_adbookpsp: '567843,5678234', - }); - } - }); - - it('should return no bids if response id does not match bidderRequestId', () => { - const body = utils.deepClone(exchangeResponse); - body.id = '999'; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should return no bids if response does not include seatbid', () => { - const body = utils.deepClone(exchangeResponse); - delete body.seatbid; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should return no bids if response does not include any bids', () => { - const body = utils.deepClone(exchangeResponse); - body.seatbid = []; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should exclude invalid video bids', () => { - const body = utils.deepClone(exchangeResponse); - - body.seatbid.shift(); - body.seatbid[0].bid[0].adid = 34; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should exclude invalid banner bids', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - body.seatbid.pop(); - - delete body.seatbid[0].bid[0].w; - delete body.seatbid[0].bid[0].h; - - request.imp[0].banner.format.push({ w: 300, h: 600 }); - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(request) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should not include invalid banner bids in targeting map', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - body.seatbid[0].bid[0].h = '600'; - - request.imp[0].banner.format.push({ w: 300, h: 600 }); - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids[0].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }); - }); - - it('should not validate banner bid dimensions if bid request has single size', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - delete body.seatbid[1]; - delete body.seatbid[0].bid[0].h; - delete body.seatbid[0].bid[0].w; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(request) } - ); - - expect(bids.length).to.equal(1); - }); - }); - - describe('getUserSyncs()', () => { - it('should return user syncs if there are included in the response and syncs are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: true, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should not return user syncs if syncs are disabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: false, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([]); - }); - - it('should return image syncs if they are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: false, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should return iframe syncs if they are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should append COPPA status to sync url', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?coppa=1', - }, - ]); - }); - - it('should append GDPR consent data to url', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(false); - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - { gdprApplies: true, consentString: 'gdprConsentString' } - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString', - }, - ]); - }); - - it('should append USP (CCPA) consent string to url', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - undefined, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?us_privacy=uspConsentString', - }, - ]); - }); - - it('should append COPPA, GDPR and USP (CCPA) url params', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - { gdprApplies: true, consentString: 'gdprConsentString' }, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - ]); - }); - - it('should respect url param syntax when appending params', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const response = utils.deepClone(exchangeResponse); - - response.ext.sync[0] = { - type: 'image', - url: 'http://sometest.com/sync/1234567?horseCount=4', - }; - - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: false, - }, - [{ body: response }], - { gdprApplies: true, consentString: 'gdprConsentString' }, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567?horseCount=4&gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - ]); - }); - }); - - describe('onBidWon()', () => { - it('should track win if win tracking is enabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(true) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - }); - - expect( - spy.calledWith( - 'https://ev.fattail.com/wins?impId=requestId&reqId=bidderRequestId&bidId=bidId' - ) - ).to.equal(true); - }); - it('should call bid.nurl if win tracking is enabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(true) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - nurl: 'http://win.example.url', - }); - - expect(spy.calledWith('http://win.example.url')).to.equal(true); - }); - it('should not track win nor call bid.nurl if win tracking is disabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(false) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - nurl: 'http://win.example.url', - }); - - expect(spy.notCalled).to.equal(true); - }); - }); -}); - -const bidderRequest = { - auctionId: 'aaccee333311', - bidderRequestId: '999ccceeee11', - timeout: 200, - refererInfo: { - page: 'http://mock-page.com', - domain: 'mock-page.com', - ref: 'http://example-domain.com/foo', - }, - gdprConsent: { - gdprApplies: 1, - consentString: 'gdprConsentString', - }, - uspConsent: 'uspConsentString', - ortb2: { - source: { - tid: 'aaccee333311' - } - } -}; - -const bannerBid = { - bidder: 'adbookpsp', - params: { - placementId: '12390123', - }, - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600], - ], - }, - }, - adUnitCode: 'div-gpt-ad-837465923534-0', - transactionId: 'sfsf89e-mck3-asf3-fe45-feksjfi123mfs', - bidId: '9873kfse', - bidderRequestId: '999ccceeee11', - auctionId: 'aaccee333311', - lineItemId: 123123123, -}; - -const bannerExchangeRequest = { - id: '999ccceeee11', - device: { - h: 100, - w: 100, - js: true, - ua: navigator.userAgent, - dnt: 0, - }, - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - site: { - domain: 'mock-page.com', - page: 'http://mock-page.com', - ref: 'http://example-domain.com/foo', - }, - source: { - fd: 1, - tid: 'aaccee333311', - }, - tmax: 200, - user: { - gdprConsentString: 'gdprConsentString', - id: '54444444-5444-4444-9444-544444444444', - }, - imp: [ - { - banner: { - format: [ - { - w: 300, - h: 250, - }, - { - w: 300, - h: 600, - }, - ], - w: 300, - h: 250, - topframe: 0, - pos: 0, - }, - ext: { - adbook: { - placementId: '12390123', - }, - }, - id: '9873kfse', - tagid: 'div-gpt-ad-837465923534-0', - }, - ], - ext: { - adbook: { - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, -}; - -const videoBid = { - bidder: 'adbookpsp', - params: { - placementId: '129576', - }, - mediaTypes: { - video: { - api: [1, 2, 4, 6], - mimes: ['video/mp4'], - playbackmethod: [2, 4, 6], - playerSize: [[400, 300]], - protocols: [3, 4, 7, 8, 10], - }, - }, - adUnitCode: 'div-gpt-ad-9383743831-6', - transactionId: 'aacc3fasf-fere-1335-8m1s-785393mc3fj', - bidId: '120kfeske', - bidderRequestId: '999ccceeee11', - auctionId: 'aaccee333311', - lineItemId: 321321321, -}; - -const videoExchangeRequest = { - id: '999ccceeee11', - device: { - h: 100, - w: 100, - js: true, - ua: navigator.userAgent, - dnt: 0, - }, - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - site: { - domain: 'mock-page.com', - page: 'http://mock-page.com', - ref: 'http://example-domain.com/foo', - }, - source: { - fd: 1, - tid: 'aaccee333311', - }, - tmax: 200, - user: { - gdprConsentString: 'gdprConsentString', - id: '54444444-5444-4444-9444-544444444444', - }, - imp: [ - { - video: { - api: [1, 2, 4, 6], - h: 300, - mimes: ['video/mp4'], - playbackmethod: [2, 4, 6], - protocols: [3, 4, 7, 8, 10], - w: 400, - }, - ext: { - adbook: { - placementId: '129576', - }, - }, - id: '120kfeske', - tagid: 'div-gpt-ad-9383743831-6', - }, - ], - ext: { - adbook: { - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, -}; - -const mixedBid = { - bidder: 'adbookpsp', - params: { - orgId: '129576', - }, - mediaTypes: { - banner: { - sizes: [[300, 600]], - }, - video: { - mimes: ['video/mp4'], - playerSize: [[300, 600]], - }, - }, - adUnitCode: 'div-gpt-ad-9383743831-5', - transactionId: 'aacc3fasf-fere-1335-8m1s-785393mc3fj', - bidId: '120kfeske', - bidderRequestId: '999ccceeee11', - auctionId: 'aaccee333311', - lineItemId: 12341234, -}; - -const mixedExchangeRequest = { - id: '999ccceeee11', - device: { - h: 100, - w: 100, - js: true, - ua: navigator.userAgent, - dnt: 0, - }, - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - site: { - domain: 'mock-page.com', - page: 'http://mock-page.com', - ref: 'http://example-domain.com/foo', - }, - source: { - fd: 1, - tid: 'aaccee333311', - }, - tmax: 200, - user: { - gdprConsentString: 'gdprConsentString', - id: '54444444-5444-4444-9444-544444444444', - }, - imp: [ - { - banner: { - format: [ - { - w: 300, - h: 600, - }, - ], - w: 300, - h: 600, - topframe: 0, - pos: 0, - }, - video: { - h: 600, - mimes: ['video/mp4'], - w: 300, - }, - ext: { - adbook: { - orgId: '129576', - }, - }, - id: '120kfeske', - tagid: 'div-gpt-ad-9383743831-5', - }, - ], - ext: { - adbook: { - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, -}; - -const exchangeBidRequest = { - id: '999ccceeee11', - tmax: 200, - imp: [ - { - id: '9873kfse', - banner: { - format: [ - { - w: 300, - h: 250, - }, - ], - }, - video: { - w: 300, - h: 250, - }, - tagid: 'div-gpt-ad-837465923534-0', - }, - { - id: '120kfeske', - banner: { - format: [ - { - w: 300, - h: 250, - }, - ], - }, - video: { - w: 300, - h: 250, - }, - tagid: 'div-gpt-ad-837465923534-0', - }, - ], - source: { - fd: 1, - tid: 'aaccee333311', - }, - site: { - domain: location.hostname, - page: location.href, - ref: 'http://prebid-test-page.io:8080/banner.html', - }, -}; - -const exchangeResponse = { - id: '999ccceeee11', - seatbid: [ - { - seat: 'adbookpsp', - group: 0, - bid: [ - { - id: 'bid123456', - w: 300, - h: 250, - impid: '9873kfse', - price: 0.5, - exp: 300, - crid: '123456789', - adm: '
ad
', - adid: '5', - dealid: 'werwetwerw', - nurl: 'http://win.example.url', - ext: { - liid: '2342345', - ordid: '567843', - }, - cat: ['IAB2-1', 'IAB2-2', 'IAB2-3'], - adomain: ['advertiser.com'], - }, - ], - }, - { - seat: 'adbookpsp', - group: 0, - bid: [ - { - id: 'bid4321', - impid: '120kfeske', - price: 0.45, - exp: 300, - crid: '543123', - adm: '', - adid: '10', - dealid: 'dsfxcxcvxc', - nurl: 'http://win.example.url', - ext: { - liid: '2121221', - ordid: '5678234', - }, - cat: ['IAB2-3'], - adomain: ['advertiser.com', 'campaign.advertiser.com'], - }, - ], - }, - ], - ext: { - sync: [ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ], - }, -}; diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index a9413860072..3cff5816356 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -134,7 +134,7 @@ describe('AdmanAdapter', function () { for (let i = 0; i < placements.length; i++) { let placement = placements[i]; expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'schain', 'bidFloor', - 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', + 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'ext'); expect(placement.ext).to.be.an('object') expect(placement.ext).to.have.key('tid') diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index 98d07575ee7..d005934d062 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -201,8 +201,7 @@ describe('ApacdexBidAdapter', function () { }, 'bidder': 'apacdex', 'params': { - 'siteId': '1a2b3c4d5e6f1a2b3c4d', - 'geo': { 'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60 } + 'siteId': '1a2b3c4d5e6f1a2b3c4d' }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -321,10 +320,6 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) }); - it('should fail to return a properly formatted request with geo defined', function () { - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.geo).to.not.deep.equal(bidRequest[0].params.geo) - }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); diff --git a/test/spec/modules/bidmaticBidAdapter_spec.js b/test/spec/modules/bidmaticBidAdapter_spec.js new file mode 100644 index 00000000000..dcf35d032ea --- /dev/null +++ b/test/spec/modules/bidmaticBidAdapter_spec.js @@ -0,0 +1,268 @@ +import { expect } from 'chai'; +import { END_POINT, spec } from 'modules/bidmaticBidAdapter.js'; +import { deepClone, deepSetValue, mergeDeep } from '../../../src/utils'; + +const expectedImp = { + 'id': '2eb89f0f062afe', + 'banner': { + 'topframe': 0, + 'format': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ] + }, + 'bidfloor': 0, + 'bidfloorcur': 'USD', + 'tagid': 'div-gpt-ad-1460505748561-0' +} + +describe('Bidmatic Bid Adapter', () => { + const GPID_RTB_EXT = { + 'ortb2Imp': { + 'ext': { + 'gpid': 'gpId', + } + }, + } + const FLOOR_RTB_EXT = { + 'ortb2Imp': { + bidfloor: 1 + }, + } + const DEFAULT_BID_REQUEST = { + 'id': '10bb57ee-712f-43e9-9769-b26d03df8a39', + 'bidder': 'bidmatic', + 'params': { + 'source': 886409, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '7d79850b-70aa-4c0f-af95-c1def0452825', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2eb89f0f062afe', + 'bidderRequestId': '1ae6c8e18f8462', + 'auctionId': '1286637c-51bc-4fdd-8e35-2435ec11775a', + 'ortb2': {} + }; + + describe('adapter interface', () => { + const bidRequest = deepClone(DEFAULT_BID_REQUEST); + + it('should validate params', () => { + expect(spec.isBidRequestValid({ + params: { + source: 1 + } + })).to.equal(true, 'source param must be a number'); + + expect(spec.isBidRequestValid({ + params: { + source: '1' + } + })).to.equal(false, 'source param must be a number'); + + expect(spec.isBidRequestValid({})).to.equal(false, 'source param must be a number'); + }); + + it('should build hb request', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0]).to.deep.equal(expectedImp); + expect(ortbRequest.data.cur).to.deep.equal(['USD']); + }); + + it('should request with source in url', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + expect(ortbRequest.url).to.equal(`${END_POINT}?source=886409`); + }); + + it('should split http reqs by sources', () => { + const bidRequest2 = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + params: { + source: 1111 + } + }); + const [ortbRequest1, ortbRequest2] = spec.buildRequests([bidRequest2, bidRequest, bidRequest2], { + bids: [bidRequest2, bidRequest, bidRequest2] + }); + expect(ortbRequest1.url).to.equal(`${END_POINT}?source=1111`); + expect(ortbRequest1.data.imp.length).to.eq(2) + expect(ortbRequest2.url).to.equal(`${END_POINT}?source=886409`); + expect(ortbRequest2.data.imp.length).to.eq(1) + }); + + it('should grab bid floor info', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(0) + expect(ortbRequest.data.imp[0].bidfloorcur).eq('USD') + }); + + it('should grab bid floor info from exts', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), FLOOR_RTB_EXT); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(1) + }); + + it('should grab bid floor info from params', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + params: { + bidfloor: 2 + } + }); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(2) + }); + + it('should set gpid as tagid', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), GPID_RTB_EXT); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].tagid).eq(GPID_RTB_EXT.ortb2Imp.ext.gpid) + }); + }) + + describe('response interpreter', () => { + const SERVER_RESPONSE = { + 'body': { + 'id': '10bb57ee-712f-43e9-9769-b26d03df8a39', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'c5BsBD5QHHgx4aS8', + 'impid': '2eb89f0f062afe', + 'price': 1, + 'adid': 'BDhclfXLcGzRMeV', + 'adm': '123', + 'adomain': [ + 'https://test.com' + ], + 'crid': 'display_300x250', + 'w': 300, + 'h': 250, + } + ], + 'seat': '1' + } + ], + 'cur': 'USD' + }, + 'headers': {} + } + + it('should return empty results', () => { + const [req] = spec.buildRequests([deepClone(DEFAULT_BID_REQUEST)], { + bids: [deepClone(DEFAULT_BID_REQUEST)] + }) + const result = spec.interpretResponse(null, { + data: req.data + }) + + expect(result.length).to.eq(0); + }); + it('should detect media type based on adm', () => { + const [req] = spec.buildRequests([deepClone(DEFAULT_BID_REQUEST)], { + bids: [deepClone(DEFAULT_BID_REQUEST)] + }) + const result = spec.interpretResponse(SERVER_RESPONSE, { + data: req.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('banner') + }); + it('should detect video adm', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [640, 480] + } + } + }) + const bannerResponse = deepClone(SERVER_RESPONSE); + const [ortbReq] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }) + deepSetValue(bannerResponse, 'body.seatbid.0.bid.0.adm', ''); + const result = spec.interpretResponse(bannerResponse, { + data: ortbReq.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('video') + }); + + it('should detect banner adm', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [640, 480] + } + } + }) + const bannerResponse = deepClone(SERVER_RESPONSE); + const [ortbReq] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }) + const result = spec.interpretResponse(bannerResponse, { + data: ortbReq.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('banner') + }); + }) +}) diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 9f7081fabd4..079357ab4fe 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -733,7 +733,7 @@ describe('The Criteo bidding adapter', function () { }, ]; const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&im=1&debug=[01]&nolog=[01]$/); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&im=1&debug=[01]&nolog=[01]$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; expect(ortbRequest.site.page).to.equal(refererUrl); @@ -896,7 +896,7 @@ describe('The Criteo bidding adapter', function () { }, ]; const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; expect(ortbRequest.site.page).to.equal(refererUrl); @@ -956,7 +956,7 @@ describe('The Criteo bidding adapter', function () { }, ]; const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; expect(ortbRequest.site.page).to.equal(refererUrl); @@ -1295,7 +1295,7 @@ describe('The Criteo bidding adapter', function () { }, ]; const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; expect(ortbRequest.imp).to.have.lengthOf(1); @@ -1355,7 +1355,7 @@ describe('The Criteo bidding adapter', function () { }, ]; const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); @@ -1399,7 +1399,7 @@ describe('The Criteo bidding adapter', function () { } ]; const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg']); @@ -1776,7 +1776,9 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request when FLEDGE is enabled', function () { const bidderRequest = { - fledgeEnabled: true, + paapi: { + enabled: true + } }; const bidRequests = [ { @@ -1806,7 +1808,9 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request when FLEDGE is disabled', function () { const bidderRequest = { - fledgeEnabled: false, + paapi: { + enabled: false + }, }; const bidRequests = [ { @@ -2582,15 +2586,15 @@ describe('The Criteo bidding adapter', function () { const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const interpretedResponse = spec.interpretResponse({ body: response }, request); expect(interpretedResponse).to.have.property('bids'); - expect(interpretedResponse).to.have.property('fledgeAuctionConfigs'); + expect(interpretedResponse).to.have.property('paapi'); expect(interpretedResponse.bids).to.have.lengthOf(0); - expect(interpretedResponse.fledgeAuctionConfigs).to.have.lengthOf(2); - expect(interpretedResponse.fledgeAuctionConfigs[0]).to.deep.equal({ + expect(interpretedResponse.paapi).to.have.lengthOf(2); + expect(interpretedResponse.paapi[0]).to.deep.equal({ bidId: 'test-bidId', impid: 'test-bidId', config: auctionConfig1, }); - expect(interpretedResponse.fledgeAuctionConfigs[1]).to.deep.equal({ + expect(interpretedResponse.paapi[1]).to.deep.equal({ bidId: 'test-bidId-2', impid: 'test-bidId-2', config: auctionConfig2, diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index fe9484b2814..0fa199ed034 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -90,13 +90,15 @@ describe('dailymotionBidAdapterTests', () => { mimes: ['video/mp4'], minduration: 5, maxduration: 30, + playbackmethod: [3], + plcmt: 1, protocols: [1, 2, 3, 4, 5, 6, 7, 8], skip: 1, skipafter: 5, skipmin: 10, startdelay: 0, w: 1280, - h: 720 + h: 720, }, }, sizes: [[1920, 1080]], @@ -112,9 +114,14 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', topics: 'topic_1, topic_2', xid: 'x123456', livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, }, }, }]; @@ -160,6 +167,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + expect(reqData.pbv).to.eql('$prebid.version$'); expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -170,9 +178,8 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.true; expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + + expect(reqData.request.mediaTypes.video).to.eql(bidRequestData[0].mediaTypes.video); expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, iabcat1: ['IAB-1'], @@ -182,10 +189,18 @@ describe('dailymotionBidAdapterTests', () => { private: bidRequestData[0].params.video.private, tags: bidRequestData[0].params.video.tags, title: bidRequestData[0].params.video.title, + url: bidRequestData[0].params.video.url, topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, duration: bidRequestData[0].params.video.duration, livestream: !!bidRequestData[0].params.video.livestream, + isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, + context: { + siteOrAppCat: '', + videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, + autoplay: bidRequestData[0].params.video.autoplay, + playerVolume: bidRequestData[0].params.video.playerVolume, + }, }); }); @@ -200,6 +215,8 @@ describe('dailymotionBidAdapterTests', () => { mimes: ['video/mp4'], minduration: 5, maxduration: 30, + playbackmethod: [3], + plcmt: 1, protocols: [1, 2, 3, 4, 5, 6, 7, 8], skip: 1, skipafter: 5, @@ -220,9 +237,15 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', topics: 'topic_1, topic_2', xid: 'x123456', livestream: 1, + // Test invalid values + isCreatedForKids: 'false', + videoViewsInSession: -1, + autoplay: 'true', + playerVolume: 12, }, }, }]; @@ -245,6 +268,13 @@ describe('dailymotionBidAdapterTests', () => { regs: { coppa: 1, }, + device: { + lmt: 1, + ifa: 'xxx', + ext: { + atts: 2, + }, + }, app: { bundle: 'app-bundle', storeurl: 'https://play.google.com/store/apps/details?id=app-bundle', @@ -276,6 +306,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + expect(reqData.pbv).to.eql('$prebid.version$'); expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -286,19 +317,14 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.true; expect(reqData.appBundle).to.eql(bidderRequestData.ortb2.app.bundle); expect(reqData.appStoreUrl).to.eql(bidderRequestData.ortb2.app.storeurl); + expect(reqData.device.lmt).to.eql(bidderRequestData.ortb2.device.lmt); + expect(reqData.device.ifa).to.eql(bidderRequestData.ortb2.device.ifa); + expect(reqData.device.atts).to.eql(bidderRequestData.ortb2.device.ext.atts); expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.mimes).to.eql(bidRequestData[0].mediaTypes.video.mimes); - expect(reqData.request.mediaTypes.video.minduration).to.eql(bidRequestData[0].mediaTypes.video.minduration); - expect(reqData.request.mediaTypes.video.maxduration).to.eql(bidRequestData[0].mediaTypes.video.maxduration); - expect(reqData.request.mediaTypes.video.protocols).to.eql(bidRequestData[0].mediaTypes.video.protocols); - expect(reqData.request.mediaTypes.video.skip).to.eql(bidRequestData[0].mediaTypes.video.skip); - expect(reqData.request.mediaTypes.video.skipafter).to.eql(bidRequestData[0].mediaTypes.video.skipafter); - expect(reqData.request.mediaTypes.video.skipmin).to.eql(bidRequestData[0].mediaTypes.video.skipmin); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); - expect(reqData.request.mediaTypes.video.w).to.eql(bidRequestData[0].mediaTypes.video.w); - expect(reqData.request.mediaTypes.video.h).to.eql(bidRequestData[0].mediaTypes.video.h); + + expect(reqData.request.mediaTypes.video).to.eql(bidRequestData[0].mediaTypes.video); + expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, iabcat1: ['IAB-1'], @@ -308,11 +334,19 @@ describe('dailymotionBidAdapterTests', () => { private: bidRequestData[0].params.video.private, tags: bidRequestData[0].params.video.tags, title: bidRequestData[0].params.video.title, + url: bidRequestData[0].params.video.url, topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, // Overriden through bidder params duration: bidderRequestData.ortb2.app.content.len, livestream: !!bidRequestData[0].params.video.livestream, + isCreatedForKids: null, + context: { + siteOrAppCat: '', + videoViewsInSession: null, + autoplay: null, + playerVolume: null, + }, }); }); @@ -337,6 +371,10 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', topics: 'topic_1, topic_2', xid: 'x123456', + isCreatedForKids: false, + videoViewsInSession: 10, + autoplay: false, + playerVolume: 0, }, }, }]; @@ -363,6 +401,7 @@ describe('dailymotionBidAdapterTests', () => { language: 'FR', keywords: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', livestream: 1, cat: ['IAB-2'], data: [ @@ -419,6 +458,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + expect(reqData.pbv).to.eql('$prebid.version$'); expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -432,9 +472,22 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.false; expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + + expect(reqData.request.mediaTypes.video).to.eql({ + ...bidRequestData[0].mediaTypes.video, + mimes: [], + minduration: 0, + maxduration: 0, + playbackmethod: [], + plcmt: 1, + protocols: [], + skip: 0, + skipafter: 0, + skipmin: 0, + w: 0, + h: 0, + }); + expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, iabcat1: ['IAB-2'], @@ -444,10 +497,18 @@ describe('dailymotionBidAdapterTests', () => { private: bidRequestData[0].params.video.private, tags: bidderRequestData.ortb2.site.content.keywords, title: bidderRequestData.ortb2.site.content.title, + url: bidderRequestData.ortb2.site.content.url, topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, duration: bidRequestData[0].params.video.duration, livestream: !!bidderRequestData.ortb2.site.content.livestream, + isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, + context: { + siteOrAppCat: bidderRequestData.ortb2.site.content.cat, + videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, + autoplay: bidRequestData[0].params.video.autoplay, + playerVolume: bidRequestData[0].params.video.playerVolume, + }, }); }); @@ -470,6 +531,7 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.config.api_key).to.eql(bidRequestDataWithApi[0].params.apiKey); expect(reqData.coppa).to.be.false; + expect(reqData.pbv).to.eql('$prebid.version$'); expect(reqData.bidder_request).to.eql({ gdprConsent: { apiVersion: 1, @@ -496,6 +558,8 @@ describe('dailymotionBidAdapterTests', () => { mimes: [], minduration: 0, maxduration: 0, + playbackmethod: [], + plcmt: 1, protocols: [], skip: 0, skipafter: 0, @@ -518,9 +582,17 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: '', title: '', + url: '', topics: '', xid: '', livestream: false, + isCreatedForKids: null, + context: { + siteOrAppCat: '', + videoViewsInSession: null, + autoplay: null, + playerVolume: null, + }, }); }); @@ -557,4 +629,83 @@ describe('dailymotionBidAdapterTests', () => { expect(spec.interpretResponse(undefined)).to.have.lengthOf(0); }); + + it('validates getUserSyncs', () => { + // Nothing sent in getUserSyncs + expect(config.runWithBidder('dailymotion', () => spec.getUserSyncs())).to.eql([]); + + // No server response + { + const responses = []; + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // No permissions + { + const responses = [{ user_syncs: [{ url: 'https://usersyncurl.com', type: 'image' }] }]; + const syncOptions = { iframeEnabled: false, pixelEnabled: false }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // Has permissions but no user_syncs urls + { + const responses = [{}]; + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // Return user_syncs urls for pixels + { + const responses = [{ + user_syncs: [ + { url: 'https://usersyncurl.com', type: 'image' }, + { url: 'https://usersyncurl2.com', type: 'image' }, + { url: 'https://usersyncurl3.com', type: 'iframe' } + ], + }]; + + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([ + { type: 'image', url: 'https://usersyncurl.com' }, + { type: 'image', url: 'https://usersyncurl2.com' }, + ]); + } + + // Return user_syncs urls for iframes + { + const responses = [{ + user_syncs: [ + { url: 'https://usersyncurl.com', type: 'image' }, + { url: 'https://usersyncurl2.com', type: 'image' }, + { url: 'https://usersyncurl3.com', type: 'iframe' } + ], + }]; + + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([ + { type: 'iframe', url: 'https://usersyncurl3.com' }, + ]); + } + }); }); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js index d488048060a..f257434fd70 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -1,117 +1,239 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/e_volutionBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/e_volutionBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'e_volution'; describe('EvolutionTechBidAdapter', function () { - let bids = [{ - bidId: '23fhj33i987f', - bidder: 'e_volution', - params: { - placementId: 0 - }, - mediaTypes: { - banner: { - sizes: [[300, 250]], + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids }, - userId: { - id5id: 'id5id' - } - }, { - bidId: '23fhj33i987f', - bidder: 'e_volution', - params: { - placementId: 0 + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - video: { - playerSize: [300, 250] + [BANNER]: { + sizes: [[300, 250]] } }, - userId: { - id5id: 'id5id' - } - }, { - bidId: '23fhj33i987f', - bidder: 'e_volution', params: { - placementId: 0 - }, - mediaTypes: { - native: {} - }, - userId: { - id5id: 'id5id' + } - }]; + } const bidderRequest = { - uspConsent: 'uspConsent', - gdprConsent: 'gdprConsent' + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com' + }, + timeout: 500 }; describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { + it('Should return true if there are bidId, params and key parameters present', function () { expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bids[0].params.placementId; - expect(spec.isBidRequestValid(bids[0])).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://service.e-volution.ai/?c=o&m=multi'); - }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.ccpa).to.be.equal('uspConsent'); - expect(data.gdpr).to.be.equal('gdprConsent'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'bidfloor', 'eids'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('banner'); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); - placement = data['placements'][1]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'bidfloor', 'eids', 'wPlayer', 'hPlayer', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', - 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('video'); + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); - placement = data['placements'][2]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'bidfloor', 'eids', 'native'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('native'); + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; }); }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -128,7 +250,8 @@ describe('EvolutionTechBidAdapter', function () { currency: 'USD', dealId: '1', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -137,15 +260,16 @@ describe('EvolutionTechBidAdapter', function () { let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -160,7 +284,8 @@ describe('EvolutionTechBidAdapter', function () { currency: 'USD', dealId: '1', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -177,6 +302,7 @@ describe('EvolutionTechBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -195,7 +321,8 @@ describe('EvolutionTechBidAdapter', function () { netRevenue: true, currency: 'USD', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -216,6 +343,7 @@ describe('EvolutionTechBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { @@ -282,18 +410,4 @@ describe('EvolutionTechBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); - describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); - it('Returns valid URL and type', function () { - if (spec.noSync) { - expect(userSync).to.be.equal(false); - } else { - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://service.e-volution.ai/?c=o&m=sync'); - } - }); - }); }); diff --git a/test/spec/modules/genericAnalyticsAdapter_spec.js b/test/spec/modules/genericAnalyticsAdapter_spec.js index 2d9c7b4ae45..f574a33bf86 100644 --- a/test/spec/modules/genericAnalyticsAdapter_spec.js +++ b/test/spec/modules/genericAnalyticsAdapter_spec.js @@ -265,7 +265,7 @@ describe('Generic analytics', () => { handler([payload, {}]); sinon.assert.calledWith(ajax, url, sinon.match.any, sinon.match(data => sinon.match(payload).test(parse(data))), - {method} + {method, keepalive: true} ); }); @@ -275,7 +275,7 @@ describe('Generic analytics', () => { handler(payload); sinon.assert.calledWith(ajax, url, sinon.match.any, sinon.match(data => sinon.match(payload).test(parse(data))), - {method} + {method, keepalive: true} ); }); }); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index ba61e0853ee..3424d172775 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -100,6 +100,28 @@ describe('gumgumAdapter', function () { describe('buildRequests', function () { let sizesArray = [[300, 250], [300, 600]]; + const bidderRequest = { + ortb2: { + site: { + content: { + data: [{ + name: 'www.iris.com', + ext: { + segtax: 500, + cids: ['iris_c73g5jq96mwso4d8'] + } + }] + }, + page: 'http://pub.com/news', + ref: 'http://google.com', + publisher: { + id: 'p10000', + domain: 'pub.com' + } + } + } + }; + let bidRequests = [ { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', @@ -259,19 +281,17 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('iriscat'); }); + it('should set the irisid param when found iris_c73g5jq96mwso4d8', function() { + const request = { ...bidRequests[0], params: { irisid: 'abc123' } }; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data).to.have.property('irisid', 'iris_c73g5jq96mwso4d8'); + }); it('should not set the iriscat param when not found', function () { const request = { ...bidRequests[0] } const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.not.have.property('iriscat'); }); - - it('should set the irisid param when found', function () { - const request = { ...bidRequests[0], params: { irisid: 'abc123' } } - const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data).to.have.property('irisid'); - }); - it('should not set the irisid param when not found', function () { const request = { ...bidRequests[0] } const bidRequest = spec.buildRequests([request])[0]; @@ -285,10 +305,21 @@ describe('gumgumAdapter', function () { }); it('should set the global placement id (gpid) if in adserver property', function () { - const req = { ...bidRequests[0], ortb2Imp: { ext: { data: { adserver: { name: 'test', adslot: 123456 } } } } } + const req = { ...bidRequests[0], + ortb2Imp: { + ext: { + gpid: '/17037559/jeusol/jeusol_D_1', + data: { + adserver: { + name: 'test', + adslot: 123456 + } + } + } + } } const bidRequest = spec.buildRequests([req])[0]; expect(bidRequest.data).to.have.property('gpid'); - expect(bidRequest.data.gpid).to.equal(123456); + expect(bidRequest.data.gpid).to.equal('/17037559/jeusol/jeusol_D_1'); }); it('should set the global placement id (gpid) if in pbadslot property', function () { diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 88cfb93850f..42c0c2afdf5 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -821,8 +821,9 @@ describe('IndexexchangeAdapter', function () { tid: 'mock-tid' } }, - fledgeEnabled: true, - defaultForSlots: 1 + paapi: { + enabled: true + }, }; const DEFAULT_OPTION_FLEDGE_ENABLED = { @@ -843,7 +844,9 @@ describe('IndexexchangeAdapter', function () { tid: 'mock-tid' } }, - fledgeEnabled: true + paapi: { + enabled: true + } }; const DEFAULT_IDENTITY_RESPONSE = { @@ -1348,34 +1351,6 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('Roundel alias adapter', function () { - const vaildBids = [DEFAULT_BANNER_VALID_BID, DEFAULT_VIDEO_VALID_BID, DEFAULT_MULTIFORMAT_BANNER_VALID_BID, DEFAULT_MULTIFORMAT_VIDEO_VALID_BID]; - const ALIAS_OPTIONS = Object.assign({ - bidderCode: 'roundel' - }, DEFAULT_OPTION); - - it('should not build requests for mediaTypes if liveramp data is unavaliable', function () { - vaildBids.forEach((validBid) => { - const request = spec.buildRequests(validBid, ALIAS_OPTIONS); - expect(request).to.be.an('array'); - expect(request).to.have.lengthOf(0); - }); - }); - - it('should build requests for mediaTypes if liveramp data is avaliable', function () { - vaildBids.forEach((validBid) => { - const cloneValidBid = utils.deepClone(validBid); - cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); - const request = spec.buildRequests(cloneValidBid, ALIAS_OPTIONS); - const payload = extractPayload(request[0]); - expect(request).to.be.an('array'); - expect(request).to.have.lengthOf.above(0); // should be 1 or more - expect(payload.user.eids).to.have.lengthOf(11); - expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); - }); - }); - }); - describe('buildRequestsIdentity', function () { let request; let payload; @@ -3464,16 +3439,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.ext.ae).to.equal(1); }); - it('impression should have ae=1 in ext when fledge module is enabled globally and default is set through setConfig', function () { - const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED_GLOBALLY); - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; - const impression = extractPayload(requestBidFloor).imp[0]; - - expect(impression.ext.ae).to.equal(1); - }); - - it('impression should have ae=1 in ext when fledge module is enabled globally but no default set through setConfig but set at ad unit level', function () { + it('impression should have ae=1 in ext when request has paapi.enabled = true and ext.ae = 1', function () { const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; @@ -3517,6 +3483,22 @@ describe('IndexexchangeAdapter', function () { expect(logWarnSpy.calledWith('error setting auction environment flag - must be an integer')).to.be.true; logWarnSpy.restore(); }); + + it('impression should have paapi extension when passed', function () { + const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); + let bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); + bid.ortb2Imp.ext.ae = 1 + bid.ortb2Imp.ext.paapi = { + requestedSize: { + width: 300, + height: 250 + } + } + const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + expect(impression.ext.paapi.requestedSize.width).to.equal(300); + expect(impression.ext.paapi.requestedSize.height).to.equal(250); + }); }); describe('integration through exchangeId and externalId', function () { @@ -4164,7 +4146,7 @@ describe('IndexexchangeAdapter', function () { beforeEach(() => { bidderRequestWithFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED, {})[0]; - bidderRequestWithFledgeEnabled.fledgeEnabled = true; + bidderRequestWithFledgeEnabled.paapi = {enabled: true}; serverResponseWithoutFledgeConfigs = { body: { @@ -4228,17 +4210,17 @@ describe('IndexexchangeAdapter', function () { } } ]; - expect(result.fledgeAuctionConfigs).to.deep.equal(expectedOutput); + expect(result.paapi).to.deep.equal(expectedOutput); }); it('should correctly interpret response without auction configs', () => { const result = spec.interpretResponse(serverResponseWithoutFledgeConfigs, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.undefined; + expect(result.paapi).to.be.undefined; }); it('should handle malformed auction configs gracefully', () => { const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfig, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.empty; + expect(result.paapi).to.be.empty; }); it('should log warning for malformed auction configs', () => { @@ -4250,7 +4232,7 @@ describe('IndexexchangeAdapter', function () { it('should return bids when protected audience auction conigs is malformed', () => { const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfigs, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.undefined; + expect(result.paapi).to.be.undefined; expect(result.length).to.be.greaterThan(0); }); }); @@ -4269,7 +4251,7 @@ describe('IndexexchangeAdapter', function () { }; bidderRequestWithFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED, {})[0]; - bidderRequestWithFledgeEnabled.fledgeEnabled = true; + bidderRequestWithFledgeEnabled.paapi = {enabled: true}; bidderRequestWithoutFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {})[0]; }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index c57c8a685e7..36794ceeae3 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -629,7 +629,7 @@ describe('jwplayerRtdProvider', function() { expect(ortb2Fragments.global).to.have.property('site'); expect(ortb2Fragments.global.site).to.have.property('content'); - expect(ortb2Fragments.global.site.content).to.have.property('id', 'jw_' + testIdForSuccess); + expect(ortb2Fragments.global.site.content).to.have.property('id', 'randomContentId'); expect(ortb2Fragments.global.site.content).to.have.property('data'); const data = ortb2Fragments.global.site.content.data; expect(data).to.have.length(3); @@ -801,7 +801,7 @@ describe('jwplayerRtdProvider', function() { describe(' Add Ortb Site Content', function () { beforeEach(() => { setOverrides({ - overrideContentId: 'always', + overrideContentId: 'whenEmpty', overrideContentUrl: 'whenEmpty', overrideContentTitle: 'whenEmpty', overrideContentDescription: 'whenEmpty' @@ -865,16 +865,16 @@ describe('jwplayerRtdProvider', function() { } }; - const expectedId = 'expectedId'; + const newId = 'newId'; const expectedUrl = 'expectedUrl'; const expectedTitle = 'expectedTitle'; const expectedDescription = 'expectedDescription'; const expectedData = { datum: 'datum' }; - addOrtbSiteContent(ortb2, expectedId, expectedData, expectedTitle, expectedDescription, expectedUrl); + addOrtbSiteContent(ortb2, newId, expectedData, expectedTitle, expectedDescription, expectedUrl); expect(ortb2).to.have.nested.property('site.random.random_sub', 'randomSub'); expect(ortb2).to.have.nested.property('app.content.id', 'appId'); expect(ortb2).to.have.nested.property('site.content.ext.random_field', 'randomField'); - expect(ortb2).to.have.nested.property('site.content.id', expectedId); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); expect(ortb2).to.have.nested.property('site.content.url', expectedUrl); expect(ortb2).to.have.nested.property('site.content.title', expectedTitle); expect(ortb2).to.have.nested.property('site.content.ext.description', expectedDescription); @@ -889,7 +889,7 @@ describe('jwplayerRtdProvider', function() { expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); - it('should override content id by default', function () { + it('should keep old content id by default', function () { const ortb2 = { site: { content: { @@ -898,9 +898,8 @@ describe('jwplayerRtdProvider', function() { } }; - const expectedId = 'expectedId'; - addOrtbSiteContent(ortb2, expectedId); - expect(ortb2).to.have.nested.property('site.content.id', expectedId); + addOrtbSiteContent(ortb2, 'newId'); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); }); it('should keep previous content id when new value is not available', function () { diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index ee89d6468a5..6a27246d394 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1808,7 +1808,7 @@ describe('kargo adapter tests', function() { }); }); - it('should return fledgeAuctionConfigs if provided in bid response', function () { + it('should return paapi if provided in bid response', function () { const auctionConfig = { seller: 'https://kargo.com', decisionLogicUrl: 'https://kargo.com/decision_logic.js', @@ -1841,11 +1841,11 @@ describe('kargo adapter tests', function() { expect(bid).to.have.property('meta').that.is.an('object'); }); - // Test properties of fledgeAuctionConfigs - expect(result.fledgeAuctionConfigs).to.have.lengthOf(3); + // Test properties of paapi + expect(result.paapi).to.have.lengthOf(3); const expectedBidIds = ['1', '3', '5']; // Expected bidIDs - result.fledgeAuctionConfigs.forEach(config => { + result.paapi.forEach(config => { expect(config).to.have.property('bidId'); expect(expectedBidIds).to.include(config.bidId); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 86437180e94..16f87394df9 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -80,7 +80,7 @@ describe('KrushmediabBidAdapter', function () { let placement = data['placements'][0]; expect(placement).to.be.an('object'); expect(placement).to.have.keys('key', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain', 'bidFloor', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', + 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); expect(placement.traffic).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); diff --git a/test/spec/modules/loganBidAdapter_spec.js b/test/spec/modules/loganBidAdapter_spec.js index a9859bbd4ae..f51f22580e2 100644 --- a/test/spec/modules/loganBidAdapter_spec.js +++ b/test/spec/modules/loganBidAdapter_spec.js @@ -79,7 +79,7 @@ describe('LoganBidAdapter', function () { expect(data).to.be.an('object'); let placement = data['placements'][0]; expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'wPlayer', 'hPlayer', 'schain', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'bidfloor'); + expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'wPlayer', 'hPlayer', 'schain', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'bidfloor'); expect(placement.adFormat).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); expect(placement.hPlayer).to.equal(playerSize[1]); diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js index 12e8ca31cbb..5c86ffc9325 100644 --- a/test/spec/modules/logicadBidAdapter_spec.js +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -182,7 +182,9 @@ describe('LogicadAdapter', function () { stack: [] }, auctionStart: 1563337198010, - fledgeEnabled: true + paapi: { + enabled: true + } }; const serverResponse = { body: { @@ -388,8 +390,8 @@ describe('LogicadAdapter', function () { const paapiRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; const paapiInterpretedResponse = spec.interpretResponse(paapiServerResponse, paapiRequest); expect(paapiInterpretedResponse).to.have.property('bids'); - expect(paapiInterpretedResponse).to.have.property('fledgeAuctionConfigs'); - expect(paapiInterpretedResponse.fledgeAuctionConfigs[0]).to.deep.equal(paapiServerResponse.body.ext.fledgeAuctionConfigs[0]); + expect(paapiInterpretedResponse).to.have.property('paapi'); + expect(paapiInterpretedResponse.paapi[0]).to.deep.equal(paapiServerResponse.body.ext.fledgeAuctionConfigs[0]); // native const nativeRequest = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index cc1a15fd733..bdfc86e7148 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1768,12 +1768,12 @@ describe('Media.net bid adapter', function () { }); it('should have valid payload when PAAPI is enabled', function () { - let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_PAAPI); }); it('should send whatever is set in ortb2imp.ext.ae in all bid requests when PAAPI is enabled', function () { - let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); let data = JSON.parse(bidReq.data); expect(data).to.deep.equal(VALID_PAYLOAD_PAAPI); expect(data.imp[0].ext).to.have.property('ae'); @@ -1955,30 +1955,30 @@ describe('Media.net bid adapter', function () { expect(bids).to.deep.equal(validBids); }); - it('should return fledgeAuctionConfigs if PAAPI response is received', function() { + it('should return paapi if PAAPI response is received', function() { let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs[0]).to.deep.equal(SERVER_RESPONSE_PAAPI.body.ext.paApiAuctionConfigs[0]); + expect(response).to.have.property('paapi'); + expect(response.paapi[0]).to.deep.equal(SERVER_RESPONSE_PAAPI.body.ext.paApiAuctionConfigs[0]); }); - it('should return fledgeAuctionConfigs if openRTB PAAPI response received', function () { + it('should return paapi if openRTB PAAPI response received', function () { let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs[0]).to.deep.equal(SERVER_RESPONSE_PAAPI_ORTB.body.ext.igi[0].igs[0]) + expect(response).to.have.property('paapi'); + expect(response.paapi[0]).to.deep.equal(SERVER_RESPONSE_PAAPI_ORTB.body.ext.igi[0].igs[0]) }); - it('should have the correlation between fledgeAuctionConfigs[0].bidId and bidreq.imp[0].id', function() { - let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + it('should have the correlation between paapi[0].bidId and bidreq.imp[0].id', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); - expect(bidRes.fledgeAuctionConfigs[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + expect(bidRes.paapi[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) }); - it('should have the correlation between fledgeAuctionConfigs[0].bidId and bidreq.imp[0].id for openRTB response', function() { - let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + it('should have the correlation between paapi[0].bidId and bidreq.imp[0].id for openRTB response', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); - expect(bidRes.fledgeAuctionConfigs[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + expect(bidRes.paapi[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) }); }); diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index a4e58afbd1b..ad88f18eb4c 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -83,7 +83,7 @@ describe('MobfoxHBBidAdapter', function () { let placement = data['placements'][0]; expect(placement).to.be.an('object'); expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'playerSize', 'wPlayer', 'hPlayer', 'schain', 'bidfloor', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', + 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); expect(placement.traffic).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 3ceaec13cd5..a6edaaabe79 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -436,13 +436,15 @@ describe('onetag', function () { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'fledgeEnabled': true + 'paapi': { + 'enabled': true + } }; let serverRequest = spec.buildRequests([bannerBid], bidderRequest); const payload = JSON.parse(serverRequest.data); expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.paapi.enabled); }); it('Should send FLEDGE eligibility flag when FLEDGE is not enabled', function () { let bidderRequest = { @@ -450,13 +452,15 @@ describe('onetag', function () { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'fledgeEnabled': false + paapi: { + enabled: false + } }; let serverRequest = spec.buildRequests([bannerBid], bidderRequest); const payload = JSON.parse(serverRequest.data); expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.paapi.enabled); }); it('Should send FLEDGE eligibility flag set to false when fledgeEnabled is not defined', function () { let bidderRequest = { @@ -485,7 +489,7 @@ describe('onetag', function () { expect(fledgeInterpretedResponse.bids).to.satisfy(function (value) { return value === null || Array.isArray(value); }); - expect(fledgeInterpretedResponse.fledgeAuctionConfigs).to.be.an('array').that.is.not.empty; + expect(fledgeInterpretedResponse.paapi).to.be.an('array').that.is.not.empty; for (let i = 0; i < interpretedResponse.length; i++) { let dataItem = interpretedResponse[i]; expect(dataItem).to.include.all.keys('requestId', 'cpm', 'width', 'height', 'ttl', 'creativeId', 'netRevenue', 'currency', 'meta', 'dealId'); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 678102a91fc..ad4ee1e74ce 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -13,11 +13,12 @@ import 'modules/priceFloors.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/paapi.js'; + import {deepClone} from 'src/utils.js'; import {version} from 'package.json'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; - const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; const BidRequestBuilder = function BidRequestBuilder(options) { @@ -1037,7 +1038,9 @@ describe('OpenxRtbAdapter', function () { it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, { ...mockBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } }); expect(request[0].data.imp[0].ext.ae).to.equal(2); }); @@ -1503,13 +1506,13 @@ describe('OpenxRtbAdapter', function () { it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test-bid-id'); }); it('should inject ortb2Imp in auctionSignals', function () { - const auctionConfig = response.fledgeAuctionConfigs[0].config; + const auctionConfig = response.paapi[0].config; expect(auctionConfig).to.deep.include({ auctionSignals: { ortb2Imp: { diff --git a/test/spec/modules/optableBidAdapter_spec.js b/test/spec/modules/optableBidAdapter_spec.js index d7f2230328e..ef04474c270 100644 --- a/test/spec/modules/optableBidAdapter_spec.js +++ b/test/spec/modules/optableBidAdapter_spec.js @@ -78,10 +78,10 @@ describe('optableBidAdapter', function() { } }; - it('maps fledgeAuctionConfigs from ext.optable.fledge.auctionconfigs', function() { + it('maps paapi from ext.optable.fledge.auctionconfigs', function() { const request = spec.buildRequests([validBid], bidderRequest); const result = spec.interpretResponse(response, request); - expect(result.fledgeAuctionConfigs).to.deep.equal([ + expect(result.paapi).to.deep.equal([ { bidId: 'bid123', config: { seller: 'https://ads.optable.co' } } ]); }); diff --git a/test/spec/modules/fledgeForGpt_spec.js b/test/spec/modules/paapiForGpt_spec.js similarity index 68% rename from test/spec/modules/fledgeForGpt_spec.js rename to test/spec/modules/paapiForGpt_spec.js index 8ab11171121..107d2c235f3 100644 --- a/test/spec/modules/fledgeForGpt_spec.js +++ b/test/spec/modules/paapiForGpt_spec.js @@ -1,11 +1,16 @@ -import {onAuctionConfigFactory, setPAAPIConfigFactory, slotConfigurator} from 'modules/fledgeForGpt.js'; +import { + getPAAPISizeHook, + onAuctionConfigFactory, + setPAAPIConfigFactory, + slotConfigurator +} from 'modules/paapiForGpt.js'; import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; import {deepSetValue} from '../../../src/utils.js'; import {config} from 'src/config.js'; -describe('fledgeForGpt module', () => { +describe('paapiForGpt module', () => { let sandbox, fledgeAuctionConfig; beforeEach(() => { @@ -97,42 +102,35 @@ describe('fledgeForGpt module', () => { }); }); describe('onAuctionConfig', () => { - [ - 'fledgeForGpt', - 'paapi.gpt' - ].forEach(namespace => { - describe(`using ${namespace} config`, () => { - Object.entries({ - 'omitted': [undefined, true], - 'enabled': [true, true], - 'disabled': [false, false] - }).forEach(([t, [autoconfig, shouldSetConfig]]) => { - describe(`when autoconfig is ${t}`, () => { - beforeEach(() => { - const cfg = {}; - deepSetValue(cfg, `${namespace}.autoconfig`, autoconfig); - config.setConfig(cfg); - }); - afterEach(() => { - config.resetConfig(); - }); + Object.entries({ + 'omitted': [undefined, false], + 'enabled': [true, true], + 'disabled': [false, false] + }).forEach(([t, [autoconfig, shouldSetConfig]]) => { + describe(`when autoconfig is ${t}`, () => { + beforeEach(() => { + const cfg = {}; + deepSetValue(cfg, `paapi.gpt.autoconfig`, autoconfig); + config.setConfig(cfg); + }); + afterEach(() => { + config.resetConfig(); + }); - it(`should ${shouldSetConfig ? '' : 'NOT'} set GPT slot configuration`, () => { - const auctionConfig = {componentAuctions: [{seller: 'mock1'}, {seller: 'mock2'}]}; - const setGptConfig = sinon.stub(); - const markAsUsed = sinon.stub(); - onAuctionConfigFactory(setGptConfig)('aid', {au1: auctionConfig, au2: null}, markAsUsed); - if (shouldSetConfig) { - sinon.assert.calledWith(setGptConfig, 'au1', auctionConfig.componentAuctions); - sinon.assert.calledWith(setGptConfig, 'au2', []); - sinon.assert.calledWith(markAsUsed, 'au1'); - } else { - sinon.assert.notCalled(setGptConfig); - sinon.assert.notCalled(markAsUsed); - } - }); - }) - }) + it(`should ${shouldSetConfig ? '' : 'NOT'} set GPT slot configuration`, () => { + const auctionConfig = {componentAuctions: [{seller: 'mock1'}, {seller: 'mock2'}]}; + const setGptConfig = sinon.stub(); + const markAsUsed = sinon.stub(); + onAuctionConfigFactory(setGptConfig)('aid', {au1: auctionConfig, au2: null}, markAsUsed); + if (shouldSetConfig) { + sinon.assert.calledWith(setGptConfig, 'au1', auctionConfig.componentAuctions); + sinon.assert.calledWith(setGptConfig, 'au2', []); + sinon.assert.calledWith(markAsUsed, 'au1'); + } else { + sinon.assert.notCalled(setGptConfig); + sinon.assert.notCalled(markAsUsed); + } + }); }) }) }); @@ -173,5 +171,29 @@ describe('fledgeForGpt module', () => { sinon.assert.calledWith(setGptConfig, au, config?.componentAuctions ?? [], true); }) }); + }); + + describe('getPAAPISizeHook', () => { + let next; + beforeEach(() => { + next = sinon.stub(); + next.bail = sinon.stub(); + }); + + it('should pick largest supported size over larger unsupported size', () => { + getPAAPISizeHook(next, [[999, 999], [300, 250], [300, 600], [1234, 4321]]); + sinon.assert.calledWith(next.bail, [300, 600]); + }); + + Object.entries({ + 'present': [], + 'supported': [[123, 4], [321, 5]], + 'defined': undefined, + }).forEach(([t, sizes]) => { + it(`should defer to next when no size is ${t}`, () => { + getPAAPISizeHook(next, sizes); + sinon.assert.calledWith(next, sizes); + }) + }) }) }); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index bc1faa23833..7814c09ea61 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -2,31 +2,32 @@ import {expect} from 'chai'; import {config} from '../../../src/config.js'; import adapterManager from '../../../src/adapterManager.js'; import * as utils from '../../../src/utils.js'; +import {deepAccess, deepClone} from '../../../src/utils.js'; import {hook} from '../../../src/hook.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; import { addPaapiConfigHook, + buyersToAuctionConfigs, getPAAPIConfig, - parseExtPrebidFledge, - registerSubmodule, - setImpExtAe, - setResponsePaapiConfigs, - reset, - parseExtIgi, - mergeBuyers, + getPAAPISize, IGB_TO_CONFIG, + mergeBuyers, + parseExtIgi, + parseExtPrebidFledge, partitionBuyers, partitionBuyersByBidder, - buyersToAuctionConfigs + registerSubmodule, + reset, + setImpExtAe, + setResponsePaapiConfigs } from 'modules/paapi.js'; import * as events from 'src/events.js'; -import { EVENTS } from 'src/constants.js'; +import {EVENTS} from 'src/constants.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {AuctionIndex} from '../../../src/auctionIndex.js'; -import {deepAccess, deepClone} from '../../../src/utils.js'; describe('paapi module', () => { let sandbox; @@ -39,412 +40,401 @@ describe('paapi module', () => { reset(); }); - [ - 'fledgeForGpt', - 'paapi' - ].forEach(configNS => { - describe(`using ${configNS} for configuration`, () => { - describe('getPAAPIConfig', function () { - let nextFnSpy, auctionConfig, paapiConfig; - before(() => { - config.setConfig({[configNS]: {enabled: true}}); - }); - beforeEach(() => { - auctionConfig = { - seller: 'bidder', - mock: 'config' - }; - paapiConfig = { - config: auctionConfig - } - nextFnSpy = sinon.spy(); - }); + describe(`using paapi configuration`, () => { + let getPAAPISizeStub; - describe('on a single auction', function () { - const auctionId = 'aid'; - beforeEach(function () { - sandbox.stub(auctionManager, 'index').value(stubAuctionIndex({auctionId})); - }); + function getPAAPISizeHook(next, sizes) { + next.bail(getPAAPISizeStub(sizes)); + } - it('should call next()', function () { - const request = {auctionId, adUnitCode: 'auc'}; - addPaapiConfigHook(nextFnSpy, request, paapiConfig); - sinon.assert.calledWith(nextFnSpy, request, paapiConfig); - }); + before(() => { + getPAAPISize.before(getPAAPISizeHook, 100); + }); - describe('igb', () => { - let igb1, igb2, buyerAuctionConfig; - beforeEach(() => { - igb1 = { - origin: 'buyer1' - }; - igb2 = { - origin: 'buyer2' - } - buyerAuctionConfig = { - seller: 'seller', - decisionLogicURL: 'seller-decision-logic' - } - config.mergeConfig({ - [configNS]: { - componentSeller: { - auctionConfig: buyerAuctionConfig - } - } - }); - }); + after(() => { + getPAAPISize.getHooks({hook: getPAAPISizeHook}).remove(); + }); - function addIgb(request, igb) { - addPaapiConfigHook(nextFnSpy, Object.assign({auctionId}, request), {igb}); - } + beforeEach(() => { + getPAAPISizeStub = sinon.stub(); + }); - it('should be collected into an auction config', () => { - addIgb({adUnitCode: 'au1'}, igb1); - addIgb({adUnitCode: 'au1'}, igb2); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - const buyerConfig = getPAAPIConfig({auctionId}).au1.componentAuctions[0]; - sinon.assert.match(buyerConfig, { - interestGroupBuyers: [igb1.origin, igb2.origin], - ...buyerAuctionConfig - }) - }); + describe('getPAAPIConfig', function () { + let nextFnSpy, auctionConfig, paapiConfig; + before(() => { + config.setConfig({paapi: {enabled: true}}); + }); + beforeEach(() => { + auctionConfig = { + seller: 'bidder', + mock: 'config' + }; + paapiConfig = { + config: auctionConfig + }; + nextFnSpy = sinon.spy(); + }); - describe('FPD', () => { - let ortb2, ortb2Imp; - beforeEach(() => { - ortb2 = {'fpd': 1}; - ortb2Imp = {'fpd': 2} - }); + describe('on a single auction', function () { + const auctionId = 'aid'; + beforeEach(function () { + sandbox.stub(auctionManager, 'index').value(stubAuctionIndex({auctionId})); + }); - function getBuyerAuctionConfig() { - addIgb({adUnitCode: 'au1', ortb2, ortb2Imp}, igb1); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + it('should call next()', function () { + const request = {auctionId, adUnitCode: 'auc'}; + addPaapiConfigHook(nextFnSpy, request, paapiConfig); + sinon.assert.calledWith(nextFnSpy, request, paapiConfig); + }); + + describe('igb', () => { + let igb1, igb2, buyerAuctionConfig; + beforeEach(() => { + igb1 = { + origin: 'buyer1' + }; + igb2 = { + origin: 'buyer2' + }; + buyerAuctionConfig = { + seller: 'seller', + decisionLogicURL: 'seller-decision-logic' + }; + config.mergeConfig({ + paapi: { + componentSeller: { + auctionConfig: buyerAuctionConfig + } } + }); + }); - it('should be added to auction config', () => { - sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { - prebid: { - ortb2, - ortb2Imp - } - }) - }); + function addIgb(request, igb) { + addPaapiConfigHook(nextFnSpy, Object.assign({auctionId}, request), {igb}); + } - it('should not override existing perBuyerSignals', () => { - const original = { - ortb2: { - fpd: 'original' - } - }; - igb1.pbs = { - prebid: deepClone(original) - } - sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { - prebid: original - }) - }) + it('should be collected into an auction config', () => { + addIgb({adUnitCode: 'au1'}, igb1); + addIgb({adUnitCode: 'au1'}, igb2); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + const buyerConfig = getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + sinon.assert.match(buyerConfig, { + interestGroupBuyers: [igb1.origin, igb2.origin], + ...buyerAuctionConfig }); }); - describe('should collect auction configs', () => { - let cf1, cf2; + describe('FPD', () => { + let ortb2, ortb2Imp; beforeEach(() => { - cf1 = {...auctionConfig, id: 1, seller: 'b1'}; - cf2 = {...auctionConfig, id: 2, seller: 'b2'}; - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, {config: cf1}); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, {config: cf2}); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + ortb2 = {'fpd': 1}; + ortb2Imp = {'fpd': 2}; }); - it('and make them available at end of auction', () => { - sinon.assert.match(getPAAPIConfig({auctionId}), { - au1: { - componentAuctions: [cf1] - }, - au2: { - componentAuctions: [cf2] + function getBuyerAuctionConfig() { + addIgb({adUnitCode: 'au1', ortb2, ortb2Imp}, igb1); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + } + + it('should be added to auction config', () => { + sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { + prebid: { + ortb2, + ortb2Imp } }); }); - it('and filter them by ad unit', () => { - const cfg = getPAAPIConfig({auctionId, adUnitCode: 'au1'}); - expect(Object.keys(cfg)).to.have.members(['au1']); - sinon.assert.match(cfg.au1, { - componentAuctions: [cf1] + it('should not override existing perBuyerSignals', () => { + const original = { + ortb2: { + fpd: 'original' + } + }; + igb1.pbs = { + prebid: deepClone(original) + }; + sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { + prebid: original }); }); + }); + }); + + describe('should collect auction configs', () => { + let cf1, cf2; + beforeEach(() => { + cf1 = {...auctionConfig, id: 1, seller: 'b1'}; + cf2 = {...auctionConfig, id: 2, seller: 'b2'}; + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, {config: cf1}); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, {config: cf2}); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + }); - it('and not return them again', () => { - getPAAPIConfig(); - const cfg = getPAAPIConfig(); - expect(cfg).to.eql({}); + it('and make them available at end of auction', () => { + sinon.assert.match(getPAAPIConfig({auctionId}), { + au1: { + componentAuctions: [cf1] + }, + au2: { + componentAuctions: [cf2] + } }); + }); - describe('includeBlanks = true', () => { - it('includes all ad units', () => { - const cfg = getPAAPIConfig({}, true); - expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); - expect(cfg.au3).to.eql(null); - }) - it('includes the targeted adUnit', () => { - expect(getPAAPIConfig({adUnitCode: 'au3'}, true)).to.eql({ - au3: null - }) - }); - it('includes the targeted auction', () => { - const cfg = getPAAPIConfig({auctionId}, true); - expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); - expect(cfg.au3).to.eql(null); - }); - it('does not include non-existing ad units', () => { - expect(getPAAPIConfig({adUnitCode: 'other'})).to.eql({}); + it('and filter them by ad unit', () => { + const cfg = getPAAPIConfig({auctionId, adUnitCode: 'au1'}); + expect(Object.keys(cfg)).to.have.members(['au1']); + sinon.assert.match(cfg.au1, { + componentAuctions: [cf1] + }); + }); + + it('and not return them again', () => { + getPAAPIConfig(); + const cfg = getPAAPIConfig(); + expect(cfg).to.eql({}); + }); + + describe('includeBlanks = true', () => { + it('includes all ad units', () => { + const cfg = getPAAPIConfig({}, true); + expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); + expect(cfg.au3).to.eql(null); + }); + it('includes the targeted adUnit', () => { + expect(getPAAPIConfig({adUnitCode: 'au3'}, true)).to.eql({ + au3: null }); - it('does not include non-existing auctions', () => { - expect(getPAAPIConfig({auctionId: 'other'})).to.eql({}); - }) + }); + it('includes the targeted auction', () => { + const cfg = getPAAPIConfig({auctionId}, true); + expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); + expect(cfg.au3).to.eql(null); + }); + it('does not include non-existing ad units', () => { + expect(getPAAPIConfig({adUnitCode: 'other'})).to.eql({}); + }); + it('does not include non-existing auctions', () => { + expect(getPAAPIConfig({auctionId: 'other'})).to.eql({}); }); }); + }); + + it('should drop auction configs after end of auction', () => { + events.emit(EVENTS.AUCTION_END, {auctionId}); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId}); + expect(getPAAPIConfig({auctionId})).to.eql({}); + }); - it('should drop auction configs after end of auction', () => { - events.emit(EVENTS.AUCTION_END, { auctionId }); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, { auctionId }); - expect(getPAAPIConfig({auctionId})).to.eql({}); + describe('FPD', () => { + let ortb2, ortb2Imp; + beforeEach(() => { + ortb2 = {fpd: 1}; + ortb2Imp = {fpd: 2}; }); - it('should use first size as requestedSize', () => { + function getComponentAuctionConfig() { addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au1', + ortb2: {fpd: 1}, + ortb2Imp: {fpd: 2} }, paapiConfig); - events.emit(EVENTS.AUCTION_END, { - auctionId, - adUnits: [ - { - code: 'au1', - mediaTypes: { - banner: { - sizes: [[200, 100], [300, 200]] - } - } - } - ] - }); - expect(getPAAPIConfig({auctionId}).au1.requestedSize).to.eql({ - width: '200', - height: '100' - }) - }) + events.emit(EVENTS.AUCTION_END, {auctionId}); + return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + } - describe('FPD', () => { - let ortb2, ortb2Imp; - beforeEach(() => { - ortb2 = {fpd: 1}; - ortb2Imp = {fpd: 2}; + it('should be added to auctionSignals', () => { + sinon.assert.match(getComponentAuctionConfig().auctionSignals, { + prebid: {ortb2, ortb2Imp} }); - - function getComponentAuctionConfig() { - addPaapiConfigHook(nextFnSpy, { - auctionId, - adUnitCode: 'au1', - ortb2: {fpd: 1}, - ortb2Imp: {fpd: 2} - }, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId}); - return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; - } - - it('should be added to auctionSignals', () => { - sinon.assert.match(getComponentAuctionConfig().auctionSignals, { - prebid: {ortb2, ortb2Imp} - }) + }); + it('should not override existing auctionSignals', () => { + auctionConfig.auctionSignals = {prebid: {ortb2: {fpd: 'original'}}}; + sinon.assert.match(getComponentAuctionConfig().auctionSignals, { + prebid: { + ortb2: {fpd: 'original'}, + ortb2Imp + } }); - it('should not override existing auctionSignals', () => { - auctionConfig.auctionSignals = {prebid: {ortb2: {fpd: 'original'}}} - sinon.assert.match(getComponentAuctionConfig().auctionSignals, { - prebid: { - ortb2: {fpd: 'original'}, - ortb2Imp - } - }) - }) - - it('should be added to perBuyerSignals', () => { - auctionConfig.interestGroupBuyers = ['buyer1', 'buyer2']; - const pbs = getComponentAuctionConfig().perBuyerSignals; - sinon.assert.match(pbs, { - buyer1: {prebid: {ortb2, ortb2Imp}}, - buyer2: {prebid: {ortb2, ortb2Imp}} - }) + }); + + it('should be added to perBuyerSignals', () => { + auctionConfig.interestGroupBuyers = ['buyer1', 'buyer2']; + const pbs = getComponentAuctionConfig().perBuyerSignals; + sinon.assert.match(pbs, { + buyer1: {prebid: {ortb2, ortb2Imp}}, + buyer2: {prebid: {ortb2, ortb2Imp}} }); + }); - it('should not override existing perBuyerSignals', () => { - auctionConfig.interestGroupBuyers = ['buyer']; - const original = { - prebid: { - ortb2: { - fpd: 'original' - } + it('should not override existing perBuyerSignals', () => { + auctionConfig.interestGroupBuyers = ['buyer']; + const original = { + prebid: { + ortb2: { + fpd: 'original' } } - auctionConfig.perBuyerSignals = { - buyer: deepClone(original) - } - sinon.assert.match(getComponentAuctionConfig().perBuyerSignals.buyer, original); - }); - }) + }; + auctionConfig.perBuyerSignals = { + buyer: deepClone(original) + }; + sinon.assert.match(getComponentAuctionConfig().perBuyerSignals.buyer, original); + }); + }); - describe('submodules', () => { - let submods; - beforeEach(() => { - submods = [1, 2].map(i => ({ - name: `test${i}`, - onAuctionConfig: sinon.stub() - })); - submods.forEach(registerSubmodule); - }); + describe('submodules', () => { + let submods; + beforeEach(() => { + submods = [1, 2].map(i => ({ + name: `test${i}`, + onAuctionConfig: sinon.stub() + })); + submods.forEach(registerSubmodule); + }); - describe('onAuctionConfig', () => { - const auctionId = 'aid'; - it('is invoked with null configs when there\'s no config', () => { - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au'] }); - submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); - }); - it('is invoked with relevant configs', () => { - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); - submods.forEach(submod => { - sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { - au1: {componentAuctions: [auctionConfig]}, - au2: {componentAuctions: [auctionConfig]}, - au3: null - }) + describe('onAuctionConfig', () => { + const auctionId = 'aid'; + it('is invoked with null configs when there\'s no config', () => { + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au']}); + submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); + }); + it('is invoked with relevant configs', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + submods.forEach(submod => { + sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { + au1: {componentAuctions: [auctionConfig]}, + au2: {componentAuctions: [auctionConfig]}, + au3: null }); }); - it('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { - submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { - markAsUsed('au1'); - }); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - expect(getPAAPIConfig()).to.eql({}); + }); + it('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { + submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { + markAsUsed('au1'); }); - it('keeps them available if they do not', () => { - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - expect(getPAAPIConfig()).to.not.be.empty; - }) + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + expect(getPAAPIConfig()).to.eql({}); + }); + it('keeps them available if they do not', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + expect(getPAAPIConfig()).to.not.be.empty; }); }); + }); - describe('floor signal', () => { - before(() => { - if (!getGlobal().convertCurrency) { - getGlobal().convertCurrency = () => null; - getGlobal().convertCurrency.mock = true; - } - }); - after(() => { - if (getGlobal().convertCurrency.mock) { - delete getGlobal().convertCurrency; - } - }); + describe('floor signal', () => { + before(() => { + if (!getGlobal().convertCurrency) { + getGlobal().convertCurrency = () => null; + getGlobal().convertCurrency.mock = true; + } + }); + after(() => { + if (getGlobal().convertCurrency.mock) { + delete getGlobal().convertCurrency; + } + }); - beforeEach(() => { - sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { - if (from === to) return amount; - if (from === 'USD' && to === 'JPY') return amount * 100; - if (from === 'JPY' && to === 'USD') return amount / 100; - throw new Error('unexpected currency conversion'); - }); + beforeEach(() => { + sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { + if (from === to) return amount; + if (from === 'USD' && to === 'JPY') return amount * 100; + if (from === 'JPY' && to === 'USD') return amount / 100; + throw new Error('unexpected currency conversion'); }); + }); - Object.entries({ - 'bids': (payload, values) => { - payload.bidsReceived = values - .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) - .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]); - }, - 'no bids': (payload, values) => { - payload.bidderRequests = values - .map((val) => ({ - bids: [{ - adUnitCode: 'au', - getFloor: () => ({floor: val.amount, currency: val.cur}) - }] - })) - .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]); - } - }).forEach(([tcase, setup]) => { - describe(`when auction has ${tcase}`, () => { - Object.entries({ - 'no currencies': { - values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], - 'bids': { - bidfloor: 100, - bidfloorcur: undefined - }, - 'no bids': { - bidfloor: 1, - bidfloorcur: undefined, - } + Object.entries({ + 'bids': (payload, values) => { + payload.bidsReceived = values + .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) + .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]); + }, + 'no bids': (payload, values) => { + payload.bidderRequests = values + .map((val) => ({ + bids: [{ + adUnitCode: 'au', + getFloor: () => ({floor: val.amount, currency: val.cur}) + }] + })) + .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]); + } + }).forEach(([tcase, setup]) => { + describe(`when auction has ${tcase}`, () => { + Object.entries({ + 'no currencies': { + values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], + 'bids': { + bidfloor: 100, + bidfloorcur: undefined }, - 'only zero values': { - values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], - 'bids': { - bidfloor: undefined, - bidfloorcur: undefined, - }, - 'no bids': { - bidfloor: undefined, - bidfloorcur: undefined, - } + 'no bids': { + bidfloor: 1, + bidfloorcur: undefined, + } + }, + 'only zero values': { + values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], + 'bids': { + bidfloor: undefined, + bidfloorcur: undefined, }, - 'matching currencies': { - values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], - 'bids': { - bidfloor: 100, - bidfloorcur: 'JPY', - }, - 'no bids': { - bidfloor: 10, - bidfloorcur: 'JPY', - } + 'no bids': { + bidfloor: undefined, + bidfloorcur: undefined, + } + }, + 'matching currencies': { + values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], + 'bids': { + bidfloor: 100, + bidfloorcur: 'JPY', }, - 'mixed currencies': { - values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], - 'bids': { - bidfloor: 10, - bidfloorcur: 'USD' - }, - 'no bids': { - bidfloor: 10, - bidfloorcur: 'JPY', - } + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', } - }).forEach(([t, testConfig]) => { - const values = testConfig.values; - const {bidfloor, bidfloorcur} = testConfig[tcase]; - - describe(`with ${t}`, () => { - let payload; - beforeEach(() => { - payload = {auctionId}; - setup(payload, values); - }); - - it('should populate bidfloor/bidfloorcur', () => { - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, payload); - const cfg = getPAAPIConfig({auctionId}).au; - const signals = cfg.auctionSignals; - sinon.assert.match(cfg.componentAuctions[0].auctionSignals, signals || {}); - expect(signals?.prebid?.bidfloor).to.eql(bidfloor); - expect(signals?.prebid?.bidfloorcur).to.eql(bidfloorcur); - }); + }, + 'mixed currencies': { + values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], + 'bids': { + bidfloor: 10, + bidfloorcur: 'USD' + }, + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', + } + } + }).forEach(([t, testConfig]) => { + const values = testConfig.values; + const {bidfloor, bidfloorcur} = testConfig[tcase]; + + describe(`with ${t}`, () => { + let payload; + beforeEach(() => { + payload = {auctionId}; + setup(payload, values); + }); + + it('should populate bidfloor/bidfloorcur', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, payload); + const cfg = getPAAPIConfig({auctionId}).au; + const signals = cfg.auctionSignals; + sinon.assert.match(cfg.componentAuctions[0].auctionSignals, signals || {}); + expect(signals?.prebid?.bidfloor).to.eql(bidfloor); + expect(signals?.prebid?.bidfloorcur).to.eql(bidfloorcur); }); }); }); @@ -452,136 +442,195 @@ describe('paapi module', () => { }); }); - describe('with multiple auctions', () => { - const AUCTION1 = 'auction1'; - const AUCTION2 = 'auction2'; - - function mockAuction(auctionId) { - return { - getAuctionId() { - return auctionId; - } + describe('requestedSize', () => { + let adUnit; + beforeEach(() => { + adUnit = { + code: 'au', }; - } + }); - function expectAdUnitsFromAuctions(actualConfig, auToAuctionMap) { - expect(Object.keys(actualConfig)).to.have.members(Object.keys(auToAuctionMap)); - Object.entries(actualConfig).forEach(([au, cfg]) => { - cfg.componentAuctions.forEach(cmp => expect(cmp.auctionId).to.eql(auToAuctionMap[au])); - }); + function getConfig() { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: adUnit.code}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: [adUnit.code], adUnits: [adUnit]}); + return getPAAPIConfig()[adUnit.code]; } - let configs; - beforeEach(() => { - const mockAuctions = [mockAuction(AUCTION1), mockAuction(AUCTION2)]; - sandbox.stub(auctionManager, 'index').value(new AuctionIndex(() => mockAuctions)); - configs = {[AUCTION1]: {}, [AUCTION2]: {}}; - Object.entries({ - [AUCTION1]: [['au1', 'au2'], ['missing-1']], - [AUCTION2]: [['au2', 'au3'], []], - }).forEach(([auctionId, [adUnitCodes, noConfigAdUnitCodes]]) => { - adUnitCodes.forEach(adUnitCode => { - const cfg = {...auctionConfig, auctionId, adUnitCode}; - configs[auctionId][adUnitCode] = cfg; - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode}, {config: cfg}); + Object.entries({ + 'adUnit.ortb2Imp.ext.paapi.requestedSize'() { + adUnit.ortb2Imp = { + ext: { + paapi: { + requestedSize: { + width: 123, + height: 321 + } + } + } + }; + }, + 'largest size'() { + getPAAPISizeStub.returns([123, 321]); + } + }).forEach(([t, setup]) => { + describe(`should be set from ${t}`, () => { + beforeEach(setup); + + it('without overriding component auctions, if set', () => { + auctionConfig.requestedSize = {width: '1px', height: '2px'}; + expect(getConfig().componentAuctions[0].requestedSize).to.eql({ + width: '1px', + height: '2px' + }); + }); + + it('on component auction, if missing', () => { + expect(getConfig().componentAuctions[0].requestedSize).to.eql({ + width: 123, + height: 321 + }); + }); + + it('on top level auction', () => { + expect(getConfig().requestedSize).to.eql({ + width: 123, + height: 321, + }); }); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes) }); }); }); + }); + }); - it('should filter by auction', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2}), {au2: AUCTION2, au3: AUCTION2}); - }); + describe('with multiple auctions', () => { + const AUCTION1 = 'auction1'; + const AUCTION2 = 'auction2'; - it('should filter by auction and ad unit', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1, adUnitCode: 'au2'}), {au2: AUCTION1}); - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2, adUnitCode: 'au2'}), {au2: AUCTION2}); - }); + function mockAuction(auctionId) { + return { + getAuctionId() { + return auctionId; + } + }; + } - it('should use last auction for each ad unit', () => { - expectAdUnitsFromAuctions(getPAAPIConfig(), {au1: AUCTION1, au2: AUCTION2, au3: AUCTION2}); + function expectAdUnitsFromAuctions(actualConfig, auToAuctionMap) { + expect(Object.keys(actualConfig)).to.have.members(Object.keys(auToAuctionMap)); + Object.entries(actualConfig).forEach(([au, cfg]) => { + cfg.componentAuctions.forEach(cmp => expect(cmp.auctionId).to.eql(auToAuctionMap[au])); }); + } - it('should filter by ad unit and use latest auction', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({adUnitCode: 'au2'}), {au2: AUCTION2}); + let configs; + beforeEach(() => { + const mockAuctions = [mockAuction(AUCTION1), mockAuction(AUCTION2)]; + sandbox.stub(auctionManager, 'index').value(new AuctionIndex(() => mockAuctions)); + configs = {[AUCTION1]: {}, [AUCTION2]: {}}; + Object.entries({ + [AUCTION1]: [['au1', 'au2'], ['missing-1']], + [AUCTION2]: [['au2', 'au3'], []], + }).forEach(([auctionId, [adUnitCodes, noConfigAdUnitCodes]]) => { + adUnitCodes.forEach(adUnitCode => { + const cfg = {...auctionConfig, auctionId, adUnitCode}; + configs[auctionId][adUnitCode] = cfg; + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode}, {config: cfg}); + }); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes)}); }); + }); - it('should keep track of which configs were returned', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); - expect(getPAAPIConfig({auctionId: AUCTION1})).to.eql({}); - expectAdUnitsFromAuctions(getPAAPIConfig(), {au2: AUCTION2, au3: AUCTION2}); - }); + it('should filter by auction', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2}), {au2: AUCTION2, au3: AUCTION2}); + }); - describe('includeBlanks = true', () => { - Object.entries({ - 'auction with blanks': { - filters: {auctionId: AUCTION1}, - expected: {au1: true, au2: true, 'missing-1': false} - }, - 'blank adUnit in an auction': { - filters: {auctionId: AUCTION1, adUnitCode: 'missing-1'}, - expected: {'missing-1': false} - }, - 'non-existing auction': { - filters: {auctionId: 'other'}, - expected: {} - }, - 'non-existing adUnit in an auction': { - filters: {auctionId: AUCTION2, adUnitCode: 'other'}, - expected: {} - }, - 'non-existing ad unit': { - filters: {adUnitCode: 'other'}, - expected: {}, - }, - 'non existing ad unit in a non-existing auction': { - filters: {adUnitCode: 'other', auctionId: 'other'}, - expected: {} - }, - 'all ad units': { - filters: {}, - expected: {'au1': true, 'au2': true, 'missing-1': false, 'au3': true} - } - }).forEach(([t, {filters, expected}]) => { - it(t, () => { - const cfg = getPAAPIConfig(filters, true); - expect(Object.keys(cfg)).to.have.members(Object.keys(expected)); - Object.entries(expected).forEach(([au, shouldBeFilled]) => { - if (shouldBeFilled) { - expect(cfg[au]).to.not.be.null; - } else { - expect(cfg[au]).to.be.null; - } - }) - }) - }) - }); + it('should filter by auction and ad unit', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1, adUnitCode: 'au2'}), {au2: AUCTION1}); + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2, adUnitCode: 'au2'}), {au2: AUCTION2}); }); - }); - describe('markForFledge', function () { - const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); + it('should use last auction for each ad unit', () => { + expectAdUnitsFromAuctions(getPAAPIConfig(), {au1: AUCTION1, au2: AUCTION2, au3: AUCTION2}); + }); - before(function () { - // navigator.runAdAuction & co may not exist, so we can't stub it normally with - // sinon.stub(navigator, 'runAdAuction') or something - Object.keys(navProps).forEach(p => { - navigator[p] = sinon.stub(); - }); - hook.ready(); - config.resetConfig(); + it('should filter by ad unit and use latest auction', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({adUnitCode: 'au2'}), {au2: AUCTION2}); }); - after(function () { - Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + it('should keep track of which configs were returned', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); + expect(getPAAPIConfig({auctionId: AUCTION1})).to.eql({}); + expectAdUnitsFromAuctions(getPAAPIConfig(), {au2: AUCTION2, au3: AUCTION2}); }); - afterEach(function () { - config.resetConfig(); + describe('includeBlanks = true', () => { + Object.entries({ + 'auction with blanks': { + filters: {auctionId: AUCTION1}, + expected: {au1: true, au2: true, 'missing-1': false} + }, + 'blank adUnit in an auction': { + filters: {auctionId: AUCTION1, adUnitCode: 'missing-1'}, + expected: {'missing-1': false} + }, + 'non-existing auction': { + filters: {auctionId: 'other'}, + expected: {} + }, + 'non-existing adUnit in an auction': { + filters: {auctionId: AUCTION2, adUnitCode: 'other'}, + expected: {} + }, + 'non-existing ad unit': { + filters: {adUnitCode: 'other'}, + expected: {}, + }, + 'non existing ad unit in a non-existing auction': { + filters: {adUnitCode: 'other', auctionId: 'other'}, + expected: {} + }, + 'all ad units': { + filters: {}, + expected: {'au1': true, 'au2': true, 'missing-1': false, 'au3': true} + } + }).forEach(([t, {filters, expected}]) => { + it(t, () => { + const cfg = getPAAPIConfig(filters, true); + expect(Object.keys(cfg)).to.have.members(Object.keys(expected)); + Object.entries(expected).forEach(([au, shouldBeFilled]) => { + if (shouldBeFilled) { + expect(cfg[au]).to.not.be.null; + } else { + expect(cfg[au]).to.be.null; + } + }); + }); + }); }); + }); + }); + + describe('markForFledge', function () { + const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); + let adUnits; - const adUnits = [{ + before(function () { + // navigator.runAdAuction & co may not exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + Object.keys(navProps).forEach(p => { + navigator[p] = sinon.stub(); + }); + hook.ready(); + config.resetConfig(); + }); + + after(function () { + Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + }); + + beforeEach(() => { + getPAAPISizeStub = sinon.stub(); + adUnits = [{ 'code': '/19968336/header-bid-tag1', 'mediaTypes': { 'banner': { @@ -597,155 +646,198 @@ describe('paapi module', () => { }, ] }]; + }); - function mark() { - return Object.fromEntries( - adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ).map(b => [b.bidderCode, b]) - ); - } + afterEach(function () { + config.resetConfig(); + }); - function expectFledgeFlags(...enableFlags) { - const bidRequests = mark(); - expect(bidRequests.appnexus.fledgeEnabled).to.eql(enableFlags[0].enabled); - expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); - bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); - - expect(bidRequests.rubicon.fledgeEnabled).to.eql(enableFlags[1].enabled); - expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); - bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); - - Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { - if (bid.ortb2Imp?.ext?.ae) { - sinon.assert.match(bid.ortb2Imp.ext.igs, { - ae: bid.ortb2Imp.ext.ae, - biddable: 1 - }); - } - }) - } + function mark() { + return Object.fromEntries( + adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ).map(b => [b.bidderCode, b]) + ); + } - describe('with setBidderConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setBidderConfig({ - bidders: ['appnexus'], - config: { - defaultForSlots: 1, - fledgeEnabled: true - } + function expectFledgeFlags(...enableFlags) { + const bidRequests = mark(); + expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); + bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); + + expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); + bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); + + Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { + if (bid.ortb2Imp?.ext?.ae) { + sinon.assert.match(bid.ortb2Imp.ext.igs, { + ae: bid.ortb2Imp.ext.ae, + biddable: 1 }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: void 0, ae: void 0}); + } + }); + } + + describe('with setConfig()', () => { + it('should set paapi.enabled correctly per bidder', function () { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true, + bidders: ['appnexus'], + defaultForSlots: 1, + } }); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: undefined}); }); - describe('with setConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { - enabled: true, - bidders: ['appnexus'], - defaultForSlots: 1, - } - }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: undefined}); + it('should set paapi.enabled correctly for all bidders', function () { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true, + defaultForSlots: 1, + } }); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + }); - it('should set fledgeEnabled correctly for all bidders', function () { + Object.entries({ + 'not set': { + cfg: {}, + componentSeller: false + }, + 'set': { + cfg: { + componentSeller: { + auctionConfig: { + decisionLogicURL: 'publisher.example' + } + } + }, + componentSeller: true + } + }).forEach(([t, {cfg, componentSeller}]) => { + it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { + paapi: { enabled: true, defaultForSlots: 1, + ...cfg } }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); }); + }); - Object.entries({ - 'not set': { - cfg: {}, - componentSeller: false - }, - 'set': { - cfg: { - componentSeller: { - auctionConfig: { - decisionLogicURL: 'publisher.example' - } - } - }, - componentSeller: true + it('should not override pub-defined ext.ae', () => { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true, + defaultForSlots: 1, } - }).forEach(([t, {cfg, componentSeller}]) => { - it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { - config.setConfig({ - [configNS]: { - enabled: true, - defaultForSlots: 1, - ...cfg - } - }); - Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); - }) - }) + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); + expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); + }); - it('should not override pub-defined ext.ae', () => { - config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { - enabled: true, - defaultForSlots: 1, - } - }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); - expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); + it('should populate ext.igs when request has ext.ae', () => { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true + } }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 3}}}); + expectFledgeFlags({enabled: true, ae: 3}, {enabled: true, ae: 3}); + }); - it('should populate ext.igs when request has ext.ae', () => { - config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { - enabled: true + it('should not override pub-defined ext.igs', () => { + config.setConfig({ + paapi: { + enabled: true + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 1, igs: {biddable: 0}}}}); + const bidReqs = mark(); + Object.values(bidReqs).flatMap(req => req.bids).forEach(bid => { + sinon.assert.match(bid.ortb2Imp.ext, { + ae: 1, + igs: { + ae: 1, + biddable: 0 } }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 3}}}); - expectFledgeFlags({enabled: true, ae: 3}, {enabled: true, ae: 3}); - }) + }); + }); - it('should not override pub-defined ext.igs', () => { - config.setConfig({ - [configNS]: { - enabled: true + it('should fill ext.ae from ext.igs, if defined', () => { + config.setConfig({ + paapi: { + enabled: true + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {igs: {}}}}); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + }); + }); + + describe('ortb2Imp.ext.paapi.requestedSize', () => { + beforeEach(() => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + } + }); + }); + + it('should default to value returned by getPAAPISize', () => { + getPAAPISizeStub.returns([123, 321]); + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + requestedSize: { + width: 123, + height: 321 } }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 1, igs: {biddable: 0}}}}); - const bidReqs = mark(); - Object.values(bidReqs).flatMap(req => req.bids).forEach(bid => { - sinon.assert.match(bid.ortb2Imp.ext, { - ae: 1, - igs: { - ae: 1, - biddable: 0 - } - }) - }) }); + }); - it('should fill ext.ae from ext.igs, if defined', () => { - config.setConfig({ - [configNS]: { - enabled: true + it('should not be overridden, if provided by the pub', () => { + adUnits[0].ortb2Imp = { + ext: { + paapi: { + requestedSize: { + width: '123px', + height: '321px' + } + } + } + }; + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + requestedSize: { + width: '123px', + height: '321px' } }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {igs: {}}}}); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}) + }); + sinon.assert.notCalled(getPAAPISizeStub); + }); + + it('should not be set if adUnit has no banner sizes', () => { + adUnits[0].mediaTypes = { + video: {} + }; + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + expect(bidRequest.ortb2Imp?.ext?.paapi?.requestedSize).to.not.exist; }); }); }); @@ -778,7 +870,7 @@ describe('paapi module', () => { ps: { priority: 2 } - } + }; }); describe('mergeBuyers', () => { @@ -831,7 +923,7 @@ describe('paapi module', () => { it('ignores igbs with duplicate origin', () => { igb2.origin = igb1.origin; expect(mergeBuyers([igb1, igb2])).to.eql(mergeBuyers([igb1])); - }) + }); }); describe('partitionBuyers', () => { @@ -841,7 +933,7 @@ describe('paapi module', () => { it('should ignore igbs that have no origin', () => { delete igb1.origin; expect(partitionBuyers([igb1, igb2])).to.eql([[igb2]]); - }) + }); it('should return a single partition when duplicates exist, but do not conflict', () => { expect(partitionBuyers([igb1, igb2, deepClone(igb1)])).to.eql([[igb1, igb2]]); }); @@ -855,7 +947,7 @@ describe('paapi module', () => { [igb3], [igb4] ]); - }) + }); }); describe('partitionBuyersByBidder', () => { @@ -875,8 +967,8 @@ describe('paapi module', () => { ])).to.eql([ [{bidder: 'a', extra: 'data'}, [igb1, igb2]], [{bidder: 'b', more: 'data'}, [igb1, igb2]] - ]) - }) + ]); + }); describe('buyersToAuctionConfig', () => { let config, partitioners, merge, igbRequests; beforeEach(() => { @@ -884,11 +976,11 @@ describe('paapi module', () => { auctionConfig: { decisionLogicURL: 'mock-decision-logic' } - } + }; partitioners = { compact: sinon.stub(), expand: sinon.stub(), - } + }; let i = 0; merge = sinon.stub().callsFake(() => ({config: i++})); igbRequests = [ @@ -911,7 +1003,7 @@ describe('paapi module', () => { sinon.assert.match(cf2, { ...config.auctionConfig, config: 1 - }) + }); sinon.assert.calledWith(partitioners.compact, igbRequests); [1, 2].forEach(mockPart => sinon.assert.calledWith(merge, mockPart)); }); @@ -933,13 +1025,42 @@ describe('paapi module', () => { const fpd = { ortb2: {fpd: 1}, ortb2Imp: {fpd: 2} - } + }; partitioners.compact.returns([[{}], [fpd]]); const [cf1, cf2] = toAuctionConfig(); expect(cf1.auctionSignals?.prebid).to.not.exist; expect(cf2.auctionSignals.prebid).to.eql(fpd); - }) - }) + }); + }); + }); + }); + + describe('getPAAPISize', () => { + before(() => { + getPAAPISize.getHooks().remove(); + }); + + Object.entries({ + 'ignores placeholders': { + in: [[1, 1], [0, 0], [3, 4]], + out: [3, 4] + }, + 'picks largest size by area': { + in: [[200, 100], [150, 150]], + out: [150, 150] + }, + 'can handle no sizes': { + in: [], + out: undefined + }, + 'can handle no input': { + in: undefined, + out: undefined + } + }).forEach(([t, {in: input, out}]) => { + it(t, () => { + expect(getPAAPISize(input)).to.eql(out); + }); }); }); @@ -952,13 +1073,13 @@ describe('paapi module', () => { }); it('imp.ext.ae should be left intact if fledge is enabled', () => { const imp = {ext: {ae: 2, igs: {biddable: 0}}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); + setImpExtAe(imp, {}, {bidderRequest: {paapi: {enabled: true}}}); expect(imp.ext).to.eql({ ae: 2, igs: { biddable: 0 } - }) + }); }); describe('response parsing', () => { @@ -1001,7 +1122,7 @@ describe('paapi module', () => { igs: configs }] } - } + }; }, 'ext.igi.igs with impid on igi'(configs) { return { @@ -1012,10 +1133,10 @@ describe('paapi module', () => { return { impid, igs: [cfg] - } + }; }) } - } + }; }, 'ext.igi.igs with conflicting impid'(configs) { return { @@ -1025,7 +1146,7 @@ describe('paapi module', () => { igs: configs }] } - } + }; } } } @@ -1058,9 +1179,9 @@ describe('paapi module', () => { parser({}, resp, ctx); expect(extractResult('config', ctx.impContext)).to.eql({}); }); - }) - }) - }) + }); + }); + }); }); describe('response ext.igi.igb', () => { @@ -1092,15 +1213,15 @@ describe('paapi module', () => { } ] } - } + }; parseExtIgi({}, resp, ctx); expect(extractResult('igb', ctx.impContext)).to.eql({ e1: [1, 2], e2: [3], }); - }) - }) - }) + }); + }); + }); describe('setResponsePaapiConfigs', () => { it('should set paapi configs/igb paired with their corresponding bid id', () => { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 6fd202d86bb..2b4cd025515 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -764,6 +764,7 @@ describe('S2S Adapter', function () { it('should set tmaxmax correctly when publisher has specified it', () => { const cfg = {...CONFIG}; + config.setConfig({s2sConfig: cfg}) // publisher has specified a tmaxmax in their setup const ortb2Fragments = { @@ -784,8 +785,9 @@ describe('S2S Adapter', function () { it('should set tmaxmax correctly when publisher has not specified it', () => { const cfg = {...CONFIG}; + config.setConfig({s2sConfig: cfg}) - // publisher has not specified a tmaxmax in their setup - so we should be + // publisher has not specified a tmaxmax in their setup - so we should be // falling back to requestBidsTimeout const ortb2Fragments = {}; const s2sCfg = {...REQUEST, cfg}; @@ -889,22 +891,6 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].video).to.exist; }); - it('should default video placement if not defined and instream', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - config.setConfig({ s2sConfig: ortb2Config }); - - let videoBid = utils.deepClone(VIDEO_REQUEST); - videoBid.ad_units[0].mediaTypes.video.context = 'instream'; - adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); - - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].banner).to.not.exist; - expect(requestBid.imp[0].video).to.exist; - expect(requestBid.imp[0].video.placement).to.equal(1); - }); - it('converts video mediaType properties into openRTB format', function () { let ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; @@ -918,7 +904,6 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].banner).to.not.exist; expect(requestBid.imp[0].video).to.exist; - expect(requestBid.imp[0].video.placement).to.equal(1); expect(requestBid.imp[0].video.w).to.equal(640); expect(requestBid.imp[0].video.h).to.equal(480); expect(requestBid.imp[0].video.playerSize).to.be.undefined; @@ -3548,12 +3533,15 @@ describe('S2S Adapter', function () { beforeEach(function () { fledgeStub = sinon.stub(); - config.setConfig({CONFIG}); + config.setConfig({ + s2sConfig: CONFIG, + }); bidderRequests = deepClone(BID_REQUESTS); - AU bidderRequests.forEach(req => { Object.assign(req, { - fledgeEnabled: true, + paapi: { + enabled: true + }, ortb2: { fpd: 1 } @@ -3561,7 +3549,7 @@ describe('S2S Adapter', function () { req.bids.forEach(bid => { Object.assign(bid, { ortb2Imp: { - fpd: 2 + fpd: 2, } }) }) @@ -3572,8 +3560,8 @@ describe('S2S Adapter', function () { function expectFledgeCalls() { const auctionId = bidderRequests[0].auctionId; - sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), {config: {id: 1}}) - sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), {config: {id: 2}}) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), sinon.match({config: {id: 1}})) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), sinon.match({config: {id: 2}})) } it('calls addComponentAuction alongside addBidResponse', function () { diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 86c8794dc4c..e1d579aaa4a 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -383,7 +383,6 @@ describe('pubGENIUS adapter', () => { w: 200, h: 100, startdelay: -1, - placement: 1, skip: 1, skipafter: 1, playbackmethod: [3, 4], diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 745def57f4e..7d42f407448 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2948,7 +2948,7 @@ describe('PubMatic adapter', function () { bidRequest[0].ortb2Imp = { ext: { ae: 1 } }; - const req = spec.buildRequests(bidRequest, { ...bidRequest, fledgeEnabled: false }); + const req = spec.buildRequests(bidRequest, { ...bidRequest, paapi: {enabled: false} }); let data = JSON.parse(req.data); if (data.imp[0].ext) { expect(data.imp[0].ext).to.not.have.property('ae'); @@ -2961,7 +2961,7 @@ describe('PubMatic adapter', function () { bidRequest[0].ortb2Imp = { ext: { ae: 1 } }; - const req = spec.buildRequests(bidRequest, { ...bidRequest, fledgeEnabled: true }); + const req = spec.buildRequests(bidRequest, { ...bidRequest, paapi: {enabled: true} }); let data = JSON.parse(req.data); expect(data.imp[0].ext.ae).to.equal(1); }); @@ -3786,9 +3786,9 @@ describe('PubMatic adapter', function () { response = spec.interpretResponse({ body: bidResponse }, bidRequest); it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test_bid_id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test_bid_id'); }); }); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index d10fea829bc..fdde8d290f4 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -181,7 +181,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, // optional playbackmethod: [1], // optional delivery: [1], // optional - placement: 1, // optional api: [2, 3] // optional }, { context: 'instream', @@ -205,7 +204,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, playbackmethod: [1], delivery: [1], - placement: 1, api: [2, 3], w: 600, h: 300 @@ -242,7 +240,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, // optional playbackmethod: [1], // optional delivery: [1], // optional - placement: 1, // optional api: [2, 3], // optional context: 'instream', playerSize: [600, 300] @@ -265,7 +262,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, playbackmethod: [1], delivery: [1], - placement: 1, api: [2, 3], w: 600, h: 300 diff --git a/test/spec/modules/ringieraxelspringerBidAdapter_spec.js b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js index 5d5fb2f66a0..3539dad9362 100644 --- a/test/spec/modules/ringieraxelspringerBidAdapter_spec.js +++ b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js @@ -301,7 +301,7 @@ describe('ringieraxelspringerBidAdapter', function () { } }]; const resp = spec.interpretResponse({body: {gctx: '1234567890'}}, bidRequest); - expect(resp).to.deep.equal({bids: [], fledgeAuctionConfigs: auctionConfigs}); + expect(resp).to.deep.equal({bids: [], paapi: auctionConfigs}); }); }); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 5174011e24e..df5a09e3e0a 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -43,12 +43,14 @@ describe('RTBHouseAdapter', () => { }); it('should return false when required params are not passed', function () { + let invalidBid = Object.assign({}, bid); delete invalidBid.params; invalidBid.params = { 'someIncorrectParam': 0 }; expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); }); @@ -460,7 +462,7 @@ describe('RTBHouseAdapter', () => { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; config.setConfig({ fledgeConfig: true }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebidfledge/bids'); expect(request.method).to.equal('POST'); }); @@ -470,7 +472,7 @@ describe('RTBHouseAdapter', () => { delete bidRequest[0].params.test; config.setConfig({ fledgeConfig: false }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, {...bidderRequest, paapi: {enabled: true}}); const data = JSON.parse(request.data); expect(data.ext).to.exist.and.to.be.a('object'); expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); @@ -490,7 +492,7 @@ describe('RTBHouseAdapter', () => { decisionLogicUrl: 'https://sellers.domain/decision.url' } }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, {...bidderRequest, paapi: {enabled: true}}); const data = JSON.parse(request.data); expect(data.ext).to.exist.and.to.be.a('object'); expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); @@ -506,7 +508,7 @@ describe('RTBHouseAdapter', () => { bidRequest[0].ortb2Imp = { ext: { ae: 2 } }; - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: false }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: false} }); let data = JSON.parse(request.data); if (data.imp[0].ext) { expect(data.imp[0].ext).to.not.have.property('ae'); @@ -519,7 +521,7 @@ describe('RTBHouseAdapter', () => { bidRequest[0].ortb2Imp = { ext: { ae: 2 } }; - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); let data = JSON.parse(request.data); expect(data.imp[0].ext.ae).to.equal(2); }); @@ -782,9 +784,9 @@ describe('RTBHouseAdapter', () => { it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test-bid-id'); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 62db78fa669..9e25300e10b 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -3695,14 +3695,14 @@ describe('the rubicon adapter', function () { }] }; - let {bids, fledgeAuctionConfigs} = spec.interpretResponse({body: response}, { + let {bids, paapi} = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); expect(bids).to.be.lengthOf(1); - expect(fledgeAuctionConfigs[0].bidId).to.equal('5432'); - expect(fledgeAuctionConfigs[0].config.random).to.equal('value'); - expect(fledgeAuctionConfigs[1].bidId).to.equal('6789'); + expect(paapi[0].bidId).to.equal('5432'); + expect(paapi[0].config.random).to.equal('value'); + expect(paapi[1].bidId).to.equal('6789'); }); it('should handle an error', function () { diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index ab099d87429..8fc29a2cef3 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -755,7 +755,7 @@ describe('sharethrough adapter spec', function () { const EXPECTED_AE_VALUE = 1; // ACT - bidderRequest['fledgeEnabled'] = true; + bidderRequest.paapi = {enabled: true}; const builtRequests = spec.buildRequests(bidRequests, bidderRequest); const ACTUAL_AE_VALUE = builtRequests[0].data.imp[0].ext.ae; diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index dcbfd297013..a5e8787e21a 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -49,6 +49,7 @@ describe('SmartHubBidAdapter', function () { playerSize: [[300, 300]], minduration: 5, maxduration: 60, + plcmt: 1, } }, params: { @@ -197,6 +198,7 @@ describe('SmartHubBidAdapter', function () { expect(placement.playerSize).to.be.an('array'); expect(placement.minduration).to.be.an('number'); expect(placement.maxduration).to.be.an('number'); + expect(placement.plcmt).to.be.an('number'); break; case NATIVE: expect(placement.native).to.be.an('object'); diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index c3d0711632e..d8ddf7a398b 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -178,7 +178,6 @@ describe('The smartx adapter', function () { 2, 3, 5, 6 ], startdelay: 0, - placement: 1, pos: 1 }); @@ -208,10 +207,6 @@ describe('The smartx adapter', function () { sdk_name: 'Prebid 1+' }); - expect(request.data.imp[0].video).to.contain({ - placement: 1 - }); - bid.mediaTypes.video.context = 'outstream'; bid.params = { @@ -251,10 +246,6 @@ describe('The smartx adapter', function () { expect(request.data.imp[0].video.startdelay).to.equal(1); - expect(request.data.imp[0].video).to.contain({ - placement: 3 - }); - expect(request.data.imp[0].bidfloor).to.equal(55); expect(request.data.imp[0].bidfloorcur).to.equal('foo'); diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 10f5ab8e89d..2d6af1f964f 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -243,7 +243,7 @@ describe('sovrnBidAdapter', function() { it('when FLEDGE is enabled, should send ortb2imp.ext.ae', function () { const bidderRequest = { ...baseBidderRequest, - fledgeEnabled: true + paapi: {enabled: true} } const bidRequest = { ...baseBidRequest, @@ -273,7 +273,9 @@ describe('sovrnBidAdapter', function() { it('when FLEDGE is enabled, but env is malformed, should not send ortb2imp.ext.ae', function () { const bidderRequest = { ...baseBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } } const bidRequest = { ...baseBidRequest, @@ -968,9 +970,9 @@ describe('sovrnBidAdapter', function() { it('should return valid fledge auction configs alongside bids', function () { const result = spec.interpretResponse(fledgeResponse) expect(result).to.have.property('bids') - expect(result).to.have.property('fledgeAuctionConfigs') - expect(result.fledgeAuctionConfigs.length).to.equal(2) - expect(result.fledgeAuctionConfigs).to.deep.equal(expectedFledgeResponse) + expect(result).to.have.property('paapi') + expect(result.paapi.length).to.equal(2) + expect(result.paapi).to.deep.equal(expectedFledgeResponse) }) it('should ignore empty fledge auction configs array', function () { const result = spec.interpretResponse(emptyFledgeResponse) diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index f2f8155ea9a..66e2b575b8b 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -408,7 +408,6 @@ describe('stroeerCore bid adapter', function () { 'timeout': expectedTimeout, 'ref': 'https://www.example.com/?search=monkey', 'mpa': true, - 'ssl': false, 'url': 'https://www.example.com/monkey/index.html', 'bids': [{ 'sid': 'NDA=', diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 55d0731ec21..bcf388a67e2 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1149,7 +1149,7 @@ describe('Taboola Adapter', function () { }, } ], - 'fledgeAuctionConfigs': [ + 'paapi': [ { 'impId': request.bids[0].bidId, 'config': { @@ -1222,7 +1222,7 @@ describe('Taboola Adapter', function () { }, } ], - 'fledgeAuctionConfigs': [ + 'paapi': [ { 'impId': request.bids[0].bidId, 'config': { diff --git a/test/spec/modules/topLevelPaapi_spec.js b/test/spec/modules/topLevelPaapi_spec.js new file mode 100644 index 00000000000..e2cad9593e9 --- /dev/null +++ b/test/spec/modules/topLevelPaapi_spec.js @@ -0,0 +1,502 @@ +import { + addPaapiConfigHook, + getPAAPIConfig, + registerSubmodule, + reset as resetPaapi +} from '../../../modules/paapi.js'; +import {config} from 'src/config.js'; +import {BID_STATUS, EVENTS} from 'src/constants.js'; +import * as events from 'src/events.js'; +import { + getPaapiAdId, + getPAAPIBids, + getRenderingDataHook, markWinningBidHook, + parsePaapiAdId, + parsePaapiSize, resizeCreativeHook, + topLevelPAAPI +} from '/modules/topLevelPaapi.js'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {expect} from 'chai/index.js'; +import {getBidToRender} from '../../../src/adRendering.js'; + +describe('topLevelPaapi', () => { + let sandbox, auctionConfig, next, auctionId, auctions; + before(() => { + resetPaapi(); + }); + beforeEach(() => { + registerSubmodule(topLevelPAAPI); + }); + afterEach(() => { + resetPaapi(); + }); + beforeEach(() => { + sandbox = sinon.createSandbox(); + auctions = {}; + sandbox.stub(auctionManager.index, 'getAuction').callsFake(({auctionId}) => auctions[auctionId]?.auction); + next = sinon.stub(); + auctionId = 'auct'; + auctionConfig = { + seller: 'mock.seller' + }; + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1 + } + }); + }); + afterEach(() => { + config.resetConfig(); + sandbox.restore(); + }); + + function addPaapiConfig(adUnitCode, auctionConfig, _auctionId = auctionId) { + let auction = auctions[_auctionId]; + if (!auction) { + auction = auctions[_auctionId] = { + auction: {}, + adUnits: {} + }; + } + if (!auction.adUnits.hasOwnProperty(adUnitCode)) { + auction.adUnits[adUnitCode] = { + code: adUnitCode, + ortb2Imp: { + ext: { + paapi: { + requestedSize: { + width: 123, + height: 321 + } + } + } + } + }; + } + addPaapiConfigHook(next, {adUnitCode, auctionId: _auctionId}, { + config: { + ...auctionConfig, + auctionId: _auctionId, + adUnitCode + } + }); + } + + function endAuctions() { + Object.entries(auctions).forEach(([auctionId, {adUnits}]) => { + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: Object.keys(adUnits), adUnits: Object.values(adUnits)}); + }); + } + + describe('when configured', () => { + let auctionConfig; + beforeEach(() => { + auctionConfig = { + seller: 'top.seller', + decisionLogicURL: 'https://top.seller/decision-logic.js' + }; + config.mergeConfig({ + paapi: { + topLevelSeller: { + auctionConfig, + autorun: false + } + } + }); + }); + + it('should augment config returned by getPAAPIConfig', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + sinon.assert.match(getPAAPIConfig().au, auctionConfig); + }); + + it('should not choke if auction config is not defined', () => { + const cfg = config.getConfig('paapi'); + delete cfg.topLevelSeller.auctionConfig; + config.setConfig(cfg); + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig().au.componentAuctions).to.exist; + }); + + it('should default resolveToConfig: false', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig()['au'].resolveToConfig).to.eql(false); + }); + + describe('when autoRun is set', () => { + let origRaa; + beforeEach(() => { + origRaa = navigator.runAdAuction; + navigator.runAdAuction = sinon.stub(); + }); + afterEach(() => { + navigator.runAdAuction = origRaa; + }); + + it('should start auctions automatically, when autoRun is set', () => { + config.mergeConfig({ + paapi: { + topLevelSeller: { + autorun: true + } + } + }) + addPaapiConfig('au', auctionConfig); + endAuctions(); + sinon.assert.called(navigator.runAdAuction); + }); + }); + + describe('getPAAPIBids', () => { + Object.entries({ + 'a string URN': { + pack: (val) => val, + unpack: (urn) => ({urn}), + canRender: true, + }, + 'a frameConfig object': { + pack: (val) => ({val}), + unpack: (val) => ({frameConfig: {val}}), + canRender: false + } + }).forEach(([t, {pack, unpack, canRender}]) => { + describe(`when runAdAuction returns ${t}`, () => { + let raa; + beforeEach(() => { + raa = sinon.stub().callsFake((cfg) => { + const {auctionId, adUnitCode} = cfg.componentAuctions[0]; + return Promise.resolve(pack(`raa-${adUnitCode}-${auctionId}`)); + }); + }); + + function getBids(filters) { + return getPAAPIBids(filters, raa); + } + + function expectBids(actual, expected) { + expect(Object.keys(actual)).to.eql(Object.keys(expected)); + Object.entries(expected).forEach(([au, val]) => { + sinon.assert.match(actual[au], val == null ? val : { + adId: sinon.match(val => parsePaapiAdId(val)[1] === au), + width: 123, + height: 321, + source: 'paapi', + ...unpack(val) + }); + }); + } + + describe('with one auction config', () => { + beforeEach(() => { + addPaapiConfig('au', auctionConfig, 'auct'); + endAuctions(); + }); + it('should resolve to raa result', () => { + return getBids({adUnitCode: 'au', auctionId}).then(result => { + sinon.assert.calledWith(raa, sinon.match({ + ...auctionConfig, + componentAuctions: sinon.match(cmp => cmp.find(cfg => sinon.match(cfg, auctionConfig))) + })); + expectBids(result, {au: 'raa-au-auct'}); + }); + }); + + Object.entries({ + 'returns null': () => Promise.resolve(), + 'throws': () => { throw new Error() }, + 'rejects': () => Promise.reject(new Error()) + }).forEach(([t, behavior]) => { + it('should resolve to null when runAdAuction returns null', () => { + raa = sinon.stub().callsFake(behavior); + return getBids({adUnitCode: 'au', auctionId: 'auct'}).then(result => { + expectBids(result, {au: null}); + }); + }); + }) + + it('should resolve to the same result when called again', () => { + getBids({adUnitCode: 'au', auctionId}); + return getBids({adUnitCode: 'au', auctionId: 'auct'}).then(result => { + sinon.assert.calledOnce(raa); + expectBids(result, {au: 'raa-au-auct'}); + }); + }); + + describe('events', () => { + beforeEach(() => { + sandbox.stub(events, 'emit'); + }); + it('should fire PAAPI_RUN_AUCTION', () => { + return Promise.all([ + getBids({adUnitCode: 'au', auctionId}), + getBids({adUnitCode: 'other', auctionId}) + ]).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.RUN_PAAPI_AUCTION, { + adUnitCode: 'au', + auctionId, + auctionConfig: sinon.match(auctionConfig) + }); + sinon.assert.neverCalledWith(events.emit, EVENTS.RUN_PAAPI_AUCTION, { + adUnitCode: 'other' + }); + }); + }); + it('should fire PAAPI_BID', () => { + return getBids({adUnitCode: 'au', auctionId}).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_BID, sinon.match({ + ...unpack('raa-au-auct'), + adUnitCode: 'au', + auctionId: 'auct' + })); + }); + }); + it('should fire PAAPI_NO_BID', () => { + raa = sinon.stub().callsFake(() => Promise.resolve(null)); + return getBids({adUnitCode: 'au', auctionId}).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_NO_BID, sinon.match({ + adUnitCode: 'au', + auctionId: 'auct' + })); + }); + }); + + it('should fire PAAPI_ERROR', () => { + raa = sinon.stub().callsFake(() => Promise.reject(new Error('message'))); + return getBids({adUnitCode: 'au', auctionId}).then(res => { + expect(res).to.eql({au: null}); + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_ERROR, sinon.match({ + adUnitCode: 'au', + auctionId: 'auct', + error: sinon.match({message: 'message'}) + })); + }); + }); + }); + + it('should hook into getBidToRender', () => { + return getBids({adUnitCode: 'au', auctionId}).then(res => { + return getBidToRender(res.au.adId).then(bidToRender => [res.au, bidToRender]) + }).then(([paapiBid, bidToRender]) => { + if (canRender) { + expect(bidToRender).to.eql(paapiBid) + } else { + expect(bidToRender).to.not.exist; + } + }); + }); + + describe('when overrideWinner is set', () => { + let mockContextual; + beforeEach(() => { + mockContextual = { + adId: 'mock', + adUnitCode: 'au' + } + sandbox.stub(auctionManager, 'findBidByAdId').returns(mockContextual); + config.mergeConfig({ + paapi: { + topLevelSeller: { + overrideWinner: true + } + } + }); + }); + + it(`should ${!canRender ? 'NOT' : ''} override winning bid for the same adUnit`, () => { + return Promise.all([ + getBids({adUnitCode: 'au', auctionId}).then(res => res.au), + getBidToRender(mockContextual.adId) + ]).then(([paapiBid, bidToRender]) => { + if (canRender) { + expect(bidToRender).to.eql(paapiBid); + expect(paapiBid.overriddenAdId).to.eql(mockContextual.adId); + } else { + expect(bidToRender).to.eql(mockContextual) + } + }) + }); + + it('should not override when the ad unit has no paapi winner', () => { + mockContextual.adUnitCode = 'other'; + return getBidToRender(mockContextual.adId).then(bidToRender => { + expect(bidToRender).to.eql(mockContextual); + }) + }); + + it('should not override when already a paapi bid', () => { + return getBids({adUnitCode: 'au', auctionId}).then(res => { + return getBidToRender(res.au.adId).then((bidToRender) => [bidToRender, res.au]); + }).then(([bidToRender, paapiBid]) => { + expect(bidToRender).to.eql(canRender ? paapiBid : mockContextual) + }) + }); + + if (canRender) { + it('should not not override when the bid was already rendered', () => { + getBids(); + return getBidToRender(mockContextual.adId).then((bid) => { + // first pass - paapi wins over contextual + expect(bid.source).to.eql('paapi'); + bid.status = BID_STATUS.RENDERED; + return getBidToRender(mockContextual.adId, false).then(bidToRender => [bid, bidToRender]) + }).then(([paapiBid, bidToRender]) => { + // if `forRender` = false (bit retrieved for x-domain events and such) + // the referenced bid is still paapi + expect(bidToRender).to.eql(paapiBid); + return getBidToRender(mockContextual.adId); + }).then(bidToRender => { + // second pass, paapi has been rendered, contextual should win + expect(bidToRender).to.eql(mockContextual); + bidToRender.status = BID_STATUS.RENDERED; + return getBidToRender(mockContextual.adId, false); + }).then(bidToRender => { + // if the contextual bid has been rendered, it's the one being referenced + expect(bidToRender).to.eql(mockContextual); + }); + }) + } + }); + }); + + it('should resolve the same result from different filters', () => { + const targets = { + auct1: ['au1', 'au2'], + auct2: ['au1', 'au3'] + }; + Object.entries(targets).forEach(([auctionId, adUnitCodes]) => { + adUnitCodes.forEach(au => addPaapiConfig(au, auctionConfig, auctionId)); + }); + endAuctions(); + return Promise.all( + [ + [ + {adUnitCode: 'au1', auctionId: 'auct1'}, + { + au1: 'raa-au1-auct1' + } + ], + [ + {}, + { + au1: 'raa-au1-auct2', + au2: 'raa-au2-auct1', + au3: 'raa-au3-auct2' + } + ], + [ + {auctionId: 'auct1'}, + { + au1: 'raa-au1-auct1', + au2: 'raa-au2-auct1' + } + ], + [ + {adUnitCode: 'au1'}, + { + au1: 'raa-au1-auct2' + } + ], + ].map(([filters, expected]) => getBids(filters).then(res => [res, expected])) + ).then(res => { + res.forEach(([actual, expected]) => { + expectBids(actual, expected); + }); + }); + }); + }); + }); + }); + }); + + describe('when not configured', () => { + it('should not alter configs returned by getPAAPIConfig', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig().au.seller).to.not.exist; + }); + }); + + describe('paapi adId', () => { + [ + ['auctionId', 'adUnitCode'], + ['auction:id', 'adUnit:code'], + ['auction:uid', 'ad:unit'] + ].forEach(([auctionId, adUnitCode]) => { + it(`can encode and decode ${auctionId}, ${adUnitCode}`, () => { + expect(parsePaapiAdId(getPaapiAdId(auctionId, adUnitCode))).to.eql([auctionId, adUnitCode]); + }); + }); + + [undefined, null, 'not-a-paapi-ad', 'paapi:/malformed'].forEach(adId => { + it(`returns null for adId ${adId}`, () => { + expect(parsePaapiAdId(adId)).to.not.exist; + }); + }); + }); + + describe('parsePaapiSize', () => { + [ + [null, null], + [undefined, null], + [123, 123], + ['123', 123], + ['123px', 123], + ['1sw', null], + ['garbage', null] + ].forEach(([input, expected]) => { + it(`can parse ${input} => ${expected}`, () => { + expect(parsePaapiSize(input)).to.eql(expected); + }); + }); + }); + + describe('rendering hooks', () => { + let next; + beforeEach(() => { + next = sinon.stub() + next.bail = sinon.stub() + }); + describe('getRenderingDataHook', () => { + it('intercepts paapi bids', () => { + getRenderingDataHook(next, { + source: 'paapi', + width: 123, + height: null, + urn: 'url' + }); + sinon.assert.calledWith(next.bail, { + width: 123, + height: null, + adUrl: 'url' + }); + }); + it('does not touch non-paapi bids', () => { + getRenderingDataHook(next, {bid: 'data'}, {other: 'options'}); + sinon.assert.calledWith(next, {bid: 'data'}, {other: 'options'}); + }); + }); + + describe('markWinnigBidsHook', () => { + beforeEach(() => { + sandbox.stub(events, 'emit'); + }); + it('handles paapi bids', () => { + const bid = {source: 'paapi'}; + markWinningBidHook(next, bid); + sinon.assert.notCalled(next); + sinon.assert.called(next.bail); + expect(bid.status).to.eql(BID_STATUS.RENDERED); + sinon.assert.calledWith(events.emit, EVENTS.BID_WON, bid); + }); + it('ignores non-paapi bids', () => { + markWinningBidHook(next, {other: 'bid'}); + sinon.assert.calledWith(next, {other: 'bid'}); + sinon.assert.notCalled(next.bail); + }); + }); + }); +}); diff --git a/test/spec/modules/trafficgateBidAdapter_spec.js b/test/spec/modules/trafficgateBidAdapter_spec.js index 9051ade6f50..9c564606186 100644 --- a/test/spec/modules/trafficgateBidAdapter_spec.js +++ b/test/spec/modules/trafficgateBidAdapter_spec.js @@ -12,6 +12,8 @@ import 'modules/priceFloors.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/paapi.js'; + import {deepClone} from 'src/utils.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; @@ -1017,7 +1019,9 @@ describe('TrafficgateOpenxRtbAdapter', function () { it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, { ...mockBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } }); expect(request[0].data.imp[0].ext.ae).to.equal(2); }); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 09e57e29a12..216142ab02e 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -874,7 +874,7 @@ describe('triplelift adapter', function () { expect(url).to.match(/(\?|&)us_privacy=1YYY/); }); it('should pass fledge signal when Triplelift is eligible for fledge', function() { - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const url = request.url; expect(url).to.match(/(\?|&)fledge=true/); @@ -1411,11 +1411,11 @@ describe('triplelift adapter', function () { let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); expect(result).to.have.property('bids'); - expect(result).to.have.property('fledgeAuctionConfigs'); - expect(result.fledgeAuctionConfigs.length).to.equal(2); - expect(result.fledgeAuctionConfigs[0].bidId).to.equal('30b31c1838de1e'); - expect(result.fledgeAuctionConfigs[1].bidId).to.equal('73edc0ba8de203'); - expect(result.fledgeAuctionConfigs[0].config).to.deep.equal( + expect(result).to.have.property('paapi'); + expect(result.paapi.length).to.equal(2); + expect(result.paapi[0].bidId).to.equal('30b31c1838de1e'); + expect(result.paapi[1].bidId).to.equal('73edc0ba8de203'); + expect(result.paapi[0].config).to.deep.equal( { 'seller': 'https://3lift.com', 'decisionLogicUrl': 'https://3lift.com/decision_logic.js', @@ -1423,7 +1423,7 @@ describe('triplelift adapter', function () { 'perBuyerSignals': { 'https://some_buyer.com': { 'a': 1 } } } ); - expect(result.fledgeAuctionConfigs[1].config).to.deep.equal( + expect(result.paapi[1].config).to.deep.equal( { 'seller': 'https://3lift.com', 'decisionLogicUrl': 'https://3lift.com/decision_logic.js', diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index 7d263f6d4f0..170ff51c6fd 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -588,7 +588,7 @@ describe('TwistDigitalBidAdapter', function () { it('should set fledge correctly if enabled', function () { config.resetConfig(); const bidderRequest = utils.deepClone(BIDDER_REQUEST); - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); const requests = adapter.buildRequests([BID], bidderRequest); expect(requests[0].data.fledge).to.equal(1); diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index abf1a54787d..662e5c0e03d 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -696,7 +696,9 @@ describe('UnrulyAdapter', function () { it('should return an array with 2 items and enabled protected audience', function () { mockBidRequests = { 'bidderCode': 'unruly', - 'fledgeEnabled': true, + 'paapi': { + enabled: true + }, 'bids': [ { 'bidder': 'unruly', @@ -782,7 +784,9 @@ describe('UnrulyAdapter', function () { it('should return an array with 2 items and enabled protected audience on only one unit', function () { mockBidRequests = { 'bidderCode': 'unruly', - 'fledgeEnabled': true, + 'paapi': { + enabled: true + }, 'bids': [ { 'bidder': 'unruly', @@ -1043,7 +1047,7 @@ describe('UnrulyAdapter', function () { mediaType: 'video' } ], - 'fledgeAuctionConfigs': [{ + 'paapi': [{ 'bidId': bidId, 'config': { 'seller': 'https://nexxen.tech', @@ -1107,7 +1111,7 @@ describe('UnrulyAdapter', function () { expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ 'bids': [], - 'fledgeAuctionConfigs': [{ + 'paapi': [{ 'bidId': bidId, 'config': { 'seller': 'https://nexxen.tech', diff --git a/test/spec/modules/viantOrtbBidAdapter_spec.js b/test/spec/modules/viantOrtbBidAdapter_spec.js index 73fdb7f3dc8..271c944e6e9 100644 --- a/test/spec/modules/viantOrtbBidAdapter_spec.js +++ b/test/spec/modules/viantOrtbBidAdapter_spec.js @@ -259,6 +259,7 @@ describe('viantOrtbBidAdapter', function () { 'skip': 1, 'skipafter': 5, 'minduration': 10, + 'placement': 1, 'maxduration': 31 } }, diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 6c75b34996c..ad5a298bb8e 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -600,7 +600,7 @@ describe('VidazooBidAdapter', function () { it('should set fledge correctly if enabled', function () { config.resetConfig(); const bidderRequest = utils.deepClone(BIDDER_REQUEST); - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); const requests = adapter.buildRequests([BID], bidderRequest); expect(requests[0].data.fledge).to.equal(1); diff --git a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js index a7379ccbab2..125f608f803 100644 --- a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js @@ -6,7 +6,7 @@ import { const {VideojsProvider, utils} = require('modules/videojsVideoProvider'); const { - PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLCMT, VPAID_MIME_TYPE, AD_POSITION } = require('libraries/video/constants/ortb.js'); const videojs = require('video.js').default; @@ -139,7 +139,7 @@ describe('videojsProvider', function () { expect(video.playbackmethod).to.include(PLAYBACK_METHODS.CLICK_TO_PLAY); expect(video.playbackend).to.equal(1); expect(video.api).to.deep.equal([2]); - expect(video.placement).to.be.equal(PLACEMENT.INSTREAM); + expect(video.plcmt).to.be.equal(PLCMT.ACCOMPANYING_CONTENT); }); it('should populate oRTB Content', function () { diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 0d03769c6a5..0653d7a8ce7 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -89,6 +89,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + ortb2: { + device: { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + site: { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -109,7 +149,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -120,7 +160,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90], [300, 250]], 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -131,7 +171,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '42dbe3a7168a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -151,7 +191,7 @@ describe('VisxAdapter', function () { }, 'bidId': '39a4e3a7168a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; @@ -217,7 +257,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -235,7 +312,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -255,7 +369,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['GBP'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); getConfigStub.restore(); @@ -277,7 +428,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['USD'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, }); getConfigStub.restore(); @@ -294,9 +482,50 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'regs': { + 'ext': { + 'gdpr': 1 + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, - 'regs': {'ext': {'gdpr': 1}} }); }); @@ -311,7 +540,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, 'regs': {'ext': {'gdpr': 0}} }); @@ -328,7 +594,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, 'regs': {'ext': {'gdpr': 1}} }); @@ -359,7 +662,44 @@ describe('VisxAdapter', function () { 'schain': schainObject } }, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -408,7 +748,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'eids': eids}} }); }); @@ -429,7 +806,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); }); @@ -448,6 +862,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + 'ortb2': { + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -467,7 +921,7 @@ describe('VisxAdapter', function () { }, 'bidId': '39aff3a7169a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a476', + 'auctionId': '1d1a030790a476' } ]; @@ -494,7 +948,6 @@ describe('VisxAdapter', function () { const payload = parseRequest(request.url); expect(payload).to.be.an('object'); expect(payload).to.have.property('auids', '903538'); - const postData = request.data; expect(postData).to.be.an('object'); expect(postData).to.deep.equal({ @@ -512,7 +965,50 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ + '124' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '124' + ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ + '99' + ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + } }); }); }); @@ -531,6 +1027,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + ortb2: { + device: { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + site: { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -544,7 +1080,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -555,7 +1091,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; let sandbox; @@ -612,7 +1148,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -641,7 +1214,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); }); @@ -669,7 +1279,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const request = spec.buildRequests(bidRequests); @@ -719,7 +1329,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' }, { 'bidder': 'visx', @@ -730,7 +1340,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '4dff80cc4ee346', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' }, { 'bidder': 'visx', @@ -741,7 +1351,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '5703af74d0472a', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' } ]; const request = spec.buildRequests(bidRequests); @@ -823,7 +1433,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const getConfigStub = sinon.stub(config, 'getConfig').returns('PLN'); @@ -888,7 +1498,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71321', 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', + 'auctionId': '1fa09aee5c84d34' }, { 'bidder': 'visx', @@ -899,7 +1509,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '300bfeb0d7183bb', 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', + 'auctionId': '1fa09aee5c84d34' } ]; const request = spec.buildRequests(bidRequests); @@ -925,7 +1535,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -936,7 +1546,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '326bde7fbf69', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -947,7 +1557,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '4e111f1b66e4', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -958,7 +1568,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '26d6f897b516', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -969,7 +1579,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '1751cd90161', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1075,7 +1685,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '5126e301f4be', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' }, { 'bidder': 'visx', @@ -1086,7 +1696,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '57b2ebe70e16', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' }, { 'bidder': 'visx', @@ -1097,7 +1707,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '225fcd44b18c', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' } ]; const request = spec.buildRequests(bidRequests); @@ -1162,7 +1772,7 @@ describe('VisxAdapter', function () { 'sizes': [[400, 300]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1214,7 +1824,7 @@ describe('VisxAdapter', function () { 'sizes': [[400, 300]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1251,7 +1861,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const request = spec.buildRequests(bidRequests); @@ -1434,13 +2044,53 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; const bidderRequest = { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + 'ortb2': { + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 604bc780d6b..4331060b1cd 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -107,7 +107,12 @@ const SAMPLE_EVENTS = { 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 + 'bidderWinsCount': 0, + 'ortb2': { + 'device': { + 'mobile': 1 + } + } } ], 'auctionStart': 1638441234544, @@ -168,7 +173,12 @@ const SAMPLE_EVENTS = { 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 + 'bidderWinsCount': 0, + 'ortb2': { + 'device': { + 'mobile': 1 + } + } } ], 'auctionStart': 1638441234544, @@ -395,8 +405,6 @@ describe('Zeta Global SSP Analytics Adapter', function () { expect(requests.length).to.equal(2); const auctionEnd = JSON.parse(requests[0].requestBody); - const auctionSucceeded = JSON.parse(requests[1].requestBody); - expect(auctionEnd).to.be.deep.equal({ zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, bidderRequests: [{ @@ -408,7 +416,10 @@ describe('Zeta Global SSP Analytics Adapter', function () { auctionId: '75e394d9', bidder: 'zeta_global_ssp', mediaType: 'BANNER', - size: '300x250' + size: '300x250', + device: { + mobile: 1 + } }] }, { bidderCode: 'appnexus', @@ -419,7 +430,10 @@ describe('Zeta Global SSP Analytics Adapter', function () { auctionId: '75e394d9', bidder: 'appnexus', mediaType: 'BANNER', - size: '300x250' + size: '300x250', + device: { + mobile: 1 + } }] }], bidsReceived: [{ @@ -434,23 +448,29 @@ describe('Zeta Global SSP Analytics Adapter', function () { cpm: 2.258302852806723 }] }); - expect(auctionSucceeded).to.be.deep.equal({ - zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, - domain: 'test-zeta-ssp.net', - page: 'test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html', - bid: { - adId: '5759bb3ef7be1e8', - requestId: '206be9a13236af', - auctionId: '75e394d9', - creativeId: '456456456', - bidder: 'zeta_global_ssp', - mediaType: 'banner', - size: '480x320', - adomain: 'example.adomain', - timeToRespond: 123, - cpm: 2.258302852806723 + const auctionSucceeded = JSON.parse(requests[1].requestBody); + expect(auctionSucceeded.zetaParams).to.be.deep.equal({ + sid: 111, + tags: { + position: 'top', + shortname: 'name' } }); + expect(auctionSucceeded.domain).to.eql('test-zeta-ssp.net'); + expect(auctionSucceeded.page).to.eql('test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html'); + expect(auctionSucceeded.bid).to.be.deep.equal({ + adId: '5759bb3ef7be1e8', + requestId: '206be9a13236af', + auctionId: '75e394d9', + creativeId: '456456456', + bidder: 'zeta_global_ssp', + mediaType: 'banner', + size: '480x320', + adomain: 'example.adomain', + timeToRespond: 123, + cpm: 2.258302852806723 + }); + expect(auctionSucceeded.device.ua).to.not.be.empty; }); }); }); diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index f6079f08460..dfa7c9e6984 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -155,7 +155,17 @@ describe('Zeta Ssp Bid Adapter', function () { { id: '59' } ] } - ] + ], + geo: { + lat: 40.0, + lon: -80.0, + type: 2, + country: 'USA', + region: 'NY', + metro: '501', + city: 'New York', + zip: '10001', + } } } }]; @@ -658,12 +668,37 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.device.sua.platform.brand).to.eql('Chrome'); expect(payload.device.sua.platform.version[0]).to.eql('102'); + // expecting the same values for user.geo and device.geo + expect(payload.device.geo.type).to.eql(2); + expect(payload.device.geo.lat).to.eql(40.0); + expect(payload.device.geo.lon).to.eql(-80.0); + expect(payload.device.geo.country).to.eql('USA'); + expect(payload.device.geo.region).to.eql('NY'); + expect(payload.device.geo.metro).to.eql('501'); + expect(payload.device.geo.city).to.eql('New York'); + expect(payload.device.geo.zip).to.eql('10001'); + expect(payload.device.ua).to.not.be.undefined; expect(payload.device.language).to.not.be.undefined; expect(payload.device.w).to.not.be.undefined; expect(payload.device.h).to.not.be.undefined; }); + it('Test provide user params', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + // expecting the same values for user.geo and device.geo + expect(payload.user.geo.type).to.eql(2); + expect(payload.user.geo.lat).to.eql(40.0); + expect(payload.user.geo.lon).to.eql(-80.0); + expect(payload.user.geo.country).to.eql('USA'); + expect(payload.user.geo.region).to.eql('NY'); + expect(payload.user.geo.metro).to.eql('501'); + expect(payload.user.geo.city).to.eql('New York'); + expect(payload.user.geo.zip).to.eql('10001'); + }); + it('Test that all empties are removed', function () { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); const payload = JSON.parse(request.data); diff --git a/test/spec/ortbConverter/video_spec.js b/test/spec/ortbConverter/video_spec.js index 8ac6d8b4d08..ab4034bb60a 100644 --- a/test/spec/ortbConverter/video_spec.js +++ b/test/spec/ortbConverter/video_spec.js @@ -30,7 +30,6 @@ describe('pbjs -> ortb video conversion', () => { h: 2, mimes: ['video/mp4'], skip: 1, - placement: 1, }, }, }, diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index df837e5547e..4d0962a0b2c 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -1,7 +1,7 @@ import * as events from 'src/events.js'; import * as utils from 'src/utils.js'; import { - doRender, + doRender, getBidToRender, getRenderingData, handleCreativeEvent, handleNativeMessage, @@ -24,6 +24,28 @@ describe('adRendering', () => { sandbox.restore(); }) + describe('getBidToRender', () => { + beforeEach(() => { + sandbox.stub(auctionManager, 'findBidByAdId').callsFake(() => 'auction-bid') + }); + it('should default to bid from auctionManager', () => { + return getBidToRender('adId', true, Promise.resolve(null)).then((res) => { + expect(res).to.eql('auction-bid'); + sinon.assert.calledWith(auctionManager.findBidByAdId, 'adId'); + }); + }); + it('should give precedence to override promise', () => { + return getBidToRender('adId', true, Promise.resolve('override')).then((res) => { + expect(res).to.eql('override'); + sinon.assert.notCalled(auctionManager.findBidByAdId); + }) + }); + it('should return undef when override rejects', () => { + return getBidToRender('adId', true, Promise.reject(new Error('any reason'))).then(res => { + expect(res).to.not.exist; + }) + }) + }) describe('getRenderingData', () => { let bidResponse; beforeEach(() => { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index ef6d1de0b30..56668759db6 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1510,39 +1510,29 @@ describe('bidderFactory', () => { paapiStub = sinon.stub(); }); - const PAAPI_PROPS = ['fledgeAuctionConfigs', 'paapi']; - - it(`should not accept both ${PAAPI_PROPS.join(' and ')}`, () => { - expect(() => { - runBidder(Object.fromEntries(PAAPI_PROPS.map(prop => [prop, [paapiConfig]]))) - }).to.throw(); - }) + describe(`when response has paapi`, () => { + it('should call paapi config hook with auction configs', function () { + runBidder({ + bids: bids, + paapi: [paapiConfig] + }); + expect(paapiStub.calledOnce).to.equal(true); + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); + }); - PAAPI_PROPS.forEach(paapiProp => { - describe(`using ${paapiProp}`, () => { - it('should call paapi config hook with auction configs', function() { + Object.entries({ + 'missing': undefined, + 'an empty array': [] + }).forEach(([t, bids]) => { + it(`should call paapi config hook with PAAPI configs even when bids is ${t}`, function () { runBidder({ - bids: bids, - [paapiProp]: [paapiConfig] - }) - expect(paapiStub.calledOnce).to.equal(true); - sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); - sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); - }) - - Object.entries({ - 'missing': undefined, - 'an empty array': [] - }).forEach(([t, bids]) => { - it(`should call paapi config hook with PAAPI configs even when bids is ${t}`, function() { - runBidder({ - bids, - [paapiProp]: [paapiConfig] - }) - expect(paapiStub.calledOnce).to.be.true; - sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); - expect(addBidResponseStub.calledOnce).to.equal(false); + bids, + paapi: [paapiConfig] }); + expect(paapiStub.calledOnce).to.be.true; + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); + expect(addBidResponseStub.calledOnce).to.equal(false); }); }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 5e833dc81b6..962ba1b0178 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -26,7 +26,8 @@ import {enrichFPD} from '../../../src/fpd/enrichment.js'; import {mockFpdEnrichments} from '../../helpers/fpd.js'; import {generateUUID} from '../../../src/utils.js'; import {getCreativeRenderer} from '../../../src/creativeRenderers.js'; -import {BID_STATUS, EVENTS, GRANULARITY_OPTIONS, TARGETING_KEYS} from 'src/constants.js'; +import { BID_STATUS, EVENTS, GRANULARITY_OPTIONS, TARGETING_KEYS } from 'src/constants.js'; +import {getBidToRender} from '../../../src/adRendering.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -200,12 +201,16 @@ window.apntag = { describe('Unit: Prebid Module', function () { let bidExpiryStub, sandbox; - + function getBidToRenderHook(next, adId) { + // make sure we can handle async bidToRender + next(adId, new Promise((resolve) => setTimeout(resolve))) + } before((done) => { hook.ready(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); resetDebugging(); sinon.stub(filters, 'isActualBid').returns(true); // stub this out so that we can use vanilla objects as bids + getBidToRender.before(getBidToRenderHook, 100); // preload creative renderer getCreativeRenderer({}).then(() => done()); }); @@ -228,6 +233,7 @@ describe('Unit: Prebid Module', function () { after(function() { auctionManager.clearAllAuctions(); filters.isActualBid.restore(); + getBidToRender.getHooks({hook: getBidToRenderHook}).remove(); }); describe('and global adUnits', () => { @@ -1245,16 +1251,25 @@ describe('Unit: Prebid Module', function () { spyAddWinningBid.restore(); }); + function renderAd(...args) { + $$PREBID_GLOBAL$$.renderAd(...args); + return new Promise((resolve) => { + setTimeout(resolve, 10); + }); + } + it('should require doc and id params', function () { - $$PREBID_GLOBAL$$.renderAd(); - var error = 'Error rendering ad (id: undefined): missing adId'; - assert.ok(spyLogError.calledWith(error), 'expected param error was logged'); + return renderAd().then(() => { + var error = 'Error rendering ad (id: undefined): missing adId'; + assert.ok(spyLogError.calledWith(error), 'expected param error was logged'); + }) }); it('should log message with bid id', function () { - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var message = 'Calling renderAd with adId :' + bidId; - assert.ok(spyLogMessage.calledWith(message), 'expected message was logged'); + return renderAd(doc, bidId).then(() => { + var message = 'Calling renderAd with adId :' + bidId; + assert.ok(spyLogMessage.calledWith(message), 'expected message was logged'); + }) }); it('should write the ad to the doc', function () { @@ -1262,23 +1277,26 @@ describe('Unit: Prebid Module', function () { ad: "" }); adResponse.ad = ""; - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); - assert.ok(doc.close.called, 'close method called'); + return renderAd(doc, bidId).then(() => { + assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); + assert.ok(doc.close.called, 'close method called'); + }) }); it('should place the url inside an iframe on the doc', function () { pushBidResponseToAuction({ adUrl: 'http://server.example.com/ad/ad.js' }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(doc.createElement, 'iframe'); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(doc.createElement, 'iframe'); + }); }); it('should log an error when no ad or url', function () { pushBidResponseToAuction({}); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.called(spyLogError); + return renderAd(doc, bidId).then(() => { + sinon.assert.called(spyLogError); + }); }); it('should log an error when not in an iFrame', function () { @@ -1286,17 +1304,19 @@ describe('Unit: Prebid Module', function () { ad: "" }); inIframe = false; - $$PREBID_GLOBAL$$.renderAd(document, bidId); - const error = `Error rendering ad (id: ${bidId}): renderAd was prevented from writing to the main document.`; - assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + return renderAd(document, bidId).then(() => { + const error = `Error rendering ad (id: ${bidId}): renderAd was prevented from writing to the main document.`; + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); }); it('should not render videos', function () { pushBidResponseToAuction({ mediatype: 'video' }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.notCalled(doc.write); + return renderAd(doc, bidId).then(() => { + sinon.assert.notCalled(doc.write); + }); }); it('should catch errors thrown when trying to write ads to the page', function () { @@ -1306,25 +1326,28 @@ describe('Unit: Prebid Module', function () { var error = { message: 'doc write error' }; doc.write = sinon.stub().throws(error); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var errorMessage = `Error rendering ad (id: ${bidId}): doc write error` - assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); + return renderAd(doc, bidId).then(() => { + var errorMessage = `Error rendering ad (id: ${bidId}): doc write error` + assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); + }); }); it('should log an error when ad not found', function () { var fakeId = 99; - $$PREBID_GLOBAL$$.renderAd(doc, fakeId); - var error = `Error rendering ad (id: ${fakeId}): Cannot find ad '${fakeId}'` - assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + return renderAd(doc, fakeId).then(() => { + var error = `Error rendering ad (id: ${fakeId}): Cannot find ad '${fakeId}'` + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); }); it('should save bid displayed to winning bid', function () { pushBidResponseToAuction({ ad: "" }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); + return renderAd(doc, bidId).then(() => { + assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); + }); }); it('fires billing url if present on s2s bid', function () { @@ -1335,22 +1358,23 @@ describe('Unit: Prebid Module', function () { burl }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - - sinon.assert.calledOnce(triggerPixelStub); - sinon.assert.calledWith(triggerPixelStub, burl); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, burl); + }); }); it('should call addWinningBid', function () { pushBidResponseToAuction({ ad: "" }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var message = 'Calling renderAd with adId :' + bidId; - sinon.assert.calledWith(spyLogMessage, message); + return renderAd(doc, bidId).then(() => { + var message = 'Calling renderAd with adId :' + bidId; + sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + }); }); it('should warn stale rendering', function () { @@ -1367,38 +1391,40 @@ describe('Unit: Prebid Module', function () { }); // First render should pass with no warning and added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.neverCalledWith(spyLogWarn, warning); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.notCalled(onStaleEvent); - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.notCalled(onStaleEvent); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - // Reset call history for spies and stubs - spyLogMessage.resetHistory(); - spyLogWarn.resetHistory(); - spyAddWinningBid.resetHistory(); - onWonEvent.resetHistory(); - onStaleEvent.resetHistory(); + // Reset call history for spies and stubs + spyLogMessage.resetHistory(); + spyLogWarn.resetHistory(); + spyAddWinningBid.resetHistory(); + onWonEvent.resetHistory(); + onStaleEvent.resetHistory(); - // Second render should have a warning but still added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledWith(spyLogWarn, warning); + // Second render should have a warning but still added to winning bids + return renderAd(doc, bidId); + }).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.calledWith(onStaleEvent, adResponse); + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.calledWith(onStaleEvent, adResponse); - // Clean up - $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + // Clean up + $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + }); }); it('should stop stale rendering', function () { @@ -1418,38 +1444,40 @@ describe('Unit: Prebid Module', function () { }); // First render should pass with no warning and added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.neverCalledWith(spyLogWarn, warning); - - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.notCalled(onStaleEvent); - - // Reset call history for spies and stubs - spyLogMessage.resetHistory(); - spyLogWarn.resetHistory(); - spyAddWinningBid.resetHistory(); - onWonEvent.resetHistory(); - onStaleEvent.resetHistory(); - - // Second render should have a warning and do not proceed further - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledWith(spyLogWarn, warning); - - sinon.assert.notCalled(spyAddWinningBid); - - sinon.assert.notCalled(onWonEvent); - sinon.assert.calledWith(onStaleEvent, adResponse); - - // Clean up - $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); - configObj.setConfig({'auctionOptions': {}}); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.neverCalledWith(spyLogWarn, warning); + + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.notCalled(onStaleEvent); + + // Reset call history for spies and stubs + spyLogMessage.resetHistory(); + spyLogWarn.resetHistory(); + spyAddWinningBid.resetHistory(); + onWonEvent.resetHistory(); + onStaleEvent.resetHistory(); + + // Second render should have a warning and do not proceed further + return renderAd(doc, bidId); + }).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.calledWith(spyLogWarn, warning); + + sinon.assert.notCalled(spyAddWinningBid); + + sinon.assert.notCalled(onWonEvent); + sinon.assert.calledWith(onStaleEvent, adResponse); + + // Clean up + $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + configObj.setConfig({'auctionOptions': {}}); + }); }); }); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 189066f7f88..664ba51ff1f 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -13,11 +13,23 @@ import 'modules/nativeRendering.js'; import {expect} from 'chai'; -import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS } from 'src/constants.js'; +import {AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS} from 'src/constants.js'; +import {getBidToRender} from '../../../src/adRendering.js'; describe('secureCreatives', () => { let sandbox; + function getBidToRenderHook(next, adId) { + // make sure that bids can be retrieved asynchronously + next(adId, new Promise((resolve) => setTimeout(resolve))) + } + before(() => { + getBidToRender.before(getBidToRenderHook); + }); + after(() => { + getBidToRender.getHooks({hook: getBidToRenderHook}).remove() + }); + beforeEach(() => { sandbox = sinon.sandbox.create(); }); @@ -30,6 +42,10 @@ describe('secureCreatives', () => { return Object.assign({origin: 'mock-origin', ports: []}, ev) } + function receive(ev) { + return Promise.resolve(receiveMessage(ev)); + } + describe('getReplier', () => { it('should use source.postMessage if no MessagePort is available', () => { const ev = { @@ -153,17 +169,17 @@ describe('secureCreatives', () => { data: JSON.stringify(data), }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + }); }); it('should allow stale rendering without config', function () { @@ -180,29 +196,26 @@ describe('secureCreatives', () => { data: JSON.stringify(data) }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - resetHistories(adResponse.renderer.render); - - receiveMessage(ev); - - sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + resetHistories(adResponse.renderer.render); + return receive(ev); + }).then(() => { + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + }); }); it('should stop stale rendering with config', function () { @@ -221,29 +234,27 @@ describe('secureCreatives', () => { data: JSON.stringify(data) }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - resetHistories(adResponse.renderer.render); - - receiveMessage(ev); - - sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.notCalled(adResponse.renderer.render); - sinon.assert.neverCalledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); - - configObj.setConfig({'auctionOptions': {}}); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + + resetHistories(adResponse.renderer.render); + return receive(ev) + }).then(() => { + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.notCalled(adResponse.renderer.render); + sinon.assert.neverCalledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + configObj.setConfig({'auctionOptions': {}}); + }); }); it('should emit AD_RENDER_FAILED if requested missing adId', () => { @@ -253,11 +264,12 @@ describe('secureCreatives', () => { adId: 'missing' }) }); - receiveMessage(ev); - sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ - reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, - adId: 'missing' - })); + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + adId: 'missing' + })); + }); }); it('should emit AD_RENDER_FAILED if creative can\'t be sent to rendering frame', () => { @@ -271,11 +283,12 @@ describe('secureCreatives', () => { adId: bidId }) }); - receiveMessage(ev) - sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ - reason: AD_RENDER_FAILED_REASON.EXCEPTION, - adId: bidId - })); + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: AD_RENDER_FAILED_REASON.EXCEPTION, + adId: bidId + })); + }) }); it('should include renderers in responses', () => { @@ -287,8 +300,9 @@ describe('secureCreatives', () => { }, data: JSON.stringify({adId: bidId, message: 'Prebid Request'}) }); - receiveMessage(ev); - sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => JSON.parse(ob).renderer === 'mock-renderer')); + return receive(ev).then(() => { + sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => JSON.parse(ob).renderer === 'mock-renderer')); + }); }); if (FEATURES.NATIVE) { @@ -318,23 +332,24 @@ describe('secureCreatives', () => { }, data: JSON.stringify({adId: bidId, message: 'Prebid Request'}) }) - receiveMessage(ev); - sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => { - const data = JSON.parse(ob); - ['width', 'height'].forEach(prop => expect(data[prop]).to.not.exist); - const native = data.native; - sinon.assert.match(native, { - ortb: bid.native.ortb, - adTemplate: bid.native.adTemplate, - rendererUrl: bid.native.rendererUrl, - }) - expect(Object.fromEntries(native.assets.map(({key, value}) => [key, value]))).to.eql({ - adTemplate: bid.native.adTemplate, - rendererUrl: bid.native.rendererUrl, - body: 'vbody' - }); - return true; - })) + return receive(ev).then(() => { + sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => { + const data = JSON.parse(ob); + ['width', 'height'].forEach(prop => expect(data[prop]).to.not.exist); + const native = data.native; + sinon.assert.match(native, { + ortb: bid.native.ortb, + adTemplate: bid.native.adTemplate, + rendererUrl: bid.native.rendererUrl, + }) + expect(Object.fromEntries(native.assets.map(({key, value}) => [key, value]))).to.eql({ + adTemplate: bid.native.adTemplate, + rendererUrl: bid.native.rendererUrl, + body: 'vbody' + }); + return true; + })) + }); }) } }); @@ -361,16 +376,16 @@ describe('secureCreatives', () => { origin: 'any origin' }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubGetAllAssetsMessage); + sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); + sinon.assert.calledOnce(ev.source.postMessage); + sinon.assert.notCalled(stubFireNativeTrackers); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + }); }); it('Prebid native should not fire BID_WON when receiveMessage is called more than once', () => { @@ -391,11 +406,12 @@ describe('secureCreatives', () => { origin: 'any origin' }); - receiveMessage(ev); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - - receiveMessage(ev); - stubEmit.withArgs(EVENTS.BID_WON, adResponse).calledOnce; + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + return receive(ev); + }).then(() => { + stubEmit.withArgs(EVENTS.BID_WON, adResponse).calledOnce; + }); }); }); @@ -422,13 +438,14 @@ describe('secureCreatives', () => { }, }) }); - receiveMessage(event); - expect(stubEmit.calledWith(EVENTS.AD_RENDER_FAILED, { - adId: bidId, - bid: adResponse, - reason: 'Fail reason', - message: 'Fail message' - })).to.equal(shouldEmit); + return receive(event).then(() => { + expect(stubEmit.calledWith(EVENTS.AD_RENDER_FAILED, { + adId: bidId, + bid: adResponse, + reason: 'Fail reason', + message: 'Fail message' + })).to.equal(shouldEmit); + }); }); it(`should${shouldEmit ? ' ' : ' not '}emit AD_RENDER_SUCCEEDED`, () => { @@ -439,12 +456,13 @@ describe('secureCreatives', () => { adId: bidId, }) }); - receiveMessage(event); - expect(stubEmit.calledWith(EVENTS.AD_RENDER_SUCCEEDED, { - adId: bidId, - bid: adResponse, - doc: null - })).to.equal(shouldEmit); + return receive(event).then(() => { + expect(stubEmit.calledWith(EVENTS.AD_RENDER_SUCCEEDED, { + adId: bidId, + bid: adResponse, + doc: null + })).to.equal(shouldEmit); + }); }); }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index fb85b410bd9..d7d7007674a 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,9 +1,8 @@ import {getAdServerTargeting} from 'test/fixtures/fixtures.js'; import {expect} from 'chai'; -import { TARGETING_KEYS } from 'src/constants.js'; +import {TARGETING_KEYS} from 'src/constants.js'; import * as utils from 'src/utils.js'; -import {getHighestCpm, getLatestHighestCpmBid, getOldestHighestCpmBid} from '../../src/utils/reducers.js'; -import {binarySearch, deepEqual, encodeMacroURI, memoize, waitForElementToLoad} from 'src/utils.js'; +import {binarySearch, deepEqual, encodeMacroURI, memoize, sizesToSizeTuples, waitForElementToLoad} from 'src/utils.js'; import {convertCamelToUnderscore} from '../../libraries/appnexusUtils/anUtils.js'; var assert = require('assert'); @@ -167,6 +166,43 @@ describe('Utils', function () { }); }); + describe('sizesToSizeTuples', () => { + Object.entries({ + 'single size, numerical': { + in: [1, 2], + out: [[1, 2]] + }, + 'single size, numerical, nested': { + in: [[1, 2]], + out: [[1, 2]] + }, + 'multiple sizes, numerical': { + in: [[1, 2], [3, 4]], + out: [[1, 2], [3, 4]] + }, + 'single size, string': { + in: '1x2', + out: [[1, 2]] + }, + 'multiple sizes, string': { + in: '1x2, 4x3', + out: [[1, 2], [4, 3]] + }, + 'incorrect size, numerical': { + in: [1], + out: [] + }, + 'incorrect size, string': { + in: '1x', + out: [] + } + }).forEach(([t, {in: input, out}]) => { + it(`can parse ${t}`, () => { + expect(sizesToSizeTuples(input)).to.eql(out); + }) + }) + }) + describe('parseSizesInput', function () { it('should return query string using multi size array', function () { var sizes = [[728, 90], [970, 90]]; diff --git a/test/test_deps.js b/test/test_deps.js index c8a3bcc9426..3f0f766b457 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -19,3 +19,4 @@ require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); require('test/mocks/xhr.js'); require('test/mocks/analyticsStub.js'); +require('test/mocks/ortbConverter.js') diff --git a/webpack.conf.js b/webpack.conf.js index 1035e985b22..5b0d864045e 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -126,7 +126,6 @@ module.exports = { }) ); const core = path.resolve('./src'); - const paapiMod = path.resolve('./modules/paapi.js'); return Object.assign(libraries, { core: { @@ -135,16 +134,6 @@ module.exports = { return module.resource && module.resource.startsWith(core); } }, - paapi: { - // fledgeForGpt imports paapi to keep backwards compat for NPM consumers - // this makes the paapi module its own chunk, pulled in by both paapi and fledgeForGpt entry points, - // to avoid duplication - // TODO: remove this in prebid 9 - name: 'chunk-paapi', - test: (module) => { - return module.resource === paapiMod; - } - } }, { default: false, defaultVendors: false