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/video/constants/ortb.js b/libraries/video/constants/ortb.js index d67c8a5f393..6b64296500e 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 */ diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index e0f7435a1ec..0118408f08d 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 @@ -28,6 +29,10 @@ const STORAGE_FPID_KEY = '33acrossIdFp'; 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') { @@ -50,7 +55,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(); @@ -78,7 +83,7 @@ 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); } @@ -90,32 +95,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 handleFpId(fpId, storageConfig = {}) { +function handleFpId(fpId, storageConfig) { fpId ? storeValue(STORAGE_FPID_KEY, fpId, storageConfig) : deleteFromStorage(STORAGE_FPID_KEY); @@ -151,7 +166,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`); @@ -183,7 +198,10 @@ export const thirthyThreeAcrossIdSubmodule = { } if (storeFpid) { - handleFpId(responseObj.fp, storageConfig); + handleFpId(responseObj.fp, { + enabledStorageTypes, + expires: storageConfig.expires + }); } cb(responseObj.envelope); @@ -193,10 +211,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 8b73a43069d..5f5e7805ff9 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/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/consentManagement.js b/modules/consentManagement.js index 346b241fc1f..c7c618635ba 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.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/consentManagementGpp.js b/modules/consentManagementGpp.js index a7bbca62205..f94048813c6 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -304,8 +304,10 @@ class GPP11Client extends 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.init(mkCmp).then(([client, gppDataPm]) => gppDataPm), {onSuccess, onError}); @@ -434,7 +436,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/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/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/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index abf58aceb45..6314ed15ff9 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -308,7 +308,7 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { * 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 */ @@ -325,7 +325,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 @@ -336,7 +336,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/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/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/paapi.js b/modules/paapi.js index 3d562e83c56..c6cd380c1c9 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -272,11 +272,11 @@ function expandFilters({auctionId, adUnitCode} = {}) { /** * Get PAAPI auction configuration. * - * @param filters - * @param filters.auctionId? optional auction filter; if omitted, the latest auction for each ad unit is used - * @param filters.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(filters = {}, includeBlanks = false) { const output = {}; 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/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/topicsFpdModule.js b/modules/topicsFpdModule.js index 523c0db326a..be3e8444dae 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -201,7 +201,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/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/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/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/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/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/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/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 5b5d8b805cd..19137d9a422 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -163,14 +163,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/fpd/enrichment.js b/src/fpd/enrichment.js index 49e2f7d7cad..65c3db65974 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 7f2d8798e2a..df8ce019ed1 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -283,7 +283,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 */ @@ -393,7 +393,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) { @@ -426,7 +426,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/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 12656951c21..2affc52ab8c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -382,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); @@ -397,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) { @@ -500,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` */ @@ -737,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}} @@ -953,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; @@ -1090,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/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index e2fb6f22cd2..2454d790f90 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'; @@ -55,7 +55,7 @@ describe('33acrossIdSystem', () => { context('if the use of a first-party ID has been enabled', () => { 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 = () => {}; @@ -64,10 +64,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeFpid: true }, - storage: { - type: 'cookie', - expires: 30 - } + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } }); callback(completeCallback); @@ -76,6 +74,7 @@ describe('33acrossIdSystem', () => { 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' @@ -88,14 +87,15 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); - expect(setCookie.calledOnceWithExactly('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 = () => {}; @@ -104,9 +104,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeFpid: true }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -131,6 +130,49 @@ describe('33acrossIdSystem', () => { 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', + storeFpid: true + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + callback(completeCallback); + + 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'); + 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(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.true; + + setCookie.restore(); + cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); + setDataInLocalStorage.restore(); + }); + }); }); context('and the response lacks a first-party ID', () => { @@ -142,9 +184,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeFpid: true }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -154,6 +195,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' @@ -166,11 +208,12 @@ describe('33acrossIdSystem', () => { })); expect(removeDataFromLocalStorage.calledOnceWithExactly('33acrossIdFp')).to.be.true; - expect(setCookie.calledOnceWithExactly('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(); }); }); }); @@ -185,8 +228,8 @@ describe('33acrossIdSystem', () => { pid: '12345' // no storeFpid param }, + enabledStorageTypes: [ 'cookie' ], storage: { - type: 'cookie', expires: 30 } }); @@ -197,6 +240,7 @@ describe('33acrossIdSystem', () => { 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' @@ -209,10 +253,11 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); - expect(setCookie.calledOnceWithExactly('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', () => { @@ -223,9 +268,8 @@ describe('33acrossIdSystem', () => { pid: '12345' // no storeFpid param }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -260,9 +304,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -272,6 +315,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' @@ -284,11 +328,12 @@ 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(); }); }); @@ -502,9 +547,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); sinon.stub(storage, 'getDataFromLocalStorage') @@ -528,9 +572,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'cookie' - } + enabledStorageTypes: [ 'cookie' ], + storage: {} }); sinon.stub(storage, 'getCookie') @@ -547,6 +590,34 @@ describe('33acrossIdSystem', () => { }); }); + context('when a first-party ID is present only in one of the enabled storage types', () => { + it('should call endpoint with the first-party ID found', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + sinon.stub(storage, 'getCookie') + .withArgs('33acrossIdFp') + .returns(''); + sinon.stub(storage, 'getDataFromLocalStorage') + .withArgs('33acrossIdFp') + .returns('33acrossIdFpValue'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('fp=33acrossIdFpValue'); + + storage.getCookie.restore(); + }); + }); + context('when a first-party ID is not present in storage', () => { it('should not call endpoint with the first-party ID included', () => { const completeCallback = () => {}; 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/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/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/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 5528705efd7..f70f614b2c8 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; }); }); });