From 77647180e5fc3c8058adcf5d642499ad3146b495 Mon Sep 17 00:00:00 2001 From: mjaworskiccx <50406214+mjaworskiccx@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:39:56 +0100 Subject: [PATCH 001/113] Ccx Bid Adapter: Add GVLID param (#9359) * adomain support * adomain support * adomain support * adomain support * adomain support * video params * docs changes * Clickonometrics adapter update * Revert "Revert "Clickonometrics Bid Adapter : add gvlid (#9198)" (#9216)" This reverts commit 6d114e83725b403fadd889202b449de225db7275. --- modules/ccxBidAdapter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 7c6b0411023..efdb8992669 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -5,6 +5,7 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'ccx' const storage = getStorageManager({bidderCode: BIDDER_CODE}); const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid' +const GVLID = 773; const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6] const SUPPORTED_VIDEO_MIMES = ['video/mp4', 'video/x-flv'] const SUPPORTED_VIDEO_PLAYBACK_METHODS = [1, 2, 3, 4] @@ -19,8 +20,7 @@ function _getDeviceObj () { function _getSiteObj (bidderRequest) { let site = {} - // TODO: does the fallback to window.location make sense? - let url = bidderRequest?.refererInfo?.page || window.location.href + let url = bidderRequest?.refererInfo?.page if (url.length > 0) { url = url.split('?')[0] } @@ -140,6 +140,7 @@ function _buildResponse (bid, currency, ttl) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { From 51c984e7088a7bdf13da4c36c988938711f0089a Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 21 Dec 2022 11:52:06 -0500 Subject: [PATCH 002/113] Revert "Ccx Bid Adapter: Add GVLID param (#9359)" (#9363) This reverts commit 77647180e5fc3c8058adcf5d642499ad3146b495. Co-authored-by: Demetrio Girardi --- modules/ccxBidAdapter.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index efdb8992669..7c6b0411023 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -5,7 +5,6 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'ccx' const storage = getStorageManager({bidderCode: BIDDER_CODE}); const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid' -const GVLID = 773; const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6] const SUPPORTED_VIDEO_MIMES = ['video/mp4', 'video/x-flv'] const SUPPORTED_VIDEO_PLAYBACK_METHODS = [1, 2, 3, 4] @@ -20,7 +19,8 @@ function _getDeviceObj () { function _getSiteObj (bidderRequest) { let site = {} - let url = bidderRequest?.refererInfo?.page + // TODO: does the fallback to window.location make sense? + let url = bidderRequest?.refererInfo?.page || window.location.href if (url.length > 0) { url = url.split('?')[0] } @@ -140,7 +140,6 @@ function _buildResponse (bid, currency, ttl) { export const spec = { code: BIDDER_CODE, - gvlid: GVLID, supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { From e2cfc185345b5f552d18d50cba8c880b4a9ecae7 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Wed, 21 Dec 2022 12:33:22 -0500 Subject: [PATCH 003/113] GPP consent module: phase one release (#9321) * GPP consent module phase 1 * various updates and added test pages * revise calling CMP, remove provisionalConsent, remove cmpDisplayStatus check, update pbs usersync * change callback check to be more strict * update logic on adding gpp data to ortb2 * update gpp metadata --- .../gpt/gpp_us_hello_world.html | 124 ++++ .../gpt/gpp_us_hello_world_iframe.html | 12 + .../gpp_us_hello_world_iframe_subpage.html | 140 +++++ modules/appnexusBidAdapter.js | 25 +- modules/bidViewability.js | 7 +- modules/consentManagement.js | 8 +- modules/consentManagementGpp.js | 386 ++++++++++++ modules/dfpAdServerVideo.js | 7 +- modules/prebidServerBidAdapter/index.js | 27 +- modules/rtdModule/index.js | 3 +- src/adapterManager.js | 21 +- src/adapters/bidderFactory.js | 10 +- src/consentHandler.js | 11 + src/prebid.js | 3 +- test/spec/modules/appnexusBidAdapter_spec.js | 46 ++ .../spec/modules/consentManagementGpp_spec.js | 577 ++++++++++++++++++ 16 files changed, 1374 insertions(+), 33 deletions(-) create mode 100644 integrationExamples/gpt/gpp_us_hello_world.html create mode 100644 integrationExamples/gpt/gpp_us_hello_world_iframe.html create mode 100644 integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html create mode 100644 modules/consentManagementGpp.js create mode 100644 test/spec/modules/consentManagementGpp_spec.js diff --git a/integrationExamples/gpt/gpp_us_hello_world.html b/integrationExamples/gpt/gpp_us_hello_world.html new file mode 100644 index 00000000000..28be86127fc --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world.html @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+ +
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/gpp_us_hello_world_iframe.html b/integrationExamples/gpt/gpp_us_hello_world_iframe.html new file mode 100644 index 00000000000..c0a62f9d72e --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world_iframe.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html b/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html new file mode 100644 index 00000000000..8c2096d614d --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+ +
+ +
+ + + \ No newline at end of file diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 77ffe0f6b94..919831b8515 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -304,6 +304,18 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } + if (bidderRequest?.gppConsent) { + payload.privacy = { + gpp: bidderRequest.gppConsent.gppString, + gpp_sid: bidderRequest.gppConsent.applicableSections + } + } else if (bidderRequest?.ortb2?.regs?.gpp) { + payload.privacy = { + gpp: bidderRequest.ortb2.regs.gpp, + gpp_sid: bidderRequest.ortb2.regs.gpp_sid + } + } + if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { // TODO: are these the correct referer values? @@ -424,8 +436,17 @@ export const spec = { } }, - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + function checkGppStatus(gppConsent) { + // this is a temporary measure to supress usersync in US-based GPP regions + // this logic will be revised when proper signals (akin to purpose1 from TCF2) can be determined for US GPP + if (gppConsent && Array.isArray(gppConsent.applicableSections)) { + return gppConsent.applicableSections.every(sec => typeof sec === 'number' && sec <= 5); + } + return true; + } + + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent) && checkGppStatus(gppConsent)) { return [{ type: 'iframe', url: 'https://acdn.adnxs.com/dmp/async_usersync.html' diff --git a/modules/bidViewability.js b/modules/bidViewability.js index 362401e6d1c..a5cab99b1a7 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -7,7 +7,7 @@ import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {isFn, logWarn, triggerPixel} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import adapterManager, {gdprDataHandler, uspDataHandler} from '../src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; import {find} from '../src/polyfill.js'; const MODULE_NAME = 'bidViewability'; @@ -44,6 +44,11 @@ export let fireViewabilityPixels = (globalModuleConfig, bid) => { const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + // TODO - need to know what to set here for queryParams... + } + bid[BID_VURL_ARRAY].forEach(url => { // add '?' if not present in URL if (Object.keys(queryParams).length > 0 && url.indexOf('?') === -1) { diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 6ca12010c74..217ceecb1c4 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -94,7 +94,7 @@ function lookupIabConsent({onSuccess, onError}) { const { cmpFrame, cmpFunction } = findCMP(); if (!cmpFrame) { - return onError('CMP not found.'); + return onError('TCF2 CMP not found.'); } // to collect the consent information from the user, we perform two calls to the CMP in parallel: // first to collect the user's consent choices represented in an encoded string (via getConsentData) @@ -314,11 +314,11 @@ export function resetConsentData() { * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) */ export function setConsentConfig(config) { - // if `config.gdpr` or `config.usp` exist, assume new config format. + // if `config.gdpr`, `config.usp` or `config.gpp` exist, assume new config format. // else for backward compatability, just use `config` - config = config && (config.gdpr || config.usp ? config.gdpr : config); + config = config && (config.gdpr || config.usp || config.gpp ? config.gdpr : config); if (!config || typeof config !== 'object') { - logWarn('consentManagement config not defined, exiting consent manager'); + logWarn('consentManagement (gdpr) config not defined, exiting consent manager'); return; } if (isStr(config.cmpApi)) { diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js new file mode 100644 index 00000000000..8a9c3f999f0 --- /dev/null +++ b/modules/consentManagementGpp.js @@ -0,0 +1,386 @@ +/** + * This module adds GPP consentManagement support to prebid.js. It interacts with + * supported CMPs (Consent Management Platforms) to grab the user's consent information + * and make it available for any GPP supported adapters to read/pass this information to + * their system and for various other features/modules in Prebid.js. + */ +import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {gppDataHandler} from '../src/adapterManager.js'; +import {includes} from '../src/polyfill.js'; +import {timedAuctionHook} from '../src/utils/perfMetrics.js'; +import { enrichFPD } from '../src/fpd/enrichment.js'; + +const DEFAULT_CMP = 'iab'; +const DEFAULT_CONSENT_TIMEOUT = 10000; +const CMP_VERSION = 1; + +export let userCMP; +export let consentTimeout; +export let staticConsentData; + +let consentData; +let addedConsentHook = false; + +// add new CMPs here, with their dedicated lookup function +const cmpCallMap = { + 'iab': lookupIabConsent, + 'static': lookupStaticConsentData +}; + +/** + * This function checks the state of the IAB gppData's applicableSection field (to ensure it's populated and has a valid value). + * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section. + * + * TODO --- The initial version of the GPP CMP API spec used this naming convention, but it was later changed as an update to the spec. + * CMPs should adjust their logic to use the new format (applicableSecctions), but that may not be the case with the initial release. + * Added support just in case for this transition period, can likely be removed at a later date... + * @param gppData represents the IAB gppData object + * @returns true|false + */ +function checkApplicableSectionIsReady(gppData) { + return gppData && Array.isArray(gppData.applicableSection) && gppData.applicableSection.length > 0 && gppData.applicableSection[0] !== 0; +} + +/** + * This function checks the state of the IAB gppData's applicableSections field (to ensure it's populated and has a valid value). + * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section. + * @param gppData represents the IAB gppData object + * @returns true|false + */ +function checkApplicableSectionsIsReady(gppData) { + return gppData && Array.isArray(gppData.applicableSections) && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== 0; +} + +/** + * 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 + */ +function lookupStaticConsentData({onSuccess, onError}) { + processCmpData(staticConsentData, {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) + */ +function lookupIabConsent({onSuccess, onError}) { + const cmpApiName = '__gpp'; + const cmpCallbacks = {}; + let registeredPostMessageResponseListener = false; + + function findCMP() { + let f = window; + let cmpFrame; + let cmpDirectAccess = false; + while (true) { + try { + if (typeof f[cmpApiName] === 'function') { + cmpFrame = f; + cmpDirectAccess = true; + break; + } + } catch (e) {} + + // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env + try { + if (f.frames['__gppLocator']) { + cmpFrame = f; + break; + } + } catch (e) {} + + if (f === window.top) break; + f = f.parent; + } + + return { + cmpFrame, + cmpDirectAccess + }; + } + + const {cmpFrame, cmpDirectAccess} = findCMP(); + + if (!cmpFrame) { + return onError('GPP CMP not found.'); + } + + const invokeCMP = (cmpDirectAccess) ? invokeCMPDirect : invokeCMPFrame; + + function invokeCMPDirect({command, callback, parameter, version = CMP_VERSION}, resultCb) { + if (typeof resultCb === 'function') { + resultCb(cmpFrame[cmpApiName](command, callback, parameter, version)); + } else { + cmpFrame[cmpApiName](command, callback, parameter, version); + } + } + + function invokeCMPFrame({command, callback, parameter, version = CMP_VERSION}, resultCb) { + const callName = `${cmpApiName}Call`; + if (!registeredPostMessageResponseListener) { + // when we get the return message, call the stashed callback; + window.addEventListener('message', readPostMessageResponse, false); + registeredPostMessageResponseListener = true; + } + + // call CMP via postMessage + const callId = Math.random().toString(); + const msg = { + [callName]: { + command: command, + parameter, + version, + callId: callId + } + }; + + // TODO? - add logic to check if random was already used in the same session, and roll another if so? + cmpCallbacks[callId] = (typeof callback === 'function') ? callback : resultCb; + cmpFrame.postMessage(msg, '*'); + + function readPostMessageResponse(event) { + const cmpDataPkgName = `${cmpApiName}Return`; + const json = (typeof event.data === 'string' && event.data.includes(cmpDataPkgName)) ? JSON.parse(event.data) : event.data; + if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) { + const payload = json[cmpDataPkgName]; + + if (cmpCallbacks.hasOwnProperty(payload.callId)) { + cmpCallbacks[payload.callId](payload.returnValue); + } + } + } + } + + const startupMsg = (cmpDirectAccess) ? 'Detected GPP CMP API is directly accessible, calling it now...' + : 'Detected GPP CMP is outside the current iframe where Prebid.js is located, calling it now...'; + logInfo(startupMsg); + + invokeCMP({ + command: 'addEventListener', + callback: function (evt) { + if (evt) { + logInfo(`Received a ${(cmpDirectAccess ? 'direct' : 'postmsg')} response from GPP CMP for event`, evt); + if (evt.eventName === 'sectionChange' || evt.pingData.cmpStatus === 'loaded') { + invokeCMP({command: 'getGPPData'}, function (gppData) { + logInfo(`Received a ${cmpDirectAccess ? 'direct' : 'postmsg'} response from GPP CMP for getGPPData`, gppData); + processCmpData(gppData, {onSuccess, onError}); + }); + } else if (evt.pingData.cmpStatus === 'error') { + onError('CMP returned with a cmpStatus:error response. Please check CMP setup.'); + } + } + } + }); +} + +/** + * Look up consent data and store it in the `consentData` global as well as `adapterManager.js`' gdprDataHandler. + * + * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra + * error arguments that will be undefined if there's no error. + */ +function loadConsentData(cb) { + let isDone = false; + let timer = null; + + function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + if (timer != null) { + clearTimeout(timer); + } + isDone = true; + gppDataHandler.setConsentData(consentData); + if (typeof cb === 'function') { + cb(shouldCancelAuction, errMsg, ...extraArgs); + } + } + + if (!includes(Object.keys(cmpCallMap), userCMP)) { + done(null, false, `GPP CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + return; + } + + const callbacks = { + onSuccess: (data) => done(data, false), + onError: function (msg, ...extraArgs) { + done(null, true, msg, ...extraArgs); + } + } + cmpCallMap[userCMP](callbacks); + + if (!isDone) { + const onTimeout = () => { + const continueToAuction = (data) => { + done(data, false, 'GPP CMP did not load, continuing auction...'); + } + processCmpData(consentData, { + onSuccess: continueToAuction, + onError: () => continueToAuction(storeConsentData(undefined)) + }) + } + if (consentTimeout === 0) { + onTimeout(); + } else { + timer = setTimeout(onTimeout, consentTimeout); + } + } +} + +/** + * Like `loadConsentData`, but cache and re-use previously loaded data. + * @param cb + */ +function loadIfMissing(cb) { + if (consentData) { + logInfo('User consent information already known. Pulling internally stored information...'); + // eslint-disable-next-line standard/no-callback-literal + cb(false); + } else { + loadConsentData(cb); + } +} + +/** + * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the + * user's encoded consent string from the supported CMP. Once obtained, the module will store this + * data as part of a gppConsent object which gets transferred to adapterManager's gppDataHandler object. + * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. + * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js + */ +export const requestBidsHook = timedAuctionHook('gpp', function requestBidsHook(fn, reqBidsConfigObj) { + loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { + if (errMsg) { + let log = logWarn; + if (shouldCancelAuction) { + log = logError; + errMsg = `${errMsg} Canceling auction as per consentManagement config.`; + } + log(errMsg, ...extraArgs); + } + + if (shouldCancelAuction) { + fn.stopTiming(); + if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { + reqBidsConfigObj.bidsBackHandler(); + } else { + logError('Error executing bidsBackHandler'); + } + } else { + fn.call(this, reqBidsConfigObj); + } + }); +}); + +/** + * This function checks the consent data provided by CMP to ensure it's in an expected state. + * If it's bad, we call `onError` + * If it's good, then we store the value and call `onSuccess` + */ +function processCmpData(consentObject, {onSuccess, onError}) { + function checkData() { + const gppString = consentObject && consentObject.gppString; + const gppSection = (checkApplicableSectionsIsReady(consentObject)) ? consentObject.applicableSections + : (checkApplicableSectionIsReady(consentObject)) ? consentObject.applicableSection : []; + + return !!( + (!Array.isArray(gppSection)) || + (Array.isArray(gppSection) && (!gppString || !isStr(gppString))) + ); + } + + if (checkData()) { + onError(`CMP returned unexpected value during lookup process.`, consentObject); + } else { + onSuccess(storeConsentData(consentObject)); + } +} + +/** + * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction + * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) + */ +function storeConsentData(cmpConsentObject) { + consentData = { + gppString: (cmpConsentObject) ? cmpConsentObject.gppString : undefined, + + fullGppData: (cmpConsentObject) || undefined, + }; + consentData.applicableSections = (checkApplicableSectionsIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSections + : (checkApplicableSectionIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSection : []; + consentData.apiVersion = CMP_VERSION; + return consentData; +} + +/** + * Simply resets the module's consentData variable back to undefined, mainly for testing purposes + */ +export function resetConsentData() { + consentData = undefined; + userCMP = undefined; + consentTimeout = undefined; + gppDataHandler.reset(); +} + +/** + * A configuration function that initializes some module variables, as well as add a hook into the requestBids function + * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) + */ +export function setConsentConfig(config) { + config = config && config.gpp; + if (!config || typeof config !== 'object') { + logWarn('consentManagement.gpp config not defined, exiting consent manager module'); + return; + } + + if (isStr(config.cmpApi)) { + userCMP = config.cmpApi; + } else { + userCMP = DEFAULT_CMP; + logInfo(`consentManagement.gpp config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); + } + + if (isNumber(config.timeout)) { + consentTimeout = config.timeout; + } else { + consentTimeout = DEFAULT_CONSENT_TIMEOUT; + logInfo(`consentManagement.gpp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); + } + + if (userCMP === 'static') { + if (isPlainObject(config.consentData)) { + staticConsentData = config.consentData; + consentTimeout = 0; + } else { + logError(`consentManagement.gpp config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); + } + } + + logInfo('consentManagement.gpp module has been activated...'); + + if (!addedConsentHook) { + $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + } + addedConsentHook = true; + gppDataHandler.enable(); + loadConsentData(); // immediately look up consent data to make it available without requiring an auction +} +config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); + +export function enrichFPDHook(next, fpd) { + return next(fpd.then(ortb2 => { + const consent = gppDataHandler.getConsentData(); + if (consent) { + if (Array.isArray(consent.applicableSections)) { + deepSetValue(ortb2, 'regs.gpp_sid', consent.applicableSections); + } + deepSetValue(ortb2, 'regs.gpp', consent.gppString); + } + return ortb2; + })); +} + +enrichFPD.before(enrichFPDHook); diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 072715b4bd6..71c9c7aa341 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -8,7 +8,7 @@ import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, bui import { config } from '../src/config.js'; import { getHook, submodule } from '../src/hook.js'; import { auctionManager } from '../src/auctionManager.js'; -import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {getPPID} from '../src/adserver.js'; @@ -119,6 +119,11 @@ export function buildDfpVideoUrl(options) { const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + // TODO - need to know what to set here for queryParams... + } + if (!queryParams.ppid) { const ppid = getPPID(); if (ppid != null) { diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 072c280aecf..b609d1a54ec 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -216,7 +216,7 @@ export function resetSyncedStatus() { /** * @param {Array} bidderCodes list of bidders to request user syncs for. */ -function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { +function queueSync(bidderCodes, gdprConsent, uspConsent, gppConsent, s2sConfig) { if (_s2sConfigs.length === _syncCount) { return; } @@ -246,6 +246,15 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { payload.us_privacy = uspConsent; } + if (gppConsent) { + // proposing the following formatting, can adjust if needed... + // update - leaving this param as an array, since it's part of a POST payload where the [] characters shouldn't matter too much + payload.gpp_sid = gppConsent.applicableSections + // should we add check if applicableSections was not equal to -1 (where user was out of scope)? + // this would be similar to what was done above for TCF + payload.gpp = gppConsent.gppString; + } + if (typeof s2sConfig.coopSync === 'boolean') { payload.coopSync = s2sConfig.coopSync; } @@ -330,7 +339,7 @@ function doBidderSync(type, url, bidder, done, timeout) { * * @param {Array} bidders a list of bidder names */ -function doClientSideSyncs(bidders, gdprConsent, uspConsent) { +function doClientSideSyncs(bidders, gdprConsent, uspConsent, gppConsent) { bidders.forEach(bidder => { let clientAdapter = adapterManager.getBidAdapter(bidder); if (clientAdapter && clientAdapter.registerSyncs) { @@ -341,7 +350,8 @@ function doClientSideSyncs(bidders, gdprConsent, uspConsent) { clientAdapter, [], gdprConsent, - uspConsent + uspConsent, + gppConsent ) ); } @@ -411,12 +421,13 @@ function getMatchingConsentUrl(urlProp, gdprConsent) { } function getConsentData(bidRequests) { - let gdprConsent, uspConsent; + let gdprConsent, uspConsent, gppConsent; if (Array.isArray(bidRequests) && bidRequests.length > 0) { gdprConsent = bidRequests[0].gdprConsent; uspConsent = bidRequests[0].uspConsent; + gppConsent = bidRequests[0].gppConsent; } - return { gdprConsent, uspConsent }; + return { gdprConsent, uspConsent, gppConsent }; } /** @@ -433,7 +444,7 @@ export function PrebidServer() { done = adapterMetrics.startTiming('total').stopBefore(done); bidRequests.forEach(req => useMetrics(req.metrics).join(adapterMetrics, {continuePropagation: false})); - let { gdprConsent, uspConsent } = getConsentData(bidRequests); + let { gdprConsent, uspConsent, gppConsent } = getConsentData(bidRequests); if (Array.isArray(_s2sConfigs)) { if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { @@ -441,7 +452,7 @@ export function PrebidServer() { .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) .filter((bidder, index, array) => (array.indexOf(bidder) === index)); - queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); + queueSync(syncBidders, gdprConsent, uspConsent, gppConsent, s2sBidRequest.s2sConfig); } processPBSRequest(s2sBidRequest, bidRequests, ajax, { @@ -450,7 +461,7 @@ export function PrebidServer() { bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); } done(); - doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); + doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, onError: done, onBid: function ({adUnit, bid}) { diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 876da8dda37..05594c63132 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -163,7 +163,7 @@ import {getHook, module} from '../../src/hook.js'; import {logError, logInfo, logWarn} from '../../src/utils.js'; import * as events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; -import adapterManager, {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js'; import {find} from '../../src/polyfill.js'; import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; @@ -246,6 +246,7 @@ function getConsentData() { return { gdpr: gdprDataHandler.getConsentData(), usp: uspDataHandler.getConsentData(), + gpp: gppDataHandler.getConsentData(), coppa: !!(config.getConfig('coppa')) } } diff --git a/src/adapterManager.js b/src/adapterManager.js index eb6a74a82a4..f4f9e59fb84 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -31,7 +31,7 @@ import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; import {adunitCounter} from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; -import {GdprConsentHandler, UspConsentHandler} from './consentHandler.js'; +import {GdprConsentHandler, UspConsentHandler, GppConsentHandler} from './consentHandler.js'; import * as events from './events.js'; import CONSTANTS from './constants.json'; import {useMetrics} from './utils/perfMetrics.js'; @@ -161,6 +161,7 @@ function getAdUnitCopyForClientAdapters(adUnits) { export let gdprDataHandler = new GdprConsentHandler(); export let uspDataHandler = new UspConsentHandler(); +export let gppDataHandler = new GppConsentHandler(); export let coppaDataHandler = { getCoppa: function() { @@ -309,17 +310,17 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a } }); - if (gdprDataHandler.getConsentData()) { - bidRequests.forEach(bidRequest => { + bidRequests.forEach(bidRequest => { + if (gdprDataHandler.getConsentData()) { bidRequest['gdprConsent'] = gdprDataHandler.getConsentData(); - }); - } - - if (uspDataHandler.getConsentData()) { - bidRequests.forEach(bidRequest => { + } + if (uspDataHandler.getConsentData()) { bidRequest['uspConsent'] = uspDataHandler.getConsentData(); - }); - } + } + if (gppDataHandler.getConsentData()) { + bidRequest['gppConsent'] = gppDataHandler.getConsentData(); + } + }); bidRequests.forEach(bidRequest => { config.runWithBidder(bidRequest.bidderCode, () => { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 7e66b849783..55c84e57062 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -214,7 +214,7 @@ export function newBidder(spec) { done(); config.runWithBidder(spec.code, () => { events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest); - registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent); + registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent, bidderRequest.gppConsent); }); } @@ -295,8 +295,8 @@ export function newBidder(spec) { return false; } - function registerSyncs(responses, gdprConsent, uspConsent) { - registerSyncInner(spec, responses, gdprConsent, uspConsent); + function registerSyncs(responses, gdprConsent, uspConsent, gppConsent) { + registerSyncInner(spec, responses, gdprConsent, uspConsent, gppConsent); } function filterAndWarn(bid) { @@ -448,14 +448,14 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe }) }, 'processBidderRequests') -export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent) { +export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent, gppConsent) { const aliasSyncEnabled = config.getConfig('userSync.aliasSyncEnabled'); if (spec.getUserSyncs && (aliasSyncEnabled || !adapterManager.aliasRegistry[spec.code])) { let filterConfig = config.getConfig('userSync.filterSettings'); let syncs = spec.getUserSyncs({ iframeEnabled: !!(filterConfig && (filterConfig.iframe || filterConfig.all)), pixelEnabled: !!(filterConfig && (filterConfig.image || filterConfig.all)), - }, responses, gdprConsent, uspConsent); + }, responses, gdprConsent, uspConsent, gppConsent); if (syncs) { if (!Array.isArray(syncs)) { syncs = [syncs]; diff --git a/src/consentHandler.js b/src/consentHandler.js index 01470a4b38c..b1b2a04c043 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -107,3 +107,14 @@ export class GdprConsentHandler extends ConsentHandler { } } } + +export class GppConsentHandler extends ConsentHandler { + getConsentMeta() { + const consentData = this.getConsentData(); + if (consentData && this.generatedTime) { + return { + generatedAt: this.generatedTime, + } + } + } +} diff --git a/src/prebid.js b/src/prebid.js index f30b4ea9bbe..a4abceb01d5 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -45,7 +45,7 @@ import {executeRenderer, isRendererRequired} from './Renderer.js'; import {createBid} from './bidfactory.js'; import {storageCallbacks} from './storageManager.js'; import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js'; -import {default as adapterManager, gdprDataHandler, getS2SBidderSet, uspDataHandler} from './adapterManager.js'; +import {default as adapterManager, gdprDataHandler, getS2SBidderSet, gppDataHandler, uspDataHandler} from './adapterManager.js'; import CONSTANTS from './constants.json'; import * as events from './events.js'; import {newMetrics, useMetrics} from './utils/perfMetrics.js'; @@ -336,6 +336,7 @@ function getConsentMetadata() { return { gdpr: gdprDataHandler.getConsentMeta(), usp: uspDataHandler.getConsentMeta(), + gpp: gppDataHandler.getConsentMeta(), coppa: !!(config.getConfig('coppa')) } } diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 5cd189da9a1..1ab8feceaeb 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -985,6 +985,52 @@ describe('AppNexusAdapter', function () { expect(payload.us_privacy).to.exist.and.to.equal(consentString); }); + it('should add gpp information to the request via bidderRequest.gppConsent', function () { + let consentString = 'abc1234'; + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': [8] + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.privacy).to.exist; + expect(payload.privacy.gpp).to.equal(consentString); + expect(payload.privacy.gpp_sid).to.deep.equal([8]); + }); + + it('should add gpp information to the request via bidderRequest.ortb2.regs', function () { + let consentString = 'abc1234'; + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'ortb2': { + 'regs': { + 'gpp': consentString, + 'gpp_sid': [7] + } + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.privacy).to.exist; + expect(payload.privacy.gpp).to.equal(consentString); + expect(payload.privacy.gpp_sid).to.deep.equal([7]); + }); + it('supports sending hybrid mobile app parameters', function () { let appRequest = Object.assign({}, bidRequests[0], diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js new file mode 100644 index 00000000000..1170f418caf --- /dev/null +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -0,0 +1,577 @@ +import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData } from 'modules/consentManagementGpp.js'; +import { gppDataHandler } from 'src/adapterManager.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import 'src/prebid.js'; + +let expect = require('chai').expect; + +describe('consentManagementGpp', function () { + describe('setConsentConfig tests:', function () { + describe('empty setConsentConfig value', function () { + beforeEach(function () { + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logWarn.restore(); + config.resetConfig(); + resetConsentData(); + }); + + it('should use system default values', function () { + setConsentConfig({ + gpp: {} + }); + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + sinon.assert.callCount(utils.logInfo, 3); + }); + + it('should exit consent manager if config is not an object', function () { + setConsentConfig(''); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); + + it('should exit consentManagement module if config is "undefined"', function () { + setConsentConfig(undefined); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); + + it('should not produce any consent metadata', function () { + setConsentConfig(undefined) + let consentMetadata = gppDataHandler.getConsentMeta(); + expect(consentMetadata).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }) + + it('should immediately look up consent data', () => { + setConsentConfig({ + gpp: { + cmpApi: 'invalid' + } + }); + expect(gppDataHandler.ready).to.be.true; + }) + }); + + describe('valid setConsentConfig value', function () { + afterEach(function () { + config.resetConfig(); + }); + + it('results in all user settings overriding system defaults', function () { + let allConfig = { + gpp: { + cmpApi: 'iab', + timeout: 7500 + } + }; + + setConsentConfig(allConfig); + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(7500); + }); + + it('should recognize config.gpp, with default cmpApi and timeout', function () { + setConsentConfig({ + gpp: {} + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + }); + + it('should enable gppDataHandler', () => { + setConsentConfig({ + gpp: {} + }); + expect(gppDataHandler.enabled).to.be.true; + }); + }); + + describe('static consent string setConsentConfig value', () => { + afterEach(() => { + config.resetConfig(); + }); + + it('results in user settings overriding system defaults for v2 spec', () => { + let staticConfig = { + gpp: { + cmpApi: 'static', + timeout: 7500, + consentData: { + applicableSections: [7], + gppString: 'ABCDEFG1234', + gppVersion: 1, + sectionId: 3, + sectionList: [] + } + } + }; + + setConsentConfig(staticConfig); + expect(userCMP).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + const consent = gppDataHandler.getConsentData(); + expect(consent.gppString).to.eql(staticConfig.gpp.consentData.gppString); + expect(consent.fullGppData).to.eql(staticConfig.gpp.consentData); + expect(staticConsentData).to.be.equal(staticConfig.gpp.consentData); + }); + }); + }); + + describe('requestBidsHook tests:', function () { + let goodConfig = { + gpp: { + cmpApi: 'iab', + timeout: 7500, + } + }; + + const staticConfig = { + gpp: { + cmpApi: 'static', + timeout: 7500, + consentData: { + gppString: 'abc12345', + applicableSections: [7] + } + } + } + + let didHookReturn; + + beforeEach(resetConsentData); + after(resetConsentData); + + describe('error checks:', function () { + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logWarn'); + sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + utils.logWarn.restore(); + utils.logError.restore(); + config.resetConfig(); + }); + + it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', function () { + let badCMPConfig = { + gpp: { + cmpApi: 'bad' + } + }; + setConsentConfig(badCMPConfig); + expect(userCMP).to.be.equal(badCMPConfig.gpp.cmpApi); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + + sinon.assert.calledOnce(utils.logWarn); + expect(didHookReturn).to.be.true; + expect(consent).to.be.null; + }); + + it('should call gppDataHandler.setConsentData() when unknown CMP api is used', () => { + setConsentConfig({ + gpp: { + cmpApi: 'invalid' + } + }); + let hookRan = false; + requestBidsHook(() => { + hookRan = true; + }, {}); + expect(hookRan).to.be.true; + expect(gppDataHandler.ready).to.be.true; + }) + + it('should throw proper errors when CMP is not found', function () { + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + // throw 2 errors; one for no bidsBackHandler and for CMP not being found (this is an error due to gdpr config) + sinon.assert.calledTwice(utils.logError); + expect(didHookReturn).to.be.false; + expect(consent).to.be.null; + expect(gppDataHandler.ready).to.be.true; + }); + + it('should not trip when adUnits have no size', () => { + setConsentConfig(staticConfig); + let ran = false; + requestBidsHook(() => { + ran = true; + }, { + adUnits: [{ + code: 'test', + mediaTypes: { + video: {} + } + }] + }); + return gppDataHandler.promise.then(() => { + expect(ran).to.be.true; + }); + }); + + it('should continue the auction immediately, without consent data, if timeout is 0', (done) => { + setConsentConfig({ + gpp: { + cmpApi: 'iab', + timeout: 0 + } + }); + window.__gpp = function () {}; + try { + requestBidsHook(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([]); + expect(consent.gppString).to.be.undefined; + done(); + }, {}) + } finally { + delete window.__gpp; + } + }); + }); + + describe('already known consentData:', function () { + let cmpStub = sinon.stub(); + + function mockCMP(cmpResponse) { + return function (...args) { + if (args[0] === 'addEventListener') { + args[1](({ + eventName: 'sectionChange' + })); + } else if (args[0] === 'getGPPData') { + return cmpResponse; + } + } + } + + beforeEach(function () { + didHookReturn = false; + window.__gpp = function () {}; + }); + + afterEach(function () { + config.resetConfig(); + cmpStub.restore(); + delete window.__gpp; + resetConsentData(); + }); + + it('should bypass CMP and simply use previously stored consentData', function () { + let testConsentData = { + applicableSections: [7], + gppString: 'xyz', + }; + + cmpStub = sinon.stub(window, '__gpp').callsFake(mockCMP(testConsentData)); + setConsentConfig(goodConfig); + requestBidsHook(() => {}, {}); + cmpStub.reset(); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + + expect(didHookReturn).to.be.true; + expect(consent.gppString).to.equal(testConsentData.gppString); + expect(consent.applicableSections).to.deep.equal(testConsentData.applicableSections); + sinon.assert.notCalled(cmpStub); + }); + }); + + describe('iframe tests', function () { + let cmpPostMessageCb = () => {}; + let stringifyResponse; + + function createIFrameMarker(frameName) { + let ifr = document.createElement('iframe'); + ifr.width = 0; + ifr.height = 0; + ifr.name = frameName; + document.body.appendChild(ifr); + return ifr; + } + + function creatCmpMessageHandler(prefix, returnEvtValue, returnGPPValue) { + return function (event) { + if (event && event.data) { + let data = event.data; + if (data[`${prefix}Call`]) { + let callId = data[`${prefix}Call`].callId; + let response; + if (data[`${prefix}Call`].command === 'addEventListener') { + response = { + [`${prefix}Return`]: { + callId, + returnValue: returnEvtValue, + success: true + } + } + } else if (data[`${prefix}Call`].command === 'getGPPData') { + response = { + [`${prefix}Return`]: { + callId, + returnValue: returnGPPValue, + success: true + } + } + } + event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); + } + } + } + } + + function testIFramedPage(testName, messageFormatString, tarConsentString, tarSections) { + it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { + stringifyResponse = messageFormatString; + setConsentConfig(goodConfig); + requestBidsHook(() => { + let consent = gppDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(consent.gppString).to.equal(tarConsentString); + expect(consent.applicableSections).to.deep.equal(tarSections); + done(); + }, {}); + }); + } + + beforeEach(function () { + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logError.restore(); + utils.logWarn.restore(); + config.resetConfig(); + resetConsentData(); + }); + + describe('v2 CMP workflow for iframe pages:', function () { + stringifyResponse = false; + let ifr2 = null; + + beforeEach(function () { + ifr2 = createIFrameMarker('__gppLocator'); + cmpPostMessageCb = creatCmpMessageHandler('__gpp', { + eventName: 'sectionChange' + }, { + gppString: 'abc12345234', + applicableSections: [7] + }); + window.addEventListener('message', cmpPostMessageCb, false); + }); + + afterEach(function () { + delete window.__gpp; // deletes the local copy made by the postMessage CMP call function + document.body.removeChild(ifr2); + window.removeEventListener('message', cmpPostMessageCb); + }); + + testIFramedPage('with/JSON response', false, 'abc12345234', [7]); + testIFramedPage('with/String response', true, 'abc12345234', [7]); + }); + }); + + describe('direct calls to CMP API tests', function () { + let cmpStub = sinon.stub(); + + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + config.resetConfig(); + cmpStub.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + resetConsentData(); + }); + + describe('v2 CMP workflow for normal pages:', function () { + beforeEach(function () { + window.__gpp = function () {}; + }); + + afterEach(function () { + delete window.__gpp; + }); + + it('performs lookup check and stores consentData for a valid existing user', function () { + let testConsentData = { + gppString: 'abc12345234', + applicableSections: [7] + }; + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.gppString).to.equal(testConsentData.gppString); + expect(consent.applicableSections).to.deep.equal(testConsentData.applicableSections); + }); + + it('produces gdpr metadata', function () { + let testConsentData = { + gppString: 'abc12345234', + applicableSections: [7] + }; + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consentMeta = gppDataHandler.getConsentMeta(); + sinon.assert.notCalled(utils.logError); + expect(consentMeta.generatedAt).to.be.above(1644367751709); + }); + + it('throws an error when processCmpData check fails + does not call requestBids callback', function () { + let testConsentData = {}; + let bidsBackHandlerReturn = false; + + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + [utils.logWarn, utils.logError].forEach((stub) => stub.reset()); + + requestBidsHook(() => { + didHookReturn = true; + }, { + bidsBackHandler: () => bidsBackHandlerReturn = true + }); + let consent = gppDataHandler.getConsentData(); + + sinon.assert.calledOnce(utils.logError); + sinon.assert.notCalled(utils.logWarn); + expect(didHookReturn).to.be.false; + expect(bidsBackHandlerReturn).to.be.true; + expect(consent).to.be.null; + expect(gppDataHandler.ready).to.be.true; + }); + + describe('when proper consent is not available', () => { + let gppStub; + + function runAuction() { + setConsentConfig({ + gpp: { + cmpApi: 'iab', + timeout: 10, + } + }); + return new Promise((resolve, reject) => { + requestBidsHook(() => { + didHookReturn = true; + }, {}); + setTimeout(() => didHookReturn ? resolve() : reject(new Error('Auction did not run')), 20); + }) + } + + function mockGppCmp(gppdata) { + gppStub.callsFake((api, cb) => { + if (api === 'addEventListener') { + // eslint-disable-next-line standard/no-callback-literal + cb({ + pingData: { + cmpStatus: 'loaded' + } + }, true); + } + if (api === 'getGPPData') { + return gppdata; + } + }); + } + + beforeEach(() => { + gppStub = sinon.stub(window, '__gpp'); + }); + + afterEach(() => { + gppStub.restore(); + }) + + it('should continue auction with null consent when CMP is unresponsive', () => { + return runAuction().then(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([]); + expect(consent.gppString).to.be.undefined; + expect(gppDataHandler.ready).to.be.true; + }); + }); + + it('should use consent provided by events other than sectionChange', () => { + mockGppCmp({ + gppString: 'mock-consent-string', + applicableSections: [7] + }); + return runAuction().then(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([7]); + expect(consent.gppString).to.equal('mock-consent-string'); + expect(gppDataHandler.ready).to.be.true; + }); + }); + }); + }); + }); + }); +}); From b4688316e76d69590c5da46dbec5b47cedea04f4 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Wed, 21 Dec 2022 10:53:05 -0700 Subject: [PATCH 004/113] Magnite Analytics Adapter : data deletion function (#9351) * add onDeletionRequest functionality to Magnite adapter * Magnite add onDataDeletionRequest unit testing --- modules/magniteAnalyticsAdapter.js | 10 +++- .../modules/magniteAnalyticsAdapter_spec.js | 50 +++++++++++++------ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 99f384e9eff..c6911897e84 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -643,7 +643,15 @@ magniteAdapter.disableAnalytics = function () { accountId = undefined; resetConfs(); magniteAdapter.originDisableAnalytics(); -} +}; + +magniteAdapter.onDataDeletionRequest = function () { + if (storage.localStorageIsEnabled()) { + storage.removeDataFromLocalStorage(COOKIE_NAME); + } else { + throw Error('Unable to access local storage, no data deleted'); + } +}; magniteAdapter.MODULE_INITIALIZED_TIME = Date.now(); magniteAdapter.referrerHostname = ''; diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 69fd7794ced..3606b4b4550 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -306,7 +306,7 @@ const ANALYTICS_MESSAGE = { describe('magnite analytics adapter', function () { let sandbox; let clock; - let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub; + let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub, removeDataFromLocalStorageStub; let gptSlot0; let gptSlotRenderEnded0; beforeEach(function () { @@ -325,6 +325,7 @@ describe('magnite analytics adapter', function () { getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + removeDataFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage') sandbox = sinon.sandbox.create(); localStorageIsEnabledStub.returns(true); @@ -355,6 +356,7 @@ describe('magnite analytics adapter', function () { getDataFromLocalStorageStub.restore(); setDataInLocalStorageStub.restore(); localStorageIsEnabledStub.restore(); + removeDataFromLocalStorageStub.restore(); magniteAdapter.disableAnalytics(); }); @@ -1932,19 +1934,21 @@ describe('magnite analytics adapter', function () { }); }); - it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { - let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); - inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); - inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); - inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; - expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); - - // not non-UTF char's in query / path which break if noDecodeWholeURL not set - inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + describe('getHostNameFromReferer', () => { + it('correctly grabs hostname from an input URL', function () { + let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); + inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); + inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; + expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); + + // not non-UTF char's in query / path which break if noDecodeWholeURL not set + inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; + expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + }); }); describe(`handle currency conversions`, () => { @@ -1985,4 +1989,22 @@ describe('magnite analytics adapter', function () { expect(bidResponseObj.bidPriceUSD).to.equal(0); }); }); + + describe('onDataDeletionRequest', () => { + it('attempts to delete the magnite cookie when local storage is enabled', () => { + magniteAdapter.onDataDeletionRequest(); + + expect(removeDataFromLocalStorageStub.getCall(0).args[0]).to.equal('mgniSession'); + }); + + it('throws an error if it cannot access the cookie', (done) => { + localStorageIsEnabledStub.returns(false); + try { + magniteAdapter.onDataDeletionRequest(); + } catch (error) { + expect(error.message).to.equal('Unable to access local storage, no data deleted'); + done(); + } + }) + }); }); From d5746c39c437b5e04505cdd75bdef3791eb28566 Mon Sep 17 00:00:00 2001 From: Bill Newman Date: Wed, 21 Dec 2022 19:56:48 +0200 Subject: [PATCH 005/113] Colosuss Bid Adapter: add support First Party Data (#9340) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix * add First Party Data Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk --- modules/colossussspBidAdapter.js | 8 ++ .../modules/colossussspBidAdapter_spec.js | 91 ++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 75e73ffda89..082fb0ac4db 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -87,6 +87,11 @@ export const spec = { logMessage(e); } + const firstPartyData = bidderRequest.ortb2 || {}; + const userObj = firstPartyData.user; + const siteObj = firstPartyData.site; + const appObj = firstPartyData.app; + // TODO: does the fallback to window.location make sense? const location = refferLocation || winLocation; let placements = []; @@ -97,6 +102,9 @@ export const spec = { secure: location.protocol === 'https:' ? 1 : 0, host: location.host, page: location.pathname, + userObj, + siteObj, + appObj, placements: placements }; diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 71e94f0da32..adb9137d892 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -56,6 +56,93 @@ describe('ColossussspAdapter', function () { referer: 'http://www.example.com', reachedTop: true, }, + ortb2: { + app: { + name: 'myappname', + keywords: 'power tools, drills', + content: { + data: [ + { + name: 'www.dataprovider1.com', + ext: { + segtax: 6 + }, + segment: [ + { + id: '687' + }, + { + id: '123' + } + ] + }, + { + name: 'www.dataprovider1.com', + ext: { + segtax: 7 + }, + segment: [ + { + id: '456' + }, + { + id: '789' + } + ] + } + ] + } + }, + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'], + page: 'https://page.example.com/here.html', + ref: 'https://ref.example.com', + keywords: 'power tools, drills', + search: 'drill', + content: { + userrating: '4', + data: [{ + name: 'www.dataprovider1.com', + ext: { + segtax: 7, + cids: ['iris_c73g5jq96mwso4d8'] + }, + segment: [ + { id: '687' }, + { id: '123' } + ] + }] + }, + ext: { + data: { + pageType: 'article', + category: 'repair' + } + } + }, + user: { + yob: 1985, + gender: 'm', + keywords: 'a,b', + data: [{ + name: 'dataprovider.com', + ext: { segtax: 4 }, + segment: [ + { id: '1' } + ] + }], + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + }, bids: [bid] } @@ -91,7 +178,7 @@ describe('ColossussspAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require', 'userObj', 'siteObj', 'appObj'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); @@ -132,7 +219,7 @@ describe('ColossussspAdapter', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require', 'userObj', 'siteObj', 'appObj'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); From 9c3abaea97f282e9e62f3a5b80bcfdbaefc7cb88 Mon Sep 17 00:00:00 2001 From: mjaworskiccx <50406214+mjaworskiccx@users.noreply.github.com> Date: Thu, 22 Dec 2022 20:10:49 +0100 Subject: [PATCH 006/113] Clickonometrics Bid Adapter: gvlid (#9367) * adomain support * adomain support * adomain support * adomain support * adomain support * video params * docs changes * Clickonometrics adapter update * Revert "Revert "Clickonometrics Bid Adapter : add gvlid (#9198)" (#9216)" This reverts commit 6d114e83725b403fadd889202b449de225db7275. * Test fix --- modules/ccxBidAdapter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 7c6b0411023..28f3fe3a166 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -5,6 +5,7 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'ccx' const storage = getStorageManager({bidderCode: BIDDER_CODE}); const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid' +const GVLID = 773; const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6] const SUPPORTED_VIDEO_MIMES = ['video/mp4', 'video/x-flv'] const SUPPORTED_VIDEO_PLAYBACK_METHODS = [1, 2, 3, 4] @@ -19,8 +20,7 @@ function _getDeviceObj () { function _getSiteObj (bidderRequest) { let site = {} - // TODO: does the fallback to window.location make sense? - let url = bidderRequest?.refererInfo?.page || window.location.href + let url = bidderRequest?.refererInfo?.page || '' if (url.length > 0) { url = url.split('?')[0] } @@ -140,6 +140,7 @@ function _buildResponse (bid, currency, ttl) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { From e5bcdaa059a3ad675ff3e3dae78423bb558f498e Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 23 Dec 2022 17:14:37 +0000 Subject: [PATCH 007/113] Prebid 7.30.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c903795e59d..51491f04fbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.30.0-pre", + "version": "7.30.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 490303874f7..6ffdac7d3a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.30.0-pre", + "version": "7.30.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 4e45ba34c76ad03b417942320c376749c9f96b44 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 23 Dec 2022 17:14:37 +0000 Subject: [PATCH 008/113] Increment version to 7.31.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51491f04fbe..7de32ef7102 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.30.0", + "version": "7.31.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6ffdac7d3a6..f96909cc6b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.30.0", + "version": "7.31.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 684691bb69263fcdda1d31b3fabe3db859e609b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Dec 2022 09:29:25 -0500 Subject: [PATCH 009/113] Bump parse-url from 7.0.2 to 8.1.0 (#9372) Bumps [parse-url](https://github.com/IonicaBizau/parse-url) from 7.0.2 to 8.1.0. - [Release notes](https://github.com/IonicaBizau/parse-url/releases) - [Commits](https://github.com/IonicaBizau/parse-url/compare/7.0.2...8.1.0) --- updated-dependencies: - dependency-name: parse-url dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 84 ++++++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7de32ef7102..2b703493403 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.28.0-pre", + "version": "7.31.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -8534,9 +8534,9 @@ } }, "node_modules/documentation": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.0.tgz", - "integrity": "sha512-4AwFzdiseEdtqR0KKLrruIQ5fvh7n5epg47P0ZyOidA5Fes5am+6xjqkDECHPwcv4pxJ6zITaHwzCoGblP0+JQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.1.tgz", + "integrity": "sha512-Y/brACCE3sNnDJPFiWlhXrqGY+NelLYVZShLGse5bT1KdohP4JkPf5T2KNq1YWhIEbDYl/1tebRLC0WYbPQxVw==", "dev": true, "dependencies": { "@babel/core": "^7.18.10", @@ -8548,7 +8548,7 @@ "chokidar": "^3.5.3", "diff": "^5.1.0", "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^12.0.0", + "git-url-parse": "^13.1.0", "github-slugger": "1.4.0", "glob": "^8.0.3", "globals-docs": "^2.4.1", @@ -11261,22 +11261,22 @@ } }, "node_modules/git-up": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-6.0.0.tgz", - "integrity": "sha512-6RUFSNd1c/D0xtGnyWN2sxza2bZtZ/EmI9448n6rCZruFwV/ezeEn2fJP7XnUQGwf0RAtd/mmUCbtH6JPYA2SA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", "dev": true, "dependencies": { "is-ssh": "^1.4.0", - "parse-url": "^7.0.2" + "parse-url": "^8.1.0" } }, "node_modules/git-url-parse": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-12.0.0.tgz", - "integrity": "sha512-I6LMWsxV87vysX1WfsoglXsXg6GjQRKq7+Dgiseo+h0skmp5Hp2rzmcEIRQot9CPA+uzU7x1x7jZdqvTFGnB+Q==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", "dev": true, "dependencies": { - "git-up": "^6.0.0" + "git-up": "^7.0.0" } }, "node_modules/github-slugger": { @@ -19420,24 +19420,21 @@ } }, "node_modules/parse-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", - "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "dev": true, "dependencies": { "protocols": "^2.0.0" } }, "node_modules/parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", - "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dev": true, "dependencies": { - "is-ssh": "^1.4.0", - "normalize-url": "^6.1.0", - "parse-path": "^5.0.0", - "protocols": "^2.0.1" + "parse-path": "^7.0.0" } }, "node_modules/parseurl": { @@ -31782,9 +31779,9 @@ } }, "documentation": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.0.tgz", - "integrity": "sha512-4AwFzdiseEdtqR0KKLrruIQ5fvh7n5epg47P0ZyOidA5Fes5am+6xjqkDECHPwcv4pxJ6zITaHwzCoGblP0+JQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.1.tgz", + "integrity": "sha512-Y/brACCE3sNnDJPFiWlhXrqGY+NelLYVZShLGse5bT1KdohP4JkPf5T2KNq1YWhIEbDYl/1tebRLC0WYbPQxVw==", "dev": true, "requires": { "@babel/core": "^7.18.10", @@ -31797,7 +31794,7 @@ "chokidar": "^3.5.3", "diff": "^5.1.0", "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^12.0.0", + "git-url-parse": "^13.1.0", "github-slugger": "1.4.0", "glob": "^8.0.3", "globals-docs": "^2.4.1", @@ -33988,22 +33985,22 @@ } }, "git-up": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-6.0.0.tgz", - "integrity": "sha512-6RUFSNd1c/D0xtGnyWN2sxza2bZtZ/EmI9448n6rCZruFwV/ezeEn2fJP7XnUQGwf0RAtd/mmUCbtH6JPYA2SA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", "dev": true, "requires": { "is-ssh": "^1.4.0", - "parse-url": "^7.0.2" + "parse-url": "^8.1.0" } }, "git-url-parse": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-12.0.0.tgz", - "integrity": "sha512-I6LMWsxV87vysX1WfsoglXsXg6GjQRKq7+Dgiseo+h0skmp5Hp2rzmcEIRQot9CPA+uzU7x1x7jZdqvTFGnB+Q==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", "dev": true, "requires": { - "git-up": "^6.0.0" + "git-up": "^7.0.0" } }, "github-slugger": { @@ -40244,24 +40241,21 @@ "dev": true }, "parse-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", - "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "dev": true, "requires": { "protocols": "^2.0.0" } }, "parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", - "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dev": true, "requires": { - "is-ssh": "^1.4.0", - "normalize-url": "^6.1.0", - "parse-path": "^5.0.0", - "protocols": "^2.0.1" + "parse-path": "^7.0.0" } }, "parseurl": { From a5ea3097aacb6052a030220bdf87131d4ae5d59b Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 26 Dec 2022 07:34:57 -0700 Subject: [PATCH 010/113] Prebid core: filter adUnits (by `adUnitCodes`) before sending them to RTD and FPD modules (#9355) --- src/prebid.js | 15 +++++++-------- test/spec/unit/pbjs_api_spec.js | 11 +++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index a4abceb01d5..3fb131fb02c 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -629,6 +629,13 @@ $$PREBID_GLOBAL$$.requestBids = (function() { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + if (adUnitCodes && adUnitCodes.length) { + // if specific adUnitCodes supplied filter adUnits for those codes + adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); + } else { + // otherwise derive adUnitCodes from adUnits + adUnitCodes = adUnits && adUnits.map(unit => unit.code); + } const ortb2Fragments = { global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null)) @@ -661,14 +668,6 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); adUnits = useMetrics(metrics).measureTime('requestBids.validate', () => checkAdUnitSetup(adUnits)); - if (adUnitCodes && adUnitCodes.length) { - // if specific adUnitCodes supplied filter adUnits for those codes - adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); - } else { - // otherwise derive adUnitCodes from adUnits - adUnitCodes = adUnits && adUnits.map(unit => unit.code); - } - function auctionDone(bids, timedOut, auctionId) { if (typeof bidsBackHandler === 'function') { try { diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 9d8ea70e200..ae9ea09908a 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1810,6 +1810,16 @@ describe('Unit: Prebid Module', function () { }) }); + it('filtering adUnits by adUnitCodes', () => { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [{code: 'one'}, {code: 'two'}], + adUnitCodes: 'two' + }); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + adUnits: [{code: 'two'}] + })); + }); + it('passing bidder-specific FPD as ortb2Fragments.bidder', () => { configObj.setBidderConfig({ bidders: ['bidderA', 'bidderC'], @@ -1869,6 +1879,7 @@ describe('Unit: Prebid Module', function () { mediaTypes: {banner: {sizes: [[300, 250]]}}, bids: [{bidder: 'bd'}] }], + adUnitCodes: ['au'], ortb2Fragments }); sinon.assert.calledWith(newAuctionStub, sinon.match({ From fdab2f10459bb81fb61d4441a1c64ff749791d6b Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Mon, 26 Dec 2022 06:36:12 -0800 Subject: [PATCH 011/113] Prebid Server: Include adUnitCode in PBS Adapter Requests (#9337) * changes * added support to pass adunitcode to pbs * updated comment * removed console log statements * addressed feedback --- .../pbsExtensions/processors/adUnitCode.js | 13 +++++++++++ libraries/pbsExtensions/processors/pbs.js | 5 ++++ .../prebidServerBidAdapter/ortbConverter.js | 1 + .../pbsExtensions/adUnitCode_spec.js | 23 +++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 libraries/pbsExtensions/processors/adUnitCode.js create mode 100644 test/spec/ortbConverter/pbsExtensions/adUnitCode_spec.js diff --git a/libraries/pbsExtensions/processors/adUnitCode.js b/libraries/pbsExtensions/processors/adUnitCode.js new file mode 100644 index 00000000000..f936e0f662f --- /dev/null +++ b/libraries/pbsExtensions/processors/adUnitCode.js @@ -0,0 +1,13 @@ +import {deepSetValue} from '../../../src/utils.js'; + +export function setImpAdUnitCode(imp, bidRequest) { + const adUnitCode = bidRequest.adUnitCode; + + if (adUnitCode) { + deepSetValue( + imp, + `ext.prebid.adunitcode`, + adUnitCode + ); + } +} diff --git a/libraries/pbsExtensions/processors/pbs.js b/libraries/pbsExtensions/processors/pbs.js index 56a2a391c76..0ed2d12fad8 100644 --- a/libraries/pbsExtensions/processors/pbs.js +++ b/libraries/pbsExtensions/processors/pbs.js @@ -3,6 +3,7 @@ import {deepAccess, isPlainObject, isStr, mergeDeep} from '../../../src/utils.js import {extPrebidMediaType} from './mediaType.js'; import {setRequestExtPrebidAliases} from './aliases.js'; import {setImpBidParams} from './params.js'; +import {setImpAdUnitCode} from './adUnitCode.js'; import {setRequestExtPrebid, setRequestExtPrebidChannel} from './requestExtPrebid.js'; import {setBidResponseVideoCache} from './video.js'; @@ -26,6 +27,10 @@ export const PBS_PROCESSORS = { // sets bid ext.prebid.bidder.[bidderCode] with bidRequest.params, passed through transformBidParams if necessary fn: setImpBidParams }, + adUnitCode: { + // sets bid ext.prebid.adunitcode + fn: setImpAdUnitCode + } }, [BID_RESPONSE]: { mediaType: { diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 0aee261c25d..83335f81bc2 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -246,6 +246,7 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste impIds.add(impId) proxyBidRequests.push({ ...adUnit, + adUnitCode: adUnit.code, ...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']), pbsData: {impId, actualBidRequests, adUnit} }); diff --git a/test/spec/ortbConverter/pbsExtensions/adUnitCode_spec.js b/test/spec/ortbConverter/pbsExtensions/adUnitCode_spec.js new file mode 100644 index 00000000000..e17f9e856b0 --- /dev/null +++ b/test/spec/ortbConverter/pbsExtensions/adUnitCode_spec.js @@ -0,0 +1,23 @@ +import {setImpAdUnitCode} from '../../../../libraries/pbsExtensions/processors/adUnitCode.js'; + +describe('pbjs -> ortb adunit code to imp[].ext.prebid.adunitcode', () => { + function setImp(bidRequest) { + const imp = {}; + setImpAdUnitCode(imp, bidRequest); + return imp; + } + + it('it sets adunitcode in ext.prebid.adunitcode when adUnitCode is present', () => { + expect(setImp({bidder: 'mockBidder', adUnitCode: 'mockAdUnit'})).to.eql({ + 'ext': { + 'prebid': { + 'adunitcode': 'mockAdUnit' + } + } + }) + }); + + it('does not set adunitcode in ext.prebid.adunitcode if adUnit is undefined', () => { + expect(setImp({bidder: 'mockBidder'})).to.eql({}); + }); +}); From 5a424e4b992aabad0d5ffa0b87d66fc078790f5e Mon Sep 17 00:00:00 2001 From: couchcrew-thomas Date: Mon, 26 Dec 2022 15:47:35 +0100 Subject: [PATCH 012/113] Feedad Bid Adapter: fixed usersync parsing (#9353) * added file scaffold * added isBidRequestValid implementation * added local prototype of ad integration * added implementation for placement ID validation * fixed video context filter * applied lint to feedad bid adapter * added unit test for bid request validation * added buildRequest unit test * added unit tests for timeout and bid won callbacks * updated bid request to FeedAd API * added parsing of feedad api bid response * added transmisison of tracking events to FeedAd Api * code cleanup * updated feedad unit tests for buildRequest method * added unit tests for event tracking implementation * added unit test for interpretResponse method * added adapter documentation * added dedicated feedad example page * updated feedad adapter to use live system * updated FeedAd adapter placement ID regex * removed groups from FeedAd adapter placement ID regex * removed dedicated feedad example page * updated imports in FeedAd adapter file to use relative paths * updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() * added GDPR fields to the FeedAd bid request * removed video from supported media types of the FeedAd adapter * increased version code of FeedAd adapter to 1.0.2 * removed unnecessary check of bidder request * fixed unit test testing for old FeedAd version * removed video media type example from documentation file * added gvlid to FeedAd adapter * added decoration parameter to adapter documentation * added pass through of additional bid parameters * added user syncs to FeedAd bid adapter * increased FeedAd bid adapter version * lint pass over FeedAd bid adapter * fixed parsing of user syncs from server response * increased FeedAd bid adapter version * fixed version code in test file --- modules/feedadBidAdapter.js | 14 ++++-- test/spec/modules/feedadBidAdapter_spec.js | 51 ++++++++++++++++------ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 0ab6def38ae..ef2e57c553f 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -7,7 +7,7 @@ import {ajax} from '../src/ajax.js'; * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.3'; +const VERSION = '1.0.4'; /** * @typedef {object} FeedAdApiBidRequest @@ -294,12 +294,20 @@ function trackingHandlerFactory(klass) { /** * Reads the user syncs off the server responses and converts them into Prebid.JS format * @param {SyncOptions} syncOptions - * @param {FeedAdApiBidResponse[]} serverResponses + * @param {ServerResponse[]} serverResponses * @param gdprConsent * @param uspConsent */ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - return serverResponses.map(response => response.ext) + return serverResponses.map(response => { + // validate response format + const ext = deepAccess(response, 'body.ext', []); + if (ext == null) { + return null; + } + return ext; + }) + .filter(ext => ext != null) .flatMap(extension => { // extract user syncs from extension const pixels = syncOptions.pixelEnabled && extension.pixels ? extension.pixels : []; diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 6db0b88a9bf..6aed670a563 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -358,14 +358,20 @@ describe('FeedAdAdapter', function () { const pixelSync2 = {type: 'image', url: 'the pixel url 2'}; const iFrameSync1 = {type: 'iframe', url: 'the iFrame url 1'}; const iFrameSync2 = {type: 'iframe', url: 'the iFrame url 2'}; + const mockServerResponse = (content) => { + if (!(content instanceof Array)) { + content = [content]; + } + return content.map(it => ({body: it})); + }; it('should pass through the syncs out of the extension fields of the server response', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync2], iframes: [iFrameSync1, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) expect(result).to.deep.equal([ pixelSync1, @@ -376,7 +382,7 @@ describe('FeedAdAdapter', function () { }); it('should concat the syncs of all responses', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1], iframes: [iFrameSync2], @@ -391,7 +397,7 @@ describe('FeedAdAdapter', function () { ext: { pixels: [pixelSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); expect(result).to.deep.equal([ pixelSync1, @@ -402,7 +408,7 @@ describe('FeedAdAdapter', function () { }); it('should filter out duplicates', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync1], iframes: [iFrameSync2, iFrameSync2], @@ -411,7 +417,7 @@ describe('FeedAdAdapter', function () { ext: { iframes: [iFrameSync2, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); expect(result).to.deep.equal([ pixelSync1, @@ -420,12 +426,12 @@ describe('FeedAdAdapter', function () { }); it('should not include iFrame syncs if the option is disabled', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync2], iframes: [iFrameSync1, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse); expect(result).to.deep.equal([ pixelSync1, @@ -434,12 +440,12 @@ describe('FeedAdAdapter', function () { }); it('should not include pixel syncs if the option is disabled', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync2], iframes: [iFrameSync1, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, serverResponse); expect(result).to.deep.equal([ iFrameSync1, @@ -448,15 +454,34 @@ describe('FeedAdAdapter', function () { }); it('should not include any syncs if the sync options are disabled or missing', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync2], iframes: [iFrameSync1, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, serverResponse); expect(result).to.deep.equal([]); }); + + it('should handle empty responses', function () { + const serverResponse = mockServerResponse([]); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) + expect(result).to.deep.equal([]); + }); + + it('should not throw if the server response is weird', function () { + const responses = [ + mockServerResponse(null), + mockServerResponse('null'), + mockServerResponse(1234), + mockServerResponse({}), + mockServerResponse([{}, 123]), + ]; + responses.forEach(it => { + expect(() => spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, it)).not.to.throw; + }); + }); }); describe('event tracking calls', function () { @@ -592,7 +617,7 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.3' + sdk_version: '1.0.4' }; subject(data); expect(server.requests.length).to.equal(1); From f794bce175d49bab6da6435d41335fb2fbf45c9c Mon Sep 17 00:00:00 2001 From: vishal-dw <109065778+vishal-dw@users.noreply.github.com> Date: Mon, 26 Dec 2022 14:49:46 +0000 Subject: [PATCH 013/113] Datawrkz adapter: Using bidRequest.getFloor() method for bid floor (#9366) * New Bid Adapter: datawrkz * New Bid Adapter: datawrkz. Test case formatting * New Bid Adatpter: datawrkz - updated import statements * Datawrkz adapter: Using bidRequest.getFloor() method for bid floor --- modules/datawrkzBidAdapter.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index 71870d6437d..193a1723403 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, getBidIdParameter, isArray, getUniqueIdentifierStr, contains } from '../src/utils.js'; +import { deepAccess, getBidIdParameter, isArray, getUniqueIdentifierStr, contains, isFn, isPlainObject } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -84,7 +84,7 @@ export const spec = { /* Generate bid request for banner adunit */ function buildBannerRequest(bidRequest, bidderRequest) { - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let adW = 0; let adH = 0; @@ -135,7 +135,7 @@ function buildNativeRequest(bidRequest, bidderRequest) { let counter = 0; let assets = []; - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let title = deepAccess(bidRequest, 'mediaTypes.native.title'); if (title && title.len) { @@ -196,7 +196,7 @@ function buildNativeRequest(bidRequest, bidderRequest) { /* Generate bid request for video adunit */ function buildVideoRequest(bidRequest, bidderRequest) { - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let sizeObj = getVideoAdUnitSize(bidRequest); @@ -414,6 +414,7 @@ function buildBannerResponse(bidRequest, bidResponse) { } let bidSizes = (deepAccess(bidRequest, 'mediaTypes.banner.sizes')) ? deepAccess(bidRequest, 'mediaTypes.banner.sizes') : bidRequest.sizes; bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -455,6 +456,7 @@ function buildNativeResponse(bidRequest, response) { return; } bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -507,6 +509,7 @@ function buildVideoResponse(bidRequest, response) { let context = bidRequest.mediaTypes.video.context; bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -630,4 +633,21 @@ function getNativeAssestObj(obj, assets) { } } +// BUILD REQUESTS: BIDFLOORS +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.bidfloor) ? bid.params.bidfloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); From b6f9b2effb5c9a51eacc1398f69c5001ab87a14e Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Mon, 26 Dec 2022 22:35:09 +0200 Subject: [PATCH 014/113] Adkernel Bid Adapter: bidbuddy.co.in alias (#9375) --- modules/adkernelBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index b0449418a87..5736c7c19ba 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -97,7 +97,8 @@ export const spec = { {code: 'motionspots'}, {code: 'sonic_twist'}, {code: 'displayioads'}, - {code: 'rtbdemand_com'} + {code: 'rtbdemand_com'}, + {code: 'bidbuddy'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 86ed8f0057fa3cb611f3fc0855244d5981d0a2b0 Mon Sep 17 00:00:00 2001 From: denys-berzoy-confiant <50574764+denys-berzoy-confiant@users.noreply.github.com> Date: Tue, 27 Dec 2022 05:58:33 +0300 Subject: [PATCH 015/113] Confiant RTD Module : initial release (#9325) * Confiant's RTD Provider Module * Confiant RTD Module: - updated script injection code to current standard - added Confiant as an exclusion to load external JS * Confiant RTD Provider: - additional param for enabling BillingEvent added - docs updated - outdated unit test removed Co-authored-by: Patrick McCann --- modules/.submodules.json | 1 + modules/confiantRtdProvider.js | 128 ++++++++++++++++++ modules/confiantRtdProvider.md | 45 ++++++ src/adloader.js | 3 +- test/spec/modules/confiantRtdProvider_spec.js | 107 +++++++++++++++ 5 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 modules/confiantRtdProvider.js create mode 100644 modules/confiantRtdProvider.md create mode 100644 test/spec/modules/confiantRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index c3016914f1b..deeee91e247 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -58,6 +58,7 @@ "blueconicRtdProvider", "browsiRtdProvider", "captifyRtdProvider", + "confiantRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", "hadronRtdProvider", diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js new file mode 100644 index 00000000000..4a524b92d75 --- /dev/null +++ b/modules/confiantRtdProvider.js @@ -0,0 +1,128 @@ +/** + * This module provides comprehensive detection of security, quality, and privacy threats by Confiant Inc, + * the industry leader in real-time detecting and blocking of bad ads + * + * The {@link module:modules/realTimeData} module is required + * The module will inject a Confiant Inc. script into the page to monitor ad impressions + * @module modules/cleanioRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { logError, generateUUID } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +/** + * Injects the Confiant Inc. configuration script into the page, based on proprtyId provided + * @param {string} propertyId + */ +function injectConfigScript(propertyId) { + const scriptSrc = `https://cdn.confiant-integrations.net/${propertyId}/gpt_and_prebid/config.js`; + + loadExternalScript(scriptSrc, 'confiant', () => {}); +} + +/** + * Set up page with Confiant integration + * @param {Object} config + */ +function setupPage(config) { + const propertyId = config?.params?.propertyId; + if (!propertyId) { + logError('Confiant pbjs module: no propertyId provided'); + return false; + } + + const confiant = window.confiant || Object.create(null); + confiant[propertyId] = confiant[propertyId] || Object.create(null); + confiant[propertyId].clientSettings = confiant[propertyId].clientSettings || Object.create(null); + confiant[propertyId].clientSettings.isMGBL = true; + confiant[propertyId].clientSettings.prebidExcludeBidders = config?.params?.prebidExcludeBidders; + confiant[propertyId].clientSettings.prebidNameSpace = config?.params?.prebidNameSpace; + + if (config?.params?.shouldEmitBillableEvent) { + if (window.frames['cnftComm']) { + subscribeToConfiantCommFrame(window); + } else { + setUpMutationObserver(); + } + } + + injectConfigScript(propertyId); + return true; +} + +/** + * Subscribe to window's message events to report Billable events + * @param {Window} targetWindow window instance to subscribe to + */ +function subscribeToConfiantCommFrame(targetWindow) { + targetWindow.addEventListener('message', reportBillableEvents); +} + +let mutationObserver; +/** + * Set up mutation observer to subscribe to Confiant's communication channel ASAP + */ +function setUpMutationObserver() { + mutationObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((addedNode) => { + if (addedNode.nodeName === 'IFRAME' && addedNode.name === 'cnftComm' && !addedNode.pbjsModuleSubscribed) { + addedNode.pbjsModuleSubscribed = true; + mutationObserver.disconnect(); + mutationObserver = null; + const iframeWindow = addedNode.contentWindow; + subscribeToConfiantCommFrame(iframeWindow); + } + }); + }); + }); + mutationObserver.observe(document.head, { childList: true, subtree: true }); +} + +/** + * Emit billable event when Confiant integration reports that it has monitored an impression + */ +function reportBillableEvents (e) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + auctionId: e.data.auctionId, + billingId: generateUUID(), + transactionId: e.data.transactionId, + type: 'impression', + vendor: 'confiant' + }); +} + +/** + * Confiant submodule registration + */ +function registerConfiantSubmodule() { + submodule('realTimeData', { + name: 'confiant', + init: (config) => { + try { + return setupPage(config); + } catch (err) { + logError(err.message); + if (mutationObserver) { + mutationObserver.disconnect(); + } + return false; + } + } + }); +} + +registerConfiantSubmodule(); + +export default { + injectConfigScript, + setupPage, + subscribeToConfiantCommFrame, + setUpMutationObserver, + reportBillableEvents, + registerConfiantSubmodule +}; diff --git a/modules/confiantRtdProvider.md b/modules/confiantRtdProvider.md new file mode 100644 index 00000000000..e92c0aabcba --- /dev/null +++ b/modules/confiantRtdProvider.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Confiant Inc. Rtd provider +Module Type: Rtd Provider +Maintainer: +``` + +Confiant’s module provides comprehensive detection of security, quality, and privacy threats across your ad stack. +Confiant is the industry leader in real-time detecting and blocking of bad ads when it comes to protecting your users and brand reputation. + +To start using this module, please contact [Confiant](https://www.confiant.com/contact) to get an account and customer key. + + +# Integration + +1) Build Prebid bundle with Confiant module included: + + +``` +gulp build --modules=confiantRtdProvider,... +``` + +2) Include the resulting bundle on your page. + +# Configuration + +Configuration of Confiant module is plain simple: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'confiant', + params: { + // so please get in touch with us so we could help you to set up the module with proper parameters + propertyId: '', // required, string param, obtained from Confiant Inc. + prebidExcludeBidders: '', // optional, comma separated list of bidders to exclude from Confiant's prebid.js integration + prebidNameSpace: '', // optional, string param, namespace for prebid.js integration + shouldEmitBillableEvent: false, // optional, boolean param, upon being set to true enables firing of the BillableEvent upon Confiant's impression scanning + } + }] + } +}); +``` diff --git a/src/adloader.js b/src/adloader.js index 64408683e9f..01a77971b93 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -20,7 +20,8 @@ const _approvedLoadExternalJSList = [ 'hadron', 'medianet', 'improvedigital', - 'aaxBlockmeter' + 'aaxBlockmeter', + 'confiant' ] /** diff --git a/test/spec/modules/confiantRtdProvider_spec.js b/test/spec/modules/confiantRtdProvider_spec.js new file mode 100644 index 00000000000..987aca2e020 --- /dev/null +++ b/test/spec/modules/confiantRtdProvider_spec.js @@ -0,0 +1,107 @@ +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js' +import * as events from '../../../src/events.js'; +import CONSTANTS from '../../../src/constants.json'; + +import confiantModule from '../../../modules/confiantRtdProvider.js'; + +const { + injectConfigScript, + setupPage, + subscribeToConfiantCommFrame, + registerConfiantSubmodule +} = confiantModule; + +describe('Confiant RTD module', function () { + describe('setupPage()', function() { + it('should return false if propertId is not present in config', function() { + expect(setupPage({})).to.be.false; + expect(setupPage({ params: {} })).to.be.false; + expect(setupPage({ params: { propertyId: '' } })).to.be.false; + }); + + it('should return true if expected parameters are present', function() { + expect(setupPage({ params: { propertyId: 'clientId' } })).to.be.true; + }); + }); + + describe('Module initialization', function() { + let insertElementStub; + beforeEach(function() { + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + afterEach(function() { + utils.insertElement.restore(); + }); + + it('should subscribe to rovided Window object', function () { + const mockWindow = { addEventListener: sinon.spy() }; + + subscribeToConfiantCommFrame(mockWindow); + + sinon.assert.calledOnce(mockWindow.addEventListener); + }); + + it('should fire BillableEvent as a result for message in comm window', function() { + let listenerCallback; + const mockWindow = { addEventListener: (a, cb) => (listenerCallback = cb) }; + let billableEventsCounter = 0; + + events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (e) => { + if (e.vendor === 'confiant') { + billableEventsCounter++; + expect(e.type).to.equal('impression'); + expect(e.billingId).to.be.a('number'); + expect(e.auctionId).to.equal('auctionId'); + expect(e.transactionId).to.equal('transactionId'); + } + }); + + subscribeToConfiantCommFrame(mockWindow); + listenerCallback({ + data: { + auctionId: 'auctionId', + transactionId: 'transactionId' + } + }); + listenerCallback({ + data: { + auctionId: 'auctionId', + transactionId: 'transactionId' + } + }); + + expect(billableEventsCounter).to.equal(2); + }); + }); + + describe('Sumbodule execution', function() { + let submoduleStub; + let insertElementStub; + beforeEach(function () { + submoduleStub = sinon.stub(hook, 'submodule'); + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + afterEach(function () { + utils.insertElement.restore(); + submoduleStub.restore(); + }); + + function initModule() { + registerConfiantSubmodule(); + + expect(submoduleStub.calledOnceWith('realTimeData')).to.equal(true); + + const registeredSubmoduleDefinition = submoduleStub.getCall(0).args[1]; + expect(registeredSubmoduleDefinition).to.be.an('object'); + expect(registeredSubmoduleDefinition).to.have.own.property('name', 'confiant'); + expect(registeredSubmoduleDefinition).to.have.own.property('init').that.is.a('function'); + + return registeredSubmoduleDefinition; + } + + it('should register Confiant submodule', function () { + initModule(); + }); + }); +}); From 10cf68607b0e6a646fb5c16b67defa8710811941 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 28 Dec 2022 17:11:36 +0000 Subject: [PATCH 016/113] Prebid 7.31.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b703493403..c2405970bde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.31.0-pre", + "version": "7.31.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index f96909cc6b4..52138286c36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.31.0-pre", + "version": "7.31.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From e13592697d17a110e6e94ecc6b2c907cb89eb45c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 28 Dec 2022 17:11:36 +0000 Subject: [PATCH 017/113] Increment version to 7.32.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2405970bde..4bfd2cea1f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.31.0", + "version": "7.32.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 52138286c36..10da88d1c6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.31.0", + "version": "7.32.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ff98601bdc868c98627ba023e2907f1611eec6c5 Mon Sep 17 00:00:00 2001 From: denys-berzoy-confiant <50574764+denys-berzoy-confiant@users.noreply.github.com> Date: Wed, 28 Dec 2022 23:38:46 +0300 Subject: [PATCH 018/113] Confiant RTD Provider: (#9382) - fix comment line --- modules/confiantRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js index 4a524b92d75..2c13ad87d75 100644 --- a/modules/confiantRtdProvider.js +++ b/modules/confiantRtdProvider.js @@ -4,7 +4,7 @@ * * The {@link module:modules/realTimeData} module is required * The module will inject a Confiant Inc. script into the page to monitor ad impressions - * @module modules/cleanioRtdProvider + * @module modules/confiantRtdProvider * @requires module:modules/realTimeData */ From 0caca74e57de50daf23854cbeccdd57ecac7a621 Mon Sep 17 00:00:00 2001 From: inna Date: Thu, 29 Dec 2022 15:39:33 +0200 Subject: [PATCH 019/113] Rise Bid Adapter: added isWrapper parameter to adapter request (#9329) * add Rise adapter * fixes * change param isOrg to org * Rise adapter * change email for rise * fix circle failed * bump * bump * bump * remove space * Upgrade Rise adapter to 5.0 * added isWrapper param * addes is_wrapper parameter to documentation * added is_wrapper to test * removed isWrapper Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: lasloche <62240785+lasloche@users.noreply.github.com> --- modules/riseBidAdapter.js | 3 ++- modules/riseBidAdapter.md | 2 ++ test/spec/modules/riseBidAdapter_spec.js | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 4325a699a3b..0c4d6148f2b 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -400,7 +400,8 @@ function generateGeneralParams(generalObject, bidderRequest) { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, device_type: getDeviceType(navigator.userAgent), ua: navigator.userAgent, - session_id: getBidIdParameter('auctionId', generalObject), + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('auctionId', generalObject), tmax: timeout }; diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 9dab3ec2a30..f0837cb5508 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -25,6 +25,8 @@ The adapter supports Video(instream). | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false | `rtbDomain` | optional | String | Sets the seller end point | "www.test.com" +| `is_wrapper` | private | Boolean | Please don't use unless your account manager asked you to | false + # Test Parameters ```javascript diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 37100073407..4fa4ff354ec 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -116,6 +116,13 @@ describe('riseAdapter', function () { expect(request.data.bids[0].placementId).to.equal(placementId); }); + it('sends the is_wrapper parameter to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('is_wrapper'); + expect(request.data.params.is_wrapper).to.equal(false); + }); + it('sends bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.url).to.equal(ENDPOINT); From 0b1ba0cecf0d7dd796a0a661c0ecb8f582f55a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Thu, 29 Dec 2022 20:12:48 +0200 Subject: [PATCH 020/113] Added video media type support (#9326) --- modules/kueezRtbBidAdapter.js | 29 +++- test/spec/modules/kueezRtbBidAdapter_spec.js | 171 ++++++++++++++++--- 2 files changed, 168 insertions(+), 32 deletions(-) diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 1ee3b7331cb..4b0e5055328 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -1,6 +1,6 @@ import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; const GVLID = 1165; @@ -55,7 +55,7 @@ function isBidRequestValid(bid) { } function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain } = bid; + const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; let { bidFloor, ext } = params; const hashUrl = hashCode(topWindowUrl); const uniqueDealId = getUniqueDealId(hashUrl); @@ -89,7 +89,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { bidderVersion: BIDDER_VERSION, prebidVersion: '$prebid.version$', res: `${screen.width}x${screen.height}`, - schain: schain + schain: schain, + mediaTypes: mediaTypes }; appendUserIdsToRequestPayload(data, userId); @@ -167,11 +168,12 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency, advertiserDomains } = result; + const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; if (!ad || !price) { return; } - output.push({ + + const response = { requestId: bidId, cpm: price, width: width, @@ -180,11 +182,22 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - ad: ad, meta: { advertiserDomains: advertiserDomains || [] } - }) + }; + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); }); return output; } catch (e) { @@ -268,7 +281,7 @@ export const spec = { code: BIDDER_CODE, version: BIDDER_VERSION, gvlid: GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, interpretResponse, diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index f9b2cd41a43..30b2a46cefb 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -1,4 +1,4 @@ -import {expect} from 'chai'; +import { expect } from 'chai'; import { spec as adapter, SUPPORTED_ID_SYSTEMS, @@ -13,8 +13,9 @@ import { getUniqueDealId, } from 'modules/kueezRtbBidAdapter.js'; import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; +import { version } from 'package.json'; +import { useFakeTimers } from 'sinon'; +import { BANNER, VIDEO } from '../../../src/mediaTypes'; const SUB_DOMAIN = 'exchange'; @@ -36,9 +37,42 @@ const BID = { 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc' + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER] }; +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -73,6 +107,23 @@ const SERVER_RESPONSE = { } }; +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['kueezrtb.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + const REQUEST = { data: { width: 300, @@ -83,7 +134,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); return parsedUrl.search; } catch (e) { return ''; @@ -111,6 +162,11 @@ describe('KueezRtbBidAdapter', function () { it('exists and is a string', function () { expect(adapter.code).to.exist.and.to.be.a('string'); }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); }); describe('validate bid requests', function () { @@ -155,7 +211,53 @@ describe('KueezRtbBidAdapter', function () { sandbox.stub(Date, 'now').returns(1000); }); - it('should build request for each size', function () { + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + prebidVersion: version, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + } + } + }); + }); + + it('should build banner request for each size', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -179,6 +281,7 @@ describe('KueezRtbBidAdapter', function () { prebidVersion: version, schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', @@ -193,7 +296,7 @@ describe('KueezRtbBidAdapter', function () { }); describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', @@ -202,7 +305,7 @@ describe('KueezRtbBidAdapter', function () { }); it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -210,7 +313,7 @@ describe('KueezRtbBidAdapter', function () { }); it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ 'url': 'https://sync.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -226,16 +329,16 @@ describe('KueezRtbBidAdapter', function () { }); it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({price: 1, ad: ''}); + const responses = adapter.interpretResponse({ price: 1, ad: '' }); expect(responses).to.be.empty; }); it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); expect(responses).to.be.empty; }); - it('should return an array of interpreted responses', function () { + it('should return an array of interpreted banner responses', function () { const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); expect(responses[0]).to.deep.equal({ @@ -254,6 +357,26 @@ describe('KueezRtbBidAdapter', function () { }); }); + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['kueezrtb.com'] + } + }); + }); + it('should take default TTL', function () { const serverResponse = utils.deepClone(SERVER_RESPONSE); delete serverResponse.body.results[0].exp; @@ -271,11 +394,11 @@ describe('KueezRtbBidAdapter', function () { const userId = (function () { switch (idSystemProvider) { case 'lipb': - return {lipbid: id}; + return { lipbid: id }; case 'parrableId': - return {eid: id}; + return { eid: id }; case 'id5id': - return {uid: id}; + return { uid: id }; default: return id; } @@ -294,18 +417,18 @@ describe('KueezRtbBidAdapter', function () { describe('alternate param names extractors', function () { it('should return undefined when param not supported', function () { - const cid = extractCID({'c_id': '1'}); - const pid = extractPID({'p_id': '1'}); - const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + const cid = extractCID({ 'c_id': '1' }); + const pid = extractPID({ 'p_id': '1' }); + const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); expect(cid).to.be.undefined; expect(pid).to.be.undefined; expect(subDomain).to.be.undefined; }); it('should return value when param supported', function () { - const cid = extractCID({'cID': '1'}); - const pid = extractPID({'Pid': '2'}); - const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + const cid = extractCID({ 'cID': '1' }); + const pid = extractPID({ 'Pid': '2' }); + const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); expect(cid).to.be.equal('1'); expect(pid).to.be.equal('2'); expect(subDomain).to.be.equal('prebid'); @@ -365,7 +488,7 @@ describe('KueezRtbBidAdapter', function () { now }); setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + const { value, created } = getStorageItem('myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -381,8 +504,8 @@ describe('KueezRtbBidAdapter', function () { }); it('should parse JSON value', function () { - const data = JSON.stringify({event: 'send'}); - const {event} = tryParseJSON(data); + const data = JSON.stringify({ event: 'send' }); + const { event } = tryParseJSON(data); expect(event).to.be.equal('send'); }); From 57555bc4a0b916bd32dbcdf5030d3ead20a103d1 Mon Sep 17 00:00:00 2001 From: "Adserver.Online" <61009237+adserver-online@users.noreply.github.com> Date: Tue, 3 Jan 2023 17:22:48 +0200 Subject: [PATCH 021/113] Aso Bid Adapter: add bcmint alias (#9387) * Add bcmint alias * kick off tests Co-authored-by: dev Co-authored-by: Chris Huie --- modules/asoBidAdapter.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index 83b182e4ca5..39c42f193b2 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -10,11 +10,11 @@ import { parseSizesInput, tryAppendQueryString } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; -import {parseDomain} from '../src/refererDetection.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { parseDomain } from '../src/refererDetection.js'; const BIDDER_CODE = 'aso'; const DEFAULT_SERVER_URL = 'https://srv.aso1.net'; @@ -27,6 +27,9 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], + aliases: [ + {code: 'bcmint'} + ], isBidRequestValid: bid => { return !!bid.params && !!bid.params.zone; From ccc9bba348275e941d39ea32284d4983cbcc20d2 Mon Sep 17 00:00:00 2001 From: Giovanni Sollazzo Date: Tue, 3 Jan 2023 17:02:37 +0100 Subject: [PATCH 022/113] AIDEM Bid Adapter: added wpar and placementId param (#9377) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object Co-authored-by: darkstar Co-authored-by: AndreaC <67786179+darkstarac@users.noreply.github.com> --- modules/aidemBidAdapter.js | 4 ++++ modules/aidemBidAdapter.md | 1 + test/spec/modules/aidemBidAdapter_spec.js | 12 ++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index 41bf680db1c..081a0324ddb 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -220,6 +220,8 @@ function setPrebidImpressionObject(bidRequests, payload) { deepSetValue(impressionObject, 'id', bidRequest.bidId); // Transaction id deepSetValue(impressionObject, 'tid', deepAccess(bidRequest, 'transactionId')); + // Placement id + deepSetValue(impressionObject, 'tagid', deepAccess(bidRequest, 'params.placementId', null)); // Publisher id deepSetValue(payload, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); // Site id @@ -265,6 +267,8 @@ function setPrebidRequestEnvironment(payload) { deepSetValue(payload, 'environment.inp.jp', window.JSON.parse.name === 'parse' && typeof window.JSON.parse.prototype === 'undefined'); deepSetValue(payload, 'environment.inp.ofe', window.Object.fromEntries.name === 'fromEntries' && typeof window.Object.fromEntries.prototype === 'undefined'); deepSetValue(payload, 'environment.inp.oa', window.Object.assign.name === 'assign' && typeof window.Object.assign.prototype === 'undefined'); + deepSetValue(payload, 'environment.wpar.innerWidth', window.innerWidth); + deepSetValue(payload, 'environment.wpar.innerHeight', window.innerHeight); } function setPrebidImpressionObjectFloor(bidRequest, impressionObject) { diff --git a/modules/aidemBidAdapter.md b/modules/aidemBidAdapter.md index a5beca97d10..342a264da01 100644 --- a/modules/aidemBidAdapter.md +++ b/modules/aidemBidAdapter.md @@ -18,6 +18,7 @@ This module is GDPR and CCPA compliant, and no 3rd party userIds are allowed. |---------------|----------|---------------------|---------------|----------| | `siteId` | required | Unique site ID | `'ABCDEF'` | `String` | | `publisherId` | required | Unique publisher ID | `'ABCDEF'` | `String` | +| `placementId` | optional | Unique publisher tag ID | `'ABCDEF'` | `String` | ### Banner Bid Params diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 472b226ab8a..71edfcf82fb 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -385,7 +385,7 @@ describe('Aidem adapter', () => { expect(payload.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_BANNER_REQUESTS.length) expect(payload.imp[0]).to.be.a('object').that.has.all.keys( - 'banner', 'id', 'mediatype', 'imp_ext', 'tid' + 'banner', 'id', 'mediatype', 'imp_ext', 'tid', 'tagid' ) expect(payload.imp[0].banner).to.be.a('object').that.has.all.keys( 'format', 'topframe' @@ -401,7 +401,7 @@ describe('Aidem adapter', () => { expect(payload.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_VIDEO_REQUESTS.length) expect(payload.imp[0]).to.be.a('object').that.has.all.keys( - 'video', 'id', 'mediatype', 'imp_ext', 'tid' + 'video', 'id', 'mediatype', 'imp_ext', 'tid', 'tagid' ) expect(payload.imp[0].video).to.be.a('object').that.has.all.keys( 'format', 'mimes', 'minDuration', 'maxDuration', 'protocols' @@ -421,6 +421,14 @@ describe('Aidem adapter', () => { 'value', 'currency' ) }); + + it('should hav wpar keys in environment object', function () { + const requests = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST); + const payload = JSON.parse(requests.data) + expect(payload).to.have.property('environment') + expect(payload.environment).to.be.a('object').that.have.property('wpar') + expect(payload.environment.wpar).to.be.a('object').that.has.keys('innerWidth', 'innerHeight') + }); }) describe('interpretResponse', () => { From a2172d9aa3b56e8992460e796760843cb9fa947c Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Tue, 3 Jan 2023 18:54:25 +0200 Subject: [PATCH 023/113] Taboola Bid Adapter: onBidWon, userSyncs, gpp support and FPD (#9376) * on-bid-won * support-fpd * support-fpd * support-fpd * support-fpd * support-fpd * support-fpd * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * position-pagetype --- modules/taboolaBidAdapter.js | 62 ++++++++-- test/spec/modules/taboolaBidAdapter_spec.js | 124 +++++++++++++++++++- 2 files changed, 172 insertions(+), 14 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 670d28ab64e..85adfc31ca5 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,13 +3,15 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {getWindowSelf} from '../src/utils.js' +import {getWindowSelf, replaceAuctionPrice} from '../src/utils.js' import {getStorageManager} from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; export const END_POINT_URL = 'https://display.bidder.taboola.com/OpenRTB/TaboolaHB/auction'; +export const USER_SYNC_IMG_URL = 'https://trc.taboola.com/sg/prebidJS/1/cm'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; @@ -81,7 +83,7 @@ export const spec = { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const {publisherId} = bidRequest.params; - const site = getSiteProperties(bidRequest.params, refererInfo); + const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); const user = { @@ -102,24 +104,32 @@ export const spec = { regs.ext.us_privacy = uspConsent; } + if (bidderRequest.ortb2?.regs?.gpp) { + regs.ext.gpp = bidderRequest.ortb2.regs.gpp; + regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + if (config.getConfig('coppa')) { regs.coppa = 1; } const ortb2 = bidderRequest.ortb2 || { + bcat: [], badv: [], - bcat: [] + wlang: [] }; const request = { id: bidderRequest.auctionId, imp: imps, site, + pageType: ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType, device, source: {fd: 1}, tmax: bidderRequest.timeout, - bcat: ortb2.bcat, - badv: ortb2.badv, + bcat: ortb2.bcat || bidRequest.params.bcat || [], + badv: ortb2.badv || bidRequest.params.badv || [], + wlang: ortb2.wlang || bidRequest.params.wlang || [], user, regs }; @@ -149,16 +159,45 @@ export const spec = { return bidResponses.map((bidResponse) => getBid(bids, currency, bidResponse)).filter(Boolean); }, + onBidWon: (bid) => { + if (bid.nurl) { + const resolvedNurl = replaceAuctionPrice(bid.nurl, bid.originalCpm); + ajax(resolvedNurl); + } + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = [] + const queryParams = []; + if (gdprConsent) { + queryParams.push(`gdpr=${Number(gdprConsent.gdprApplies && 1)}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString || '')}`); + } + + if (uspConsent) { + queryParams.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + + if (gppConsent) { + queryParams.push('gpp=' + encodeURIComponent(gppConsent)); + } + + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: USER_SYNC_IMG_URL + (queryParams.length ? '?' + queryParams.join('&') : '') + }); + } + return syncs; + }, }; -function getSiteProperties({publisherId, bcat = []}, refererInfo) { +function getSiteProperties({publisherId}, refererInfo, ortb2) { const {getPageUrl, getReferrer} = internal; return { id: publisherId, name: publisherId, - domain: refererInfo?.domain || window.location?.host, - page: getPageUrl(refererInfo), - ref: getReferrer(refererInfo), + domain: ortb2?.site?.domain || refererInfo?.domain || window.location?.host, + page: ortb2?.site?.page || getPageUrl(refererInfo), + ref: ortb2?.site?.ref || getReferrer(refererInfo), publisher: { id: publisherId }, @@ -170,11 +209,12 @@ function getSiteProperties({publisherId, bcat = []}, refererInfo) { function getImps(validBidRequests) { return validBidRequests.map((bid, id) => { - const {tagId} = bid.params; + const {tagId, position} = bid.params; const imp = { id: id + 1, banner: getBanners(bid), - tagid: tagId + tagid: tagId, + position: position } if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 5bde75cd0b6..a3a765d28cf 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import {spec, internal, END_POINT_URL, userData} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config' import * as utils from '../../../src/utils' +import {server} from '../../mocks/xhr' describe('Taboola Adapter', function () { let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie, commonBidRequest; @@ -91,6 +92,31 @@ describe('Taboola Adapter', function () { }) }) + describe('onBidWon', function () { + it('onBidWon exist as a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + + it('should resolve price macro in nurl', function () { + const nurl = 'http://win.example.com/${AUCTION_PRICE}'; + const bid = { + requestId: 1, + cpm: 2, + originalCpm: 3.4, + creativeId: 1, + ttl: 60, + netRevenue: true, + mediaType: 'banner', + ad: '...', + width: 300, + height: 250, + nurl: nurl + } + spec.onBidWon(bid); + expect(server.requests[0].url).to.equals('http://win.example.com/3.4') + }); + }); + describe('buildRequests', function () { const defaultBidRequest = { ...createBidRequest(), @@ -137,6 +163,7 @@ describe('Taboola Adapter', function () { 'source': {'fd': 1}, 'bcat': [], 'badv': [], + 'wlang': [], 'user': { 'buyeruid': 0, 'ext': {}, @@ -184,7 +211,7 @@ describe('Taboola Adapter', function () { expect(resData.imp[0].bidfloorcur).to.deep.equal('USD'); }); - it('should pass bid floor even if they is a bid floor param', function () { + it('should pass bid floor even if it is a bid floor param', function () { const optionalParams = { bidfloor: 0.25, bidfloorcur: 'EUR' @@ -206,6 +233,21 @@ describe('Taboola Adapter', function () { expect(resData.imp[0].bidfloorcur).to.deep.equal('USD'); }); + it('should pass impression position', function () { + const optionalParams = { + position: 2 + }; + + const bidRequest = { + ...defaultBidRequest, + params: {...commonBidRequest.params, ...optionalParams} + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].position).to.deep.equal(2); + }); + it('should pass bidder timeout', function () { const bidderRequest = { ...commonBidderRequest, @@ -216,6 +258,40 @@ describe('Taboola Adapter', function () { expect(resData.tmax).to.equal(500); }); + describe('first party data', function () { + it('should parse first party data', function () { + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + wlang: ['de'], + } + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.bcat).to.deep.equal(bidderRequest.ortb2.bcat) + expect(resData.badv).to.deep.equal(bidderRequest.ortb2.badv) + expect(resData.wlang).to.deep.equal(bidderRequest.ortb2.wlang) + }); + + it('should pass pageType if exists in ortb2', function () { + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + ext: { + data: { + pageType: 'news' + } + } + } + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.pageType).to.deep.equal(bidderRequest.ortb2.ext.data.pageType); + }); + }); + describe('handle privacy segments when building request', function () { it('should pass GDPR consent', function () { const bidderRequest = { @@ -234,6 +310,20 @@ describe('Taboola Adapter', function () { expect(resData.regs.ext.gdpr).to.equal(1) }); + it('should pass GPP consent if exist in ortb2', function () { + const ortb2 = { + regs: { + gpp: 'testGpp', + gpp_sid: [1, 2, 3] + } + } + + const res = spec.buildRequests([defaultBidRequest], {...commonBidderRequest, ortb2}) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.gpp).to.equal('testGpp') + expect(resData.regs.ext.gpp_sid).to.deep.equal([1, 2, 3]) + }); + it('should pass us privacy consent', function () { const bidderRequest = { refererInfo: { @@ -552,8 +642,36 @@ describe('Taboola Adapter', function () { }); }) - describe('userData', function () { - // todo: add UT for getUserSyncs + describe('getUserSyncs', function () { + const usersyncUrl = 'https://trc.taboola.com/sg/prebidJS/1/cm'; + + it('should not return user sync if pixelEnabled is false', function () { + const res = spec.getUserSyncs({pixelEnabled: false}); + expect(res).to.be.an('array').that.is.empty; + }); + + it('should return user sync if pixelEnabled is true', function () { + const res = spec.getUserSyncs({pixelEnabled: true}); + expect(res).to.deep.equal([{type: 'image', url: usersyncUrl}]); + }); + + it('should pass consent tokens values', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'GDPR_CONSENT'}, 'USP_CONSENT')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT', 'GPP_STRING')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT&gpp=GPP_STRING` + }]); + }); }) describe('internal functions', function () { From 348ba1da17739175f64c02120ab99cfe853aa49d Mon Sep 17 00:00:00 2001 From: nkloeber <100145701+nkloeber@users.noreply.github.com> Date: Tue, 3 Jan 2023 18:11:12 +0100 Subject: [PATCH 024/113] Yieldlab Bid Adapter: read and pass UserIdsAsEids atype information (#9370) * YieldlabBidAdapter read atype information from UserIdsAsEids and pass it as query parameter (atypes={idprovider}:{atype},{idprovider2}:{atype2},...) * Update type hint and add semi colons Co-authored-by: Christoph Kipping <29540638+kippsterr@users.noreply.github.com> --- modules/yieldlabBidAdapter.js | 16 ++++++++++++++++ test/spec/modules/yieldlabBidAdapter_spec.js | 12 +++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 88e1494d416..6e4f6644140 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -62,6 +62,7 @@ export const spec = { } if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { query.ids = createUserIdString(bid.userIdAsEids) + query.atypes = createUserIdAtypesString(bid.userIdAsEids) } if (bid.params.customParams && isPlainObject(bid.params.customParams)) { for (const prop in bid.params.customParams) { @@ -303,6 +304,21 @@ function createUserIdString(eids) { return str.join(',') } +/** + * Creates a string from an array of eids with ID provider and atype if atype exists + * @param {Array.<{source: String, uids: Array.<{id: String, atype: Number, ext: Object}>}>} eids + * @returns {String} idprovider:atype,idprovider2:atype2,... + */ +function createUserIdAtypesString(eids) { + const str = []; + for (let i = 0; i < eids.length; i++) { + if (eids[i].uids[0].atype) { + str.push(eids[i].source + ':' + eids[i].uids[0].atype); + } + } + return str.join(','); +} + /** * Creates a querystring out of an object with key-values * @param {Object} obj diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 5df0e93d34e..07d42df1319 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -45,6 +45,12 @@ const DEFAULT_REQUEST = () => ({ id: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', atype: 1 }] + }, { + source: 'digitrust.de', + uids: [{ + id: 'd8aa10fa-d86c-451d-aad8-5f16162a9e64', + atype: 2 + }] }], schain: { ver: '1.0', @@ -271,7 +277,11 @@ describe('yieldlabBidAdapter', () => { }) it('passes userids to bid request', () => { - expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg') + expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg%2Cdigitrust.de%3Ad8aa10fa-d86c-451d-aad8-5f16162a9e64') + }) + + it('passes atype to bid request', () => { + expect(request.url).to.include('atypes=netid.de%3A1%2Cdigitrust.de%3A2') }) it('passes extra params to bid request', () => { From 1960ed811bd923a445aece06b20f06e11589bf1d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 3 Jan 2023 12:54:41 -0700 Subject: [PATCH 025/113] Medianet RTD module: fix `getTargetingData` to retrieve correct adUnits (#9392) --- modules/medianetRtdProvider.js | 8 ++++---- test/spec/modules/medianetRtdProvider_spec.js | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js index 3e01d0e5631..5a159b39081 100644 --- a/modules/medianetRtdProvider.js +++ b/modules/medianetRtdProvider.js @@ -59,14 +59,14 @@ function onAuctionInitEvent(auctionInit) { }, SOURCE)); } -function getTargetingData(adUnitCode) { - const adUnits = getAdUnits(undefined, adUnitCode); +function getTargetingData(adUnitCodes, config, consent, auction) { + const adUnits = getAdUnits(auction.adUnits, adUnitCodes); let targetingData = {}; if (window.mnjs.loaded && isFn(window.mnjs.getTargetingData)) { - targetingData = window.mnjs.getTargetingData(adUnitCode, adUnits, SOURCE) || {}; + targetingData = window.mnjs.getTargetingData(adUnitCodes, adUnits, SOURCE) || {}; } const targeting = {}; - adUnitCode.forEach(adUnitCode => { + adUnitCodes.forEach(adUnitCode => { targeting[adUnitCode] = targeting[adUnitCode] || {}; targetingData[adUnitCode] = targetingData[adUnitCode] || {}; targeting[adUnitCode] = { diff --git a/test/spec/modules/medianetRtdProvider_spec.js b/test/spec/modules/medianetRtdProvider_spec.js index 7d73ecd5d44..f9d4ef7c2cf 100644 --- a/test/spec/modules/medianetRtdProvider_spec.js +++ b/test/spec/modules/medianetRtdProvider_spec.js @@ -66,12 +66,12 @@ describe('medianet realtime module', function () { describe('getTargeting should work correctly', function () { it('should return empty if not loaded', function () { window.mnjs.loaded = false; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([]), {}); + assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([], {}, {}, {}), {}); }); it('should return ad unit codes when ad units are present', function () { const adUnitCodes = ['code1', 'code2']; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes), { + assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes, {}, {}, {}), { code1: {'mnadc': 'code1'}, code2: {'mnadc': 'code2'}, }); @@ -79,7 +79,7 @@ describe('medianet realtime module', function () { it('should call mnjs.getTargetingData if loaded', function () { window.mnjs.loaded = true; - medianetRTD.medianetRtdModule.getTargetingData([]); + medianetRTD.medianetRtdModule.getTargetingData([], {}, {}, {}); assert.equal(getTargetingDataSpy.called, true); }); }); From febd71b40884ebc23dfb448f8a7096168eaffdcb Mon Sep 17 00:00:00 2001 From: Krzysztof Desput Date: Wed, 4 Jan 2023 11:20:42 +0100 Subject: [PATCH 026/113] Holid Bid Adapter: initial release (#9371) * Holid bid adapter * Adjust test to various device sizes * Include first party data from ortb2 object * Remove trailing spaces in test --- modules/holidBidAdapter.js | 170 ++++++++++++++++++++++ modules/holidBidAdapter.md | 36 +++++ test/spec/modules/holidBidAdapter_spec.js | 165 +++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 modules/holidBidAdapter.js create mode 100644 modules/holidBidAdapter.md create mode 100644 test/spec/modules/holidBidAdapter_spec.js diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js new file mode 100644 index 00000000000..be5a7a044c3 --- /dev/null +++ b/modules/holidBidAdapter.js @@ -0,0 +1,170 @@ +import { + deepAccess, + getBidIdParameter, + isStr, + logMessage, + triggerPixel, +} from '../src/utils.js' +import * as events from '../src/events.js' +import CONSTANTS from '../src/constants.json' +import { BANNER } from '../src/mediaTypes.js' + +import { registerBidder } from '../src/adapters/bidderFactory.js' + +const BIDDER_CODE = 'holid' +const GVLID = 1177 +const ENDPOINT = 'https://helloworld.holid.io/openrtb2/auction' +const COOKIE_SYNC_ENDPOINT = 'https://null.holid.io/sync.html' +const TIME_TO_LIVE = 300 +let wurlMap = {} + +events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler) + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.adUnitID + }, + + buildRequests: function (validBidRequests, _bidderRequest) { + return validBidRequests.map((bid) => { + const requestData = { + ...bid.ortb2, + id: bid.auctionId, + imp: [getImp(bid)], + } + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(requestData), + bidId: bid.bidId, + } + }) + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = [] + + if (!serverResponse.body.seatbid) { + return [] + } + + serverResponse.body.seatbid.map((response) => { + response.bid.map((bid) => { + const requestId = bidRequest.bidId + const auctionId = bidRequest.auctionId + const wurl = deepAccess(bid, 'ext.prebid.events.win') + const bidResponse = { + requestId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + creativeId: bid.crid, + currency: serverResponse.body.cur, + netRevenue: true, + ttl: TIME_TO_LIVE, + } + + addWurl({ auctionId, requestId, wurl }) + + bidResponses.push(bidResponse) + }) + }) + + return bidResponses + }, + + getUserSyncs(optionsType, serverResponse, gdprConsent, uspConsent) { + if (!serverResponse || serverResponse.length === 0) { + return [] + } + + const syncs = [] + + if (optionsType.iframeEnabled) { + const queryParams = [] + + queryParams.push('bidders=' + getBidders(serverResponse)) + queryParams.push('gdpr=' + +gdprConsent.gdprApplies) + queryParams.push('gdpr_consent=' + gdprConsent.consentString) + queryParams.push('usp_consent=' + (uspConsent || '')) + + let strQueryParams = queryParams.join('&') + + if (strQueryParams.length > 0) { + strQueryParams = '?' + strQueryParams + } + + syncs.push({ + type: 'iframe', + url: COOKIE_SYNC_ENDPOINT + strQueryParams + '&type=iframe', + }) + + return syncs + } + }, +} + +function getImp(bid) { + const imp = { + ext: { + prebid: { + storedrequest: { + id: getBidIdParameter('adUnitID', bid.params), + }, + }, + }, + } + const sizes = + bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes + + if (deepAccess(bid, 'mediaTypes.banner')) { + imp.banner = { + format: sizes.map((size) => { + return { w: size[0], h: size[1] } + }), + } + } + + return imp +} + +function getBidders(serverResponse) { + const bidders = serverResponse + .map((res) => Object.keys(res.body.ext.responsetimemillis)) + .flat(1) + + return encodeURIComponent(JSON.stringify([...new Set(bidders)])) +} + +function addWurl(auctionId, adId, wurl) { + if ([auctionId, adId].every(isStr)) { + wurlMap[`${auctionId}${adId}`] = wurl + } +} + +function removeWurl(auctionId, adId) { + delete wurlMap[`${auctionId}${adId}`] +} + +function getWurl(auctionId, adId) { + if ([auctionId, adId].every(isStr)) { + return wurlMap[`${auctionId}${adId}`] + } +} + +function bidWonHandler(bid) { + const wurl = getWurl(bid.auctionId, bid.adId) + if (wurl) { + logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) + triggerPixel(wurl) + removeWurl(bid.auctionId, bid.adId) + } +} + +registerBidder(spec) diff --git a/modules/holidBidAdapter.md b/modules/holidBidAdapter.md new file mode 100644 index 00000000000..1d83918c00a --- /dev/null +++ b/modules/holidBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Holid Bid Adapter +Module Type: Bidder Adapter +Maintainer: richard@holid.se +``` + +# Description + +Currently module supports only banner mediaType. + +# Test Parameters + +## Sample Banner Ad Unit + +```js +var adUnits = [ + { + code: 'bannerAdUnit', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'holid', + params: { + adUnitID: '12345', + }, + }, + ], + }, +] +``` diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js new file mode 100644 index 00000000000..e18a5ac58f4 --- /dev/null +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -0,0 +1,165 @@ +import { expect } from 'chai' +import { spec } from 'modules/holidBidAdapter.js' + +describe('holidBidAdapterTests', () => { + const bidRequestData = { + bidder: 'holid', + adUnitCode: 'test-div', + bidId: 'bid-id', + auctionId: 'test-id', + params: { adUnitID: '12345' }, + mediaTypes: { banner: {} }, + sizes: [[300, 250]], + ortb2: { + site: { + publisher: { + domain: 'https://foo.bar', + } + }, + regs: { + gdpr: 1, + }, + user: { + ext: { + consent: 'G4ll0p1ng_Un1c0rn5', + } + }, + device: { + h: 410, + w: 1860, + } + } + } + + describe('isBidRequestValid', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + + it('should return true', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false when required params are not passed', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + delete bid.params.adUnitID + + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + const request = spec.buildRequests([bid], bid) + const payload = JSON.parse(request[0].data) + + it('should include ext in imp', () => { + expect(payload.imp[0].ext).to.exist + expect(payload.imp[0].ext).to.deep.equal({ + prebid: { storedrequest: { id: '12345' } }, + }) + }) + + it('should include banner format in imp', () => { + expect(payload.imp[0].banner).to.exist + expect(payload.imp[0].banner).to.deep.equal({ + format: [{ w: 300, h: 250 }], + }) + }) + + it('should include ortb2 first party data', () => { + expect(payload.device.w).to.equal(1860) + expect(payload.device.h).to.equal(410) + expect(payload.user.ext.consent).to.equal('G4ll0p1ng_Un1c0rn5') + expect(payload.regs.gdpr).to.equal(1) + }) + }) + + describe('interpretResponse', () => { + const serverResponse = { + body: { + id: 'test-id', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: 'testbidid', + price: 0.4, + adm: 'test-ad', + adid: 789456, + crid: 1234, + w: 300, + h: 250, + }, + ], + }, + ], + }, + } + + const interpretedResponse = spec.interpretResponse( + serverResponse, + bidRequestData + ) + + it('should interpret response', () => { + expect(interpretedResponse[0].requestId).to.equal(bidRequestData.bidId) + expect(interpretedResponse[0].cpm).to.equal( + serverResponse.body.seatbid[0].bid[0].price + ) + expect(interpretedResponse[0].ad).to.equal( + serverResponse.body.seatbid[0].bid[0].adm + ) + expect(interpretedResponse[0].creativeId).to.equal( + serverResponse.body.seatbid[0].bid[0].crid + ) + expect(interpretedResponse[0].width).to.equal( + serverResponse.body.seatbid[0].bid[0].w + ) + expect(interpretedResponse[0].height).to.equal( + serverResponse.body.seatbid[0].bid[0].h + ) + expect(interpretedResponse[0].currency).to.equal(serverResponse.body.cur) + }) + }) + + describe('getUserSyncs', () => { + it('should return user sync', () => { + const optionsType = { + iframeEnabled: true, + pixelEnabled: true, + } + const serverResponse = [ + { + body: { + ext: { + responsetimemillis: { + 'test seat 1': 2, + 'test seat 2': 1, + }, + }, + }, + }, + ] + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + } + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + const expectedUserSyncs = [ + { + type: 'iframe', + url: 'https://null.holid.io/sync.html?bidders=%5B%22test%20seat%201%22%2C%22test%20seat%202%22%5D&gdpr=1&gdpr_consent=dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig&usp_consent=mkjvbiniwot4827obfoy8sdg8203gb&type=iframe', + }, + ] + + const userSyncs = spec.getUserSyncs( + optionsType, + serverResponse, + gdprConsent, + uspConsent + ) + + expect(userSyncs).to.deep.equal(expectedUserSyncs) + }) + }) +}) From 1238d30cc986d3ac9c378329dc4e7f4bb37a9da7 Mon Sep 17 00:00:00 2001 From: Torsten Date: Thu, 5 Jan 2023 12:16:41 +0100 Subject: [PATCH 027/113] Appnexus Bid Adapter : add video data from the request to the bid response (#9396) * Appnexus adapter: add video data from the request to the bid response * kick off tests * remove change Co-authored-by: Chris Huie --- modules/appnexusBidAdapter.js | 1 + test/spec/modules/appnexusBidAdapter_spec.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 919831b8515..7465ab15780 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -731,6 +731,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { displayUrl: nativeAd.displayurl, clickTrackers: nativeAd.link.click_trackers, impressionTrackers: nativeAd.impression_trackers, + video: nativeAd.video, javascriptTrackers: jsTrackers }; if (nativeAd.main_img) { diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 1ab8feceaeb..bae2417c278 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1630,7 +1630,10 @@ describe('AppNexusAdapter', function () { 'phone': '1234567890', 'address': '28 W 23rd St, New York, NY 10010', 'privacy_link': 'https://appnexus.com/?url=privacy_url', - 'javascriptTrackers': '' + 'javascriptTrackers': '', + 'video': { + 'content': '' + } }; let bidderRequest = { bids: [{ @@ -1644,6 +1647,7 @@ describe('AppNexusAdapter', function () { expect(result[0].native.body).to.equal('Cool description great stuff'); expect(result[0].native.cta).to.equal('Do it'); expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + expect(result[0].native.video.content).to.equal(''); }); } From 978926d8e318985c04d5c5cc79851a32efb5ffc7 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 5 Jan 2023 17:22:43 +0100 Subject: [PATCH 028/113] AdagioBidAdapter: Remove some params (#9398) --- modules/adagioBidAdapter.js | 4 --- modules/adagioBidAdapter.md | 26 +++---------------- test/spec/modules/adagioBidAdapter_spec.js | 29 +--------------------- 3 files changed, 5 insertions(+), 54 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 4f86a914982..12fd50c6636 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -669,10 +669,8 @@ function autoFillParams(bid) { } // extra params - setExtraParam(bid, 'environment'); setExtraParam(bid, 'pagetype'); setExtraParam(bid, 'category'); - setExtraParam(bid, 'subcategory'); } function getPageDimensions() { @@ -1094,8 +1092,6 @@ export const spec = { bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; bidObj.category = bidReq.params.category; - bidObj.subcategory = bidReq.params.subcategory; - bidObj.environment = bidReq.params.environment; } bidResponses.push(bidObj); }); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index 9b48caa8233..b804a72fea9 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -17,12 +17,10 @@ Below, the list of Adagio params and where they can be set. | Param name | Global config | AdUnit config | | ---------- | ------------- | ------------- | | siteId | x | -| organizationId (obsolete) | | x -| site (obsolete) | | x +| organizationId * | | x +| site * | | x | pagetype | x | x -| environment | x | x | category | x | x -| subcategory | x | x | useAdUnitCodeAsAdUnitElementId | x | x | useAdUnitCodeAsPlacement | x | x | placement | | x @@ -31,6 +29,8 @@ Below, the list of Adagio params and where they can be set. | video | | x | native | | x +_* These params are deprecated in favor the Global configuration setup, see below._ + ### Global configuration The global configuration is used to store params once instead of duplicate them to each adUnit. The values will be used as "params" in the ad-request. @@ -49,9 +49,7 @@ pbjs.setConfig({ // - underscores `_` // Also, each param can have at most 50 unique active values (case-insensitive). pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. - environment: 'mobile', // Recommended. Environment where the page is displayed. category: 'sport', // Recommended. Category of the content displayed in the page. - subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value }, @@ -62,9 +60,7 @@ pbjs.setConfig({ Adagio will use FPD data as fallback for the params below: - pagetype -- environment - category -- subcategory If the FPD value is an array, the 1st value of this array will be used. @@ -107,9 +103,7 @@ var adUnits = [ debug: true, adagio: { pagetype: 'article', - environment: 'mobile', category: 'sport', - subcategory: 'handball', useAdUnitCodeAsAdUnitElementId: false, useAdUnitCodeAsPlacement: false, } @@ -208,12 +202,6 @@ var adUnits = [ return bidResponse.site; } }, - { - key: "environment", - val: function (bidResponse) { - return bidResponse.environment; - } - }, { key: "placement", val: function (bidResponse) { @@ -231,12 +219,6 @@ var adUnits = [ val: function (bidResponse) { return bidResponse.category; } - }, - { - key: "subcategory", - val: function (bidResponse) { - return bidResponse.subcategory; - } } ] } diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 436d481c4a1..8abdc621922 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -149,10 +149,8 @@ describe('Adagio bid adapter', () => { site: { ext: { data: { - environment: 'desktop', pagetype: 'abc', - category: ['cat1', 'cat2', 'cat3'], - subcategory: [] + category: ['cat1', 'cat2', 'cat3'] } } } @@ -167,17 +165,11 @@ describe('Adagio bid adapter', () => { return utils.deepAccess(config, key); }); - setExtraParam(bid, 'environment'); - expect(bid.params.environment).to.equal('desktop'); - setExtraParam(bid, 'pagetype') expect(bid.params.pagetype).to.equal('article'); setExtraParam(bid, 'category'); expect(bid.params.category).to.equal('cat1'); // Only the first value is kept - - setExtraParam(bid, 'subcategory'); - expect(bid.params.subcategory).to.be.undefined; }); it('should use the adUnit param unit if defined', function() { @@ -784,8 +776,6 @@ describe('Adagio bid adapter', () => { adUnitElementId: 'gpt-adunit-code', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', supportIObs: true }, adUnitCode: 'adunit-code', @@ -833,8 +823,6 @@ describe('Adagio bid adapter', () => { site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', aDomain: ['advertiser.com'], mediaType: 'banner', meta: { @@ -868,8 +856,6 @@ describe('Adagio bid adapter', () => { site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', aDomain: ['advertiser.com'], mediaType: 'banner', meta: { @@ -1392,19 +1378,6 @@ describe('Adagio bid adapter', () => { }); describe.skip('optional params auto detection', function() { - it('should auto detect environment', function() { - const getDeviceStub = sandbox.stub(_features, 'getDevice'); - - getDeviceStub.returns(5); - expect(adagio.autoDetectEnvironment()).to.eq('tablet'); - - getDeviceStub.returns(4); - expect(adagio.autoDetectEnvironment()).to.eq('mobile'); - - getDeviceStub.returns(2); - expect(adagio.autoDetectEnvironment()).to.eq('desktop'); - }); - it('should auto detect adUnitElementId when GPT is used', function() { sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); From 408221b84507cb130de9ef647f021f5f75d05070 Mon Sep 17 00:00:00 2001 From: couchcrew-thomas Date: Thu, 5 Jan 2023 17:34:58 +0100 Subject: [PATCH 029/113] Feedad Bid Adapter: added new bid request parameters (#9397) * added file scaffold * added isBidRequestValid implementation * added local prototype of ad integration * added implementation for placement ID validation * fixed video context filter * applied lint to feedad bid adapter * added unit test for bid request validation * added buildRequest unit test * added unit tests for timeout and bid won callbacks * updated bid request to FeedAd API * added parsing of feedad api bid response * added transmisison of tracking events to FeedAd Api * code cleanup * updated feedad unit tests for buildRequest method * added unit tests for event tracking implementation * added unit test for interpretResponse method * added adapter documentation * added dedicated feedad example page * updated feedad adapter to use live system * updated FeedAd adapter placement ID regex * removed groups from FeedAd adapter placement ID regex * removed dedicated feedad example page * updated imports in FeedAd adapter file to use relative paths * updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() * added GDPR fields to the FeedAd bid request * removed video from supported media types of the FeedAd adapter * increased version code of FeedAd adapter to 1.0.2 * removed unnecessary check of bidder request * fixed unit test testing for old FeedAd version * removed video media type example from documentation file * added gvlid to FeedAd adapter * added decoration parameter to adapter documentation * added pass through of additional bid parameters * added user syncs to FeedAd bid adapter * increased FeedAd bid adapter version * lint pass over FeedAd bid adapter * fixed parsing of user syncs from server response * increased FeedAd bid adapter version * fixed version code in test file * added adapter and prebid version to bid request parameters * removed TODO item * added missing test case for user syncs * increased adapter version to 1.0.5 --- modules/feedadBidAdapter.js | 12 ++++++----- test/spec/modules/feedadBidAdapter_spec.js | 24 +++++++++++++++++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index ef2e57c553f..7b684efab3c 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -7,7 +7,7 @@ import {ajax} from '../src/ajax.js'; * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.4'; +const VERSION = '1.0.5'; /** * @typedef {object} FeedAdApiBidRequest @@ -16,7 +16,8 @@ const VERSION = '1.0.4'; * @property {number} ad_type * @property {string} client_token * @property {string} placement_id - * @property {string} sdk_version + * @property {string} prebid_adapter_version + * @property {string} prebid_sdk_version * @property {boolean} app_hybrid * * @property {string} [app_bundle_id] @@ -181,7 +182,8 @@ function createApiBidRParams(request) { ad_type: 0, client_token: request.params.clientToken, placement_id: request.params.placementId, - sdk_version: `prebid_${VERSION}`, + prebid_adapter_version: VERSION, + prebid_sdk_version: '$prebid.version$', app_hybrid: false, }); } @@ -207,7 +209,6 @@ function buildRequests(validBidRequests, bidderRequest) { }) }); data.bids.forEach(bid => BID_METADATA[bid.bidId] = { - // TODO: is 'page' the right value here? referer: data.refererInfo.page, transactionId: bid.transactionId }); @@ -266,7 +267,8 @@ function createTrackingParams(data, klass) { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: VERSION + prebid_adapter_version: VERSION, + prebid_sdk_version: '$prebid.version$', }; } diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 6aed670a563..8cbd6907890 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -4,6 +4,7 @@ import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import {server} from 'test/mocks/xhr.js'; const CODE = 'feedad'; +const EXPECTED_ADAPTER_VERSION = '1.0.5'; describe('FeedAdAdapter', function () { describe('Public API', function () { @@ -300,6 +301,20 @@ describe('FeedAdAdapter', function () { expect(result.data.gdprApplies).to.equal(request.gdprConsent.gdprApplies); expect(result.data.consentIabTcf).to.equal(request.gdprConsent.consentString); }); + it('should include adapter and prebid version', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids[0].params.prebid_adapter_version).to.equal(EXPECTED_ADAPTER_VERSION); + expect(result.data.bids[0].params.prebid_sdk_version).to.equal('$prebid.version$'); + }); }); describe('interpretResponse', function () { @@ -482,6 +497,12 @@ describe('FeedAdAdapter', function () { expect(() => spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, it)).not.to.throw; }); }); + + it('should return empty array if the body extension is null', function () { + const response = mockServerResponse({ext: null}); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, response); + expect(result).to.deep.equal([]); + }); }); describe('event tracking calls', function () { @@ -617,7 +638,8 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.4' + prebid_adapter_version: EXPECTED_ADAPTER_VERSION, + prebid_sdk_version: '$prebid.version$', }; subject(data); expect(server.requests.length).to.equal(1); From 768c1d3c59e855db01d6135efab220cbdc749b39 Mon Sep 17 00:00:00 2001 From: Christoph <29540638+kippsterr@users.noreply.github.com> Date: Fri, 6 Jan 2023 13:07:51 +0100 Subject: [PATCH 030/113] Yieldlab Bid Adapter: code style updates (#9386) * Consistently add trailing comma and semicolons everywhere * Use shorthand object property function definition * Fix typo and update type hint --- modules/yieldlabBidAdapter.js | 270 ++++---- test/spec/modules/yieldlabBidAdapter_spec.js | 610 +++++++++---------- 2 files changed, 440 insertions(+), 440 deletions(-) diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 6e4f6644140..cadeb9c1300 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,17 +1,17 @@ -import { _each, deepAccess, isArray, isFn, isPlainObject, timestamp } from '../src/utils.js' -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { find } from '../src/polyfill.js' -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js' -import { Renderer } from '../src/Renderer.js' +import { _each, deepAccess, isArray, isFn, isPlainObject, timestamp } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { find } from '../src/polyfill.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const ENDPOINT = 'https://ad.yieldlab.net' -const BIDDER_CODE = 'yieldlab' -const BID_RESPONSE_TTL_SEC = 300 -const CURRENCY_CODE = 'EUR' -const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event' -const GVLID = 70 -const DIMENSION_SIGN = 'x' +const ENDPOINT = 'https://ad.yieldlab.net'; +const BIDDER_CODE = 'yieldlab'; +const BID_RESPONSE_TTL_SEC = 300; +const CURRENCY_CODE = 'EUR'; +const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event'; +const GVLID = 70; +const DIMENSION_SIGN = 'x'; export const spec = { code: BIDDER_CODE, @@ -22,11 +22,11 @@ export const spec = { * @param {object} bid * @returns {boolean} */ - isBidRequestValid: function (bid) { + isBidRequestValid(bid) { if (bid && bid.params && bid.params.adslotId && bid.params.supplyId) { - return true + return true; } - return false + return false; }, /** @@ -35,85 +35,85 @@ export const spec = { * @param [bidderRequest] * @returns {ServerRequest|ServerRequest[]} */ - buildRequests: function (validBidRequests, bidderRequest) { + buildRequests(validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const adslotIds = [] + const adslotIds = []; const adslotSizes = []; const adslotFloors = []; - const timestamp = Date.now() + const timestamp = Date.now(); const query = { ts: timestamp, - json: true - } + json: true, + }; _each(validBidRequests, function (bid) { - adslotIds.push(bid.params.adslotId) - const sizes = extractSizes(bid) + adslotIds.push(bid.params.adslotId); + const sizes = extractSizes(bid); if (sizes.length > 0) { - adslotSizes.push(bid.params.adslotId + ':' + sizes.join('|')) + adslotSizes.push(bid.params.adslotId + ':' + sizes.join('|')); } if (bid.params.extId) { query.id = bid.params.extId; } if (bid.params.targeting) { - query.t = createTargetingString(bid.params.targeting) + query.t = createTargetingString(bid.params.targeting); } if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { - query.ids = createUserIdString(bid.userIdAsEids) - query.atypes = createUserIdAtypesString(bid.userIdAsEids) + query.ids = createUserIdString(bid.userIdAsEids); + query.atypes = createUserIdAtypesString(bid.userIdAsEids); } if (bid.params.customParams && isPlainObject(bid.params.customParams)) { for (const prop in bid.params.customParams) { - query[prop] = bid.params.customParams[prop] + query[prop] = bid.params.customParams[prop]; } } if (bid.schain && isPlainObject(bid.schain) && Array.isArray(bid.schain.nodes)) { - query.schain = createSchainString(bid.schain) + query.schain = createSchainString(bid.schain); } - const iabContent = getContentObject(bid) + const iabContent = getContentObject(bid); if (iabContent) { - query.iab_content = createIabContentString(iabContent) + query.iab_content = createIabContentString(iabContent); } - const floor = getBidFloor(bid, sizes) + const floor = getBidFloor(bid, sizes); if (floor) { adslotFloors.push(bid.params.adslotId + ':' + floor); } - }) + }); if (bidderRequest) { if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { // TODO: is 'page' the right value here? - query.pubref = bidderRequest.refererInfo.page + query.pubref = bidderRequest.refererInfo.page; } if (bidderRequest.gdprConsent) { - query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true; if (query.gdpr) { - query.consent = bidderRequest.gdprConsent.consentString + query.consent = bidderRequest.gdprConsent.consentString; } } } - const adslots = adslotIds.join(',') + const adslots = adslotIds.join(','); if (adslotSizes.length > 0) { - query.sizes = adslotSizes.join(',') + query.sizes = adslotSizes.join(','); } if (adslotFloors.length > 0) { - query.floor = adslotFloors.join(',') + query.floor = adslotFloors.join(','); } - const queryString = createQueryString(query) + const queryString = createQueryString(query); return { method: 'GET', url: `${ENDPOINT}/yp/${adslots}?${queryString}`, validBidRequests: validBidRequests, - queryParams: query - } + queryParams: query, + }; }, /** @@ -122,29 +122,29 @@ export const spec = { * @param {BidRequest} originalBidRequest * @returns {Bid[]} */ - interpretResponse: function (serverResponse, originalBidRequest) { - const bidResponses = [] - const timestamp = Date.now() - const reqParams = originalBidRequest.queryParams + interpretResponse(serverResponse, originalBidRequest) { + const bidResponses = []; + const timestamp = Date.now(); + const reqParams = originalBidRequest.queryParams; originalBidRequest.validBidRequests.forEach(function (bidRequest) { if (!serverResponse.body) { - return + return; } const matchedBid = find(serverResponse.body, function (bidResponse) { - return bidRequest.params.adslotId == bidResponse.id - }) + return bidRequest.params.adslotId == bidResponse.id; + }); if (matchedBid) { - const adUnitSize = bidRequest.sizes.length === 2 && !isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] - const adSize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : (matchedBid.adsize !== undefined) ? parseSize(matchedBid.adsize) : adUnitSize - const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : '' - const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : '' - const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : '' - const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : '' - const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : '' - const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : '' + const adUnitSize = bidRequest.sizes.length === 2 && !isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0]; + const adSize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : (matchedBid.adsize !== undefined) ? parseSize(matchedBid.adsize) : adUnitSize; + const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : ''; + const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : ''; + const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : ''; + const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : ''; + const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : ''; + const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : ''; const bidResponse = { requestId: bidRequest.bidId, @@ -159,38 +159,38 @@ export const spec = { referrer: '', ad: ``, meta: { - advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a' - } - } + advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a', + }, + }; if (isVideo(bidRequest, adType)) { - const playersize = getPlayerSize(bidRequest) + const playersize = getPlayerSize(bidRequest); if (playersize) { - bidResponse.width = playersize[0] - bidResponse.height = playersize[1] + bidResponse.width = playersize[0]; + bidResponse.height = playersize[1]; } - bidResponse.mediaType = VIDEO - bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}${iabContent}` + bidResponse.mediaType = VIDEO; + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}${iabContent}`; if (isOutstream(bidRequest)) { const renderer = Renderer.install({ id: bidRequest.bidId, url: OUTSTREAMPLAYER_URL, - loaded: false - }) - renderer.setRender(outstreamRender) - bidResponse.renderer = renderer + loaded: false, + }); + renderer.setRender(outstreamRender); + bidResponse.renderer = renderer; } } if (isNative(bidRequest, adType)) { // there may be publishers still rely on it - const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` - bidResponse.adUrl = url - bidResponse.mediaType = NATIVE - const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2) + const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}`; + bidResponse.adUrl = url; + bidResponse.mediaType = NATIVE; + const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2); const nativeImageAsset = nativeImageAssetObj ? nativeImageAssetObj.img : { url: '', w: 0, h: 0 }; - const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1) - const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3) + const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1); + const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3); bidResponse.native = { title: nativeTitleAsset ? nativeTitleAsset.title.text : '', body: nativeBodyAsset ? nativeBodyAsset.data.value : '', @@ -204,10 +204,10 @@ export const spec = { }; } - bidResponses.push(bidResponse) + bidResponses.push(bidResponse); } - }) - return bidResponses + }); + return bidResponses; }, /** @@ -219,13 +219,13 @@ export const spec = { * @param {string} uspConsent Is the US Privacy Consent string. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; if (syncOptions.iframeEnabled) { const params = []; params.push(`ts=${timestamp()}`); - params.push(`type=h`) + params.push(`type=h`); if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); } @@ -234,12 +234,12 @@ export const spec = { } syncs.push({ type: 'iframe', - url: `${ENDPOINT}/d/6846326/766/2x2?${params.join('&')}` + url: `${ENDPOINT}/d/6846326/766/2x2?${params.join('&')}`, }); } return syncs; - } + }, }; /** @@ -249,7 +249,7 @@ export const spec = { * @returns {Boolean} */ function isVideo(format, adtype) { - return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video' + return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video'; } /** @@ -259,7 +259,7 @@ function isVideo(format, adtype) { * @returns {Boolean} */ function isNative(format, adtype) { - return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native' + return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native'; } /** @@ -268,8 +268,8 @@ function isNative(format, adtype) { * @returns {Boolean} */ function isOutstream(format) { - const context = deepAccess(format, 'mediaTypes.video.context') - return (context === 'outstream') + const context = deepAccess(format, 'mediaTypes.video.context'); + return (context === 'outstream'); } /** @@ -278,30 +278,30 @@ function isOutstream(format) { * @returns {Array} */ function getPlayerSize(format) { - const playerSize = deepAccess(format, 'mediaTypes.video.playerSize') - return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize + const playerSize = deepAccess(format, 'mediaTypes.video.playerSize'); + return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize; } /** - * Expands a 'WxH' string as a 2-element [W, H] array + * Expands a 'WxH' string to a 2-element [W, H] array * @param {String} size * @returns {Array} */ function parseSize(size) { - return size.split(DIMENSION_SIGN).map(Number) + return size.split(DIMENSION_SIGN).map(Number); } /** * Creates a string out of an array of eids with source and uid - * @param {Array} eids + * @param {Array.<{source: String, uids: Array.<{id: String, atype: Number, ext: Object}>}>} eids * @returns {String} */ function createUserIdString(eids) { - const str = [] + const str = []; for (let i = 0; i < eids.length; i++) { - str.push(eids[i].source + ':' + eids[i].uids[0].id) + str.push(eids[i].source + ':' + eids[i].uids[0].id); } - return str.join(',') + return str.join(','); } /** @@ -325,18 +325,18 @@ function createUserIdAtypesString(eids) { * @returns {String} */ function createQueryString(obj) { - const str = [] + const str = []; for (const p in obj) { if (obj.hasOwnProperty(p)) { - const val = obj[p] + const val = obj[p]; if (p !== 'schain' && p !== 'iab_content') { - str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)) + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)); } else { - str.push(p + '=' + val) + str.push(p + '=' + val); } } } - return str.join('&') + return str.join('&'); } /** @@ -345,15 +345,15 @@ function createQueryString(obj) { * @returns {String} */ function createTargetingString(obj) { - const str = [] + const str = []; for (const p in obj) { if (obj.hasOwnProperty(p)) { - const key = p - const val = obj[p] - str.push(key + '=' + val) + const key = p; + const val = obj[p]; + str.push(key + '=' + val); } } - return str.join('&') + return str.join('&'); } /** @@ -362,13 +362,13 @@ function createTargetingString(obj) { * @returns {String} */ function createSchainString(schain) { - const ver = schain.ver || '' - const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : '' - const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext'] + const ver = schain.ver || ''; + const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : ''; + const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext']; const nodesString = schain.nodes.reduce((acc, node) => { - return acc += `!${keys.map(key => node[key] ? encodeURIComponentWithBangIncluded(node[key]) : '').join(',')}` - }, '') - return `${ver},${complete}${nodesString}` + return acc += `!${keys.map(key => node[key] ? encodeURIComponentWithBangIncluded(node[key]) : '').join(',')}`; + }, ''); + return `${ver},${complete}${nodesString}`; } /** @@ -380,15 +380,15 @@ function createSchainString(schain) { */ function getContentObject(bid) { if (bid.params.iabContent && isPlainObject(bid.params.iabContent)) { - return bid.params.iabContent + return bid.params.iabContent; } const globalContent = deepAccess(bid, 'ortb2.site') ? deepAccess(bid, 'ortb2.site.content') - : deepAccess(bid, 'ortb2.app.content') + : deepAccess(bid, 'ortb2.app.content'); if (globalContent && isPlainObject(globalContent)) { - return globalContent + return globalContent; } - return undefined + return undefined; } /** @@ -401,15 +401,15 @@ function getContentObject(bid) { * @returns {String} */ function createIabContentString(iabContent) { - const arrKeys = ['keywords', 'cat'] - const str = [] + const arrKeys = ['keywords', 'cat']; + const str = []; const transformObjToParam = (obj = {}, extraKey = '') => { for (const key in obj) { if ((arrKeys.indexOf(key) !== -1 && Array.isArray(obj[key]))) { // Array of defined keyword which have to be joined into one value from "key: [value1, value2, value3]" to "key:value1|value2|value3" - str.push(''.concat(key, ':', obj[key].map(node => encodeURIComponent(node)).join('|'))) + str.push(''.concat(key, ':', obj[key].map(node => encodeURIComponent(node)).join('|'))); } else if (typeof obj[key] !== 'object') { - str.push(''.concat(extraKey + key, ':', encodeURIComponent(obj[key]))) + str.push(''.concat(extraKey + key, ':', encodeURIComponent(obj[key]))); } else { // Object has to be further flattened transformObjToParam(obj[key], ''.concat(extraKey, key, '.')); @@ -417,7 +417,7 @@ function createIabContentString(iabContent) { } return str.join(','); }; - return encodeURIComponent(transformObjToParam(iabContent)) + return encodeURIComponent(transformObjToParam(iabContent)); } /** @@ -426,7 +426,7 @@ function createIabContentString(iabContent) { * @returns {String} */ function encodeURIComponentWithBangIncluded(str) { - return encodeURIComponent(str).replace(/!/g, '%21') + return encodeURIComponent(str).replace(/!/g, '%21'); } /** @@ -435,11 +435,11 @@ function encodeURIComponentWithBangIncluded(str) { */ function outstreamRender(bid) { bid.renderer.push(() => { - window.ma_width = bid.width - window.ma_height = bid.height - window.ma_vastUrl = bid.vastUrl - window.ma_container = bid.adUnitCode - window.document.dispatchEvent(new Event('ma-start-event')) + window.ma_width = bid.width; + window.ma_height = bid.height; + window.ma_vastUrl = bid.vastUrl; + window.ma_container = bid.adUnitCode; + window.document.dispatchEvent(new Event('ma-start-event')); }); } @@ -450,33 +450,33 @@ function outstreamRender(bid) { * @returns {string[]} */ function extractSizes(bid) { - const { mediaTypes } = bid // see https://docs.prebid.org/dev-docs/adunit-reference.html#examples - const sizes = [] + const { mediaTypes } = bid; // see https://docs.prebid.org/dev-docs/adunit-reference.html#examples + const sizes = []; if (isPlainObject(mediaTypes)) { - const { [BANNER]: bannerType } = mediaTypes + const { [BANNER]: bannerType } = mediaTypes; // only applies for multi size Adslots -> BANNER if (bannerType && isArray(bannerType.sizes)) { if (isArray(bannerType.sizes[0])) { // multiple sizes given - sizes.push(bannerType.sizes) + sizes.push(bannerType.sizes); } else { // just one size provided as array -> wrap to uniformly flatten later - sizes.push([bannerType.sizes]) + sizes.push([bannerType.sizes]); } } // The bid top level field `sizes` is deprecated and should not be used anymore. Keeping it for compatibility. } else if (isArray(bid.sizes)) { if (isArray(bid.sizes[0])) { - sizes.push(bid.sizes) + sizes.push(bid.sizes); } else { - sizes.push([bid.sizes]) + sizes.push([bid.sizes]); } } /** @type {Set} */ - const deduplicatedSizeStrings = new Set(sizes.flat().map(([width, height]) => width + DIMENSION_SIGN + height)) + const deduplicatedSizeStrings = new Set(sizes.flat().map(([width, height]) => width + DIMENSION_SIGN + height)); - return Array.from(deduplicatedSizeStrings) + return Array.from(deduplicatedSizeStrings); } /** @@ -497,7 +497,7 @@ function getBidFloor(bid, sizes) { const floor = bid.getFloor({ currency: CURRENCY_CODE, mediaType: mediaType !== undefined && spec.supportedMediaTypes.includes(mediaType) ? mediaType : '*', - size: sizes.length !== 1 ? '*' : sizes[0].split(DIMENSION_SIGN) + size: sizes.length !== 1 ? '*' : sizes[0].split(DIMENSION_SIGN), }); if (floor.currency === CURRENCY_CODE) { return (floor.floor * 100).toFixed(0); @@ -505,4 +505,4 @@ function getBidFloor(bid, sizes) { return undefined; } -registerBidder(spec) +registerBidder(spec); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 07d42df1319..e5151cf789c 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -1,7 +1,7 @@ import { config } from 'src/config.js'; -import { expect } from 'chai' -import { spec } from 'modules/yieldlabBidAdapter.js' -import { newBidder } from 'src/adapters/bidderFactory.js' +import { expect } from 'chai'; +import { spec } from 'modules/yieldlabBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const DEFAULT_REQUEST = () => ({ bidder: 'yieldlab', @@ -11,11 +11,11 @@ const DEFAULT_REQUEST = () => ({ targeting: { key1: 'value1', key2: 'value2', - notDoubleEncoded: 'value3,value4' + notDoubleEncoded: 'value3,value4', }, customParams: { extraParam: true, - foo: 'bar' + foo: 'bar', }, extId: 'abc', iabContent: { @@ -31,8 +31,8 @@ const DEFAULT_REQUEST = () => ({ cat: ['cat1', 'cat2,ppp', 'cat3|||//'], context: '7', keywords: ['k1,', 'k2..'], - live: '0' - } + live: '0', + }, }, bidderRequestId: '143346cf0f1731', auctionId: '2e41f65424c87c', @@ -43,14 +43,14 @@ const DEFAULT_REQUEST = () => ({ source: 'netid.de', uids: [{ id: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - atype: 1 - }] + atype: 1, + }], }, { source: 'digitrust.de', uids: [{ id: 'd8aa10fa-d86c-451d-aad8-5f16162a9e64', - atype: 2 - }] + atype: 2, + }], }], schain: { ver: '1.0', @@ -59,32 +59,32 @@ const DEFAULT_REQUEST = () => ({ { asi: 'indirectseller.com', sid: '1', - hp: 1 + hp: 1, }, { asi: 'indirectseller2.com', name: 'indirectseller2 name with comma , and bang !', sid: '2', - hp: 1 - } - ] - } -}) + hp: 1, + }, + ], + }, +}); const VIDEO_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { mediaTypes: { video: { playerSize: [[640, 480]], - context: 'instream' - } - } -}) + context: 'instream', + }, + }, +}); const NATIVE_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { mediaTypes: { - native: {} - } -}) + native: {}, + }, +}); const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { params: { @@ -119,7 +119,7 @@ const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { name: 'bar', cattax: 532, cat: [1, 'foo', true], - domain: 'producer.test' + domain: 'producer.test', }, data: { id: 'foo', @@ -129,8 +129,8 @@ const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { value: 'bar', ext: { foo: { - bar: 'bar' - } + bar: 'bar', + }, }, }, { name: 'foo2', @@ -139,27 +139,27 @@ const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { test: { nums: { int: 123, - float: 123.123 + float: 123.123, }, bool: true, - string: 'foo2' - } - } + string: 'foo2', + }, + }, }], }, network: { id: 'foo', name: 'bar', - domain: 'network.test' + domain: 'network.test', }, channel: { id: 'bar', name: 'foo', - domain: 'channel.test' - } - } - } -}) + domain: 'channel.test', + }, + }, + }, +}); const RESPONSE = { advertiser: 'yieldlab', @@ -169,173 +169,173 @@ const RESPONSE = { price: 1, pid: 2222, adsize: '728x90', - adtype: 'BANNER' -} + adtype: 'BANNER', +}; const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { adtype: 'NATIVE', native: { link: { - url: 'https://www.yieldlab.de' + url: 'https://www.yieldlab.de', }, assets: [ { id: 1, title: { - text: 'This is a great headline' - } + text: 'This is a great headline', + }, }, { id: 2, img: { url: 'https://localhost:8080/yl-logo100x100.jpg', w: 100, - h: 100 - } + h: 100, + }, }, { id: 3, data: { - value: 'Native body value' - } - } + value: 'Native body value', + }, + }, ], imptrackers: [ 'http://localhost:8080/ve?d=ODE9ZSY2MTI1MjAzNjMzMzYxPXN0JjA0NWUwZDk0NTY5Yi05M2FiLWUwZTQtOWFjNy1hYWY0MzFiZj1kaXQmMj12', 'http://localhost:8080/md/1111/9efa4e76-2030-4f04-bb9f-322541f8d611?mdata=false&pvid=false&ids=x:1', - 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0' - ] - } -}) + 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0', + ], + }, +}); const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { - adtype: 'VIDEO' -}) + adtype: 'VIDEO', +}); const PVID_RESPONSE = Object.assign({}, VIDEO_RESPONSE, { - pvid: '43513f11-55a0-4a83-94e5-0ebc08f54a2c' -}) + pvid: '43513f11-55a0-4a83-94e5-0ebc08f54a2c', +}); const REQPARAMS = { json: true, - ts: 1234567890 -} + ts: 1234567890, +}; const REQPARAMS_GDPR = Object.assign({}, REQPARAMS, { gdpr: true, - consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA' -}) + consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', +}); const REQPARAMS_IAB_CONTENT = Object.assign({}, REQPARAMS, { - iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0' -}) + iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0', +}); describe('yieldlabBidAdapter', () => { describe('instantiation from spec', () => { it('is working properly', () => { - const yieldlabBidAdapter = newBidder(spec) - expect(yieldlabBidAdapter.callBids).to.exist.and.to.be.a('function') - }) - }) + const yieldlabBidAdapter = newBidder(spec); + expect(yieldlabBidAdapter.callBids).to.exist.and.to.be.a('function'); + }); + }); describe('isBidRequestValid', () => { it('should return true when all required parameters are found', () => { const request = { params: { adslotId: '1111', - supplyId: '2222' - } - } - expect(spec.isBidRequestValid(request)).to.equal(true) - }) + supplyId: '2222', + }, + }; + expect(spec.isBidRequestValid(request)).to.equal(true); + }); it('should return false when required parameters are missing', () => { - expect(spec.isBidRequestValid({})).to.equal(false) - }) - }) + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); describe('buildRequests', () => { - const bidRequests = [DEFAULT_REQUEST()] + const bidRequests = [DEFAULT_REQUEST()]; describe('default functionality', () => { - let request + let request; before(() => { - request = spec.buildRequests(bidRequests) - }) + request = spec.buildRequests(bidRequests); + }); it('sends bid request to ENDPOINT via GET', () => { - expect(request.method).to.equal('GET') - }) + expect(request.method).to.equal('GET'); + }); it('returns a list of valid requests', () => { - expect(request.validBidRequests).to.eql(bidRequests) - }) + expect(request.validBidRequests).to.eql(bidRequests); + }); it('passes single-encoded targeting to bid request', () => { - expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4') - }) + expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4'); + }); it('passes userids to bid request', () => { - expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg%2Cdigitrust.de%3Ad8aa10fa-d86c-451d-aad8-5f16162a9e64') - }) + expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg%2Cdigitrust.de%3Ad8aa10fa-d86c-451d-aad8-5f16162a9e64'); + }); it('passes atype to bid request', () => { - expect(request.url).to.include('atypes=netid.de%3A1%2Cdigitrust.de%3A2') - }) + expect(request.url).to.include('atypes=netid.de%3A1%2Cdigitrust.de%3A2'); + }); it('passes extra params to bid request', () => { - expect(request.url).to.include('extraParam=true&foo=bar') - }) + expect(request.url).to.include('extraParam=true&foo=bar'); + }); it('passes unencoded schain string to bid request', () => { - expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') - }) + expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,'); + }); it('passes iab_content string to bid request', () => { - expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') - }) + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0'); + }); it('passes correct size to bid request', () => { - expect(request.url).to.include('728x90') - }) + expect(request.url).to.include('728x90'); + }); it('passes external id to bid request', () => { - expect(request.url).to.include('id=abc') - }) - }) + expect(request.url).to.include('id=abc'); + }); + }); describe('iab_content handling', () => { const siteConfig = { ortb2: { site: { content: { - id: 'id_from_config' - } - } - } - } + id: 'id_from_config', + }, + }, + }, + }; beforeEach(() => { - config.setConfig(siteConfig) - }) + config.setConfig(siteConfig); + }); afterEach(() => { - config.resetConfig() - }) + config.resetConfig(); + }); it('generates iab_content string from bidder params', () => { - const request = spec.buildRequests(bidRequests) - expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') - }) + const request = spec.buildRequests(bidRequests); + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0'); + }); it('generates iab_content string from first party data if not provided in bidder params', () => { - const requestWithoutIabContent = DEFAULT_REQUEST() - delete requestWithoutIabContent.params.iabContent + const requestWithoutIabContent = DEFAULT_REQUEST(); + delete requestWithoutIabContent.params.iabContent; - const request = spec.buildRequests([{...requestWithoutIabContent, ...siteConfig}]) - expect(request.url).to.include('iab_content=id%3Aid_from_config') - }) + const request = spec.buildRequests([{...requestWithoutIabContent, ...siteConfig}]); + expect(request.url).to.include('iab_content=id%3Aid_from_config'); + }); it('flattens the iabContent, encodes the values, joins the keywords into one value, and than encodes the iab_content request param ', () => { const expectedIabContentValue = encodeURIComponent( @@ -382,18 +382,18 @@ describe('yieldlabBidAdapter', () => { 'channel.id:bar,' + 'channel.name:foo,' + 'channel.domain:channel.test' - ) - const request = spec.buildRequests([IAB_REQUEST()], REQPARAMS) - expect(request.url).to.include('iab_content=' + expectedIabContentValue) - }) - }) + ); + const request = spec.buildRequests([IAB_REQUEST()], REQPARAMS); + expect(request.url).to.include('iab_content=' + expectedIabContentValue); + }); + }); it('passes unencoded schain string to bid request when complete == 0', () => { - const schainRequest = DEFAULT_REQUEST() + const schainRequest = DEFAULT_REQUEST(); schainRequest.schain.complete = 0; // - const request = spec.buildRequests([schainRequest]) - expect(request.url).to.include('schain=1.0,0!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') - }) + const request = spec.buildRequests([schainRequest]); + expect(request.url).to.include('schain=1.0,0!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,'); + }); it('passes encoded referer to bid request', () => { const refererRequest = spec.buildRequests(bidRequests, { @@ -402,123 +402,123 @@ describe('yieldlabBidAdapter', () => { numIframes: 0, reachedTop: true, page: 'https://www.yieldlab.de/test?with=querystring', - stack: ['https://www.yieldlab.de/test?with=querystring'] - } - }) + stack: ['https://www.yieldlab.de/test?with=querystring'], + }, + }); - expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring') - }) + expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring'); + }); it('passes gdpr flag and consent if present', () => { const gdprRequest = spec.buildRequests(bidRequests, { gdprConsent: { consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', - gdprApplies: true - } - }) + gdprApplies: true, + }, + }); - expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA') - expect(gdprRequest.url).to.include('gdpr=true') - }) + expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(gdprRequest.url).to.include('gdpr=true'); + }); describe('sizes handling', () => { it('passes correct size to bid request for mediaType banner', () => { const bannerRequest = DEFAULT_REQUEST(); bannerRequest.mediaTypes = { banner: { - sizes: [[123, 456]] - } - } + sizes: [[123, 456]], + }, + }; // when mediaTypes is present it has precedence over the sizes field (728, 90) - let request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('sizes') - expect(request.url).to.include('123x456') - - bannerRequest.mediaTypes.banner.sizes = [123, 456] - request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('123x456') - - bannerRequest.mediaTypes.banner.sizes = [[123, 456], [320, 240]] - request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('123x456') - expect(request.url).to.include('320x240') - }) + let request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('sizes'); + expect(request.url).to.include('123x456'); + + bannerRequest.mediaTypes.banner.sizes = [123, 456]; + request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('123x456'); + + bannerRequest.mediaTypes.banner.sizes = [[123, 456], [320, 240]]; + request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('123x456'); + expect(request.url).to.include('320x240'); + }); it('passes correct sizes to bid request when mediaType is not present', () => { // information is taken from the top level sizes field const sizesRequest = DEFAULT_REQUEST(); - let request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('sizes') - expect(request.url).to.include('728x90') + let request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('sizes'); + expect(request.url).to.include('728x90'); - sizesRequest.sizes = [[728, 90]] - request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('728x90') + sizesRequest.sizes = [[728, 90]]; + request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('728x90'); - sizesRequest.sizes = [[728, 90], [320, 240]] - request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('728x90') - }) + sizesRequest.sizes = [[728, 90], [320, 240]]; + request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('728x90'); + }); it('does not pass the sizes parameter for mediaType video', () => { const videoRequest = VIDEO_REQUEST(); - let request = spec.buildRequests([videoRequest], REQPARAMS) - expect(request.url).to.not.include('sizes') - }) + let request = spec.buildRequests([videoRequest], REQPARAMS); + expect(request.url).to.not.include('sizes'); + }); it('does not pass the sizes parameter for mediaType native', () => { const nativeRequest = NATIVE_REQUEST(); - let request = spec.buildRequests([nativeRequest], REQPARAMS) - expect(request.url).to.not.include('sizes') - }) - }) - }) + let request = spec.buildRequests([nativeRequest], REQPARAMS); + expect(request.url).to.not.include('sizes'); + }); + }); + }); describe('interpretResponse', () => { - let bidRequest + let bidRequest; before(() => { - bidRequest = DEFAULT_REQUEST() - }) + bidRequest = DEFAULT_REQUEST(); + }); it('handles nobid responses', () => { - expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) - expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) - }) + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0); + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0); + }); it('should get correct bid response', () => { - const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [bidRequest], queryParams: REQPARAMS}) - - expect(result[0].requestId).to.equal('2d925f27f5079f') - expect(result[0].cpm).to.equal(0.01) - expect(result[0].width).to.equal(728) - expect(result[0].height).to.equal(90) - expect(result[0].creativeId).to.equal('1111') - expect(result[0].dealId).to.equal(2222) - expect(result[0].currency).to.equal('EUR') - expect(result[0].netRevenue).to.equal(false) - expect(result[0].ttl).to.equal(300) - expect(result[0].referrer).to.equal('') - expect(result[0].meta.advertiserDomains).to.equal('yieldlab') - expect(result[0].ad).to.include('') From 36834f40c300fb862e84fd975b42656761708a9b Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 17 Jan 2023 11:58:27 -0800 Subject: [PATCH 048/113] PBjs Core (Price Floors) : Support inverseBidAdjustment function (#9395) * support inverseBidAdjustment function * pass in bidRequest object to adjustments * dont do fake bids bobby duh --- modules/priceFloors.js | 16 ++- test/spec/modules/priceFloors_spec.js | 155 ++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 5 deletions(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 8b5976149d0..32b3cbaa607 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -179,10 +179,10 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { /** * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ -export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) { +export function getBiddersCpmAdjustment(bidderName, inputCpm, bid, bidRequest) { const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); if (adjustmentFunction) { - return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); + return parseFloat(adjustmentFunction(inputCpm, { ...bid, cpm: inputCpm }, bidRequest)); } return parseFloat(inputCpm); } @@ -249,8 +249,14 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: // if cpmAdjustment flag is true and we have a valid floor then run the adjustment on it if (floorData.enforcement.bidAdjustment && floorInfo.matchingFloor) { - let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor); - floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + // pub provided inverse function takes precedence, otherwise do old adjustment stuff + const inverseFunction = bidderSettings.get(bidRequest.bidder, 'inverseBidAdjustment'); + if (inverseFunction) { + floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest); + } else { + let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor, {}, bidRequest); + floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + } } if (floorInfo.matchingFloor) { @@ -731,7 +737,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a } // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists - adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid); + adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid, matchingBidRequest); // add necessary data information for analytics adapters / floor providers would possibly need addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index b7d771814d0..f232631d73d 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1495,6 +1495,161 @@ describe('the price floors module', function () { }); }); + it('should use inverseFloorAdjustment function before bidder cpm adjustment', function () { + let functionUsed; + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Rubicon Adjustment'; + bidCpm *= 0.5; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Rubicon Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.5; + }, + }, + appnexus: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Appnexus Adjustment'; + bidCpm *= 0.75; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Appnexus Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.75; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // start with banner as only mediaType + bidRequest.mediaTypes = { banner: { sizes: [[300, 250]] } }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus', + }; + + // should be same as the adjusted calculated inverses above test (banner) + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // should use rubicon inverse + expect(functionUsed).to.equal('Rubicon Inverse'); + + // appnexus just using banner should be same + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + + // now since asking for 'video' only mediaType inverse function should include the .18 + bidRequest.mediaTypes = { video: { context: 'instream' } }; + expect(bidRequest.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 2.36 + }); + + expect(functionUsed).to.equal('Rubicon Inverse'); + + // now since asking for 'video' inverse function should include the .18 + appnexusBid.mediaTypes = { video: { context: 'instream' } }; + expect(appnexusBid.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 1.5734 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + }); + + it('should pass inverseFloorAdjustment the bidRequest object so it can be used', function () { + // Adjustment factors based on Bid Media Type + const mediaTypeFactors = { + banner: 0.5, + native: 0.7, + video: 0.9 + } + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidCpm * mediaTypeFactors[bidResponse.mediaType]; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + // For the inverse we add up each mediaType in the request and divide by number of Mt's to get the inverse number + let factor = Object.keys(bidRequest.mediaTypes).reduce((sum, mediaType) => sum += mediaTypeFactors[mediaType], 0); + factor = factor / Object.keys(bidRequest.mediaTypes).length; + return bidCpm / factor; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // banner only should be 2 + bidRequest.mediaTypes = { banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // native only should be 1.4286 + bidRequest.mediaTypes = { native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video only should be 1.1112 + bidRequest.mediaTypes = { video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.1112 + }); + + // video and banner should even out to 0.7 factor so 1.4286 + bidRequest.mediaTypes = { video: {}, banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video and native should even out to 0.8 factor so -- 1.25 + bidRequest.mediaTypes = { video: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.25 + }); + + // banner and native should even out to 0.6 factor so -- 1.6667 + bidRequest.mediaTypes = { banner: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.6667 + }); + + // all 3 banner video and native should even out to 0.7 factor so -- 1.4286 + bidRequest.mediaTypes = { banner: {}, native: {}, video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + }); + it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { getGlobal().bidderSettings = { rubicon: { From 31b662cef4819dabc196fbf631f440b792025e72 Mon Sep 17 00:00:00 2001 From: Tachfine Date: Tue, 17 Jan 2023 22:43:46 +0100 Subject: [PATCH 049/113] Criteo Bid Adapter : Bump Publisher Tag version (#9429) Update reference to version 133 (latest) --- modules/criteoBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 5567103db69..a056454564c 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -27,7 +27,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 132; +export const FAST_BID_VERSION_CURRENT = 133; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; From 2c12cd27481cc0c248f6afe52f704faa0cbeba13 Mon Sep 17 00:00:00 2001 From: ccorbo Date: Wed, 18 Jan 2023 15:09:08 -0500 Subject: [PATCH 050/113] IX Bid Adapter: retrieve user/agent hints and fix tmax issue (#9394) * feat: passthrough gpp information when it is provided [PB-1395] * chore: passthrough using module [PB-1395] * IX Bid Adapter Changes: change mtype logic, useragent client hints, change tmax logic * remove fallback for tmax timeout Co-authored-by: Chris Corbo --- modules/ixBidAdapter.js | 31 +++++-- package-lock.json | 2 +- test/spec/modules/ixBidAdapter_spec.js | 123 +++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 13 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 27e6d0aee02..bd598cf2d04 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -380,8 +380,8 @@ function parseBid(rawBid, currency, bidRequest) { bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; - - if (rawBid.mtype == MEDIA_TYPES.Video) { + // If mtype = video is passed and vastURl is not set, set vastxml + if (rawBid.mtype == MEDIA_TYPES.Video && ((rawBid.ext && !rawBid.ext.vasturl) || !rawBid.ext)) { bid.vastXml = rawBid.adm; } else if (rawBid.ext && rawBid.ext.vasturl) { bid.vastUrl = rawBid.ext.vasturl; @@ -425,7 +425,6 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.adomain && rawBid.adomain.length > 0) { bid.meta.advertiserDomains = rawBid.adomain; } - return bid; } @@ -629,8 +628,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } const r = {}; - const tmax = config.getConfig('bidderTimeout'); - + const tmax = deepAccess(bidderRequest, 'timeout'); // Since bidderRequestId are the same for different bid request, just use the first one. r.id = validBidRequests[0].bidderRequestId.toString(); r.site = {}; @@ -720,6 +718,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (pageUrl) { r.site.page = pageUrl; } + + if (bidderRequest.gppConsent) { + deepSetValue(r, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(r, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } } if (config.getConfig('coppa')) { @@ -856,7 +859,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { currentRequestSize += currentImpressionSize; const fpd = deepAccess(bidderRequest, 'ortb2') || {}; - if (!isEmpty(fpd) && !isFpdAdded) { r.ext.ixdiag.fpd = true; @@ -876,6 +878,23 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } }); + if (fpd.device) { + const sua = {...fpd.device.sua}; + if (!isEmpty(sua)) { + deepSetValue(r, 'device.sua', sua); + } + } + + if (fpd.hasOwnProperty('regs') && !bidderRequest.gppConsent) { + if (fpd.regs.hasOwnProperty('gpp') && typeof fpd.regs.gpp == 'string') { + deepSetValue(r, 'regs.gpp', fpd.regs.gpp) + } + + if (fpd.regs.hasOwnProperty('gpp_sid') && Array.isArray(fpd.regs.gpp_sid)) { + deepSetValue(r, 'regs.gpp_sid', fpd.regs.gpp_sid) + } + } + const clonedRObject = deepClone(r); clonedRObject.site = mergeDeep({}, clonedRObject.site, site); diff --git a/package-lock.json b/package-lock.json index 9701ff8c91c..54d48311d36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.31.0-pre", + "version": "7.32.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 0de300e4a32..2327376d9ac 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -658,7 +658,7 @@ describe('IndexexchangeAdapter', function () { } }; - const DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET = { + const DEFAULT_VIDEO_BID_RESPONSE_WITH_XML_ADM = { cur: 'USD', id: '1aa2bb3cc4de', seatbid: [ @@ -675,7 +675,6 @@ describe('IndexexchangeAdapter', function () { mtype: 2, adm: ' Test In-Stream Video Date: Wed, 18 Jan 2023 21:39:08 +0100 Subject: [PATCH 051/113] PBjs Core (Promises): fix static method GreedyPromise.resolve not working with Angular + Zone.js (#9426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Webpack v5 complain about named export from JSON modules * Index Exchange Adapter: fix "Should not import the named export 'EVENTS'.'AUCTION_DEBUG' (imported as 'EVENTS') from default-exporting module (only default export is available soon)"" * fix: Uncaught TypeError: Cannot read properties of undefined (reading 'getSlotElementId') * fix: Uncaught TypeError: Cannot read properties of undefined (reading 'getSlotElementId') * fix #9422 * refactor: fix linting error Co-authored-by: Javier Marín --- src/utils/promise.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/utils/promise.js b/src/utils/promise.js index 97c64a96f8b..69e40791ab1 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -90,6 +90,14 @@ export class GreedyPromise extends (getGlobal().Promise || Promise) { res.#parent = this; return res; } + + static resolve(value) { + return new this(resolve => resolve(value)) + } + + static reject(error) { + return new this((resolve, reject) => reject(error)) + } } /** From 27884fc39175960ee54c4a5735ec9d4a594c68a1 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 19 Jan 2023 03:30:27 -0700 Subject: [PATCH 052/113] USP consent management: handle errors from CMPs that cannot deal with `registerDeletion` (#9434) --- modules/consentManagementUsp.js | 20 +++++++++---------- .../spec/modules/consentManagementUsp_spec.js | 17 ++++++++++++++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 0a99ec4a913..fcc13152aa9 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -100,6 +100,14 @@ function lookupUspConsent({onSuccess, onError}) { return onError('USP CMP not found.'); } + function registerDataDelHandler(invoker, arg2) { + try { + invoker('registerDeletion', arg2, adapterManager.callDataDeletionRequest); + } catch (e) { + logError('Error invoking CMP `registerDeletion`:', e); + } + } + // to collect the consent information from the user, we perform a call to USPAPI // to collect the user's consent choices represented as a string (via getUSPData) @@ -115,11 +123,7 @@ function lookupUspConsent({onSuccess, onError}) { USPAPI_VERSION, callbackHandler.consentDataCallback ); - uspapiFunction( - 'registerDeletion', - USPAPI_VERSION, - adapterManager.callDataDeletionRequest - ) + registerDataDelHandler(uspapiFunction, USPAPI_VERSION); } else { logInfo( 'Detected USP CMP is outside the current iframe where Prebid.js is located, calling it now...' @@ -129,11 +133,7 @@ function lookupUspConsent({onSuccess, onError}) { uspapiFrame, callbackHandler.consentDataCallback ); - callUspApiWhileInIframe( - 'registerDeletion', - uspapiFrame, - adapterManager.callDataDeletionRequest - ); + registerDataDelHandler(callUspApiWhileInIframe, uspapiFrame); } let listening = false; diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index f9c3cd5890e..e98486754ab 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -8,7 +8,7 @@ import { } from 'modules/consentManagementUsp.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; -import adapterManager, {uspDataHandler} from 'src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler} from 'src/adapterManager.js'; import 'src/prebid.js'; import {defer} from '../../../src/utils/promise.js'; @@ -508,7 +508,20 @@ describe('consentManagement', function () { sinon.assert.notCalled(adapterManager.callDataDeletionRequest); listener(); sinon.assert.calledOnce(adapterManager.callDataDeletionRequest); - }) + }); + + it('does not fail if CMP does not support registerDeletion', () => { + sandbox.stub(window, '__uspapi').callsFake((cmd, _, cb) => { + if (cmd === 'registerDeletion') { + throw new Error('CMP not compliant'); + } else if (cmd === 'getUSPData') { + // eslint-disable-next-line standard/no-callback-literal + cb({uspString: 'string'}, true); + } + }); + setConsentConfig(goodConfig); + expect(uspDataHandler.getConsentData()).to.eql('string'); + }); }); }); }); From 352fcb3d4a27b6f1948b0616db945f0ed976c8b4 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Thu, 19 Jan 2023 15:08:48 +0100 Subject: [PATCH 053/113] nexx360 Bid Adapter: aliases list update (#9439) * ssp added to meta.demandSource * aliases update --- modules/nexx360BidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 96738f586c1..b04bb47543f 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -21,7 +21,8 @@ export const spec = { gvlid: GVLID, aliases: [ { code: 'revenuemaker' }, - { code: 'firstid-ssp' }, + { code: 'first-id', gvlid: 1178 }, + { code: 'adwebone' }, ], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, From 452fbabe9144d869f1b898e35f3328b333f8f007 Mon Sep 17 00:00:00 2001 From: Wiem Zine El Abidine Date: Thu, 19 Jan 2023 15:26:21 +0100 Subject: [PATCH 054/113] Update live-connect-js version (#9438) * update live-connect-js * fix * fix package-lock.json --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 54d48311d36..fcdd783bb75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.32.0-pre", + "version": "7.33.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.1" + "live-connect-js": "3.0.5" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16229,9 +16229,9 @@ "dev": true }, "node_modules/live-connect-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.1.tgz", - "integrity": "sha512-rwB0IQfuKPJM+I96nLyq8Utr3LkQ7Z/iuq/xKlWDckQRJLYyWkk7F7yaavf/VsjazzLK2dpJeXGijoDkK4Vz8g==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.5.tgz", + "integrity": "sha512-KqxE+V/050nK2tUx8PnAtQBOK4E29WVasQTrLkkCwSebmV5uqMu+VMcwhJSbnyh/g+GhDAE/LL9RB6X9vcmLrg==", "dependencies": { "tiny-hashes": "1.0.1" }, @@ -37865,9 +37865,9 @@ "dev": true }, "live-connect-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.1.tgz", - "integrity": "sha512-rwB0IQfuKPJM+I96nLyq8Utr3LkQ7Z/iuq/xKlWDckQRJLYyWkk7F7yaavf/VsjazzLK2dpJeXGijoDkK4Vz8g==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.5.tgz", + "integrity": "sha512-KqxE+V/050nK2tUx8PnAtQBOK4E29WVasQTrLkkCwSebmV5uqMu+VMcwhJSbnyh/g+GhDAE/LL9RB6X9vcmLrg==", "requires": { "tiny-hashes": "1.0.1" } diff --git a/package.json b/package.json index 58c50ee882e..6101967c552 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.1" + "live-connect-js": "3.0.5" }, "optionalDependencies": { "fsevents": "^2.3.2" From 1e318fde36659a6b072bfbc46463efb038f75422 Mon Sep 17 00:00:00 2001 From: Yohan Boutin Date: Thu, 19 Jan 2023 18:44:03 +0100 Subject: [PATCH 055/113] enable video/banner mediatypes for inImage/inBanner/inArticle/inScreen (#9417) --- modules/seedtagBidAdapter.js | 26 +++++-- test/spec/modules/seedtagBidAdapter_spec.js | 75 ++++++++++++++++----- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index d5abb89437b..f54245a41ab 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -60,6 +60,10 @@ function hasVideoMediaType(bid) { return !!bid.mediaTypes && !!bid.mediaTypes.video; } +function hasBannerMediaType(bid) { + return !!bid.mediaTypes && !!bid.mediaTypes.banner; +} + function hasMandatoryDisplayParams(bid) { const p = bid.params; return ( @@ -72,17 +76,27 @@ function hasMandatoryDisplayParams(bid) { function hasMandatoryVideoParams(bid) { const videoParams = getVideoParams(bid); - return ( + let isValid = !!bid.params.publisherId && !!bid.params.adUnitId && hasVideoMediaType(bid) && !!videoParams.playerSize && isArray(videoParams.playerSize) && - videoParams.playerSize.length > 0 && - // only instream is supported for video - videoParams.context === 'instream' && - bid.params.placement === 'inStream' - ); + videoParams.playerSize.length > 0; + + switch (bid.params.placement) { + // instream accept only video format + case 'inStream': + return isValid && videoParams.context === 'instream'; + // outstream accept banner/native/video format + default: + return ( + isValid && + videoParams.context === 'outstream' && + hasBannerMediaType(bid) && + hasMandatoryDisplayParams(bid) + ); + } } function buildBidRequest(validBidRequest) { diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index cfdd3365269..3627296975b 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -33,30 +33,60 @@ function createInStreamSlotConfig(mediaType) { }); } +const createBannerSlotConfig = (placement, mediatypes) => { + return getSlotConfigs(mediatypes || { banner: {} }, { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement, + }); +}; + describe('Seedtag Adapter', function () { describe('isBidRequestValid method', function () { describe('returns true', function () { describe('when banner slot config has all mandatory params', () => { - describe('and placement has the correct value', function () { - const createBannerSlotConfig = (placement) => { - return getSlotConfigs( - { banner: {} }, - { - publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement, - } + const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; + placements.forEach((placement) => { + it(placement + 'should be valid', function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement) ); - }; - const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; - placements.forEach((placement) => { - it('should be ' + placement, function () { + expect(isBidRequestValid).to.equal(true); + }); + + it( + placement + + ' should be valid when has display and video mediatypes, and video context is outstream', + function () { const isBidRequestValid = spec.isBidRequestValid( - createBannerSlotConfig(placement) + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'outstream', + playerSize: [[600, 200]], + }, + }) ); expect(isBidRequestValid).to.equal(true); - }); - }); + } + ); + + it( + placement + + " shouldn't be valid when has display and video mediatypes, and video context is instream", + function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + }) + ); + expect(isBidRequestValid).to.equal(false); + } + ); }); }); describe('when video slot has all mandatory params', function () { @@ -70,7 +100,18 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid(slotConfig); expect(isBidRequestValid).to.equal(true); }); - it('should return true, when video context is instream, but placement is not inStream', function () { + it('should return true, when video context is instream and mediatype is video and banner', function () { + const slotConfig = createInStreamSlotConfig({ + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + banner: {}, + }); + const isBidRequestValid = spec.isBidRequestValid(slotConfig); + expect(isBidRequestValid).to.equal(true); + }); + it('should return false, when video context is instream, but placement is not inStream', function () { const slotConfig = getSlotConfigs( { video: { From 26e53fe98fafa9f058605e1cb729edde310eb503 Mon Sep 17 00:00:00 2001 From: pkwisniowski-id5 <121963897+pkwisniowski-id5@users.noreply.github.com> Date: Fri, 20 Jan 2023 11:15:21 +0100 Subject: [PATCH 056/113] The payload extended with document.referer and canonicalUrl (#9416) --- modules/id5IdSystem.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index c2c4a97c62e..a0745df37c8 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -247,7 +247,9 @@ class IdFetchFlow { 'gdpr': hasGdpr, 'nbPage': nbPage, 'o': 'pbjs', - 'rf': referer.topmostLocation, + 'tml': referer.topmostLocation, + 'ref': referer.ref, + 'cu': referer.canonicalUrl, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', From 4096bec171ba5736988cdeb4fdf77aef79f98204 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 20 Jan 2023 16:49:38 +0000 Subject: [PATCH 057/113] Prebid 7.33.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fcdd783bb75..ed18633033b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.33.0-pre", + "version": "7.33.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6101967c552..07d848a5592 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.33.0-pre", + "version": "7.33.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 165233b99cd87326eb0b3b96f796aa6767dc6d7c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 20 Jan 2023 16:49:38 +0000 Subject: [PATCH 058/113] Increment version to 7.34.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ed18633033b..3d5072df524 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.33.0", + "version": "7.34.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 07d848a5592..4b52267af26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.33.0", + "version": "7.34.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 678d13653f381c6c56395f12150c12d3bda1da4c Mon Sep 17 00:00:00 2001 From: Daria Boyko Date: Mon, 23 Jan 2023 10:54:57 +0200 Subject: [PATCH 059/113] Admixer Bid Adapter : adding floor module support and new alias (#9427) * add floor module support * bidFloor update * Update admixerBidAdapter.md * Update admixerBidAdapter.js * remove tests * tests * floor test * Update admixerBidAdapter_spec.js * Update admixerBidAdapter_spec.js * Update admixerBidAdapter.js * https endpoint * lint bugs fix --- modules/admixerBidAdapter.js | 30 ++- test/spec/modules/admixerBidAdapter_spec.js | 200 ++++++++++++++------ 2 files changed, 166 insertions(+), 64 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 90031ed7f5d..8dbcfdafd32 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,12 +1,14 @@ -import { logError } from '../src/utils.js'; +import {logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'admixer'; -const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'smn']; +const BIDDER_CODE_ADX = 'admixeradx'; +const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'admixeradx']; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; +const ADX_ENDPOINT_URL = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; export const spec = { code: BIDDER_CODE, aliases: ALIASES, @@ -57,6 +59,10 @@ export const spec = { if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + let bidFloor = getBidFloor(bidderRequest); + if (bidFloor) { + payload.bidFloor = bidFloor; + } } validRequest.forEach((bid) => { let imp = {}; @@ -65,7 +71,11 @@ export const spec = { }); return { method: 'POST', - url: endpointUrl || ENDPOINT_URL, + url: + endpointUrl || + (bidderRequest.bidderCode === BIDDER_CODE_ADX + ? ADX_ENDPOINT_URL + : ENDPOINT_URL), data: payload, }; }, @@ -96,4 +106,16 @@ export const spec = { return pixels; } }; +function getBidFloor(bid) { + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0; + } +} registerBidder(spec); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 228b87ae4d5..26caaf5b70f 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -4,8 +4,10 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; +const BIDDER_CODE_ADX = 'admixeradx'; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ENDPOINT_URL_CUSTOM = 'https://custom.admixer.net/prebid.aspx'; +const ENDPOINT_URL_ADX = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; describe('AdmixerAdapter', function () { @@ -16,18 +18,22 @@ describe('AdmixerAdapter', function () { expect(adapter.callBids).to.be.exist.and.to.be.a('function'); }); }); + // inv-nets.admixer.net/adxprebid.1.2.aspx describe('isBidRequestValid', function () { let bid = { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', }; it('should return true when required params found', function () { @@ -38,7 +44,7 @@ describe('AdmixerAdapter', function () { let bid = Object.assign({}, bid); delete bid.params; bid.params = { - 'placementId': 0 + placementId: 0, }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -47,22 +53,25 @@ describe('AdmixerAdapter', function () { describe('buildRequests', function () { let validRequest = [ { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, ]; let bidderRequest = { bidderCode: BIDDER_CODE, refererInfo: { - page: 'https://example.com' - } + page: 'https://example.com', + }, }; it('should add referrer and imp to be equal bidRequest', function () { @@ -81,49 +90,122 @@ describe('AdmixerAdapter', function () { it('sends bid request to CUSTOM_ENDPOINT via GET', function () { config.setBidderConfig({ bidders: [BIDDER_CODE], // one or more bidders - config: {[BIDDER_CODE]: {endpoint_url: ENDPOINT_URL_CUSTOM}} + config: { [BIDDER_CODE]: { endpoint_url: ENDPOINT_URL_CUSTOM } }, }); - const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest)); + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests(validRequest, bidderRequest) + ); expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); expect(request.method).to.equal('POST'); }); }); + describe('buildRequestsAdmixerADX', function () { + let validRequest = [ + { + bidder: BIDDER_CODE_ADX, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE_ADX, + refererInfo: { + page: 'https://example.com', + }, + }; + + it('sends bid request to ADX ENDPOINT', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT_URL_ADX); + expect(request.method).to.equal('POST'); + }); + }); + + describe('checkFloorGetting', function () { + let validRequest = [ + { + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE, + refererInfo: { + page: 'https://example.com', + }, + }; + it('gets floor', function () { + bidderRequest.getFloor = () => { + return { floor: 0.6 }; + }; + const request = spec.buildRequests(validRequest, bidderRequest); + const payload = request.data; + expect(payload.bidFloor).to.deep.equal(0.6); + }); + }); + describe('interpretResponse', function () { let response = { body: { - ads: [{ - 'currency': 'USD', - 'cpm': 6.210000, - 'ad': '
ad
', - 'width': 300, - 'height': 600, - 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', - 'ttl': 360, - 'netRevenue': false, - 'requestId': '5e4e763b6bc60b', - 'dealId': 'asd123', - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - }] - } + ads: [ + { + currency: 'USD', + cpm: 6.21, + ad: '
ad
', + width: 300, + height: 600, + creativeId: 'ccca3e5e-0c54-4761-9667-771322fbdffc', + ttl: 360, + netRevenue: false, + requestId: '5e4e763b6bc60b', + dealId: 'asd123', + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, + ], + }, }; it('should get correct bid response', function () { const ads = response.body.ads; let expectedResponse = [ { - 'requestId': ads[0].requestId, - 'cpm': ads[0].cpm, - 'creativeId': ads[0].creativeId, - 'width': ads[0].width, - 'height': ads[0].height, - 'ad': ads[0].ad, - 'currency': ads[0].currency, - 'netRevenue': ads[0].netRevenue, - 'ttl': ads[0].ttl, - 'dealId': ads[0].dealId, - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - } + requestId: ads[0].requestId, + cpm: ads[0].cpm, + creativeId: ads[0].creativeId, + width: ads[0].width, + height: ads[0].height, + ad: ads[0].ad, + currency: ads[0].currency, + netRevenue: ads[0].netRevenue, + ttl: ads[0].ttl, + dealId: ads[0].dealId, + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, ]; let result = spec.interpretResponse(response); @@ -141,18 +223,16 @@ describe('AdmixerAdapter', function () { describe('getUserSyncs', function () { let imgUrl = 'https://example.com/img1'; let frmUrl = 'https://example.com/frm2'; - let responses = [{ - body: { - cm: { - pixels: [ - imgUrl - ], - iframes: [ - frmUrl - ], - } - } - }]; + let responses = [ + { + body: { + cm: { + pixels: [imgUrl], + iframes: [frmUrl], + }, + }, + }, + ]; it('Returns valid values', function () { let userSyncAll = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, responses); From 35be6c81a4a60cfbe442893bbe020e00f88d7546 Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Mon, 23 Jan 2023 16:09:39 +0300 Subject: [PATCH 060/113] Admatic Bid Adapter : bugfix with AdserverCurrency param (#9451) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix --- modules/admaticBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 148424c0a98..808c788fcb9 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,5 +1,6 @@ import { getValue, logError, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; let SYNC_URL = ''; const BIDDER_CODE = 'admatic'; @@ -35,7 +36,7 @@ export const spec = { const bids = validBidRequests.map(buildRequestObject); const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); - const currency = getValue(validBidRequests[0].params, 'currency') || 'TRY'; + const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; const bidderName = validBidRequests[0].bidder; const payload = { From 2741fe9b2bacebedacd7ed7edf7db304fc5dafd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Stropa?= <51368253+jaropenx@users.noreply.github.com> Date: Mon, 23 Jan 2023 14:43:15 +0100 Subject: [PATCH 061/113] added support for user agent client hints (#9445) --- modules/openxBidAdapter.js | 5 +++ test/spec/modules/openxBidAdapter_spec.js | 41 +++++++++++++++++++ test/spec/modules/openxOrtbBidAdapter_spec.js | 41 +++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 14525cd0cfc..b6f4523bb50 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -268,6 +268,11 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { nocache: new Date().getTime() }; + const userAgentClientHints = deepAccess(bidderRequest, 'ortb2.device.sua'); + if (userAgentClientHints) { + defaultParams.sua = JSON.stringify(userAgentClientHints); + } + const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); if (userDataSegments.length > 0) { defaultParams.sm = userDataSegments; diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index cc1c2d1e607..8fe220aa202 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1729,6 +1729,47 @@ describe('OpenxAdapter', function () { }); }); }); + describe('with user agent client hints', function () { + it('should add json query param sua with BidRequest.device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests([bidRequest], bidderRequestWithUserAgentClientHints); + expect(request[0].data.sua).to.exist; + const payload = JSON.parse(request[0].data.sua); + expect(payload).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests([bidRequest], bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.sua).to.not.exist; + }); + }); }); }) diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index be6427f607b..4295cb36df2 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -579,6 +579,47 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device.sua).to.not.exist; + }); + }); }); context('when there is a consent management framework', function () { From 9808b3f352764268a46f50b43416c999a93c0f59 Mon Sep 17 00:00:00 2001 From: Mikhail Ivanchenko Date: Mon, 23 Jan 2023 21:59:43 +0300 Subject: [PATCH 062/113] nextMillenniumBidAdapter: fix replaceGetUserMacro function (#9442) * add video support * fix replaceUserMacro func * Add tests Co-authored-by: Mikhail Ivanchenko --- modules/nextMillenniumBidAdapter.js | 28 +++++++++++++------ .../modules/nextMillenniumBidAdapter_spec.js | 16 +++++++++-- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index b5d0e15d078..655f5fc3a35 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -271,15 +271,25 @@ export const spec = { }; function replaceUsersyncMacros(url, gdprConsent, uspConsent) { - const { consentString, gdprApplies } = gdprConsent; - - return url.replace( - '{{.GDPR}}', Number(gdprApplies) - ).replace( - '{{.GDPRConsent}}', consentString - ).replace( - '{{.USPrivacy}}', uspConsent - ); + const { consentString, gdprApplies } = gdprConsent || {}; + + if (gdprApplies) { + const gdpr = Number(gdprApplies); + url = url.replace('{{.GDPR}}', gdpr); + + if (gdpr == 1 && consentString && consentString.length > 0) { + url = url.replace('{{.GDPRConsent}}', consentString); + } + } else { + url = url.replace('{{.GDPR}}', 0); + url = url.replace('{{.GDPRConsent}}', ''); + } + + if (uspConsent) { + url = url.replace('{{.USPrivacy}}', uspConsent); + } + + return url; }; function getAdEl(bid) { diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 90f0d8ae15c..571bb5fc584 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -49,7 +49,7 @@ describe('nextMillenniumBidAdapterTests', function() { cur: 'USD', ext: { sync: { - image: ['urlA'], + image: ['urlA?gdpr={{.GDPR}}'], iframe: ['urlB'], } } @@ -132,7 +132,7 @@ describe('nextMillenniumBidAdapterTests', function() { let userSync = spec.getUserSyncs(syncOptions, [serverResponse], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.equal('image'); - expect(userSync[0].url).to.equal('urlA'); + expect(userSync[0].url).to.equal('urlA?gdpr=1'); syncOptions.iframeEnabled = true; syncOptions.pixelEnabled = false; @@ -142,6 +142,18 @@ describe('nextMillenniumBidAdapterTests', function() { expect(userSync[0].url).to.equal('urlB'); }); + it('Test getUserSyncs function if GDPR is undefined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + } + + let userSync = spec.getUserSyncs(syncOptions, [serverResponse], undefined, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.equal('image'); + expect(userSync[0].url).to.equal('urlA?gdpr=0'); + }); + it('Request params check without GDPR Consent', function () { delete bidRequestData[0].gdprConsent const request = spec.buildRequests(bidRequestData, bidRequestData[0]); From eafc9887bddc467596496e2d2c3d9f1259d912a8 Mon Sep 17 00:00:00 2001 From: Andy Rusiecki Date: Mon, 23 Jan 2023 14:05:38 -0500 Subject: [PATCH 063/113] kargo - adding support for vast url in bid response (#9447) --- modules/kargoBidAdapter.js | 6 ++++- test/spec/modules/kargoBidAdapter_spec.js | 32 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 2c33c3f61d1..6a1c1cf94b0 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -116,7 +116,11 @@ export const spec = { }; if (meta.mediaType == VIDEO) { - bidResponse.vastXml = adUnit.adm; + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; + } } bidResponses.push(bidResponse); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 565b83704fa..525c00d655c 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -556,6 +556,17 @@ describe('kargo adapter tests', function () { mediaType: 'video', metadata: {}, currency: 'EUR' + }, + 6: { + id: 'bar', + cpm: 2.5, + adm: '', + admUrl: 'https://foobar.com/vast_adm', + width: 300, + height: 250, + mediaType: 'video', + metadata: {}, + currency: 'EUR' } }}, { currency: 'USD', @@ -584,6 +595,11 @@ describe('kargo adapter tests', function () { params: { placementId: 'bar' } + }, { + bidId: 6, + params: { + placementId: 'bar' + } }] }); var expectation = [{ @@ -664,6 +680,22 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'video' } + }, { + requestId: '6', + cpm: 2.5, + width: 300, + height: 250, + ad: '', + vastUrl: 'https://foobar.com/vast_adm', + ttl: 300, + creativeId: 'bar', + dealId: undefined, + netRevenue: true, + currency: 'EUR', + mediaType: 'video', + meta: { + mediaType: 'video' + } }]; expect(resp).to.deep.equal(expectation); }); From 6d9887a672b28a4d7018a491839b06fbdd90f0b1 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 23 Jan 2023 12:36:13 -0700 Subject: [PATCH 064/113] openxOrtbBidAdapter: fix device.sua test (#9452) --- test/spec/modules/openxOrtbBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index 4295cb36df2..3bb1958c5fe 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -617,7 +617,7 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); - expect(request[0].data.device.sua).to.not.exist; + expect(request[0].data.device?.sua).to.not.exist; }); }); }); From b77951dd732a60863fb7c45ce05eb301836007f1 Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 23 Jan 2023 21:01:10 +0100 Subject: [PATCH 065/113] Criteo Bid Adapter : Bump Publisher Tag version (#9450) Co-authored-by: v.raybaud --- modules/criteoBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index a056454564c..fb42066a008 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -27,7 +27,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 133; +export const FAST_BID_VERSION_CURRENT = 134; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; From 11b4c97f0867e383575e7eab275e2941b7d9791a Mon Sep 17 00:00:00 2001 From: samous Date: Tue, 24 Jan 2023 05:01:05 +0100 Subject: [PATCH 066/113] BLIINK Bid Adapter: fix ttl (#9443) * fix(bliink): bid ttl * fix(bliink): ttl unit tests Co-authored-by: Samous --- modules/bliinkBidAdapter.js | 2 +- test/spec/modules/bliinkBidAdapter_spec.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 127b534c989..ee814807331 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -125,7 +125,7 @@ export const buildBid = (bidResponse) => { requestId: deepAccess(bidResponse, 'extras.transaction_id'), width: deepAccess(bidResponse, `creative.${bid.mediaType}.width`) || 1, height: deepAccess(bidResponse, `creative.${bid.mediaType}.height`) || 1, - ttl: 3600, + ttl: 300, netRevenue: true, }); }; diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index b8994b86847..8e96bd76940 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -130,7 +130,7 @@ const getConfigCreative = () => { creativeId: '34567erty', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -145,7 +145,7 @@ const getConfigCreativeVideo = (isNoVast) => { requestId: '6a204ce130280d', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -395,7 +395,7 @@ const testsInterpretResponse = [ mediaType: 'video', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(vastXml.replace(/\\"/g, '"')) }] @@ -421,7 +421,7 @@ const testsInterpretResponse = [ mediaType: 'banner', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, width: 300 }] }, @@ -505,7 +505,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -535,7 +535,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -552,7 +552,7 @@ const testsBuildBid = [ height: 250, creativeId: getConfigCreative().creativeId, ad: getConfigBannerBid().creative.banner.adm, - ttl: 3600, + ttl: 300, netRevenue: true, } } From 57f6109c79fc6cbdffe6dfaf66ee1d96331ff49e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 13:23:05 -0500 Subject: [PATCH 067/113] Bump ua-parser-js from 0.7.32 to 0.7.33 (#9456) Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.32 to 0.7.33. - [Release notes](https://github.com/faisalman/ua-parser-js/releases) - [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md) - [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.32...0.7.33) --- updated-dependencies: - dependency-name: ua-parser-js dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d5072df524..6298a19479c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.33.0-pre", + "version": "7.34.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -2797,9 +2797,9 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true, "funding": [ { @@ -8453,9 +8453,9 @@ } }, "node_modules/devtools/node_modules/ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true, "funding": [ { @@ -22962,9 +22962,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", - "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true, "funding": [ { @@ -27305,9 +27305,9 @@ } }, "ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true }, "uuid": { @@ -31718,9 +31718,9 @@ } }, "ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true }, "uuid": { @@ -43058,9 +43058,9 @@ } }, "ua-parser-js": { - "version": "0.7.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", - "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true }, "uglify-js": { From a009ced31f55c5402bf92b773561468abb8508a6 Mon Sep 17 00:00:00 2001 From: Patrick Loughrey Date: Tue, 24 Jan 2023 13:26:22 -0500 Subject: [PATCH 068/113] Triplelift Bid Adapter: Support for GPP in bid requests (#9455) * prioritize topmostlocation * adds test for topmostlocation / referrer * cleanup * delete param after test * TL-32803: Update referrer logic * TL-32803: Update referrer logic * TL-34204: Add support for GPP Co-authored-by: Nick Llerandi Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> --- modules/tripleliftBidAdapter.js | 4 ++++ test/spec/modules/tripleliftBidAdapter_spec.js | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 7f6ec90c7b9..3ce44d067bf 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -154,6 +154,10 @@ function _buildPostBody(bidRequests, bidderRequest) { if (!isEmpty(ext)) { data.ext = ext; } + + if (bidderRequest?.ortb2?.regs?.gpp) { + data.regs = Object.assign({}, bidderRequest.ortb2.regs); + } return data; } diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 5cfa64184f9..0447cb4d5d6 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -1168,6 +1168,19 @@ describe('triplelift adapter', function () { } }) }); + it('should add gpp consent data to bid request object if gpp data exists', function() { + bidderRequest.ortb2 = { + regs: { + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + } + } + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.regs).to.deep.equal({ + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + }) + }); }); describe('interpretResponse', function () { From 97c149daf215f5f0b178c9fb88f29a3fd9b5b32c Mon Sep 17 00:00:00 2001 From: Mike Marcus Date: Tue, 24 Jan 2023 17:05:59 -0500 Subject: [PATCH 069/113] Lotame Panorama ID Module : add safari handling (#9418) * GRUE-176 work in progress, trying to make the id URL dynamic. * GRUE-176 After some attempts with leveraging environment variables, decided to look for an environment variable passed on configuration. * GRUE-177 WIP added a fix for handling timestamps, plus there's some temporary debugging that I was using to understand flow. * GRUE-177 Fixed bug with localStorage checking for empty, included null as a possible return value. * GRUE-177 updated the environment handling for dev, qa, and prod. I'm still on the fence on whether we need this, but it's allowing the tests to pass currently, so leaving it in for now. * GRUE-178 removed the dynamic URL handling for the ID endpoint. We will manage that change with the build process for testing. * GRUE-339 changes to check for browser, and accomodate Safari with a different URL. * GRUE-339 changes to check for browser, and accomodate Safari with a different URL. * GRUE-339 Removed the obfuscation from the Safari URL, as it was deemed unnecessary. * GRUE-339 corrected the safari id endpoint - I had forgotten that it was different than the usual one. * GRUE-339 Updated test to cover Safari handling. * GRUE-340 Updated the variable name for the cookieless domain, to remove the emphasis on Safari and better illustrate that this is a general approach. Co-authored-by: Mark Conrad Co-authored-by: Mark --- modules/lotamePanoramaIdSystem.js | 10 ++++++- .../modules/lotamePanoramaIdSystem_spec.js | 26 ++++++++++++------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 832d98e4f83..883c931824b 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -29,6 +29,7 @@ const DAY_MS = 60 * 60 * 24 * 1000; const MISSING_CORE_CONSENT = 111; const GVLID = 95; const ID_HOST = 'id.crwdcntrl.net'; +const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net'; export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); let cookieDomain; @@ -252,6 +253,13 @@ export const lotamePanoramaIdSubmodule = { usPrivacy = getFromStorage('us_privacy'); } + const getRequestHost = function() { + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + return ID_HOST_COOKIELESS; + } + return ID_HOST; + } + const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { @@ -288,7 +296,7 @@ export const lotamePanoramaIdSubmodule = { const url = buildUrl({ protocol: 'https', - host: ID_HOST, + host: getRequestHost(), pathname: '/id', search: isEmpty(queryParams) ? undefined : queryParams, }); diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index 5dc055ac080..ea538db08e1 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -18,6 +18,7 @@ describe('LotameId', function() { let removeFromLocalStorageStub; let timeStampStub; let uspConsentDataStub; + let requestHost; const nowTimestamp = new Date().getTime(); @@ -33,6 +34,11 @@ describe('LotameId', function() { ); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + requestHost = 'https://c.ltmsphrcl.net/id'; + } else { + requestHost = 'https://id.crwdcntrl.net/id'; + } }); afterEach(function () { @@ -69,7 +75,7 @@ describe('LotameId', function() { }); it('should call the remote server when getId is called', function () { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); expect(callBackSpy.calledOnce).to.be.true; }); @@ -439,7 +445,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -471,7 +477,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -503,7 +509,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -531,7 +537,7 @@ describe('LotameId', function() { it('should not include the gdpr consent string on the url', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true' + `${requestHost}?gdpr_applies=true` ); }); }); @@ -560,7 +566,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -589,7 +595,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -613,7 +619,7 @@ describe('LotameId', function() { }); it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); }); }); @@ -835,7 +841,7 @@ describe('LotameId', function() { it('should pass the usp consent string and client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&us_privacy=1NNN&c=1234' + `${requestHost}?gdpr_applies=false&us_privacy=1NNN&c=1234` ); }); @@ -923,7 +929,7 @@ describe('LotameId', function() { it('should pass client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&c=1234' + `${requestHost}?gdpr_applies=false&c=1234` ); }); From a10a0fa431d3ce1ad160169457cad82818bcadf9 Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Wed, 25 Jan 2023 16:08:35 +0100 Subject: [PATCH 070/113] Adnuntius Bid Adapter: Bug fix for multiple mime types. (#9458) --- modules/adnuntiusBidAdapter.js | 74 +++--- test/spec/modules/adnuntiusBidAdapter_spec.js | 240 +++++++++--------- 2 files changed, 156 insertions(+), 158 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 5ad5436e732..ea3b723b316 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; -import { isStr, deepAccess, logInfo } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { isStr, deepAccess } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -8,7 +8,7 @@ const BIDDER_CODE = 'adnuntius'; const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' -const DEFAULT_NATIVE = 'native' +// const DEFAULT_NATIVE = 'native' const checkSegment = function (segment) { if (isStr(segment)) return segment; @@ -28,31 +28,31 @@ const getSegmentsFromOrtb = function (ortb2) { return segments } -function createNative(ad) { - const native = {}; - const assets = ad.assets - native.title = ad.text.title.content; - native.image = { - url: assets.image.cdnId, - height: assets.image.height, - width: assets.image.width, - }; - if (assets.icon) { - native.icon = { - url: assets.icon.cdnId, - height: assets.icon.height, - width: assets.icon.width, - }; - } - - native.sponsoredBy = ad.text.sponsoredBy?.content || ''; - native.body = ad.text.body?.content || ''; - native.cta = ad.text.cta?.content || ''; - native.clickUrl = ad.destinationUrls.destination || ''; - native.impressionTrackers = ad.impressionTrackingUrls || [ad.renderedPixel]; - - return native; -} +// function createNative(ad) { +// const native = {}; +// const assets = ad.assets +// native.title = ad.text.title.content; +// native.image = { +// url: assets.image.cdnId, +// height: assets.image.height, +// width: assets.image.width, +// }; +// if (assets.icon) { +// native.icon = { +// url: assets.icon.cdnId, +// height: assets.icon.height, +// width: assets.icon.width, +// }; +// } + +// native.sponsoredBy = ad.text.sponsoredBy?.content || ''; +// native.body = ad.text.body?.content || ''; +// native.cta = ad.text.cta?.content || ''; +// native.clickUrl = ad.destinationUrls.destination || ''; +// native.impressionTrackers = ad.impressionTrackingUrls || [ad.renderedPixel]; + +// return native; +// } const handleMeta = function () { const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }) @@ -73,7 +73,7 @@ const getUsi = function (meta, ortb2, bidderRequest) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { return !!(bid.bidId || (bid.params.member && bid.params.invCode)); }, @@ -109,9 +109,9 @@ export const spec = { network += '_video' } - if (bid.mediaTypes && bid.mediaTypes.native) { - network += '_native' - } + // if (bid.mediaTypes && bid.mediaTypes.native) { + // network += '_native' + // } bidRequests[network] = bidRequests[network] || []; bidRequests[network].push(bid); @@ -130,7 +130,7 @@ export const spec = { const network = networkKeys[j]; const networkRequest = [...request] if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } - if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } + // if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } requests.push({ method: 'POST', url: ENDPOINT_URL + '?' + networkRequest.join('&'), @@ -170,15 +170,13 @@ export const spec = { if (adUnit.vastXml) { adResponse[adUnit.targetId].vastXml = adUnit.vastXml adResponse[adUnit.targetId].mediaType = VIDEO - } else if (ad.assets && ad.assets.image && ad.text && ad.text.title && ad.text.body && ad.destinationUrls && ad.destinationUrls.destination) { - adResponse[adUnit.targetId].native = createNative(ad); - adResponse[adUnit.targetId].mediaType = NATIVE; + // } else if (ad.assets && ad.assets.image && ad.text && ad.text.title && ad.text.body && ad.destinationUrls && ad.destinationUrls.destination) { + // adResponse[adUnit.targetId].native = createNative(ad); + // adResponse[adUnit.targetId].mediaType = NATIVE; } else { adResponse[adUnit.targetId].ad = adUnit.html } - logInfo('BID', adResponse) - return adResponse } else return response }, {}); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index ee585862800..b787a52d6f2 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -71,29 +71,29 @@ describe('adnuntiusBidAdapter', function () { } ] - const nativeBidderRequest = [ - { - bidId: '123', - bidder: 'adnuntius', - params: { - auId: '8b6bc', - network: 'adnuntius', - }, - mediaTypes: { - native: { - title: { - required: true - }, - image: { - required: true - }, - body: { - required: true - } - } - }, - } - ] + // const nativeBidderRequest = [ + // { + // bidId: '123', + // bidder: 'adnuntius', + // params: { + // auId: '8b6bc', + // network: 'adnuntius', + // }, + // mediaTypes: { + // native: { + // title: { + // required: true + // }, + // image: { + // required: true + // }, + // body: { + // required: true + // } + // } + // }, + // } + // ] const singleBidRequest = { bid: [ @@ -107,9 +107,9 @@ describe('adnuntiusBidAdapter', function () { bid: videoBidderRequest } - const nativeBidRequest = { - bid: nativeBidderRequest - } + // const nativeBidRequest = { + // bid: nativeBidderRequest + // } const serverResponse = { body: { @@ -237,83 +237,83 @@ describe('adnuntiusBidAdapter', function () { ] } } - const serverNativeResponse = { - body: { - 'adUnits': [ - { - 'auId': '000000000008b6bc', - 'targetId': '123', - 'html': '

hi!

', - 'matchedAdCount': 1, - 'responseId': 'adn-rsp-1460129238', - 'ads': [ - { - 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', - 'assets': { - 'image': { - 'cdnId': 'https://assets.adnuntius.com/K9rfXC6wJvgVuy4Fbt5P8oEEGXme9ZaP8BNDzz3OMGQ.jpg', - 'width': '300', - 'height': '250' - } - }, - 'text': { - 'body': { - 'content': 'Testing Native ad from Adnuntius', - 'length': '32', - 'minLength': '0', - 'maxLength': '100' - }, - 'title': { - 'content': 'Native Ad', - 'length': '9', - 'minLength': '5', - 'maxLength': '100' - } - }, - 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'urls': { - 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' - }, - 'urlsEsc': { - 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' - }, - 'destinationUrls': { - 'destination': 'http://google.com' - }, - 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, - 'bid': { 'amount': 0.005, 'currency': 'NOK' }, - 'cost': { 'amount': 0.005, 'currency': 'NOK' }, - 'impressionTrackingUrls': [], - 'impressionTrackingUrlsEsc': [], - 'adId': 'adn-id-1347343135', - 'selectedColumn': '0', - 'selectedColumnPosition': '0', - 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'creativeWidth': '980', - 'creativeHeight': '120', - 'creativeId': 'wgkq587vgtpchsx1', - 'lineItemId': 'scyjdyv3mzgdsnpf', - 'layoutId': 'sw6gtws2rdj1kwby', - 'layoutName': 'Responsive image' - }, - - ] - }, - { - 'auId': '000000000008b6bc', - 'targetId': '456', - 'matchedAdCount': 0, - 'responseId': 'adn-rsp-1460129238', - } - ] - } - } + // const serverNativeResponse = { + // body: { + // 'adUnits': [ + // { + // 'auId': '000000000008b6bc', + // 'targetId': '123', + // 'html': '

hi!

', + // 'matchedAdCount': 1, + // 'responseId': 'adn-rsp-1460129238', + // 'ads': [ + // { + // 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', + // 'assets': { + // 'image': { + // 'cdnId': 'https://assets.adnuntius.com/K9rfXC6wJvgVuy4Fbt5P8oEEGXme9ZaP8BNDzz3OMGQ.jpg', + // 'width': '300', + // 'height': '250' + // } + // }, + // 'text': { + // 'body': { + // 'content': 'Testing Native ad from Adnuntius', + // 'length': '32', + // 'minLength': '0', + // 'maxLength': '100' + // }, + // 'title': { + // 'content': 'Native Ad', + // 'length': '9', + // 'minLength': '5', + // 'maxLength': '100' + // } + // }, + // 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'urls': { + // 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' + // }, + // 'urlsEsc': { + // 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' + // }, + // 'destinationUrls': { + // 'destination': 'http://google.com' + // }, + // 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, + // 'bid': { 'amount': 0.005, 'currency': 'NOK' }, + // 'cost': { 'amount': 0.005, 'currency': 'NOK' }, + // 'impressionTrackingUrls': [], + // 'impressionTrackingUrlsEsc': [], + // 'adId': 'adn-id-1347343135', + // 'selectedColumn': '0', + // 'selectedColumnPosition': '0', + // 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + // 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + // 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'creativeWidth': '980', + // 'creativeHeight': '120', + // 'creativeId': 'wgkq587vgtpchsx1', + // 'lineItemId': 'scyjdyv3mzgdsnpf', + // 'layoutId': 'sw6gtws2rdj1kwby', + // 'layoutName': 'Responsive image' + // }, + + // ] + // }, + // { + // 'auId': '000000000008b6bc', + // 'targetId': '456', + // 'matchedAdCount': 0, + // 'responseId': 'adn-rsp-1460129238', + // } + // ] + // } + // } describe('inherited functions', function () { it('exists and is a function', function () { @@ -531,21 +531,21 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].vastXml).to.equal(serverVideoResponse.body.adUnits[0].vastXml); }); }); - describe('interpretNativeResponse', function () { - it('should return valid response when passed valid server response', function () { - const interpretedResponse = spec.interpretResponse(serverNativeResponse, nativeBidRequest); - const ad = serverNativeResponse.body.adUnits[0].ads[0] - expect(interpretedResponse).to.have.lengthOf(1); - expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); - expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); - expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); - expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); - expect(interpretedResponse[0].netRevenue).to.equal(false); - expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); - expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); - expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); - expect(interpretedResponse[0].native.body).to.equal(serverNativeResponse.body.adUnits[0].ads[0].text.body.content); - }); - }); + // describe('interpretNativeResponse', function () { + // it('should return valid response when passed valid server response', function () { + // const interpretedResponse = spec.interpretResponse(serverNativeResponse, nativeBidRequest); + // const ad = serverNativeResponse.body.adUnits[0].ads[0] + // expect(interpretedResponse).to.have.lengthOf(1); + // expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); + // expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + // expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + // expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + // expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + // expect(interpretedResponse[0].netRevenue).to.equal(false); + // expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + // expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + // expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); + // expect(interpretedResponse[0].native.body).to.equal(serverNativeResponse.body.adUnits[0].ads[0].text.body.content); + // }); + // }); }); From 3c536e181ed104bcfdc9766bea8034afe123482a Mon Sep 17 00:00:00 2001 From: Krzysztof Desput Date: Wed, 25 Jan 2023 16:18:39 +0100 Subject: [PATCH 071/113] Holid bid adapter: skip user syncs when no bidders in bid response (#9462) --- modules/holidBidAdapter.js | 13 ++++++---- test/spec/modules/holidBidAdapter_spec.js | 29 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index be5a7a044c3..92b0a1c18db 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -85,11 +85,12 @@ export const spec = { } const syncs = [] + const bidders = getBidders(serverResponse) - if (optionsType.iframeEnabled) { + if (optionsType.iframeEnabled && bidders) { const queryParams = [] - queryParams.push('bidders=' + getBidders(serverResponse)) + queryParams.push('bidders=' + bidders) queryParams.push('gdpr=' + +gdprConsent.gdprApplies) queryParams.push('gdpr_consent=' + gdprConsent.consentString) queryParams.push('usp_consent=' + (uspConsent || '')) @@ -107,6 +108,8 @@ export const spec = { return syncs } + + return [] }, } @@ -136,10 +139,12 @@ function getImp(bid) { function getBidders(serverResponse) { const bidders = serverResponse - .map((res) => Object.keys(res.body.ext.responsetimemillis)) + .map((res) => Object.keys(res.body.ext.responsetimemillis || [])) .flat(1) - return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + if (bidders.length) { + return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + } } function addWurl(auctionId, adId, wurl) { diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js index e18a5ac58f4..e55befd213a 100644 --- a/test/spec/modules/holidBidAdapter_spec.js +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -161,5 +161,34 @@ describe('holidBidAdapterTests', () => { expect(userSyncs).to.deep.equal(expectedUserSyncs) }) + + it('should return empty user syncs when responsetimemillis is not defined', () => { + const optionsType = { + iframeEnabled: true, + pixelEnabled: true, + } + const serverResponse = [ + { + body: { + ext: {}, + }, + }, + ] + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + } + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + const expectedUserSyncs = [] + + const userSyncs = spec.getUserSyncs( + optionsType, + serverResponse, + gdprConsent, + uspConsent + ) + + expect(userSyncs).to.deep.equal(expectedUserSyncs) + }) }) }) From da42b1b607d4d463551e3eaf8ef5bfd02f94370e Mon Sep 17 00:00:00 2001 From: sag-jonhil <78849369+sag-jonhil@users.noreply.github.com> Date: Wed, 25 Jan 2023 17:29:10 +0100 Subject: [PATCH 072/113] Seeding Alliance Bid Adapter: add banner support and get endpoint-url from config (#9404) * add seedingAlliance Adapter * add two native default params * ... * ... * seedingAlliance Adapter: add two more default native params * updating seedingAlliance Adapter * seedingAlliance Adapter * quickfix no bids + net revenue * bugfix replace auction price * change URL and add versioning * add vendorId to seedingAllianceAdapter * optimize code + banner support * add newline at the end of file * fix ci/circleci error * add new specs Co-authored-by: SeedingAllianceTech <55976067+SeedingAllianceTech@users.noreply.github.com> Co-authored-by: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Co-authored-by: Hendrick Musche --- modules/seedingAllianceBidAdapter.js | 245 +++++++++--------- .../modules/seedingAllianceAdapter_spec.js | 89 +++++-- 2 files changed, 195 insertions(+), 139 deletions(-) diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index 64b9cd5d4aa..29953da7ffa 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -2,120 +2,111 @@ 'use strict'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE } from '../src/mediaTypes.js'; -import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import { _map, isArray, isEmpty, deepSetValue, replaceAuctionPrice } from '../src/utils.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const BIDDER_CODE = 'seedingAlliance'; const GVL_ID = 371; +const BIDDER_CODE = 'seedingAlliance'; const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; -const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; +const NATIVE_ASSET_IDS = { 0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon' }; const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - - body: { - id: 1, - name: 'data', - type: 2 - }, - - sponsoredBy: { - id: 2, - name: 'data', - type: 1 - }, - - image: { - id: 3, - type: 3, - name: 'img' - }, - - cta: { - id: 4, - type: 12, - name: 'data' - }, - - icon: { - id: 5, - type: 1, - name: 'img' - } + title: { id: 0, name: 'title' }, + body: { id: 1, name: 'data', type: 2 }, + sponsoredBy: { id: 2, name: 'data', type: 1 }, + image: { id: 3, type: 3, name: 'img' }, + cta: { id: 4, type: 12, name: 'data' }, + icon: { id: 5, type: 1, name: 'img' } }; export const spec = { code: BIDDER_CODE, - gvlid: GVL_ID, + supportedMediaTypes: [NATIVE, BANNER], - supportedMediaTypes: [NATIVE], - - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { return !!bid.params.adUnitId; }, - buildRequests: (validBidRequests, bidderRequest) => { + buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = bidderRequest.auctionId; - const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; let url = bidderRequest.refererInfo.page; - const imp = validBidRequests.map((bid, id) => { - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - - const asset = { - required: bidParams.required & 1 - }; + const imps = validBidRequests.map((bidRequest, id) => { + const imp = { + id: String(id + 1), + tagid: bidRequest.params.adUnitId + }; - if (props) { - asset.id = props.id; + /** + * Native Ad + */ + if (bidRequest.nativeParams) { + const assets = _map(bidRequest.nativeParams, (nativeAsset, key) => { + const props = NATIVE_PARAMS[key]; + + if (props) { + let wmin, hmin, w, h; + let aRatios = nativeAsset.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } - let w, h; + if (nativeAsset.sizes) { + const sizes = flatten(nativeAsset.sizes); + w = parseInt(sizes[0], 10); + h = parseInt(sizes[1], 10); + } - if (bidParams.sizes) { - w = bidParams.sizes[0]; - h = bidParams.sizes[1]; + const asset = { + id: props.id, + required: nativeAsset.required & 1 + }; + + asset[props.name] = { + len: nativeAsset.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } else { + // TODO Filter impressions with required assets we don't support } + }).filter(Boolean); - asset[props.name] = { - len: bidParams.len, - type: props.type, - w, - h - }; + imp.native = { + request: { + assets + } + }; + } else { + let sizes = transformSizes(bidRequest.sizes); - return asset; + imp.banner = { + format: sizes, + w: sizes[0] ? sizes[0].w : 0, + h: sizes[0] ? sizes[0].h : 0 } - }) - .filter(Boolean); + } - if (bid.params.url) { - url = bid.params.url; + if (bidRequest.params.url) { + url = bidRequest.params.url; } - return { - id: String(id + 1), - tagid: bid.params.adUnitId, - tid: tid, - pt: pt, - native: { - request: { - assets - } - } - }; + return imp; }); const request = { @@ -123,12 +114,9 @@ export const spec = { site: { page: url }, - device: { - ua: navigator.userAgent - }, - cur, - imp, - user: {}, + cur: [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR], + imp: imps, + tmax: bidderRequest.timeout, regs: { ext: { gdpr: 0, @@ -137,23 +125,22 @@ export const spec = { } }; - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { + request.user = {}; + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); } return { method: 'POST', - url: ENDPOINT_URL, + url: config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL, data: JSON.stringify(request), - options: { - contentType: 'application/json' - }, - bids: validBidRequests + bidRequests: validBidRequests }; }, - interpretResponse: function(serverResponse, { bids }) { + interpretResponse: function (serverResponse, { bidRequests }) { if (isEmpty(serverResponse.body)) { return []; } @@ -165,35 +152,72 @@ export const spec = { return result; }, []) : []; - return bids - .map((bid, id) => { + return bidRequests + .map((bidRequest, id) => { const bidResponse = bidResponses[id]; + const type = bidRequest.nativeParams ? NATIVE : BANNER; + if (bidResponse) { - return { - requestId: bid.bidId, + const bidObject = { + requestId: bidRequest.bidId, // TODO get this value from response? cpm: bidResponse.price, creativeId: bidResponse.crid, - ttl: 1000, - netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), + ttl: 600, + netRevenue: true, currency: cur, - mediaType: NATIVE, + mediaType: type, bidderCode: BIDDER_CODE, - native: parseNative(bidResponse), meta: { advertiserDomains: bidResponse.adomain && bidResponse.adomain.length > 0 ? bidResponse.adomain : [] } }; + + if (type === NATIVE) { + bidObject.native = parseNative(bidResponse); + bidObject.mediaType = NATIVE; + } + + if (type === BANNER) { + bidObject.ad = replaceAuctionPrice(bidResponse.adm, bidResponse.price); + bidObject.width = bidResponse.w; + bidObject.height = bidResponse.h; + bidObject.mediaType = BANNER; + } + + return bidObject; } }) .filter(Boolean); } }; -registerBidder(spec); +function transformSizes(requestSizes) { + if (!isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (isArray(requestSizes[0])) { + return requestSizes.map(item => ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + })); + } + + return []; +} + +function flatten(arr) { + return [].concat(...arr); +} function parseNative(bid) { - const {assets, link, imptrackers} = bid.adm.native; + const { assets, link, imptrackers } = bid.adm.native; let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); @@ -228,15 +252,4 @@ function parseNative(bid) { return result; } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - -function flatten(arr) { - return [].concat(...arr); -} +registerBidder(spec); diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 81af9546ff0..6086db01de4 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -38,7 +38,7 @@ describe('SeedingAlliance adapter', function () { }); it('should have default request structure', function () { - let keys = 'site,device,cur,imp,user,regs'.split(','); + let keys = 'site,cur,imp,regs'.split(','); let validBidRequests = [{ bidId: 'bidId', params: {} @@ -60,14 +60,17 @@ describe('SeedingAlliance adapter', function () { assert.equal(request.id, validBidRequests[0].auctionId); }); - it('Verify the device', function () { + it('Verify the site url', function () { + let siteUrl = 'https://www.yourdomain.tld/your-directory/'; let validBidRequests = [{ bidId: 'bidId', - params: {} + params: { + url: siteUrl + } }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); - assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.site.page, siteUrl); }); it('Verify native asset ids', function () { @@ -109,7 +112,7 @@ describe('SeedingAlliance adapter', function () { }); describe('interpretResponse', function () { - const goodResponse = { + const goodNativeResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', @@ -136,51 +139,91 @@ describe('SeedingAlliance adapter', function () { ] } }; + + const goodBannerResponse = { + body: { + cur: 'EUR', + id: 'b4516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [ + { + seat: 'seedingAlliance', + bid: [{ + adm: '', + impid: 1, + price: 0.90, + h: 250, + w: 300 + }] + } + ] + } + }; + const badResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', seatbid: [] }}; - const bidRequest = { + const bidNativeRequest = { data: {}, - bids: [{ bidId: 'bidId1' }] + bidRequests: [{bidId: 'bidId1', nativeParams: {title: {required: true, len: 800}}}] + }; + + const bidBannerRequest = { + data: {}, + bidRequests: [{bidId: 'bidId1', sizes: [300, 250]}] }; it('should return null if body is missing or empty', function () { - const result = spec.interpretResponse(badResponse, bidRequest); + const result = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); delete badResponse.body - const result1 = spec.interpretResponse(badResponse, bidRequest); + const result1 = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); }); it('should return the correct params', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; - - assert.deepEqual(result[0].currency, goodResponse.body.cur); - assert.deepEqual(result[0].requestId, bidRequest.bids[0].bidId); - assert.deepEqual(result[0].cpm, bid.price); - assert.deepEqual(result[0].creativeId, bid.crid); - assert.deepEqual(result[0].mediaType, 'native'); - assert.deepEqual(result[0].bidderCode, 'seedingAlliance'); + const resultNative = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bidNative = goodNativeResponse.body.seatbid[0].bid[0]; + + assert.deepEqual(resultNative[0].bidderCode, 'seedingAlliance'); + assert.deepEqual(resultNative[0].currency, goodNativeResponse.body.cur); + assert.deepEqual(resultNative[0].requestId, bidNativeRequest.bidRequests[0].bidId); + assert.deepEqual(resultNative[0].cpm, bidNative.price); + assert.deepEqual(resultNative[0].creativeId, bidNative.crid); + assert.deepEqual(resultNative[0].mediaType, 'native'); + + const resultBanner = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + + assert.deepEqual(resultBanner[0].bidderCode, 'seedingAlliance'); + assert.deepEqual(resultBanner[0].mediaType, 'banner'); + assert.deepEqual(resultBanner[0].width, bidBannerRequest.bidRequests[0].sizes[0]); + assert.deepEqual(resultBanner[0].height, bidBannerRequest.bidRequests[0].sizes[1]); }); - it('should return the correct tracking links', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; + it('should return the correct native tracking links', function () { + const result = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bid = goodNativeResponse.body.seatbid[0].bid[0]; const regExpPrice = new RegExp('price=' + bid.price); result[0].native.clickTrackers.forEach(function (clickTracker) { - assert.ok(clickTracker.search(regExpPrice) > -1); + assert.ok(clickTracker.search(regExpPrice) > -1); }); result[0].native.impressionTrackers.forEach(function (impTracker) { - assert.ok(impTracker.search(regExpPrice) > -1); + assert.ok(impTracker.search(regExpPrice) > -1); }); }); + + it('should return the correct banner content', function () { + const result = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + const bid = goodBannerResponse.body.seatbid[0].bid[0]; + const regExpContent = new RegExp(''); + + assert.ok(result[0].ad.search(regExpContent) > -1); + }); }); }); From 628c22931d9d49c11a014b1a7fb6197fb4b04b69 Mon Sep 17 00:00:00 2001 From: EMX Digital <43830380+EMXDigital@users.noreply.github.com> Date: Wed, 25 Jan 2023 09:16:16 -0800 Subject: [PATCH 073/113] Emx Digital Bid Adapter : adding US Privacy string support (#9461) * adding ccpa support for emx_digital adapter * emx_digital ccpa compliance: lint fix * emx 3.0 compliance update * fix outstream renderer issue, update test spec * refactor formatVideoResponse function to use core-js/find * Add support for schain forwarding * Resolved issue with Schain object location * prebid 5.0 floor module and advertiserDomain support * liveramp idl and uid2.0 support for prebid * gpid support * remove utils ext * remove empty line * remove trailing spaces * move gpid test module * move gpid test module * removing trailing spaces from unit test * remove comments from unit test * Include us_privacy string in redirects (#8) * include us_privacy string in redirects * added test cases for us_privacy and gdpr * added test cases for gdpr without usp * updated test case when no privacy strings and fixed package-lock.json * revert package-lock.json Co-authored-by: EMXDigital * kick off ci tests Co-authored-by: Nick Colletti Co-authored-by: Nick Colletti Co-authored-by: Kiyoshi Hara Co-authored-by: Dan Bogdan Co-authored-by: Jherez Taylor Co-authored-by: EMXDigital Co-authored-by: Rakesh Balakrishnan Co-authored-by: Kevin Co-authored-by: Chris Huie --- modules/emx_digitalBidAdapter.js | 11 +++++-- .../modules/emx_digitalBidAdapter_spec.js | 31 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index eb69e76a837..99f313b9484 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -354,16 +354,23 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { const syncs = []; + const consentParams = []; if (syncOptions.iframeEnabled) { let url = 'https://biddr.brealtime.com/check.html'; if (gdprConsent && typeof gdprConsent.consentString === 'string') { // add 'gdpr' only if 'gdprApplies' is defined if (typeof gdprConsent.gdprApplies === 'boolean') { - url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); } else { - url += `?gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`?gdpr_consent=${gdprConsent.consentString}`); } } + if (uspConsent && typeof uspConsent.consentString === 'string') { + consentParams.push(`usp=${uspConsent.consentString}`); + } + if (consentParams.length > 0) { + url = url + '?' + consentParams.join('&'); + } syncs.push({ type: 'iframe', url: url diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index d99318b5ddc..d80d0f3e875 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -707,7 +707,7 @@ describe('emx_digital Adapter', function () { })); }); - it('returns valid advertiser domain', function () { + it('returns valid advertiser domains', function () { const bidResponse = utils.deepClone(serverResponse); let result = spec.interpretResponse({body: bidResponse}); expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); @@ -724,6 +724,7 @@ describe('emx_digital Adapter', function () { expect(syncs).to.not.be.an('undefined'); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html') }); it('should pass gdpr params', function () { @@ -734,6 +735,34 @@ describe('emx_digital Adapter', function () { expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); expect(syncs[0].url).to.contains('gdpr=0'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=0&gdpr_consent=test') + }); + + it('should pass us_privacy string', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, { + consentString: 'test', + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('usp=test'); + }); + + it('should pass us_privacy and gdpr strings', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, + { + gdprApplies: true, + consentString: 'test' + }, + { + consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=1'); + expect(syncs[0].url).to.contains('usp=test'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=1&gdpr_consent=test&usp=test') }); }); }); From fd952368c8bd3ca070645d28aedf9fcdca592da8 Mon Sep 17 00:00:00 2001 From: Jason Piros Date: Wed, 25 Jan 2023 09:17:40 -0800 Subject: [PATCH 074/113] consumableBidAdapter: add gdpr and usp sync params (#9463) --- modules/consumableBidAdapter.js | 22 ++++++++- .../spec/modules/consumableBidAdapter_spec.js | 46 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index c91f1a7f906..4e2a92fb594 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -180,12 +180,26 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + let syncUrl = 'https://sync.serverbid.com/ss/' + siteId + '.html'; + if (syncOptions.iframeEnabled) { + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl = appendUrlParam(syncUrl, `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); + } else { + syncUrl = appendUrlParam(syncUrl, `gdpr=0&gdpr_consent=${gdprConsent.consentString}`); + } + } + + if (uspConsent && uspConsent.consentString) { + syncUrl = appendUrlParam(syncUrl, `us_privacy=${uspConsent.consentString}`); + } + if (!serverResponses || serverResponses.length === 0 || !serverResponses[0].body.bdr || serverResponses[0].body.bdr !== 'cx') { return [{ type: 'iframe', - url: 'https://sync.serverbid.com/ss/' + siteId + '.html' + url: syncUrl }]; } } @@ -294,4 +308,8 @@ function getBidFloor(bid, sizes) { return floor; } +function appendUrlParam(url, queryString) { + return `${url}${url.indexOf('?') > -1 ? '&' : '?'}${queryString}`; +} + registerBidder(spec); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index d1b310624a6..556dce447b9 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -625,6 +625,52 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + it('should return a sync url if iframe syncs are enabled and GDPR applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and GDPR is undefined', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: undefined, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=0&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and USP applies', function () { + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?us_privacy=USP_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled, GDPR and USP applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING&us_privacy=USP_CONSENT_STRING'); + }) + it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { let syncOptions = {'pixelEnabled': true}; let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE]); From ff84384903473b6fdf57fdedba659645ff04041b Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Wed, 25 Jan 2023 11:48:41 -0700 Subject: [PATCH 075/113] PBS Bid Adapter : site should not exist when app is present (#9258) * Update prebidServerBidAdapter_spec.js * Update prebidServerBidAdapter_spec.js * fix test * remove app from site test * add site/app/dooh function * fix config * remove deepSetValue * add to ortb converter * add check * add back publisher.id * fix linting * ortb conversion lib: leave only one of dooh, app, or site in the request Co-authored-by: Demetrio Girardi --- libraries/ortbConverter/processors/default.js | 20 +++++++++++++++- .../prebidServerBidAdapter/ortbConverter.js | 2 +- .../modules/prebidServerBidAdapter_spec.js | 22 ++++++++++++++++++ .../ortbConverter/default_processors_spec.js | 23 +++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 test/spec/ortbConverter/default_processors_spec.js diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 8d44de00fa2..1d6bfb8424e 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -1,4 +1,4 @@ -import {deepSetValue, mergeDeep} from '../../../src/utils.js'; +import {deepSetValue, logWarn, mergeDeep} from '../../../src/utils.js'; import {bannerResponseProcessor, fillBannerImp} from './banner.js'; import {fillVideoImp, fillVideoResponse} from './video.js'; import {setResponseMediaType} from './mediaType.js'; @@ -20,6 +20,10 @@ export const DEFAULT_PROCESSORS = { appFpd: fpdFromTopLevelConfig('app'), siteFpd: fpdFromTopLevelConfig('site'), deviceFpd: fpdFromTopLevelConfig('device'), + onlyOneClient: { + priority: -99, + fn: onlyOneClientSection + }, props: { // sets request properties id, tmax, test, source.tid fn(ortbRequest, bidderRequest) { @@ -133,3 +137,17 @@ function fpdFromTopLevelConfig(prop) { } } } + +export function onlyOneClientSection(ortbRequest) { + ['dooh', 'app', 'site'].reduce((found, section) => { + if (ortbRequest[section] != null && Object.keys(ortbRequest[section]).length > 0) { + if (found != null) { + logWarn(`ORTB request specifies both '${found}' and '${section}'; dropping the latter.`) + delete ortbRequest[section]; + } else { + found = section; + } + } + return found; + }, null); +} diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 83335f81bc2..e35a3825826 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -47,7 +47,7 @@ const PBS_CONVERTER = ortbConverter({ request.tmax = s2sBidRequest.s2sConfig.timeout; deepSetValue(request, 'source.tid', proxyBidderRequest.auctionId); - [request.app, request.site].forEach(section => { + [request.app, request.dooh, request.site].forEach(section => { if (section && !section.publisher?.id) { deepSetValue(section, 'publisher.id', s2sBidRequest.s2sConfig.accountId); } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9da242381be..999a4477d19 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1505,6 +1505,28 @@ describe('S2S Adapter', function () { }); }); + it('site should not be present when app is present', function () { + const _config = { + s2sConfig: CONFIG, + app: { bundle: 'com.test.app' }, + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.site).to.not.exist; + expect(requestBid.app).to.exist.and.to.be.a('object'); + }); + it('adds appnexus aliases to request', function () { config.setConfig({ s2sConfig: CONFIG }); diff --git a/test/spec/ortbConverter/default_processors_spec.js b/test/spec/ortbConverter/default_processors_spec.js new file mode 100644 index 00000000000..48204b2c861 --- /dev/null +++ b/test/spec/ortbConverter/default_processors_spec.js @@ -0,0 +1,23 @@ +import {onlyOneClientSection} from '../../../libraries/ortbConverter/processors/default.js'; + +describe('onlyOneClientSection', () => { + [ + [['app'], 'app'], + [['site'], 'site'], + [['dooh'], 'dooh'], + [['app', 'site'], 'app'], + [['dooh', 'app', 'site'], 'dooh'], + [['dooh', 'site'], 'dooh'] + ].forEach(([sections, winner]) => { + it(`should leave only ${winner} in request when it contains ${sections.join(', ')}`, () => { + const req = Object.fromEntries(sections.map(s => [s, {foo: 'bar'}])); + onlyOneClientSection(req); + expect(Object.keys(req)).to.eql([winner]); + }) + }); + it('should not choke if none of the sections are in the request', () => { + const req = {}; + onlyOneClientSection(req); + expect(req).to.eql({}); + }); +}); From ed385ba9a5dd1d795fd7830267832eb8d61bb4d4 Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Wed, 25 Jan 2023 12:48:49 -0800 Subject: [PATCH 076/113] updated pbs filterSettings to sync with pbjs config filterSettings (#9423) --- modules/prebidServerBidAdapter/index.js | 15 ++- .../modules/prebidServerBidAdapter_spec.js | 116 ++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b609d1a54ec..924748ce197 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -222,10 +222,23 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, gppConsent, s2sConfig) } _syncCount++; + let filterSettings = {}; + const userSyncFilterSettings = getConfig('userSync.filterSettings'); + + if (userSyncFilterSettings) { + const { all, iframe, image } = userSyncFilterSettings; + const ifrm = iframe || all; + const img = image || all; + + if (ifrm) filterSettings = Object.assign({ iframe: ifrm }, filterSettings); + if (img) filterSettings = Object.assign({ image: img }, filterSettings); + } + const payload = { uuid: generateUUID(), bidders: bidderCodes, - account: s2sConfig.accountId + account: s2sConfig.accountId, + filterSettings }; let userSyncLimit = s2sConfig.userSyncLimit; diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 999a4477d19..cffc75f6949 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-trailing-spaces */ import {expect} from 'chai'; import { PrebidServer as Adapter, @@ -1725,6 +1726,121 @@ describe('S2S Adapter', function () { }]); }); + describe('filterSettings', function () { + const getRequestBid = userSync => { + let cookieSyncConfig = utils.deepClone(CONFIG); + const s2sBidRequest = utils.deepClone(REQUEST); + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; + s2sBidRequest.s2sConfig = cookieSyncConfig; + + config.setConfig({ userSync, s2sConfig: cookieSyncConfig }); + + let bidRequest = utils.deepClone(BID_REQUESTS); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + return JSON.parse(server.requests[0].requestBody); + } + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the all key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['appnexus', 'rubicon', 'pubmatic'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + }, + 'iframe': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the iframe key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + iframe: { + bidders: ['rubicon', 'pubmatic'], + filter: 'include' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': '*', + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['rubicon', 'pubmatic'], + 'filter': 'include' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the image and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + image: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the all and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + }); + it('adds limit to the cookie_sync request if userSyncLimit is greater than 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; From bd57c8dffe2effe149d3f0e71ab07605c578dc8b Mon Sep 17 00:00:00 2001 From: Jose Cabal-Ugaz <6942011+josecu@users.noreply.github.com> Date: Wed, 25 Jan 2023 17:59:49 -0500 Subject: [PATCH 077/113] ArcSpan RTD Module: Initial Release (#9459) * Create arcspanRtdProvider.md * Added ArcSpan RTD Provider * Implemented alter bid request function in ArcSpan RTD Provider * Added unit tests for ArcSpan RTD Provider * Added more unit tests for ArcSpan RTD Provider * Load ArcSpan scripts using Prebid script loader * Fixed ArcSpan RTD module unit tests * Adding ArcSpan to submodules.json * Load ArcSpan script only if not already on the page * Load ArcSpan script only if not already on the page --- modules/.submodules.json | 1 + modules/arcspanRtdProvider.js | 73 ++++++++ modules/arcspanRtdProvider.md | 11 ++ src/adloader.js | 3 +- test/spec/modules/arcspanRtdProvider_spec.js | 187 +++++++++++++++++++ 5 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 modules/arcspanRtdProvider.js create mode 100644 modules/arcspanRtdProvider.md create mode 100644 test/spec/modules/arcspanRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index deeee91e247..a535fd4988d 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -55,6 +55,7 @@ "aaxBlockmeterRtdProvider", "airgridRtdProvider", "akamaiDapRtdProvider", + "arcspanRtdProvider", "blueconicRtdProvider", "browsiRtdProvider", "captifyRtdProvider", diff --git a/modules/arcspanRtdProvider.js b/modules/arcspanRtdProvider.js new file mode 100644 index 00000000000..a7ffa059279 --- /dev/null +++ b/modules/arcspanRtdProvider.js @@ -0,0 +1,73 @@ +import { submodule } from '../src/hook.js'; +import { mergeDeep } from '../src/utils.js'; +import {loadExternalScript} from '../src/adloader.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'arcspan'; + +/** @type {RtdSubmodule} */ +export const arcspanSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: alterBidRequests, +}; + +function init(config, userConsent) { + if (typeof config.params.silo === 'undefined') { + return false; + } + if (typeof window.arcobj2 === 'undefined') { + var scriptUrl; + if (config.params.silo === 'test') { + scriptUrl = 'https://localhost:8080/as.js'; + } else { + scriptUrl = 'https://silo' + config.params.silo + '.p7cloud.net/as.js'; + } + loadExternalScript(scriptUrl, SUBMODULE_NAME); + } + return true; +} + +function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { + var _v1 = []; + var _v1s = []; + var _v2 = []; + var arcobj1 = window.arcobj1; + if (typeof arcobj1 != 'undefined') { + if (typeof arcobj1.page_iab_codes.text != 'undefined') { _v1 = _v1.concat(arcobj1.page_iab_codes.text); } + if (typeof arcobj1.page_iab_codes.images != 'undefined') { _v1 = _v1.concat(arcobj1.page_iab_codes.images); } + if (typeof arcobj1.page_iab.text != 'undefined') { _v1s = _v1s.concat(arcobj1.page_iab.text); } + if (typeof arcobj1.page_iab.images != 'undefined') { _v1s = _v1s.concat(arcobj1.page_iab.images); } + if (typeof arcobj1.page_iab_newcodes.text != 'undefined') { _v2 = [...new Set([..._v2, ...arcobj1.page_iab_newcodes.text])]; } + if (typeof arcobj1.page_iab_newcodes.images != 'undefined') { _v2 = [...new Set([..._v2, ...arcobj1.page_iab_newcodes.images])]; } + + var _content = {}; + _content.data = []; + var p = {}; + p.name = 'arcspan'; + p.segment = []; + p.ext = { segtax: 6 }; + _v2.forEach(function (e) { + p.segment = p.segment.concat({ id: e }); + }); + _content.data = _content.data.concat(p); + var _ortb2 = { + site: { + name: 'arcspan', + domain: new URL(location.href).hostname, + cat: _v1, + sectioncat: _v1, + pagecat: _v1, + page: location.href, + ref: document.referrer, + keywords: _v1s.toString(), + content: _content, + }, + }; + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, _ortb2); + } + callback(); +} + +submodule(MODULE_NAME, arcspanSubmodule); diff --git a/modules/arcspanRtdProvider.md b/modules/arcspanRtdProvider.md new file mode 100644 index 00000000000..4aa1de02acf --- /dev/null +++ b/modules/arcspanRtdProvider.md @@ -0,0 +1,11 @@ +# Overview + +Module Name: ArcSpan Rtd Provider + +Module Type: Rtd Provider + +Maintainer: engineering@arcspan.com + +# Description + +RTD provider for ArcSpan Technologies. Contact jcabalugaz@arcspan.com for more information. diff --git a/src/adloader.js b/src/adloader.js index 01a77971b93..f0b7f7f3e8c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -21,7 +21,8 @@ const _approvedLoadExternalJSList = [ 'medianet', 'improvedigital', 'aaxBlockmeter', - 'confiant' + 'confiant', + 'arcspan' ] /** diff --git a/test/spec/modules/arcspanRtdProvider_spec.js b/test/spec/modules/arcspanRtdProvider_spec.js new file mode 100644 index 00000000000..c75075d8e05 --- /dev/null +++ b/test/spec/modules/arcspanRtdProvider_spec.js @@ -0,0 +1,187 @@ +import { arcspanSubmodule } from 'modules/arcspanRtdProvider.js'; +import { expect } from 'chai'; +import { loadExternalScript } from 'src/adloader.js'; + +describe('arcspanRtdProvider', function () { + describe('init', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('successfully initializes with a valid silo ID', function () { + expect(arcspanSubmodule.init(getGoodConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://silo13.p7cloud.net/as.js'); + loadExternalScript.resetHistory(); + }); + + it('fails to initialize with a missing silo ID', function () { + expect(arcspanSubmodule.init(getBadConfig())).to.equal(false); + expect(loadExternalScript.called).to.be.not.ok; + loadExternalScript.resetHistory(); + }); + + it('drops localhost script for test silo', function () { + expect(arcspanSubmodule.init(getTestConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://localhost:8080/as.js'); + loadExternalScript.resetHistory(); + }); + }); + + describe('alterBidRequests', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('alters the bid request 1', function () { + setIAB({ + raw: { + images: [ + 'Religion & Spirituality', + 'Medical Health>Substance Abuse', + 'Religion & Spirituality>Astrology', + 'Medical Health', + 'Events & Attractions', + ], + }, + codes: { + images: ['IAB23-10', 'IAB7', 'IAB7-42', 'IAB15-1'], + }, + newcodes: { + images: ['150', '453', '311', '456', '286'], + }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Religion & Spirituality,Medical Health>Substance Abuse,Religion & Spirituality>Astrology,Medical Health,Events & Attractions' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '150' }, + { id: '453' }, + { id: '311' }, + { id: '456' }, + { id: '286' } + ]); + }); + }); + + it('alters the bid request 2', function () { + setIAB({ + raw: { text: ['Sports', 'Sports>Soccer'] }, + codes: { text: ['IAB17', 'IAB17-44'] }, + newcodes: { text: ['483', '533'] }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Sports,Sports>Soccer' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB17', + 'IAB17_44' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '483' }, + { id: '533' } + ]); + }); + }); + }); +}); + +function getGoodConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 13, + }, + }; +} + +function getBadConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + notasilo: 1, + }, + }; +} + +function getTestConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 'test', + }, + }; +} + +function setIAB(vjson) { + window.arcobj2 = {}; + window.arcobj2.cat = 0; + if (typeof vjson.codes != 'undefined') { + window.arcobj2.cat = 1; + if (typeof vjson.codes.images != 'undefined') { + vjson.codes.images.forEach(function f(e, i) { + vjson.codes.images[i] = e.replace('-', '_'); + }); + } + if (typeof vjson.codes.text != 'undefined') { + vjson.codes.text.forEach(function f(e, i) { + vjson.codes.text[i] = e.replace('-', '_'); + }); + } + window.arcobj2.sampled = 1; + window.arcobj1 = {}; + window.arcobj1.page_iab_codes = vjson.codes; + window.arcobj1.page_iab = vjson.raw; + window.arcobj1.page_iab_newcodes = vjson.newcodes; + } +} From 39c2f87a2b791363e1c4066176b41a0572aa803f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 26 Jan 2023 06:46:58 -0700 Subject: [PATCH 078/113] Update issue tracker action to use new gh api (#9466) --- .github/workflows/issue_tracker.yml | 41 ++++++++++++++++++----------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 4a9502e38c1..29082a4990a 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -29,21 +29,30 @@ jobs: gh api graphql -f query=' query($org: String!, $number: Int!) { organization(login: $org){ - projectNext(number: $number) { + projectV2(number: $number) { id fields(first:100) { nodes { - id - name - settings + ... on ProjectV2Field { + id + name + } + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } } } } } }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json - echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV - echo 'DATE_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV + echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV + echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name=="'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV - name: Add issue to project env: @@ -52,9 +61,9 @@ jobs: run: | gh api graphql -f query=' mutation($project:ID!, $issue:ID!) { - addProjectNextItem(input: {projectId: $project, contentId: $issue}) { - projectNextItem { - id, + addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) { + item { + id content { ... on Issue { createdAt @@ -67,8 +76,8 @@ jobs: } }' -f project=$PROJECT_ID -f issue=$ISSUE_ID > issue_data.json - echo 'ITEM_ID='$(jq '.data.addProjectNextItem.projectNextItem.id' issue_data.json) >> $GITHUB_ENV - echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectNextItem.projectNextItem.content.createdAt' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_ID='$(jq '.data.addProjectV2ItemById.item.id' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectV2ItemById.item.content.createdAt' issue_data.json | cut -c 2-11) >> $GITHUB_ENV - name: Set fields env: @@ -79,15 +88,17 @@ jobs: $project: ID! $item: ID! $date_field: ID! - $date_value: String! + $date_value: Date! ) { - set_creation_date: updateProjectNextItemField(input: { + set_creation_date: updateProjectV2ItemFieldValue(input: { projectId: $project itemId: $item fieldId: $date_field - value: $date_value + value: { + date: $date_value + } }) { - projectNextItem { + projectV2Item { id } } From 199349c30733e2aff1a43ababb613fa41caffec7 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 26 Jan 2023 15:20:39 +0000 Subject: [PATCH 079/113] Prebid 7.34.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6298a19479c..925a29338b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.34.0-pre", + "version": "7.34.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 4b52267af26..c5d5f83eb2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.34.0-pre", + "version": "7.34.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 9a5f08ce3a9a86a337c9718bc159bdb5c814251d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 26 Jan 2023 15:20:40 +0000 Subject: [PATCH 080/113] Increment version to 7.35.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 925a29338b4..6249ef52c04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.34.0", + "version": "7.35.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c5d5f83eb2c..6467045121b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.34.0", + "version": "7.35.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 60f96abe1f0068820f25b99d9084165d050c25a8 Mon Sep 17 00:00:00 2001 From: Eugene Rachitskiy Date: Thu, 26 Jan 2023 10:54:52 -0500 Subject: [PATCH 081/113] PulsePoint Bid Adapter: support timeout/tmax (#9465) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * ET-12672 - passing tmax value to PulsePoint bidder * ET-12672 - using 500ms as a default and adding formatting Co-authored-by: anand-venkatraman --- modules/pulsepointBidAdapter.js | 2 ++ test/spec/modules/pulsepointBidAdapter_spec.js | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 25f82fb60d9..015e80d5692 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -16,6 +16,7 @@ const DEFAULT_BID_TTL = 20; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'video', 'battr', 'bcat', 'badv', 'bidfloor']; +const DEFAULT_TMAX = 500; /** * PulsePoint Bid Adapter. @@ -54,6 +55,7 @@ export const spec = { user: user(bidRequests[0], bidderRequest), regs: regs(bidderRequest), source: source(bidRequests[0].schain), + tmax: bidderRequest.timeout || DEFAULT_TMAX, }; return { method: 'POST', diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 825b3abf432..60dca9e6da0 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -2,7 +2,6 @@ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; import {deepClone} from 'src/utils.js'; -import { config } from 'src/config.js'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -225,6 +224,8 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[1].banner).to.not.equal(null); expect(ortbRequest.imp[1].banner.w).to.equal(728); expect(ortbRequest.imp[1].banner.h).to.equal(90); + // tmax + expect(ortbRequest.tmax).to.equal(500); }); it('Verify parse response', function () { @@ -918,4 +919,13 @@ describe('PulsePoint Adapter Tests', function () { } }); }); + + it('Verify bid request timeouts', function () { + const mkRequest = (bidderRequest) => spec.buildRequests(slotConfigs, bidderRequest).data; + // assert default is used when no bidderRequest.timeout value is available + expect(mkRequest(bidderRequest).tmax).to.equal(500) + + // assert bidderRequest value is used when available + expect(mkRequest(Object.assign({}, { timeout: 6000 }, bidderRequest)).tmax).to.equal(6000) + }); }); From ba7d0d0026484396d3098da5dfc96586ca68568b Mon Sep 17 00:00:00 2001 From: joseluis laso Date: Thu, 26 Jan 2023 19:44:41 +0100 Subject: [PATCH 082/113] hadronId user id submodule: force localStorage (#9432) * Storing hadronId in localStorage after getting it from server * reverting hadronId documentation --- modules/hadronIdSystem.js | 69 +++++++++++++----------- test/spec/modules/hadronIdSystem_spec.js | 10 ++-- test/spec/modules/userId_spec.js | 33 +++++++----- 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index 2f10245cd59..a75c03ee1c4 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -8,8 +8,9 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; -import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; +import {isFn, isStr, isPlainObject, logError, logInfo} from '../src/utils.js'; +const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; @@ -18,8 +19,9 @@ export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'hadron'} /** * Param or default. - * @param {String} param + * @param {String|function} param * @param {String} defaultVal + * @param arg */ function paramOrDefault(param, defaultVal, arg) { if (isFn(param)) { @@ -53,11 +55,11 @@ export const hadronIdSubmodule = { * @returns {Object} */ decode(value) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); + const hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); if (isStr(hadronId)) { return {hadronId: hadronId}; } - return (value && typeof value['hadronId'] === 'string') ? { 'hadronId': value['hadronId'] } : undefined; + return (value && typeof value['hadronId'] === 'string') ? {'hadronId': value['hadronId']} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -70,37 +72,40 @@ export const hadronIdSubmodule = { config.params = {}; } const partnerId = config.params.partnerId | 0; - - const url = urlAddParams( - paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), - `partner_id=${partnerId}&_it=prebid` - ); - + let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); + if (isStr(hadronId)) { + return {id: {hadronId}}; + } const resp = function (callback) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); - if (isStr(hadronId)) { - const responseObj = {hadronId: hadronId}; - callback(responseObj); - } else { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - logError(error); - } + let responseObj = {}; + const callbacks = { + success: response => { + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + logError(error); } - callback(responseObj); - }, - error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); + logInfo(`Response from backend is ${responseObj}`); + hadronId = responseObj['hadronId']; + storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId); + responseObj = {id: {hadronId}}; } - }; - ajax(url, callbacks, undefined, {method: 'GET'}); - } + callback(responseObj); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + logInfo('HadronId not found in storage, calling backend...'); + const url = urlAddParams( + // config.params.url and config.params.urlArg are not documented + // since their use is for debugging purposes only + paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), + `partner_id=${partnerId}&_it=prebid` + ); + ajax(url, callbacks, undefined, {method: 'GET'}); }; return {callback: resp}; } diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index ca9eadc7fd4..c998ef2cf14 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -24,7 +24,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid?partner_id=0&_it=prebid`); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); it('gets a cached hadronid', function() { @@ -33,10 +33,8 @@ describe('HadronIdSystem', function () { }; getDataFromLocalStorageStub.withArgs('auHadronId').returns('tstCachedHadronId1'); - const callbackSpy = sinon.spy(); - const callback = hadronIdSubmodule.getId(config).callback; - callback(callbackSpy); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'tstCachedHadronId1'}); + const result = hadronIdSubmodule.getId(config); + expect(result).to.deep.equal({ id: { hadronId: 'tstCachedHadronId1' } }); }); it('allows configurable id url', function() { @@ -51,7 +49,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq('https://hadronid.publync.com?partner_id=0&_it=prebid'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); }); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 99e0681e547..5403d842e02 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -897,7 +897,7 @@ describe('User ID', function () { storage: {name: 'intentIqId', type: 'cookie'} }, { name: 'hadronId', - storage: {name: 'hadronId', type: 'cookie'} + storage: {name: 'hadronId', type: 'html5'} }, { name: 'zeotapIdPlus' }, { @@ -1872,8 +1872,8 @@ describe('User ID', function () { it('test hook from hadronId html5', function (done) { // simulate existing browser local storage values - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'random-ls-identifier'})); - localStorage.setItem('hadronId_exp', ''); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); init(config); setSubmoduleRegistry([hadronIdSubmodule]); @@ -1883,15 +1883,15 @@ describe('User ID', function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('random-ls-identifier'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'audigent.com', - uids: [{id: 'random-ls-identifier', atype: 1}] + uids: [{id: 'testHadronId1', atype: 1}] }); }); }); localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp', ''); + localStorage.removeItem('hadronId_exp'); done(); }, {adUnits}); }); @@ -2125,7 +2125,9 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + // hadronId only supports localStorage + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); @@ -2149,7 +2151,7 @@ describe('User ID', function () { ['netId', 'netId', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['hadronId', 'hadronId', 'cookie'], + ['hadronId', 'hadronId', 'html5'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'], @@ -2192,7 +2194,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); // also check that criteo id was copied to bid expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); @@ -2231,7 +2233,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); @@ -2284,7 +2287,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); @@ -2320,7 +2324,7 @@ describe('User ID', function () { }, { name: 'zeotapIdPlus' }, { - name: 'hadronId', storage: {name: 'hadronId', type: 'cookie'} + name: 'hadronId', storage: {name: 'hadronId', type: 'html5'} }, { name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} }, { @@ -2388,7 +2392,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userId.uid2).to.deep.equal({ id: 'Sample_AD_Token' }); @@ -2420,7 +2424,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); From 5787552f19a06c3f777da0f0531e4032c501d3ed Mon Sep 17 00:00:00 2001 From: Mark Kuhar Date: Thu, 26 Jan 2023 20:40:58 +0100 Subject: [PATCH 083/113] Outbrain Bid Adapter: added video support (#9405) * add video support * add more video props --- modules/outbrainBidAdapter.js | 109 +++++++++- test/spec/modules/outbrainBidAdapter_spec.js | 200 +++++++++++++++++-- 2 files changed, 285 insertions(+), 24 deletions(-) diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 3db1da0d689..6bcbc6a1cba 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -4,11 +4,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray } from '../src/utils.js'; +import { NATIVE, BANNER, VIDEO } from '../src/mediaTypes.js'; +import { OUTSTREAM } from '../src/video.js'; +import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'outbrain'; const GVLID = 164; @@ -22,11 +24,12 @@ const NATIVE_PARAMS = { body: { id: 4, name: 'data', type: 2 }, cta: { id: 1, type: 12, name: 'data' } }; +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [ NATIVE, BANNER ], + supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], isBidRequestValid: (bid) => { if (typeof bid.params !== 'object') { return false; @@ -50,7 +53,7 @@ export const spec = { return ( !!config.getConfig('outbrain.bidderUrl') && - !!(bid.nativeParams || bid.sizes) + (!!(bid.nativeParams || bid.sizes) || isValidVideoRequest(bid)) ); }, buildRequests: (validBidRequests, bidderRequest) => { @@ -85,6 +88,8 @@ export const spec = { assets: getNativeAssets(bid) }) } + } else if (isVideoRequest(bid)) { + imp.video = getVideoAsset(bid); } else { imp.banner = { format: transformSizes(bid.sizes) @@ -163,7 +168,12 @@ export const spec = { return bids.map((bid, id) => { const bidResponse = bidResponses[id]; if (bidResponse) { - const type = bid.nativeParams ? NATIVE : BANNER; + let type = BANNER; + if (bid.nativeParams) { + type = NATIVE; + } else if (isVideoRequest(bid)) { + type = VIDEO; + } const bidObject = { requestId: bid.bidId, cpm: bidResponse.price, @@ -176,10 +186,16 @@ export const spec = { }; if (type === NATIVE) { bidObject.native = parseNative(bidResponse); - } else { + } else if (type === BANNER) { bidObject.ad = bidResponse.adm; bidObject.width = bidResponse.w; bidObject.height = bidResponse.h; + } else if (type === VIDEO) { + bidObject.vastXml = bidResponse.adm; + const videoContext = deepAccess(bid, 'mediaTypes.video.context'); + if (videoContext === OUTSTREAM) { + bidObject.renderer = createRenderer(bid); + } } bidObject.meta = {}; if (bidResponse.adomain && bidResponse.adomain.length > 0) { @@ -304,6 +320,27 @@ function getNativeAssets(bid) { }).filter(Boolean); } +function getVideoAsset(bid) { + const sizes = flatten(bid.mediaTypes.video.playerSize); + return { + w: parseInt(sizes[0], 10), + h: parseInt(sizes[1], 10), + protocols: bid.mediaTypes.video.protocols, + playbackmethod: bid.mediaTypes.video.playbackmethod, + mimes: bid.mediaTypes.video.mimes, + skip: bid.mediaTypes.video.skip, + delivery: bid.mediaTypes.video.delivery, + api: bid.mediaTypes.video.api, + minbitrate: bid.mediaTypes.video.minbitrate, + maxbitrate: bid.mediaTypes.video.maxbitrate, + minduration: bid.mediaTypes.video.minduration, + maxduration: bid.mediaTypes.video.maxduration, + startdelay: bid.mediaTypes.video.startdelay, + placement: bid.mediaTypes.video.placement, + linearity: bid.mediaTypes.video.linearity + }; +} + /* Turn bid request sizes into ut-compatible format */ function transformSizes(requestSizes) { if (!isArray(requestSizes)) { @@ -338,3 +375,63 @@ function _getFloor(bid, type) { } return null; } + +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +function createRenderer(bid) { + let config = {}; + let playerUrl = OUTSTREAM_RENDERER_URL; + let render = function (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: bid.sizes, + targetId: bid.adUnitCode, + adResponse: { content: bid.vastXml } + }); + }); + }; + + let externalRenderer = deepAccess(bid, 'mediaTypes.video.renderer'); + if (!externalRenderer) { + externalRenderer = deepAccess(bid, 'renderer'); + } + + if (externalRenderer) { + config = externalRenderer.options; + playerUrl = externalRenderer.url; + render = externalRenderer.render; + } + + const renderer = Renderer.install({ + id: bid.adUnitCode, + url: playerUrl, + config: config, + adUnitCode: bid.adUnitCode, + loaded: false + }); + try { + renderer.setRender(render); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function isValidVideoRequest(bid) { + const videoAdUnit = deepAccess(bid, 'mediaTypes.video') + if (!videoAdUnit) { + return false; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (videoAdUnit.context == '') { + return false; + } + + return true; +} diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index 5d7bebc1de1..f5ce00ed8df 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -1,7 +1,7 @@ -import {expect} from 'chai'; -import {spec} from 'modules/outbrainBidAdapter.js'; -import {config} from 'src/config.js'; -import {server} from 'test/mocks/xhr'; +import { expect } from 'chai'; +import { spec } from 'modules/outbrainBidAdapter.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr'; import { createEidsArray } from 'modules/userId/eids.js'; describe('Outbrain Adapter', function () { @@ -45,6 +45,26 @@ describe('Outbrain Adapter', function () { ] } + const videoBidRequestParams = { + mediaTypes: { + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + } + describe('isBidRequestValid', function () { before(() => { config.setConfig({ @@ -93,6 +113,34 @@ describe('Outbrain Adapter', function () { } expect(spec.isBidRequestValid(bid)).to.equal(true) }) + it('should succeed when bid contains video', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...videoBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail when bid contains insufficient video information', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) it('should fail if publisher id is not set', function () { const bid = { bidder: 'outbrain', @@ -298,6 +346,61 @@ describe('Outbrain Adapter', function () { expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) + it('should build video request', function () { + const bidRequest = { + ...commonBidRequest, + ...videoBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + video: { + w: 640, + h: 480, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + mimes: ['video/mp4'], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + ], + ext: { + prebid: { + channel: { + name: 'pbjs', version: '$prebid.version$' + } + } + } + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + it('should pass optional parameters in request', function () { const bidRequest = { ...commonBidRequest, @@ -390,7 +493,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - config.setConfig({coppa: true}) + config.setConfig({ coppa: true }) const res = spec.buildRequests([bidRequest], commonBidderRequest) const resData = JSON.parse(res.data) @@ -412,7 +515,7 @@ describe('Outbrain Adapter', function () { let res = spec.buildRequests([bidRequest], commonBidderRequest); const resData = JSON.parse(res.data) expect(resData.user.ext.eids).to.deep.equal([ - {source: 'liveramp.com', uids: [{id: 'id-value', atype: 3}]} + { source: 'liveramp.com', uids: [{ id: 'id-value', atype: 3 }] } ]); }); @@ -421,7 +524,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - bidRequest.getFloor = function() { + bidRequest.getFloor = function () { return { currency: 'USD', floor: 1.23, @@ -619,6 +722,67 @@ describe('Outbrain Adapter', function () { const res = spec.interpretResponse(serverResponse, request) expect(res).to.deep.equal(expectedRes) }); + + it('should interpret video response', function () { + const serverResponse = { + body: { + id: '123', + seatbid: [ + { + bid: [ + { + id: '111', + impid: '1', + price: 1.1, + adm: '\u003cVAST version="3.0"\u003e\u003cAd\u003e\u003cInLine\u003e\u003cAdSystem\u003ezemanta\u003c/AdSystem\u003e\u003cAdTitle\u003e1\u003c/AdTitle\u003e\u003cImpression\u003ehttp://win.com\u003c/Impression\u003e\u003cImpression\u003ehttp://example.com/imptracker\u003c/Impression\u003e\u003cCreatives\u003e\u003cCreative\u003e\u003cLinear\u003e\u003cDuration\u003e00:00:25\u003c/Duration\u003e\u003cTrackingEvents\u003e\u003cTracking event="start"\u003ehttp://example.com/start\u003c/Tracking\u003e\u003cTracking event="progress" offset="00:00:03"\u003ehttp://example.com/p3s\u003c/Tracking\u003e\u003c/TrackingEvents\u003e\u003cVideoClicks\u003e\u003cClickThrough\u003ehttp://link.com\u003c/ClickThrough\u003e\u003c/VideoClicks\u003e\u003cMediaFiles\u003e\u003cMediaFile delivery="progressive" type="video/mp4" bitrate="700" width="640" height="360"\u003ehttps://example.com/123_360p.mp4\u003c/MediaFile\u003e\u003c/MediaFiles\u003e\u003c/Linear\u003e\u003c/Creative\u003e\u003c/Creatives\u003e\u003c/InLine\u003e\u003c/Ad\u003e\u003c/VAST\u003e', + adid: '100', + cid: '5', + crid: '29998660', + cat: ['cat-1'], + adomain: [ + 'example.com' + ], + nurl: 'http://example.com/win/${AUCTION_PRICE}' + } + ], + seat: '100', + group: 1 + } + ], + bidid: '456', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...videoBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'video', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + vastXml: 'zemanta1http://win.comhttp://example.com/imptracker00:00:25http://example.com/starthttp://example.com/p3shttp://link.comhttps://example.com/123_360p.mp4', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); }) }) @@ -637,41 +801,41 @@ describe('Outbrain Adapter', function () { }) it('should return user sync if pixel enabled with outbrain config', function () { - const ret = spec.getUserSyncs({pixelEnabled: true}) - expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) + const ret = spec.getUserSyncs({ pixelEnabled: true }) + expect(ret).to.deep.equal([{ type: 'image', url: usersyncUrl }]) }) it('should not return user sync if pixel disabled', function () { - const ret = spec.getUserSyncs({pixelEnabled: false}) + const ret = spec.getUserSyncs({ pixelEnabled: false }) expect(ret).to.be.an('array').that.is.empty }) it('should not return user sync if url is not set', function () { config.resetConfig() - const ret = spec.getUserSyncs({pixelEnabled: true}) + const ret = spec.getUserSyncs({ pixelEnabled: true }) expect(ret).to.be.an('array').that.is.empty }) - it('should pass GDPR consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + it('should pass GDPR consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` }]); }); - it('should pass US consent', function() { + it('should pass US consent', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` }]); }); - it('should pass GDPR and US consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + it('should pass GDPR and US consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` }]); }); From 9a83c0307f7d18e5f6a495c4681e098909eb9d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+smart-adserver@users.noreply.github.com> Date: Thu, 26 Jan 2023 21:04:47 +0100 Subject: [PATCH 084/113] Smartadserver Bid Adapter: support floors per media type (#9437) * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Rework payloads enriching Co-authored-by: Meven Courouble --- modules/smartadserverBidAdapter.js | 48 ++++----- .../modules/smartadserverBidAdapter_spec.js | 97 ++++++++++++++++++- 2 files changed, 116 insertions(+), 29 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 6ff0e592542..719d621b056 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -13,6 +13,7 @@ export const spec = { gvlid: GVL_ID, aliases: ['smart'], // short code supportedMediaTypes: [BANNER, VIDEO], + /** * Determines whether or not the given bid request is valid. * @@ -131,7 +132,6 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info - const adServerCurrency = config.getConfig('currency.adServerCurrency'); const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data')); const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data')); @@ -144,7 +144,6 @@ export const spec = { pageid: bid.params.pageId, formatid: bid.params.formatId, currencyCode: adServerCurrency, - bidfloor: bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency), targeting: bid.params.target && bid.params.target !== '' ? bid.params.target : undefined, buid: bid.params.buId && bid.params.buId !== '' ? bid.params.buId : undefined, appname: bid.params.appName && bid.params.appName !== '' ? bid.params.appName : undefined, @@ -175,24 +174,28 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); const bannerMediaType = deepAccess(bid, 'mediaTypes.banner'); - const isAdUnitContainingVideo = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); - if (!isAdUnitContainingVideo && bannerMediaType) { - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && !bannerMediaType) { - spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && bannerMediaType) { - // If there are video and banner media types in the ad unit, we clone the payload - // to create a specific one for video. - let videoPayload = deepClone(payload); + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const isSupportedVideoContext = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); - spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + if (bannerMediaType || isSupportedVideoContext) { + let type; + if (bannerMediaType) { + type = BANNER; + payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); + if (isSupportedVideoContext) { + let videoPayload = deepClone(payload); + spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); + videoPayload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, VIDEO); + bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + } + } else { + type = VIDEO; + spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); + } + + payload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, type); bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); } else { bidRequests.push({}); @@ -253,24 +256,21 @@ export const spec = { * * @param {object} bid Bid request object * @param {string} currency Ad server currency + * @param {string} mediaType Bid media type * @return {number} Floor price */ - getBidFloor: function (bid, currency) { + getBidFloor: function (bid, currency, mediaType) { if (!isFn(bid.getFloor)) { return DEFAULT_FLOOR; } const floor = bid.getFloor({ currency: currency || 'USD', - mediaType: '*', + mediaType, size: '*' }); - if (isPlainObject(floor) && !isNaN(floor.floor)) { - return floor.floor; - } - - return DEFAULT_FLOOR; + return isPlainObject(floor) && !isNaN(floor.floor) ? floor.floor : DEFAULT_FLOOR; }, /** diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index db61983c9c9..4dacb356894 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; import { spec } from 'modules/smartadserverBidAdapter.js'; @@ -394,7 +395,6 @@ describe('Smart bid adapter tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with GDPR', function () { @@ -446,7 +446,6 @@ describe('Smart bid adapter tests', function () { describe('ccpa/us privacy tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with us privacy', function () { @@ -475,7 +474,6 @@ describe('Smart bid adapter tests', function () { describe('Instream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const INSTREAM_DEFAULT_PARAMS = [{ @@ -746,7 +744,6 @@ describe('Smart bid adapter tests', function () { describe('Outstream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const OUTSTREAM_DEFAULT_PARAMS = [{ @@ -1055,6 +1052,17 @@ describe('Smart bid adapter tests', function () { }); describe('Floors module', function () { + const getFloor = (bid) => { + switch (bid.mediaType) { + case BANNER: + return { currency: 'USD', floor: 1.93 }; + case VIDEO: + return { currency: 'USD', floor: 2.72 }; + default: + return {}; + } + }; + it('should include floor from bid params', function() { const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS))[0].data); expect(bidRequest.bidfloor).to.deep.equal(DEFAULT_PARAMS[0].params.bidfloor); @@ -1094,12 +1102,91 @@ describe('Smart bid adapter tests', function () { const floor = spec.getBidFloor(bidRequest, null); expect(floor).to.deep.equal(0); }); + + it('should take floor from bidder params over ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73, bidfloor: 1.25 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.25); + }); + + it('should take floor from banner ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.93); + }); + + it('should take floor from video ad unit', function() { + const bidRequest = [{ + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(2.72); + }); + + it('should take floor from multiple media type ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 600]] + }, + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const requests = spec.buildRequests(bidRequest); + expect(requests).to.have.lengthOf(2); + + const requestContents = requests.map(r => JSON.parse(r.data)); + const videoRequest = requestContents.filter(r => r.videoData)[0]; + expect(videoRequest).to.not.equal(null).and.to.not.be.undefined; + expect(videoRequest).to.have.property('bidfloor').and.to.equal(2.72); + + const bannerRequest = requestContents.filter(r => !r.videoData)[0]; + expect(bannerRequest).to.not.equal(null).and.to.not.be.undefined; + expect(bannerRequest).to.have.property('bidfloor').and.to.equal(1.93); + }); }); describe('Verify bid requests with multiple mediaTypes', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); var DEFAULT_PARAMS_MULTIPLE_MEDIA_TYPES = [{ From f520803a19f0c62aacb82847e757d52d65250345 Mon Sep 17 00:00:00 2001 From: preved-medved Date: Fri, 27 Jan 2023 11:24:35 +0000 Subject: [PATCH 085/113] Smartytech Bid Adapter: Add video format (#9388) * Add new bid adapter for company smartytech * change domain to prod * update unit tests * remove unused code * remove unused code * add video type * update documentation --- modules/smartytechBidAdapter.js | 77 +++++++- modules/smartytechBidAdapter.md | 77 ++++---- .../spec/modules/smartytechBidAdapter_spec.js | 186 ++++++++++++++++-- 3 files changed, 283 insertions(+), 57 deletions(-) diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index 231ca315de8..9f275a761c7 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -1,29 +1,77 @@ +import {buildUrl, deepAccess} from '../src/utils.js' +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {buildUrl} from '../src/utils.js' const BIDDER_CODE = 'smartytech'; export const ENDPOINT_PROTOCOL = 'https'; export const ENDPOINT_DOMAIN = 'server.smartytech.io'; -export const ENDPOINT_PATH = '/hb/bidder'; +export const ENDPOINT_PATH = '/hb/v2/bidder'; export const spec = { + supportedMediaTypes: [ BANNER, VIDEO ], code: BIDDER_CODE, isBidRequestValid: function (bidRequest) { - return !!parseInt(bidRequest.params.endpointId); + return ( + !!parseInt(bidRequest.params.endpointId) && + spec._validateBanner(bidRequest) && + spec._validateVideo(bidRequest) + ); + }, + + _validateBanner: function(bidRequest) { + const bannerAdUnit = deepAccess(bidRequest, 'mediaTypes.banner'); + + if (bannerAdUnit === undefined) { + return true; + } + + if (!Array.isArray(bannerAdUnit.sizes)) { + return false; + } + + return true; + }, + + _validateVideo: function(bidRequest) { + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); + + if (videoAdUnit === undefined) { + return true; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (!videoAdUnit.context) { + return false; + } + + return true; }, buildRequests: function (validBidRequests, bidderRequest) { const referer = bidderRequest?.refererInfo?.page || window.location.href; const bidRequests = validBidRequests.map((validBidRequest) => { - return { + let video = deepAccess(validBidRequest, 'mediaTypes.video', false); + let banner = deepAccess(validBidRequest, 'mediaTypes.banner', false); + + let oneRequest = { endpointId: validBidRequest.params.endpointId, adUnitCode: validBidRequest.adUnitCode, - sizes: validBidRequest.sizes, - bidId: validBidRequest.bidId, - referer: referer + referer: referer, + bidId: validBidRequest.bidId }; + + if (video) { + oneRequest.video = video; + } else if (banner) { + oneRequest.banner = banner; + } + + return oneRequest }); let adPartnerRequestUrl = buildUrl({ @@ -55,12 +103,14 @@ export const spec = { bid: validBids.find(b => b.adUnitCode === key), response: responseBody[key] } - }).map(item => spec.adResponse(item.bid.bidId, item.response)); + }).map(item => spec._adResponse(item.bid, item.response)); }, - adResponse: function (requestId, response) { + _adResponse: function (request, response) { const bidObject = { - requestId, + requestId: request.bidId, + adUnitCode: request.adUnitCode, + bidderCode: BIDDER_CODE, ad: response.ad, cpm: response.cpm, width: response.width, @@ -69,7 +119,14 @@ export const spec = { creativeId: response.creativeId, netRevenue: true, currency: response.currency, + mediaType: BANNER } + + if (response.mediaType === VIDEO) { + bidObject.vastXml = response.ad; + bidObject.mediaType = VIDEO; + } + return bidObject; }, diff --git a/modules/smartytechBidAdapter.md b/modules/smartytechBidAdapter.md index dbfc2833c78..9df57ddbde7 100644 --- a/modules/smartytechBidAdapter.md +++ b/modules/smartytechBidAdapter.md @@ -1,44 +1,55 @@ # Overview -Module Name: SmartyTech Bidder Adapter - -Module Type: Bidder Adapter - +``` +Module Name: SmartyTech Bid Adapter +Module Type: Bidder Adapter Maintainer: info@adpartner.pro +``` # Description -You can use this adapter to get a bid from smartytech.io. +Connects to SmartyTech's exchange for bids. -About us : https://smartytech.io +SmartyTech bid adapter supports Banner and Video -# Test Parameters +# Sample Ad Unit: For Publishers +## Sample Banner Ad Unit +``` +var adUnits = [{ + code: '/123123123/prebidjs-banner', + mediaTypes: { + banner: { + sizes: [ + [300, 301], + [300, 250] + ] + } + }, + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}]; +``` -```javascript - var adUnits = [ - { - code: 'div-smartytech-example', - sizes: [[300, 250]], - bids: [ - { - bidder: "smartytech", - params: { - endpointId: 14 - } - } - ] +## Sample Video Ad Unit +``` +var videoAdUnit = { + code: '/123123123/video-vast-banner', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + } }, - { - code: 'div-smartytech-example-2', - sizes: [[300, 250]], - bids: [ - { - bidder: "smartytech", - params: { - endpointId: 14 - } - } - ] - } -]; + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}; ``` diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index 78503d7a6f0..b41b0280235 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -19,20 +19,116 @@ describe('SmartyTechDSPAdapter: inherited functions', function () { describe('SmartyTechDSPAdapter: isBidRequestValid', function () { it('Invalid bid request. Should return false', function () { - const invalidBidFixture = { + const bidFixture = { params: { use_id: 13144375 } } - expect(spec.isBidRequestValid(invalidBidFixture)).to.be.false + + expect(spec.isBidRequestValid(bidFixture)).to.be.false }); it('Valid bid request. Should return true', function () { - const validBidFixture = { + const bidFixture = { params: { endpointId: 13144375 } } - expect(spec.isBidRequestValid(validBidFixture)).to.be.true + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check video block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check playerSize', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check context', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid video bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'instream' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check banner block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check banner sizes', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid banner bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true }); }); @@ -42,12 +138,29 @@ function mockRandomSizeArray(len) { }); } -function mockBidRequestListData(size) { +function mockBidRequestListData(mediaType, size) { return Array.apply(null, {length: size}).map((i, index) => { const id = Math.floor(Math.random() * 800) * (index + 1); + let mediaTypes; + + if (mediaType == 'video') { + mediaTypes = { + video: { + playerSize: mockRandomSizeArray(1), + context: 'instream' + }, + } + } else { + mediaTypes = { + banner: { + sizes: mockRandomSizeArray(index + 1) + } + } + } + return { adUnitCode: `adUnitCode-${id}`, - sizes: mockRandomSizeArray(index + 1), + mediaTypes: mediaTypes, bidId: `bidId-${id}`, params: { endpointId: id @@ -66,18 +179,27 @@ function mockRefererData() { function mockResponseData(requestData) { let data = {} - requestData.data.forEach((request, index) => { - const sizeArrayIndex = Math.floor(Math.random() * (request.sizes.length - 1)); const rndIndex = Math.floor(Math.random() * 800); + let width, height, mediaType; + if (request.video !== undefined) { + width = request.video.playerSize[0][0]; + height = request.video.playerSize[0][1]; + mediaType = 'video'; + } else { + width = request.banner.sizes[0][0]; + height = request.banner.sizes[0][1]; + mediaType = 'banner'; + } data[request.adUnitCode] = { ad: `ad-${rndIndex}`, - width: request.sizes[sizeArrayIndex][0], - height: request.sizes[sizeArrayIndex][1], + width: width, + height: height, creativeId: `creative-id-${index}`, cpm: Math.floor(Math.random() * 100), - currency: `UAH-${rndIndex}` + currency: `UAH-${rndIndex}`, + mediaType: mediaType }; }); return { @@ -89,7 +211,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { let mockBidRequest; let mockReferer; beforeEach(() => { - mockBidRequest = mockBidRequestListData(8); + mockBidRequest = mockBidRequestListData('banner', 8); mockReferer = mockRefererData(); }); it('has return data', () => { @@ -108,7 +230,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { const data = spec.buildRequests(mockBidRequest, mockReferer).data; data.forEach((request, index) => { expect(request.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode); - expect(request.sizes).to.be.equal(mockBidRequest[index].sizes); + expect(request.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner); expect(request.bidId).to.be.equal(mockBidRequest[index].bidId); expect(request.endpointId).to.be.equal(mockBidRequest[index].params.endpointId); expect(request.referer).to.be.equal(mockReferer.refererInfo.page); @@ -122,7 +244,7 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => { let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData(2); + const brData = mockBidRequestListData('banner', 2); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { @@ -157,6 +279,42 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => { expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + }); + }); +}); + +describe('SmartyTechDSPAdapter: interpretResponse video', () => { + let mockBidRequest; + let mockReferer; + let request; + let mockResponse; + beforeEach(() => { + const brData = mockBidRequestListData('video', 2); + mockReferer = mockRefererData(); + request = spec.buildRequests(brData, mockReferer); + mockBidRequest = { + data: brData + } + mockResponse = mockResponseData(request); + }); + + it('interpretResponse: convert to correct data', () => { + const keys = Object.keys(mockResponse.body); + const data = spec.interpretResponse(mockResponse, mockBidRequest); + + data.forEach((responseItem, index) => { + expect(responseItem.ad).to.be.equal(mockResponse.body[keys[index]].ad); + expect(responseItem.cpm).to.be.equal(mockResponse.body[keys[index]].cpm); + expect(responseItem.creativeId).to.be.equal(mockResponse.body[keys[index]].creativeId); + expect(responseItem.currency).to.be.equal(mockResponse.body[keys[index]].currency); + expect(responseItem.netRevenue).to.be.true; + expect(responseItem.ttl).to.be.equal(60); + expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); + expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); + expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + expect(responseItem.vastXml).to.be.equal(mockResponse.body[keys[index]].ad); }); }); }); From a0e514cf9ad882f9f8bc1675d9f88a0a638e33c3 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 27 Jan 2023 06:56:19 -0700 Subject: [PATCH 086/113] Core & priceFloors: pass bid request to `bidCpmAdjustment`; warn about invalid `adUnit.floors` definitions (#9441) * Core & priceFloors: pass `bidRequest` as third arg to `bidCpmAdjustment` * Floors: warn when adUnit.floors is not valid --- modules/priceFloors.js | 15 ++++---- src/auction.js | 13 +------ src/utils/cpm.js | 17 +++++++++ test/spec/unit/utils/cpm_spec.js | 64 ++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 src/utils/cpm.js create mode 100644 test/spec/unit/utils/cpm_spec.js diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 32b3cbaa607..92aecb0ca50 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -28,6 +28,7 @@ import {auctionManager} from '../src/auctionManager.js'; import {IMP, PBS, registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; import {beConvertCurrency} from '../src/utils/currency.js'; +import {adjustCpm} from '../src/utils/cpm.js'; /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. @@ -179,12 +180,8 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { /** * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ -export function getBiddersCpmAdjustment(bidderName, inputCpm, bid, bidRequest) { - const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); - if (adjustmentFunction) { - return parseFloat(adjustmentFunction(inputCpm, { ...bid, cpm: inputCpm }, bidRequest)); - } - return parseFloat(inputCpm); +export function getBiddersCpmAdjustment(inputCpm, bid, bidRequest) { + return parseFloat(adjustCpm(inputCpm, {...bid, cpm: inputCpm}, bidRequest)); } /** @@ -254,7 +251,7 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: if (inverseFunction) { floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest); } else { - let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor, {}, bidRequest); + let cpmAdjustment = getBiddersCpmAdjustment(floorInfo.matchingFloor, null, bidRequest); floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; } } @@ -313,6 +310,8 @@ export function getFloorDataFromAdUnits(adUnits) { // copy over the new rules into our values object Object.assign(accum.values, newRules); } + } else if (adUnit.floors != null) { + logWarn(`adUnit '${adUnit.code}' provides an invalid \`floor\` definition, it will be ignored for floor calculations`, adUnit); } return accum; }, {}); @@ -737,7 +736,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a } // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists - adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid, matchingBidRequest); + adjustedCpm = getBiddersCpmAdjustment(adjustedCpm, bid, matchingBidRequest); // add necessary data information for analytics adapters / floor providers would possibly need addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); diff --git a/src/auction.js b/src/auction.js index 87397b0dc15..41e6fe3565b 100644 --- a/src/auction.js +++ b/src/auction.js @@ -93,6 +93,7 @@ import CONSTANTS from './constants.json'; import {GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {createBid} from './bidfactory.js'; +import {adjustCpm} from './utils/cpm.js'; const { syncUsers } = userSync; @@ -971,17 +972,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { } export function adjustBids(bid) { - let code = bid.bidderCode; - let bidPriceAdjusted = bid.cpm; - const bidCpmAdjustment = bidderSettings.get(code || null, 'bidCpmAdjustment'); - - if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { - try { - bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); - } catch (e) { - logError('Error during bid adjustment', 'bidmanager.js', e); - } - } + let bidPriceAdjusted = adjustCpm(bid.cpm, bid); if (bidPriceAdjusted >= 0) { bid.cpm = bidPriceAdjusted; diff --git a/src/utils/cpm.js b/src/utils/cpm.js new file mode 100644 index 00000000000..07113e7c944 --- /dev/null +++ b/src/utils/cpm.js @@ -0,0 +1,17 @@ +import {auctionManager} from '../auctionManager.js'; +import {bidderSettings} from '../bidderSettings.js'; +import {logError} from '../utils.js'; + +export function adjustCpm(cpm, bidResponse, bidRequest, {index = auctionManager.index, bs = bidderSettings} = {}) { + bidRequest = bidRequest || index.getBidRequest(bidResponse); + const bidCpmAdjustment = bs.get(bidResponse?.bidderCode || bidRequest?.bidder, 'bidCpmAdjustment'); + + if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { + try { + return bidCpmAdjustment(cpm, Object.assign({}, bidResponse), bidRequest); + } catch (e) { + logError('Error during bid adjustment', e); + } + } + return cpm; +} diff --git a/test/spec/unit/utils/cpm_spec.js b/test/spec/unit/utils/cpm_spec.js new file mode 100644 index 00000000000..9d104b04d09 --- /dev/null +++ b/test/spec/unit/utils/cpm_spec.js @@ -0,0 +1,64 @@ +import {adjustCpm} from '../../../../src/utils/cpm.js'; + +describe('adjustCpm', () => { + const bidderCode = 'mockBidder'; + let adjustmentFn, bs, index; + beforeEach(() => { + bs = { + get: sinon.stub() + } + index = { + getBidRequest: sinon.stub() + } + adjustmentFn = sinon.stub().callsFake((cpm) => cpm * 2); + }) + + it('throws when neither bidRequest nor bidResponse are provided', () => { + expect(() => adjustCpm(1)).to.throw(); + }) + + it('always provides an object as bidResponse for the adjustment fn', () => { + bs.get.callsFake(() => adjustmentFn); + adjustCpm(1, null, {bidder: bidderCode}, {index, bs}); + sinon.assert.calledWith(adjustmentFn, 1, {}); + }); + + describe('when no bidRequest is provided', () => { + Object.entries({ + 'unavailable': undefined, + 'found': {foo: 'bar'} + }).forEach(([t, req]) => { + describe(`and it is ${t} in the index`, () => { + beforeEach(() => { + bs.get.callsFake(() => adjustmentFn); + index.getBidRequest.callsFake(() => req) + }); + + it('provides it to the adjustment fn', () => { + const bidResponse = {bidderCode}; + adjustCpm(1, bidResponse, undefined, {index, bs}); + sinon.assert.calledWith(index.getBidRequest, bidResponse); + sinon.assert.calledWith(adjustmentFn, 1, bidResponse, req); + }) + }) + }) + }); + + Object.entries({ + 'bidResponse': [{bidderCode}], + 'bidRequest': [null, {bidder: bidderCode}], + }).forEach(([t, [bidResp, bidReq]]) => { + describe(`when passed ${t}`, () => { + beforeEach(() => { + bs.get.callsFake((bidder) => { if (bidder === bidderCode) return adjustmentFn }); + }); + it('retrieves the correct bidder code', () => { + expect(adjustCpm(1, bidResp, bidReq, {bs, index})).to.eql(2); + }); + it('passes them to the adjustment fn', () => { + adjustCpm(1, bidResp, bidReq, {bs, index}); + sinon.assert.calledWith(adjustmentFn, 1, bidResp == null ? sinon.match.any : bidResp, bidReq); + }); + }); + }) +}); From d56aaba3edff7094072416638d2c0fa039102614 Mon Sep 17 00:00:00 2001 From: Paulius Imbrasas <880130+CremboC@users.noreply.github.com> Date: Fri, 27 Jan 2023 21:53:17 +0000 Subject: [PATCH 087/113] Fixes potential error when reading _pssps localStorage key (#9474) --- modules/permutiveRtdProvider.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index c64080f3308..305e175bf2c 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -311,16 +311,19 @@ export function isPermutiveOnPage () { * @return {Object} */ export function getSegments (maxSegs) { - const legacySegs = readSegments('_psegs').map(Number).filter(seg => seg >= 1000000).map(String) - const _ppam = readSegments('_ppam') - const _pcrprs = readSegments('_pcrprs') + const legacySegs = readSegments('_psegs', []).map(Number).filter(seg => seg >= 1000000).map(String) + const _ppam = readSegments('_ppam', []) + const _pcrprs = readSegments('_pcrprs', []) const segments = { ac: [..._pcrprs, ..._ppam, ...legacySegs], - rubicon: readSegments('_prubicons'), - appnexus: readSegments('_papns'), - gam: readSegments('_pdfps'), - ssp: readSegments('_pssps'), + rubicon: readSegments('_prubicons', []), + appnexus: readSegments('_papns', []), + gam: readSegments('_pdfps', []), + ssp: readSegments('_pssps', { + cohorts: [], + ssps: [] + }), } for (const bidder in segments) { @@ -338,15 +341,17 @@ export function getSegments (maxSegs) { /** * Gets an array of segment IDs from LocalStorage - * or returns an empty array + * or return the default value provided. + * @template A * @param {string} key - * @return {string[]|number[]} + * @param {A} defaultValue + * @return {A} */ -function readSegments (key) { +function readSegments (key, defaultValue) { try { - return JSON.parse(storage.getDataFromLocalStorage(key) || '[]') + return JSON.parse(storage.getDataFromLocalStorage(key)) || defaultValue } catch (e) { - return [] + return defaultValue } } From 2c9fc250ffb2f24c77e415a8ea80d68cdd77fa37 Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Mon, 30 Jan 2023 12:07:39 +0200 Subject: [PATCH 088/113] SmartyadsBidAdapter: update request params (#9472) Co-authored-by: Vasyl Rishko --- modules/smartyadsBidAdapter.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md index f078d905e62..e0d6023a794 100644 --- a/modules/smartyadsBidAdapter.md +++ b/modules/smartyadsBidAdapter.md @@ -10,6 +10,15 @@ Maintainer: supply@smartyads.com Module that connects to SmartyAds' demand sources +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :------------------------ | :------------------- | +| `sourceid` | required (for prebid.js) | placement ID | "0" | +| `host` | required (for prebid-server) | const value, set to "prebid" | "prebid" | +| `accountid` | required (for prebid-server) | partner ID | "1901" | +| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | + # Test Parameters ``` var adUnits = [ From aa033bdf1f5092e0083303b7cd36da94bebe9fb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 07:38:24 -0700 Subject: [PATCH 089/113] Bump tibdex/github-app-token from 1.7.0 to 1.8.0 (#9479) Bumps [tibdex/github-app-token](https://github.com/tibdex/github-app-token) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/tibdex/github-app-token/releases) - [Commits](https://github.com/tibdex/github-app-token/compare/021a2405c7f990db57f5eae5397423dcc554159c...b62528385c34dbc9f38e5f4225ac829252d1ea92) --- updated-dependencies: - dependency-name: tibdex/github-app-token dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue_tracker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 29082a4990a..69cf4c5fc7f 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@021a2405c7f990db57f5eae5397423dcc554159c + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: app_id: ${{ secrets.ISSUE_APP_ID }} private_key: ${{ secrets.ISSUE_APP_PEM }} From b1de163ef0171ae2f72f2791ced3a1312601d8d4 Mon Sep 17 00:00:00 2001 From: Espen <2290914+espen-j@users.noreply.github.com> Date: Tue, 31 Jan 2023 10:21:05 +0100 Subject: [PATCH 090/113] C-Wire Bid Adapter: Code refactorings (#9446) * Reduce c-wire adapter to basic functionality (c-wire/creatives#3) * Minimize processing of bids * Add basic tests for adapter * Read cw_creative parameter from url and pass it as creativeId to the request (c-wire/creative#9) * Attach slot dimensions and maxWidth to request (c-wire/creatives#22) * Add feature flag support (c-wire/creatives#22) * Propagate cw_debug flag to ad server payload * Implement CWID (c-wire/prebid#3) * Add maxHeight CSS attribute (c-wire/creatives#22) * Update Prebid endpoint (c-wire/prebid#3) * Rename referrer to old property name * Map pageViewId to auctionId (c-wire/prebid#3) * Re-introduce pageId as required parameter (c-wire/prebid#3) * Map response body's bid.html property to bid.ad (c-wire/prebid#3) * Rename creativeId property to cwcreative in the payload (c-wire/prebid#3) * Flatten pageId and placementId into bid object (c-wire/prebid#3) * Send bid won and error events (c-wire/prebid#3) * Align cw* parameters with documentation and PBS adapter (c-wire/prebid#3) * QA Fix featureFlag check * Add refgroups from URL parameters (c-wire/prebid#3) * Inline adapter specific payload (c-wire/prebid#3) * Make pageViewId per prebid instance (c-wire/prebid#3) * Extract cwire extensions into own method (c-wire/prebid#3) * Update documentation (c-wire/prebid#3) * Add validations for placementId and pageId (c-wire/prebid#3) * QA Fix linting error * Add prebid version to payload (c-wire/prebid#3) --- modules/cwireBidAdapter.js | 411 +++++++-------- modules/cwireBidAdapter.md | 29 +- test/spec/modules/cwireBidAdapter_spec.js | 584 +++++++++------------- 3 files changed, 433 insertions(+), 591 deletions(-) diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index a11899609bc..604d7235d0f 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -1,307 +1,234 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {OUTSTREAM} from '../src/video.js'; -import { - deepAccess, - generateUUID, - getBidIdParameter, - getParameterByName, - getValue, - isArray, - isNumber, - isStr, - logError, - logWarn, - parseSizesInput, -} from '../src/utils.js'; -import {Renderer} from '../src/Renderer.js'; -import {find} from '../src/polyfill.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {generateUUID, getParameterByName, isNumber, logError, logInfo} from '../src/utils.js'; // ------------------------------------ const BIDDER_CODE = 'cwire'; -export const ENDPOINT_URL = 'https://embed.cwi.re/delivery/prebid'; -export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer.min.js'; -// ------------------------------------ -export const CW_PAGE_VIEW_ID = generateUUID(); -const LS_CWID_KEY = 'cw_cwid'; -const CW_GROUPS_QUERY = 'cwgroups'; -const CW_CREATIVE_QUERY = 'cwcreative'; +const CWID_KEY = 'cw_cwid'; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const BID_ENDPOINT = 'https://prebid.cwi.re/v1/bid'; +export const EVENT_ENDPOINT = 'https://prebid.cwi.re/v1/event'; /** - * ------------------------------------ - * ------------------------------------ - * @param bid - * @returns {Array} + * Allows limiting ad impressions per site render. Unique per prebid instance ID. */ -export function getSlotSizes(bid) { - return parseSizesInput(getAllMediaSizes(bid)); -} +export const pageViewId = generateUUID(); + +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** - * ------------------------------------ - * ------------------------------------ + * Retrieve dimensions and CSS max height/width from a given slot and attach the properties to the bidRequest. * @param bid - * @returns {*[]} + * @returns {*&{cwExt: {dimensions: {width: number, height: number}, style: {maxWidth: number, maxHeight: number}}}} */ -export function getAllMediaSizes(bid) { - let playerSizes = deepAccess(bid, 'mediaTypes.video.playerSize'); - let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes'); - let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); - - const sizes = []; - - if (isArray(playerSizes)) { - playerSizes.forEach((s) => { - sizes.push(s); - }) - } - - if (isArray(videoSizes)) { - videoSizes.forEach((s) => { - sizes.push(s); - }) +function slotDimensions(bid) { + let adUnitCode = bid.adUnitCode; + let slotEl = document.getElementById(adUnitCode); + + if (slotEl) { + logInfo(`Slot element found: ${adUnitCode}`) + const slotW = slotEl.offsetWidth + const slotH = slotEl.offsetHeight + const cssMaxW = slotEl.style?.maxWidth; + const cssMaxH = slotEl.style?.maxHeight; + logInfo(`Slot dimensions (w/h): ${slotW} / ${slotH}`) + logInfo(`Slot Styles (maxW/maxH): ${cssMaxW} / ${cssMaxH}`) + + bid = { + ...bid, + cwExt: { + dimensions: { + width: slotW, + height: slotH, + }, + style: { + ...(cssMaxW) && { + maxWidth: cssMaxW + }, + ...(cssMaxH) && { + maxHeight: cssMaxH + } + } + } + } } + return bid +} - if (isArray(bannerSizes)) { - bannerSizes.forEach((s) => { - sizes.push(s); - }) +/** + * Extracts feature flags from a comma-separated url parameter `cwfeatures`. + * + * @returns *[] + */ +function getFeatureFlags() { + let ffParam = getParameterByName('cwfeatures') + if (ffParam) { + return ffParam.split(',') } - return sizes; + return [] } -const getQueryVariable = (variable) => { - let value = getParameterByName(variable); - if (value === '') { - value = null; +function getRefGroups() { + const groups = getParameterByName('cwgroups') + if (groups) { + return groups.split(',') } - return value; -}; + return [] +} /** - * ------------------------------------ - * ------------------------------------ - * @param validBidRequests - * @returns {*[]} + * Reads the CWID from local storage. */ -export const mapSlotsData = function(validBidRequests) { - const slots = []; - validBidRequests.forEach(bid => { - const bidObj = {}; - // get testing / debug params - let cwcreative = getValue(bid.params, 'cwcreative'); - let refgroups = getValue(bid.params, 'refgroups'); - let cwapikey = getValue(bid.params, 'cwapikey'); +function getCwid() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(CWID_KEY) : null; +} - // get the pacement and page ids - let placementId = getValue(bid.params, 'placementId'); - let pageId = getValue(bid.params, 'pageId'); - // get the rest of the auction/bid/transaction info - bidObj.auctionId = getBidIdParameter('auctionId', bid); - bidObj.adUnitCode = getBidIdParameter('adUnitCode', bid); - bidObj.bidId = getBidIdParameter('bidId', bid); - bidObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); - bidObj.placementId = placementId; - bidObj.pageId = pageId; - bidObj.mediaTypes = getBidIdParameter('mediaTypes', bid); - bidObj.transactionId = getBidIdParameter('transactionId', bid); - bidObj.sizes = getSlotSizes(bid); - bidObj.cwcreative = cwcreative; - bidObj.refgroups = refgroups; - bidObj.cwapikey = cwapikey; - slots.push(bidObj); - }); +function hasCwid() { + return storage.localStorageIsEnabled() && storage.getDataFromLocalStorage(CWID_KEY); +} - return slots; -}; +/** + * Store the CWID to local storage. + */ +function updateCwid(cwid) { + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(CWID_KEY, cwid) + } else { + logInfo(`Could not set CWID ${cwid} in localstorage`); + } +} + +/** + * Extract and collect cwire specific extensions. + */ +function getCwExtension() { + const cwId = getCwid(); + const cwCreative = getParameterByName('cwcreative') + const cwGroups = getRefGroups() + const cwFeatures = getFeatureFlags(); + // Enable debug flag by passing ?cwdebug=true as url parameter. + // Note: pbjs_debug=true enables it on prebid level + // More info: https://docs.prebid.org/troubleshooting/troubleshooting-guide.html#turn-on-prebidjs-debug-messages + const debug = getParameterByName('cwdebug'); + + return { + ...(cwId) && { + cwid: cwId + }, + ...(cwGroups.length > 0) && { + refgroups: cwGroups + }, + ...(cwFeatures.length > 0) && { + featureFlags: cwFeatures + }, + ...(cwCreative) && { + cwcreative: cwCreative + }, + ...(debug) && { + debug: true + } + }; +} export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER], + /** - * Determines whether or not the given bid request is valid. + * Determines whether the given bid request is valid. * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function(bid) { - bid.params = bid.params || {}; - - if (bid.params.cwcreative && !isStr(bid.params.cwcreative)) { - logError('cwcreative must be of type string!'); - return false; - } - - if (!bid.params.placementId || !isNumber(bid.params.placementId)) { - logError('placementId not provided or invalid'); + isBidRequestValid: function (bid) { + if (!bid.params?.placementId || !isNumber(bid.params.placementId)) { + logError('placementId not provided or not a number'); return false; } - if (!bid.params.pageId || !isNumber(bid.params.pageId)) { - logError('pageId not provided'); + if (!bid.params?.pageId || !isNumber(bid.params.pageId)) { + logError('pageId not provided or not a number'); return false; } - return true; }, /** - * ------------------------------------ - * itterate trough slots array and try - * to extract first occurence of a given - * key, if not found - return null - * ------------------------------------ - */ - getFirstValueOrNull: function(slots, key) { - const found = slots.find((item) => { - return (typeof item[key] !== 'undefined'); - }); - - return (found) ? found[key] : null; - }, - - /** - * ------------------------------------ - * Make a server request from the - * list of BidRequests. - * ------------------------------------ - * @param {validBidRequests[]} - an array of bids + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} validBidRequests An array of bids. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests, bidderRequest) { - let slots = []; - let referer; - try { - referer = bidderRequest?.refererInfo?.page; - slots = mapSlotsData(validBidRequests); - } catch (e) { - logWarn(e); - } + buildRequests: function (validBidRequests, bidderRequest) { + // There are more fields on the refererInfo object + let referrer = bidderRequest?.refererInfo?.page - let refgroups = []; - - const cwCreative = getQueryVariable(CW_CREATIVE_QUERY) || null; - const cwCreativeIdFromConfig = this.getFirstValueOrNull(slots, 'cwcreative'); - const refGroupsFromConfig = this.getFirstValueOrNull(slots, 'refgroups'); - const cwApiKeyFromConfig = this.getFirstValueOrNull(slots, 'cwapikey'); - const rgQuery = getQueryVariable(CW_GROUPS_QUERY); - - if (refGroupsFromConfig !== null) { - refgroups = refGroupsFromConfig.split(','); - } - - if (rgQuery !== null) { - // override if query param is present - refgroups = []; - refgroups = rgQuery.split(','); - } - - const localStorageCWID = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(LS_CWID_KEY) : null; + // process bid requests + let processed = validBidRequests + .map(bid => slotDimensions(bid)) + // Flattens the pageId and placement Id for backwards compatibility. + .map((bid) => ({...bid, pageId: bid.params?.pageId, placementId: bid.params?.placementId})); + const extensions = getCwExtension(); const payload = { - cwid: localStorageCWID, - refgroups, - cwcreative: cwCreative || cwCreativeIdFromConfig, - slots: slots, - cwapikey: cwApiKeyFromConfig, - httpRef: referer || '', - pageViewId: CW_PAGE_VIEW_ID, + slots: processed, + httpRef: referrer, + // TODO: Verify whether the auctionId and the usage of pageViewId make sense. + pageViewId: pageViewId, + sdk: { + version: '$prebid.version$' + }, + ...extensions }; - + const payloadString = JSON.stringify(payload); return { method: 'POST', - url: ENDPOINT_URL, - data: payload + url: BID_ENDPOINT, + data: payloadString, }; }, - /** * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - - try { - if (typeof bidRequest.data === 'string') { - bidRequest.data = JSON.parse(bidRequest.data); + interpretResponse: function (serverResponse, bidRequest) { + if (!hasCwid()) { + const cwid = serverResponse.body?.cwid + if (cwid) { + updateCwid(cwid); } - const serverBody = serverResponse.body; - serverBody.bids.forEach((br) => { - const bidReq = find(bidRequest.data.slots, bid => bid.bidId === br.requestId); - - let mediaType = BANNER; - - const bidResponse = { - requestId: br.requestId, - cpm: br.cpm, - bidderCode: BIDDER_CODE, - width: br.dimensions[0], - height: br.dimensions[1], - creativeId: br.creativeId, - currency: br.currency, - netRevenue: br.netRevenue, - ttl: br.ttl, - meta: { - advertiserDomains: br.adomains ? br.advertiserDomains : [], - }, - - }; - - // ------------------------------------ - // IF BANNER - // ------------------------------------ - - if (deepAccess(bidReq, 'mediaTypes.banner')) { - bidResponse.ad = br.html; - } - // ------------------------------------ - // IF VIDEO - // ------------------------------------ - if (deepAccess(bidReq, 'mediaTypes.video')) { - mediaType = VIDEO; - bidResponse.vastXml = br.vastXml; - bidResponse.videoScript = br.html; - const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context'); - if (mediaTypeContext === OUTSTREAM) { - const r = Renderer.install({ - id: bidResponse.requestId, - adUnitCode: bidReq.adUnitCode, - url: RENDERER_URL, - loaded: false, - config: { - ...deepAccess(bidReq, 'mediaTypes.video'), - ...deepAccess(br, 'outstream', {}) - } - }); + } - // set renderer - try { - bidResponse.renderer = r; - bidResponse.renderer.setRender(function(bid) { - if (window.CWIRE && window.CWIRE.outstream) { - window.CWIRE.outstream.renderAd(bid); - } - }); - } catch (err) { - logWarn('Prebid Error calling setRender on newRenderer', err); - } - } - } + // Rename `html` response property to `ad` as used by prebid. + const bids = serverResponse.body?.bids.map(({html, ...rest}) => ({...rest, ad: html})); + return bids || []; + }, - bidResponse.mediaType = mediaType; - bidResponses.push(bidResponse); - }); - } catch (e) { - logWarn(e); + onBidWon: function (bid) { + logInfo(`Bid won.`) + const event = { + type: 'BID_WON', + payload: { + bid: bid + } } + navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) + }, - return bidResponses; + onBidderError: function (error, bidderRequest) { + logInfo(`Bidder error: ${error}`) + const event = { + type: 'BID_ERROR', + payload: { + error: error, + bidderRequest: bidderRequest + } + } + navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) }, + }; registerBidder(spec); diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index 188894cd202..026c2943c6b 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -1,25 +1,27 @@ # Overview -Module Name: C-WIRE Bid Adapter -Module Type: Adagio Adapter -Maintainer: publishers@cwire.ch +``` +Module Name: C-WIRE Bid Adapter +Module Type: Bidder Adapter +Maintainer: devs@cwire.ch +``` ## Description -Connects to C-WIRE demand source to fetch bids. +Prebid.js Adapter for C-Wire. ## Configuration - Below, the list of C-WIRE params and where they can be set. -| Param name | Global config | AdUnit config | Type | Required | -| ---------- | ------------- | ------------- |--------| ---------| -| pageId | | x | number | YES | -| placementId | | x | number | YES | -| refgroups | | x | string | NO | -| cwcreative | | x | string | NO | -| cwapikey | | x | string | NO | +| Param name | URL parameter | AdUnit config | Type | Required | +|-------------|:-------------:|:-------------:|:--------:|:-------------:| +| pageId | | x | number | YES | +| placementId | | x | number | YES | +| cwgroups | x | | string | NO | +| cwcreative | x | | string | NO | +| cwdebug | x | | boolean | NO | +| cwfeatures | x | | string | NO | ### adUnit configuration @@ -32,7 +34,7 @@ var adUnits = [ bidder: 'cwire', mediaTypes: { banner: { - sizes: [[1, 1]], + sizes: [[400, 600]], } }, params: { @@ -40,7 +42,6 @@ var adUnits = [ placementId: 2211521, // required - number cwcreative: '42', // optional - id of creative to force refgroups: 'test-user', // optional - name of group or coma separated list of groups to force - cwapikey: 'api_key_xyz', // optional - api key for integration testing } }] } diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index f116b184b8c..88c54212aff 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -1,382 +1,296 @@ -import { expect } from 'chai'; -import * as utils from '../../../src/utils.js'; -import { config } from '../../../src/config.js'; -import { - spec, - CW_PAGE_VIEW_ID, - ENDPOINT_URL, - RENDERER_URL, -} from '../../../modules/cwireBidAdapter.js'; -import * as prebidGlobal from 'src/prebidGlobal.js'; - -// ------------------------------------ -// Bid Request Builder -// ------------------------------------ - -const BID_DEFAULTS = { - request: { - bidder: 'cwire', - auctionId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - transactionId: 'txaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - bidId: 'bid123445', - bidderRequestId: 'brid12345', - code: 'original-div', - }, - params: { - placementId: 123456, - pageId: 777, - }, - sizes: [[300, 250], [1, 1]], -}; - -const BidderRequestBuilder = function BidderRequestBuilder(options) { - const defaults = { - bidderCode: 'cwire', - auctionId: BID_DEFAULTS.request.auctionId, - bidderRequestId: BID_DEFAULTS.request.bidderRequestId, - transactionId: BID_DEFAULTS.request.transactionId, - timeout: 3000, - }; - - const request = { - ...defaults, - ...options - }; - - this.build = () => request; -}; - -const BidRequestBuilder = function BidRequestBuilder(options, deleteKeys) { - const defaults = JSON.parse(JSON.stringify(BID_DEFAULTS)); - - const request = { - ...defaults.request, - ...options - }; - - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request[k]; - }) - } - - this.withParams = (options, deleteKeys) => { - request.params = { - ...defaults.params, - ...options - }; - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request.params[k]; - }) - } - return this; - }; - - this.build = () => request; -}; +import {expect} from 'chai'; +import {newBidder} from '../../../src/adapters/bidderFactory'; +import {BID_ENDPOINT, spec, storage} from '../../../modules/cwireBidAdapter'; +import {deepClone, logInfo} from '../../../src/utils'; +import * as utils from 'src/utils.js'; +import {sandbox, stub} from 'sinon'; +import {config} from '../../../src/config'; describe('C-WIRE bid adapter', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); + config.setConfig({debug: true}); + const adapter = newBidder(spec); + let bidRequests = [ + { + 'bidder': 'cwire', + 'params': { + 'pageId': '4057', + 'placementId': 'ad-slot-bla' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + const response = { + body: { + 'cwid': '2ef90743-7936-4a82-8acf-e73382a64e94', + 'hash': '17112D98BBF55D3A', + 'bids': [{ + 'html': '

Hello world

', + 'cpm': 100, + 'currency': 'CHF', + 'dimensions': [1, 1], + 'netRevenue': true, + 'creativeId': '3454', + 'requestId': '2c634d4ca5ccfb', + 'placementId': 177, + 'transactionId': 'b4b32618-1350-4828-b6f0-fbb5c329e9a4', + 'ttl': 360 + }] + } + } + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + expect(spec.isBidRequestValid).to.exist.and.to.be.a('function'); + expect(spec.buildRequests).to.exist.and.to.be.a('function'); + expect(spec.interpretResponse).to.exist.and.to.be.a('function'); + }); }); - - afterEach(() => { - sandbox.restore(); - config.resetConfig(); + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(BID_ENDPOINT); + expect(request.method).to.equal('POST'); + }); }); + describe('buildRequests with given creative', function () { + let utilsStub; - // START TESTING - describe('C-WIRE - isBidRequestValid', function () { - it('should return true when required params found', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - expect(spec.isBidRequestValid(bid01)).to.equal(true); + before(function () { + utilsStub = stub(utils, 'getParameterByName').callsFake(function () { + return 'str-str' + }); }); - it('should fail if there is no placementId', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.placementId - expect(spec.isBidRequestValid(bid01)).to.equal(false); + after(function () { + utilsStub.restore(); }); - it('should fail if invalid placementId type', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.placementId; - bid01.placementId = '322'; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + it('should add creativeId if url parameter given', function () { + // set from bid.params + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.cwcreative).to.exist; + expect(payload.cwcreative).to.deep.equal('str-str'); + }); + }) + + describe('buildRequests reads adUnit offsetWidth and offsetHeight', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + offsetWidth: 200, + offsetHeight: 250 + }); }); + it('width and height should be set', function () { + let bidRequest = deepClone(bidRequests[0]); - it('should fail if there is no pageId', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.pageId - expect(spec.isBidRequestValid(bid01)).to.equal(false); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const el = document.getElementById(`${bidRequest.adUnitCode}`) + + logInfo(JSON.stringify(payload)) + + expect(el).to.exist; + expect(payload.slots[0].cwExt.dimensions.width).to.equal(200); + expect(payload.slots[0].cwExt.dimensions.height).to.equal(250); + expect(payload.slots[0].cwExt.style.maxHeight).to.not.exist; + expect(payload.slots[0].cwExt.style.maxWidth).to.not.exist; + }); + after(function () { + sandbox.restore() + }); + }); + describe('buildRequests reads style attributes', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + style: { + maxWidth: '400px', + maxHeight: '350px', + } + }); }); + it('css maxWidth should be set', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const el = document.getElementById(`${bidRequest.adUnitCode}`) + + logInfo(JSON.stringify(payload)) - it('should fail if invalid pageId type', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.pageId; - bid01.params.pageId = '3320'; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + expect(el).to.exist; + expect(payload.slots[0].cwExt.style.maxWidth).to.eq('400px'); + !expect(payload.slots[0].cwExt.style.maxHeight).to.eq('350px'); }); + after(function () { + sandbox.restore() + }); + }); - it('should fail if cwcreative of type number', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.cwcreative; - bid01.params.cwcreative = 3320; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + describe('buildRequests reads feature flags', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'feature1,feature2' + }); }); - it('should pass with valid cwcreative of type string', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - bid01.params.cwcreative = 'i-am-a-string'; - expect(spec.isBidRequestValid(bid01)).to.equal(true); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.featureFlags).to.exist; + expect(payload.featureFlags).to.include.members(['feature1', 'feature2']); + }); + after(function () { + sandbox.restore() }); }); - describe('C-WIRE - buildRequests()', function () { - it('creates a valid request', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '54321', - cwapikey: 'xxx-xxx-yyy-zzz-uuid', - refgroups: 'group_1', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01], bidderRequest01); - - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal('54321'); - expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz-uuid'); - expect(requests.data.refgroups[0]).to.equal('group_1'); + describe('buildRequests reads cwgroups flag', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'group1,group2' + }); }); - it('creates a valid request - read debug params from second bid', function () { - const bid01 = new BidRequestBuilder().withParams().build(); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); - const bid02 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '1234', - cwapikey: 'api_key_5', - refgroups: 'group_5', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02], bidderRequest01); - - expect(requests.data.slots.length).to.equal(2); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('1234'); - expect(requests.data.cwapikey).to.equal('api_key_5'); - expect(requests.data.refgroups[0]).to.equal('group_5'); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.refgroups).to.exist; + expect(payload.refgroups).to.include.members(['group1', 'group2']); + }); + after(function () { + sandbox.restore() }); + }) - it('creates a valid request - read debug params from first bid, ignore second', function () { - const bid01 = new BidRequestBuilder() - .withParams({ - cwcreative: '33', - cwapikey: 'api_key_33', - refgroups: 'group_33', - }).build(); - - const bid02 = new BidRequestBuilder() - .withParams({ - cwcreative: '1234', - cwapikey: 'api_key_5', - refgroups: 'group_5', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02], bidderRequest01); - - expect(requests.data.slots.length).to.equal(2); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('33'); - expect(requests.data.cwapikey).to.equal('api_key_33'); - expect(requests.data.refgroups[0]).to.equal('group_33'); + describe('buildRequests reads debug flag', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'true' + }); }); - it('creates a valid request - read debug params from 3 different slots', function () { - const bid01 = new BidRequestBuilder() - .withParams({ - cwcreative: '33', - }).build(); - - const bid02 = new BidRequestBuilder() - .withParams({ - cwapikey: 'api_key_5', - }).build(); - - const bid03 = new BidRequestBuilder() - .withParams({ - refgroups: 'group_5', - }).build(); - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02, bid03], bidderRequest01); - - expect(requests.data.slots.length).to.equal(3); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('33'); - expect(requests.data.cwapikey).to.equal('api_key_5'); - expect(requests.data.refgroups[0]).to.equal('group_5'); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.debug).to.exist; + expect(payload.debug).to.equal(true); + }); + after(function () { + sandbox.restore() }); + }) - it('creates a valid request - config is overriden by URL params', function () { - // for whatever reason stub for getWindowLocation does not work - // so this was the closest way to test for get params - const params = sandbox.stub(utils, 'getParameterByName'); - params.withArgs('cwgroups').returns('group_2'); - params.withArgs('cwcreative').returns('654321'); - - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '54321', - cwapikey: 'xxx-xxx-yyy-zzz', - refgroups: 'group_1', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01], bidderRequest01); - - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal('654321'); - expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz'); - expect(requests.data.refgroups[0]).to.equal('group_2'); + describe('buildRequests reads cw_id from Localstorage', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => 'taerfagerg'); }); - it('creates a valid request - if params are not set, null or empty array are sent to the API', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + it('cw_id is set', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const requests = spec.buildRequests([bid01], bidderRequest01); + logInfo(JSON.stringify(payload)) - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal(null); - expect(requests.data.cwapikey).to.equal(null); - expect(requests.data.refgroups.length).to.equal(0); + expect(payload.cwid).to.exist; + expect(payload.cwid).to.equal('taerfagerg'); }); - }); + after(function () { + sandbox.restore() + }); + }) - describe('C-WIRE - interpretResponse()', function () { - const serverResponse = { - body: { - bids: [{ - html: '

AD CONTENT

', - currency: 'CHF', - cpm: 43.37, - dimensions: [1, 1], - netRevenue: true, - creativeId: '1337', - requestId: BID_DEFAULTS.request.bidId, - ttl: 3500, - }], - } - }; - - const expectedResponse = [{ - ad: JSON.parse(JSON.stringify(serverResponse.body.bids[0].html)), - bidderCode: BID_DEFAULTS.request.bidder, - cpm: JSON.parse(JSON.stringify(serverResponse.body.bids[0].cpm)), - creativeId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].creativeId)), - currency: JSON.parse(JSON.stringify(serverResponse.body.bids[0].currency)), - height: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[0])), - width: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[1])), - netRevenue: JSON.parse(JSON.stringify(serverResponse.body.bids[0].netRevenue)), - requestId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].requestId)), - ttl: JSON.parse(JSON.stringify(serverResponse.body.bids[0].ttl)), - meta: { - advertiserDomains: [], - }, - mediaType: 'banner', - }] - - it('correctly parses response', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + describe('buildRequests maps flattens params for legacy compat', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({}); + }); + it('pageId flattened', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); - const requests = spec.buildRequests([bid01], bidderRequest01); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const response = spec.interpretResponse(serverResponse, requests); - expect(response).to.deep.equal(expectedResponse); + logInfo(JSON.stringify(payload)) + + expect(payload.slots[0].pageId).to.exist; + }); + after(function () { + sandbox.restore() }); + }) - it('attaches renderer', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'outstream', - } - } - }).withParams().build(); - const bidderRequest01 = new BidderRequestBuilder().build(); + describe('pageId and placementId are required params', function () { + it('invalid request', function () { + let bidRequest = deepClone(bidRequests[0]); + delete bidRequest.params + + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.false; + }) - const _serverResponse = utils.deepClone(serverResponse); - _serverResponse.body.bids[0].vastXml = ''; + it('valid request', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const _expectedResponse = utils.deepClone(expectedResponse); - _expectedResponse[0].mediaType = 'video'; - _expectedResponse[0].videoScript = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].html)); - _expectedResponse[0].vastXml = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].vastXml)); - delete _expectedResponse[0].ad; + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - const requests = spec.buildRequests([bid01], bidderRequest01); - expect(requests.data.slots[0].sizes).to.deep.equal(['640x480']); + it('cwcreative must be of type string', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const response = spec.interpretResponse(_serverResponse, requests); - expect(response[0].renderer).to.exist; - expect(response[0].renderer.url).to.equals(RENDERER_URL); - expect(response[0].renderer.loaded).to.equals(false); + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - delete response[0].renderer; - expect(response).to.deep.equal(_expectedResponse); - }); + it('build request adds pageId', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.slots[0].pageId).to.exist; + }) + }); + + describe('process serverResponse', function () { + it('html to ad mapping', function () { + let bidResponse = deepClone(response); + const bids = spec.interpretResponse(bidResponse, {}); + + expect(bids[0].ad).to.exist; + }) }); }); From 9be4e4d5e7b1306a1ead728bc6a755a8c98d3e76 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Tue, 31 Jan 2023 20:36:35 +0100 Subject: [PATCH 091/113] bidderCode fix (#9485) --- modules/nexx360BidAdapter.js | 3 +-- test/spec/modules/nexx360BidAdapter_spec.js | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index b04bb47543f..6ead44175e9 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -258,14 +258,13 @@ function interpretResponse(response, req) { currency: respBody.cur || 'USD', netRevenue: true, ttl: 120, - bidderCode: allowAlternateBidderCodes ? `n360-${bid.ssp}` : 'nexx360', mediaType: bid.type === 'banner' ? 'banner' : 'video', meta: { advertiserDomains: bid.adomain, demandSource: ssp, }, }; - // if (bid.dealid) response.dealid = bid.dealid; + if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ssp}`; if (response.mediaType === 'banner') { response.adUrl = bid.adUrl; diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index a5da8b29fbc..7645ee59f63 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -372,7 +372,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); expect(output[0].currency).to.be.eql(response.body.cur); @@ -411,7 +410,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); @@ -454,7 +452,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); expect(output[0].currency).to.be.eql(response.body.cur); @@ -490,7 +487,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); From cb1a982182816e8a8b47ec1c769dd40bfd223c37 Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Tue, 31 Jan 2023 21:42:40 +0200 Subject: [PATCH 092/113] Taboola Bid Adapter: pass nurl to bidResponse (#9482) * nurl-bugfix * nurl-bugfix --- modules/taboolaBidAdapter.js | 3 ++- test/spec/modules/taboolaBidAdapter_spec.js | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 2e10a8d8951..026d5a098df 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -279,7 +279,7 @@ function getBid(bids, currency, bidResponse) { return; } const { - price: cpm, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} + price: cpm, nurl, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} } = bidResponse; let requestId = bids[bidResponse.impid - 1].bidId; if (advertiserDomains && advertiserDomains.length > 0) { @@ -297,6 +297,7 @@ function getBid(bids, currency, bidResponse) { width, height, meta, + nurl, netRevenue: true }; } diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 17cc5fc0213..0c01244033e 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -460,7 +460,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + } ], 'seat': '14204545260' @@ -546,7 +548,8 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' }, { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', @@ -562,7 +565,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + } ], 'seat': '14204545260' @@ -586,6 +591,7 @@ describe('Taboola Adapter', function () { ad: multiServerResponse.body.seatbid[0].bid[0].adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -601,6 +607,7 @@ describe('Taboola Adapter', function () { ad: multiServerResponse.body.seatbid[0].bid[1].adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -625,6 +632,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -651,6 +659,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, From 4796d132242ea2a4b6bbeecbded027a861faa79e Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 1 Feb 2023 14:15:26 +0200 Subject: [PATCH 093/113] Vidazoo Bid Adapter: support for gpp consent and bid data (#9480) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * feat(module): pass gpp consent and bid data to server. * fix(module): change spec bidder timeout to 3000. --------- Co-authored-by: Udi Talias Co-authored-by: roman --- modules/vidazooBidAdapter.js | 78 +++++++++++++------ test/spec/modules/vidazooBidAdapter_spec.js | 83 +++++++++++++++------ 2 files changed, 117 insertions(+), 44 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index bb3e4abd838..96dcc182436 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,8 +1,9 @@ -import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { bidderSettings } from '../src/bidderSettings.js'; +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {bidderSettings} from '../src/bidderSettings.js'; +import { config } from '../src/config.js'; const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; @@ -26,11 +27,11 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { - const parsedUrl = parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -58,10 +59,23 @@ function isBidRequestValid(bid) { return !!(extractCID(params) && extractPID(params)); } -function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; - const { ext } = params; - let { bidFloor } = params; +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + const {ext} = params; + let {bidFloor} = params; const hashUrl = hashCode(topWindowUrl); const dealId = getNextDealId(hashUrl); const uniqueDealId = getUniqueDealId(hashUrl); @@ -110,7 +124,14 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { isStorageAllowed: isStorageAllowed, gpid: gpid, cat: cat, - pagecat: pagecat + pagecat: pagecat, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout }; appendUserIdsToRequestPayload(data, userId); @@ -127,6 +148,14 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { data.usPrivacy = bidderRequest.uspConsent; } + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + const dto = { method: 'POST', url: `${createDomain(subDomain)}/prebid/multi/${cId}`, @@ -169,10 +198,11 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { function buildRequests(validBidRequests, bidderRequest) { // TODO: does the fallback make sense here? const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); requests.push(request); }); return requests; @@ -182,14 +212,14 @@ function interpretResponse(serverResponse, request) { if (!serverResponse || !serverResponse.body) { return []; } - const { bidId } = request.data; - const { results } = serverResponse.body; + const {bidId} = request.data; + const {results} = serverResponse.body; let output = []; try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; + const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -228,8 +258,8 @@ function interpretResponse(serverResponse, request) { function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { let syncs = []; - const { iframeEnabled, pixelEnabled } = syncOptions; - const { gdprApplies, consentString = '' } = gdprConsent; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` @@ -253,7 +283,9 @@ export function hashCode(s, prefix = '_') { let h = 0 let i = 0; if (l > 0) { - while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } } return prefix + h; } @@ -310,7 +342,8 @@ export function getCacheOpt() { export function getStorageItem(key) { try { return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { } + } catch (e) { + } return null; } @@ -318,9 +351,10 @@ export function getStorageItem(key) { export function setStorageItem(key, value, timestamp) { try { const created = timestamp || Date.now(); - const data = JSON.stringify({ value, created }); + const data = JSON.stringify({value, created}); storage.setDataInLocalStorage(key, data); - } catch (e) { } + } catch (e) { + } } export function tryParseJSON(value) { diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 2ddb65469af..134e3e66256 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import { spec as adapter, SUPPORTED_ID_SYSTEMS, @@ -15,9 +15,10 @@ import { getVidazooSessionId, } from 'modules/vidazooBidAdapter.js'; import * as utils from 'src/utils.js'; -import { version } from 'package.json'; -import { useFakeTimers } from 'sinon'; -import { BANNER, VIDEO } from '../../../src/mediaTypes'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; const SUB_DOMAIN = 'openrtb'; @@ -38,6 +39,10 @@ const BID = { 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'mediaTypes': [BANNER], @@ -53,6 +58,10 @@ const VIDEO_BID = { 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { 'subDomain': SUB_DOMAIN, @@ -85,6 +94,8 @@ const BIDDER_REQUEST = { 'consentString': 'consent_string', 'gdprApplies': true }, + 'gppString': 'gpp_string', + 'gppSid': [7], 'uspConsent': 'consent_string', 'refererInfo': { 'page': 'https://www.greatsite.com', @@ -94,6 +105,10 @@ const BIDDER_REQUEST = { 'site': { 'cat': ['IAB2'], 'pagecat': ['IAB2-2'] + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] } }, }; @@ -147,7 +162,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -226,6 +241,9 @@ describe('VidazooBidAdapter', function () { it('should build video request', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -243,6 +261,15 @@ describe('VidazooBidAdapter', function () { gdpr: 1, gdprConsent: 'consent_string', usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + bidderRequestId: '12a8ae9ada9c13', gpid: '', prebidVersion: version, ptrace: '1000', @@ -278,6 +305,9 @@ describe('VidazooBidAdapter', function () { }); it('should build banner request for each size', function () { + config.setConfig({ + bidderTimeout: 3000 + }); const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -288,6 +318,15 @@ describe('VidazooBidAdapter', function () { gdprConsent: 'consent_string', gdpr: 1, usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', @@ -324,7 +363,7 @@ describe('VidazooBidAdapter', function () { describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', @@ -333,7 +372,7 @@ describe('VidazooBidAdapter', function () { }); it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -341,7 +380,7 @@ describe('VidazooBidAdapter', function () { }); it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ 'url': 'https://sync.cootlogix.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -357,12 +396,12 @@ describe('VidazooBidAdapter', function () { }); it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({ price: 1, ad: '' }); + const responses = adapter.interpretResponse({price: 1, ad: ''}); expect(responses).to.be.empty; }); it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); expect(responses).to.be.empty; }); @@ -422,11 +461,11 @@ describe('VidazooBidAdapter', function () { const userId = (function () { switch (idSystemProvider) { case 'lipb': - return { lipbid: id }; + return {lipbid: id}; case 'parrableId': - return { eid: id }; + return {eid: id}; case 'id5id': - return { uid: id }; + return {uid: id}; default: return id; } @@ -445,18 +484,18 @@ describe('VidazooBidAdapter', function () { describe('alternate param names extractors', function () { it('should return undefined when param not supported', function () { - const cid = extractCID({ 'c_id': '1' }); - const pid = extractPID({ 'p_id': '1' }); - const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); expect(cid).to.be.undefined; expect(pid).to.be.undefined; expect(subDomain).to.be.undefined; }); it('should return value when param supported', function () { - const cid = extractCID({ 'cID': '1' }); - const pid = extractPID({ 'Pid': '2' }); - const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); expect(cid).to.be.equal('1'); expect(pid).to.be.equal('2'); expect(subDomain).to.be.equal('prebid'); @@ -569,7 +608,7 @@ describe('VidazooBidAdapter', function () { now }); setStorageItem('myKey', 2020); - const { value, created } = getStorageItem('myKey'); + const {value, created} = getStorageItem('myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -585,8 +624,8 @@ describe('VidazooBidAdapter', function () { }); it('should parse JSON value', function () { - const data = JSON.stringify({ event: 'send' }); - const { event } = tryParseJSON(data); + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); expect(event).to.be.equal('send'); }); From 76f00021c4aa5a9248dcdea8be5c90a6e144f8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+smart-adserver@users.noreply.github.com> Date: Wed, 1 Feb 2023 13:23:46 +0100 Subject: [PATCH 094/113] Smartadserver Bid Adapter: support GPP consent (#9489) * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Add GPP support * Rework payloads enriching --------- Co-authored-by: Meven Courouble --- modules/smartadserverBidAdapter.js | 19 +++++++++++---- .../modules/smartadserverBidAdapter_spec.js | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 719d621b056..c07b2abe933 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -160,10 +160,21 @@ export const spec = { sdc: sellerDefinedContext }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + + if (bidderRequest.gppConsent) { + payload.gpp = bidderRequest.gppConsent.gppString; + payload.gpp_sid = bidderRequest.gppConsent.applicableSections; + } + + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } } if (bid && bid.userId) { diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 4dacb356894..97250ad0ebc 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -443,6 +443,30 @@ describe('Smart bid adapter tests', function () { }); }); + describe('GPP', function () { + it('should be added to payload when gppConsent available in bidder request', function () { + const options = { + gppConsent: { + gppString: 'some-gpp-string', + applicableSections: [3, 5] + } + }; + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, options); + const payload = JSON.parse(request[0].data); + + expect(payload).to.have.property('gpp').and.to.equal(options.gppConsent.gppString); + expect(payload).to.have.property('gpp_sid').and.to.be.an('array'); + expect(payload.gpp_sid).to.have.lengthOf(2).and.to.deep.equal(options.gppConsent.applicableSections); + }); + + it('should be undefined on payload when gppConsent unavailable in bidder request', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, {}); + const payload = JSON.parse(request[0].data); + + expect(payload.gpp).to.be.undefined; + }); + }); + describe('ccpa/us privacy tests', function () { afterEach(function () { config.resetConfig(); From fc5d01439423f3a3f78dffe4d545c37c0247445f Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Wed, 1 Feb 2023 04:46:11 -0800 Subject: [PATCH 095/113] Prebid Core: Added aliasRegistry to the Public API (#9467) * added aliasRegistry to the public api * reverted a few changes made for local dev * addressed feedback --- src/prebid.js | 8 ++++++++ test/spec/unit/pbjs_api_spec.js | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/prebid.js b/src/prebid.js index 3fb131fb02c..9ad72409990 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -915,6 +915,14 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { } }; +/** + * @alias module:pbjs.aliasRegistry + */ +$$PREBID_GLOBAL$$.aliasRegistry = adapterManager.aliasRegistry; +config.getConfig('aliasRegistry', config => { + if (config.aliasRegistry === 'private') delete $$PREBID_GLOBAL$$.aliasRegistry; +}); + /** * The bid response object returned by an external bidder adapter during the auction. * @typedef {Object} AdapterBidResponse diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index ae9ea09908a..0867cb0e399 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -3017,6 +3017,20 @@ describe('Unit: Prebid Module', function () { }); }); + describe('aliasRegistry', function () { + it('should return the same value as adapterManager.aliasRegistry by default', function () { + const adapterManagerAliasRegistry = adapterManager.aliasRegistry; + const pbjsAliasRegistry = $$PREBID_GLOBAL$$.aliasRegistry; + assert.equal(adapterManagerAliasRegistry, pbjsAliasRegistry); + }); + + it('should return undefined if the aliasRegistry config option is set to private', function () { + configObj.setConfig({ aliasRegistry: 'private' }); + const pbjsAliasRegistry = $$PREBID_GLOBAL$$.aliasRegistry; + assert.equal(pbjsAliasRegistry, undefined); + }); + }); + describe('setPriceGranularity', function () { it('should log error when not passed granularity', function () { const logErrorSpy = sinon.spy(utils, 'logError'); From be7bd91ff86273aa0eceb8dfa945a49f4ff31560 Mon Sep 17 00:00:00 2001 From: guiann Date: Wed, 1 Feb 2023 13:55:38 +0100 Subject: [PATCH 096/113] AdYouLike Bid Adapter : add pbjs version information (#9476) * add required clickurl in every native adrequest * allows the native response to be given as is to prebid if possible * add unit tests on new Native case * Handle meta object in bid response with default addomains array * fix icon retrieval in Native case * Update priorities in case of multiple mediatypes given * improve robustness and fix associated unit test on picture urls * add support for params.size parameter * add unit test on new size format * Makes sure the playerSize format is consistent * enable Vast response on bidder adapter * fix lint errors * add test on Vast format case * add userId to bidrequest * revert package-lock.json changes * improve multiple mediatype handling * Expose adyoulike GVL id * fix icurl issue when retreiving icon for Native mediatype * update unit tests on icon url in native mediatype * target video endpoint when video mediatype is present * add unit test on video endpoint * detect if bid request has video * remove console log * Add size information in Video bid + unit tests * Remove unused method (old video retrieval) * update pagereferrer and pageUrl values * improve null robustness in native getAssetValue * change function body and add unit test * fix pageUrl in case not given i ortb2 * adjust pageUrl and referrer values * add unit tests on new priority behaviour * add pbjsversion in bid request * add unit test --------- Co-authored-by: GuillaumeA --- modules/adyoulikeBidAdapter.js | 2 ++ test/spec/modules/adyoulikeBidAdapter_spec.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index f7473b3bad4..e6cdc7698bf 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -126,6 +126,8 @@ export const spec = { payload.userId = createEidsArray(bidderRequest.userId); } + payload.pbjs_version = '$prebid.version$'; + const data = JSON.stringify(payload); const options = { withCredentials: true diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index e6a153d501a..24cede20352 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -722,6 +722,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -736,6 +737,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -758,6 +760,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Bids['bid_id_1'].TransactionID).to.be.equal('bid_id_1_transaction_id'); expect(payload.Bids['bid_id_3'].TransactionID).to.be.equal('bid_id_3_transaction_id'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); }); it('sends bid request to endpoint setted by parameters', function () { From c925e50c6b191c8141f3593c66c10587d3863162 Mon Sep 17 00:00:00 2001 From: Zachary Carlin Date: Wed, 1 Feb 2023 15:54:23 -0500 Subject: [PATCH 097/113] Sonobi Bid Adapter: add additional sizes to bid request (#9413) * Added mediaTypes.video playerSize and sizes. * Save. * Order from least to most important. 1. Deprecated bid.size 2. bid.params.sizes. 3. mediaTypes.video.playerSize 4. mediaTypes.video.sizes 5. mediaTypes.banner.sizes * check for null. * Accepting multiple uniques sizes from different props. * Comments. * Added mediaTypes.video playerSize and sizes. * Save. * Order from least to most important. 1. Deprecated bid.size 2. bid.params.sizes. 3. mediaTypes.video.playerSize 4. mediaTypes.video.sizes 5. mediaTypes.banner.sizes * check for null. * Accepting multiple uniques sizes from different props. * Comments. * Circle CI error. Circle CI failing on line 298 due to trailing space, but there isnt one. * Circle CI error. Circle CI failing on line 298 due to trailing space, but there isnt one. * Readded hello_world.html --------- Co-authored-by: Zac Carlin --- integrationExamples/gpt/hello_world.html | 0 modules/sonobiBidAdapter.js | 27 ++++++++++++++-------- test/spec/modules/sonobiBidAdapter_spec.js | 3 ++- 3 files changed, 19 insertions(+), 11 deletions(-) mode change 100755 => 100644 integrationExamples/gpt/hello_world.html diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html old mode 100755 new mode 100644 diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 3c841cc4d8a..f9cc1f3b353 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -295,22 +295,29 @@ function _findBidderRequest(bidderRequests, bidId) { } } +// This function takes all the possible sizes. +// returns string csv. function _validateSize(bid) { - if (deepAccess(bid, 'mediaTypes.video')) { - return ''; // Video bids arent allowed to override sizes via the trinity request + let size = []; + if (deepAccess(bid, 'mediaTypes.video.playerSize')) { + size.push(deepAccess(bid, 'mediaTypes.video.playerSize')) } - - if (bid.params.sizes) { - return parseSizesInput(bid.params.sizes).join(','); + if (deepAccess(bid, 'mediaTypes.video.sizes')) { + size.push(deepAccess(bid, 'mediaTypes.video.sizes')) + } + if (deepAccess(bid, 'params.sizes')) { + size.push(deepAccess(bid, 'params.sizes')); } if (deepAccess(bid, 'mediaTypes.banner.sizes')) { - return parseSizesInput(deepAccess(bid, 'mediaTypes.banner.sizes')).join(','); + size.push(deepAccess(bid, 'mediaTypes.banner.sizes')) } - - // Handle deprecated sizes definition - if (bid.sizes) { - return parseSizesInput(bid.sizes).join(','); + if (deepAccess(bid, 'sizes')) { + size.push(deepAccess(bid, 'sizes')) } + // Pass the 2d sizes array into parseSizeInput to flatten it into an array of x separated sizes. + // Then throw it into Set to uniquify it. + // Then spread it to an array again. Then join it into a csv of sizes. + return [...new Set(parseSizesInput(...size))].join(','); } function _validateSlot(bid) { diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 7803cfff394..a9382f092e2 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -287,6 +287,7 @@ describe('SonobiBidAdapter', function () { }, mediaTypes: { video: { + sizes: [[300, 250], [300, 600]], context: 'outstream' } } @@ -331,7 +332,7 @@ describe('SonobiBidAdapter', function () { }]; let keyMakerData = { - '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d||f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', }; From 3b396cfa8228402fe14c3bb4fcba49f17a15b570 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Wed, 1 Feb 2023 16:02:03 -0500 Subject: [PATCH 098/113] Add ESLint Plugin Recommendation for VSCODE (#9498) Just a quality of life improvement for any VSCode users. --- .devcontainer/devcontainer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0176b8317b3..104d9a38132 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "nickdodd79.gulptasks" + "nickdodd79.gulptasks", + "dbaeumer.vscode-eslint" ], // 9999 is web server, 9876 is karma From fef5c5a62c465fb8982c286e6f158dd28f57aed0 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:03:57 -0500 Subject: [PATCH 099/113] Deprecate zeusPrimeRtdProvider submodule (#9358) Deprecates the zeusPrimeRtdProvider submodule and updates the associated documentation and tests. --- modules/zeusPrimeRtdProvider.js | 326 +------------- modules/zeusPrimeRtdProvider.md | 3 + .../spec/modules/zeusPrimeRtdProvider_spec.js | 410 ------------------ 3 files changed, 6 insertions(+), 733 deletions(-) delete mode 100644 test/spec/modules/zeusPrimeRtdProvider_spec.js diff --git a/modules/zeusPrimeRtdProvider.js b/modules/zeusPrimeRtdProvider.js index 28c46957c50..2a455a14f7b 100644 --- a/modules/zeusPrimeRtdProvider.js +++ b/modules/zeusPrimeRtdProvider.js @@ -1,328 +1,8 @@ -/** - * This module adds Zeus Insights For Publishers (ZIP) provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * - * This module will request the article topics for the current page and add them as page keyvalues - * for the ad requests. - * - * @module modules/zeusInsightsForPublishersRtdProvider - * @requires module:modules/realTimeData - */ - -import { logInfo, logError, logWarn, logMessage } from '../src/utils.js' +import { logWarn } from '../src/utils.js' import { submodule } from '../src/hook.js' -import { ajaxBuilder } from '../src/ajax.js' - -class Logger { - get showDebug() { - if (this._showDebug === true || this._showDebug === false) { - return this._showDebug - } - - return window.zeusPrime?.debug || false - } - set showDebug(shouldShow) { - this._showDebug = shouldShow - } - - get error() { - return logError.bind(this, 'zeusPrimeRtdProvider: ') - } - get warn() { - return logWarn.bind(this, 'zeusPrimeRtdProvider: ') - } - get info() { - return logInfo.bind(this, 'zeusPrimeRtdProvider: ') - } - get debug() { - if (this.showDebug) { - return logMessage.bind(this, 'zeusPrimeRtdProvider: ') - } - - return () => {} - } -} - -var logger = new Logger() - -function loadCommandQueue() { - window.zeusPrime = window.zeusPrime || { cmd: [] } - const queue = [...window.zeusPrime.cmd] - - window.zeusPrime.cmd = [] - window.zeusPrime.cmd.push = (callback) => { - callback(window.zeusPrime) - } - - queue.forEach((callback) => callback(window.zeusPrime)) -} - -function markStatusComplete(key) { - const status = window?.zeusPrime?.status - if (status) { - status[key] = true - } -} - -function createStatus() { - if (window.zeusPrime && !window.zeusPrime.status) { - Object.defineProperty(window.zeusPrime, 'status', { - enumerable: false, - value: { - initComplete: false, - primeKeyValueSet: false, - insightsReqSent: false, - insightsReqReceived: false, - insightsKeyValueSet: false, - scriptComplete: false, - }, - }) - } -} - -function loadPrimeQueryParams() { - try { - const params = new URLSearchParams(window.location.search) - params.forEach((paramValue, paramKey) => { - if (!paramKey.startsWith('zeus_prime_')) { - return - } - - let key = paramKey.replace('zeus_prime_', '') - let value = paramValue.toLowerCase() - - if (value === 'true' || value === '1') { - value = true - } else if (value === 'false' || value === '0') { - value = false - } - - window.zeusPrime[key] = value - }) - } catch (_) {} -} - -const DEFAULT_API = 'https://insights.zeustechnology.com' - -function init(gamId = null, options = {}) { - window.zeusPrime = window.zeusPrime || { cmd: [] } - - window.zeusPrime.gamId = gamId || options.gamId || window.zeusPrime.gamId || undefined - window.zeusPrime.api = DEFAULT_API - window.zeusPrime.hostname = options.hostname || window.location?.hostname || '' - window.zeusPrime.pathname = options.pathname || window.location?.pathname || '' - window.zeusPrime.pageUrl = `${window.zeusPrime.hostname}${window.zeusPrime.pathname}` - window.zeusPrime.pageHash = options.pageHash || null - window.zeusPrime.debug = window.zeusPrime.debug || options.debug === true || false - window.zeusPrime.disabled = window.zeusPrime.disabled || options.disabled === true || false - - loadPrimeQueryParams() - - logger.showDebug = window.zeusPrime.debug - - createStatus() - markStatusComplete('initComplete') -} - -function setTargeting() { - const { gamId, hostname } = window.zeusPrime - - if (typeof gamId !== 'string') { - throw new Error(`window.zeusPrime.gamId must be a string. Received: ${String(gamId)}`) - } - - addKeyValueToGoogletag(`zeus_${gamId}`, hostname) - logger.debug(`Setting zeus_${gamId}=${hostname}`) - markStatusComplete('primeKeyValueSet') -} -function setPrimeAsDisabled() { - addKeyValueToGoogletag('zeus_prime', 'false') - logger.debug('Disabling prime; Setting key-value zeus_prime to false') -} - -function addKeyValueToGoogletag(key, value) { - window.googletag = window.googletag || { cmd: [] } - window.googletag.cmd.push(function () { - window.googletag.pubads().setTargeting(key, value) - }) -} - -function isInsightsPage(pathname = '') { - const NOT_SECTIONS = [ - { - test: /\/search/, - type: 'search', - }, - { - test: /\/author/, - type: 'author', - }, - { - test: /\/event/, - type: 'event', - }, - { - test: /\/homepage/, - type: 'front', - }, - { - test: /^\/?$/, - type: 'front', - }, - ] - - const typeObj = NOT_SECTIONS.find((pg) => pathname.match(pg.test)) - return typeObj === undefined -} - -async function getUrlHash(canonical) { - try { - const buf = await window.crypto.subtle.digest( - 'SHA-1', - new TextEncoder('utf-8').encode(canonical) - ) - const hashed = Array.prototype.map - .call(new Uint8Array(buf), (x) => `00${x.toString(16)}`.slice(-2)) - .join('') - - return hashed - } catch (e) { - logger.error('Failed to load hash', e.message) - logger.debug('Exception', e) - return '' - } -} - -async function sendPrebidRequest(url) { - return new Promise((resolve, reject) => { - const ajax = ajaxBuilder() - ajax(url, { - success: (responseText, response) => { - resolve({ - ...response, - status: response.status, - json: () => JSON.parse(responseText), - }) - }, - - error: (responseText, response) => { - if (!response.status) { - reject(response) - } - - let json = responseText - if (responseText) { - try { - json = JSON.parse(responseText) - } catch (_) { - json = null - } - } - - resolve({ - status: response.status, - json: () => json || null, - responseValue: json, - }) - }, - }) - }) -} - -async function requestTopics() { - const { api, hostname, pageUrl } = window.zeusPrime - - if (!window.zeusPrime.pageHash) { - window.zeusPrime.pageHash = await getUrlHash(pageUrl) - } - - const pageHash = window.zeusPrime.pageHash - const zeusInsightsUrl = `${api}/${hostname}/${pageHash}?article_location=${pageUrl}` - - logger.debug('Requesting topics', zeusInsightsUrl) - try { - markStatusComplete('insightsReqSent') - const response = await sendPrebidRequest(zeusInsightsUrl) - if (response.status === 200) { - logger.debug('topics found') - markStatusComplete('insightsReqReceived') - return await response.json() - } else if ( - response.status === 204 || - response.status < 200 || - (response.status >= 300 && response.status <= 399) - ) { - logger.debug('no topics found') - markStatusComplete('insightsReqReceived') - return null - } else { - logger.error(`Topics request returned error: ${response.status}`) - markStatusComplete('insightsReqReceived') - return null - } - } catch (e) { - logger.error('failed to request topics', e) - return null - } -} - -function setTopicsTargeting(topics = []) { - if (topics.length === 0) { - return - } - - window.googletag = window.googletag || { cmd: [] } - window.googletag.cmd.push(function () { - window.googletag.pubads().setTargeting('zeus_insights', topics) - }) - - markStatusComplete('insightsKeyValueSet') -} - -async function startTopicsRequest() { - if (isInsightsPage(window.zeusPrime.pathname)) { - const response = await requestTopics() - if (response) { - setTopicsTargeting(response?.topics) - } - } else { - logger.debug('This page is not eligible for topics, request will be skipped') - } -} - -async function run(gamId, options = {}) { - logger.showDebug = options.debug || false - - try { - init(gamId, options) - loadCommandQueue() - - if (window.zeusPrime.disabled) { - setPrimeAsDisabled() - } else { - setTargeting() - await startTopicsRequest() - } - } catch (e) { - logger.error('Failed to run.', e.message || e) - } finally { - markStatusComplete('scriptComplete') - } -} - -/** - * @preserve - * Initializes the ZeusPrime RTD Submodule. The config provides the GamID for this - * site that is used to configure Prime. - * @param {object} config The Prebid configuration for this module. - * @param {object} config.params The parameters for this module. - * @param {string} config.params.gamId The Gam ID (or Network Code) in GAM for this site. - */ -function initModule(config) { - const { params } = config || {} - const { gamId, ...rest } = params || {} - run(gamId, rest) +function initModule() { + logWarn('Zeus Prime has been deprecated. This module will be removed in Prebid 8.') } /** diff --git a/modules/zeusPrimeRtdProvider.md b/modules/zeusPrimeRtdProvider.md index f3a6c5018d5..40b44e76f1c 100644 --- a/modules/zeusPrimeRtdProvider.md +++ b/modules/zeusPrimeRtdProvider.md @@ -1,5 +1,8 @@ # Overview +# NOTE: ZEUS PRIME HAS BEEN DEPRECATED! +# THIS MODULE WILL BE REMOVED IN PREBID 8. + Module Name: Zeus Prime RTD Provider Module Type: Rtd Provider Maintainer: support@zeustechnology.com diff --git a/test/spec/modules/zeusPrimeRtdProvider_spec.js b/test/spec/modules/zeusPrimeRtdProvider_spec.js deleted file mode 100644 index 294d98df377..00000000000 --- a/test/spec/modules/zeusPrimeRtdProvider_spec.js +++ /dev/null @@ -1,410 +0,0 @@ -import { zeusPrimeSubmodule } from 'modules/zeusPrimeRtdProvider'; -import { server } from 'test/mocks/xhr.js'; -import * as utils from 'src/utils'; - -async function waitForStatus(statusVar) { - const MAX_COUNT = 20; - let count = 0; - while ( - count <= MAX_COUNT && - window.zeusPrime.status && - window.zeusPrime.status[statusVar] !== true - ) { - count += 1; - await new Promise((resolve) => setTimeout(resolve, 10)); - } - - if (count === MAX_COUNT) { - throw new Error('Timeout waiting for zeusPrimeRtdProvider to complete'); - } -} - -/** - * Execute all the commands in the googletag.cmd queue. - */ -function executeGoogletagTargeting() { - window.googletag.cmd.forEach((cmd) => cmd()); -} - -describe('Zeus Prime RTD submodule', () => { - let logErrorSpy; - let logMessageSpy; - let setTargetingStub; - let originalGtag; - - beforeEach(() => { - logErrorSpy = sinon.spy(utils, 'logError'); - logMessageSpy = sinon.spy(utils, 'logMessage'); - setTargetingStub = sinon.stub(); - window.zeusPrime = { cmd: [] }; - originalGtag = window.googletag; - window.googletag = { - cmd: [], - pubads: () => ({ - setTargeting: setTargetingStub, - }), - }; - - // Mock subtle since this doesnt exists in some test environments due to security in newer browsers. - if (typeof window.crypto.subtle === 'undefined') { - Object.defineProperty(crypto, 'subtle', { value: { digest: () => 'mockHash' } }) - } - }); - - afterEach(() => { - logErrorSpy.restore(); - logMessageSpy.restore(); - window.googletag = originalGtag; - }); - - it('should init and set key-value for zeus_', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_1234', 'www.example.com'); - }); - - it('should init and set key-value for zeus_ from command queue', async () => { - window.zeusPrime.cmd.push((prime) => (prime.gamId = '9876')); - zeusPrimeSubmodule.init({ - params: { - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_9876', 'www.example.com'); - }); - - it('should init with values from location and set key-value for zeus_', async () => { - zeusPrimeSubmodule.init({ params: { gamId: '1234' } }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_1234', 'localhost'); - expect(window.zeusPrime.pathname).to.equal('/context.html'); - }); - - it('should emit error when gamId is not set', async () => { - zeusPrimeSubmodule.init({}); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(0); - sinon.assert.callCount(setTargetingStub, 0); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Failed to run.', - 'window.zeusPrime.gamId must be a string. Received: undefined' - ); - }); - - it('should not make a call to the server when url is a homepage', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - expect(server.requests).to.have.length(0); - }); - - it('should make a call to the server and set key-vlaue when url is an article page and returns topics', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - '{"topics": ["bs0"]}' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(2); - sinon.assert.calledTwice(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith(setTargetingStub.secondCall, 'zeus_insights', [ - 'bs0', - ]); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue when server returns 204', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond(204, { 'Content-Type': 'application/json' }); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue when server returns empty topics array', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Respond - server.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - '{ "topics": [] }' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue and emit error when server returns error status (400)', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond( - 404, - { 'Content-Type': 'application/json' }, - '{"message": "Not found"}' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Topics request returned error: 404' - ); - }); - - it('should not set insights keyvalue and emit error when response is not received (network request)', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].error(); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'failed to request topics' - ); - }); - - it('fails gracefully when crypto fails', async () => { - const digestStub = sinon.stub(window.crypto.subtle, 'digest'); - digestStub.throwsException('Failed to generate digest.'); - - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Failed to load hash' - ); - - digestStub.restore(); - }); - - it('script should add zeus_prime key and not send request when disabled is set', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - disabled: true, - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(0); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledWith(setTargetingStub, 'zeus_prime', 'false'); - }); - - it('debug true enables debug logging', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - debug: true, - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.called(logMessageSpy); - sinon.assert.notCalled(logErrorSpy); - }); - - it('debug false disables debug logging', async () => { - window.zeusPrime.disabled = false; - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.notCalled(logMessageSpy); - sinon.assert.notCalled(logErrorSpy); - }); -}); From b04f56d7b76b2493803939948e6096227decc3f0 Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Thu, 2 Feb 2023 05:46:13 -0800 Subject: [PATCH 100/113] Consent Management: Added config option for user action timeout (#9365) * progress * fixed tests and refactored * reverted some changes made while devloping on my local * in progress * updated action timeout logic * removed comment * reverted a few changes * reverted another change * addressed feedback * refactored actionTimeout logic --- modules/consentManagement.js | 38 +++++++++++++--- test/spec/modules/consentManagement_spec.js | 50 ++++++++++++++++++++- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 217ceecb1c4..bdf6649bcba 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -18,12 +18,16 @@ const CMP_VERSION = 2; export let userCMP; export let consentTimeout; +export let actionTimeout; export let gdprScope; export let staticConsentData; let consentData; let addedConsentHook = false; let provisionalConsent; +let onTimeout; +let timer = null; +let actionTimer = null; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { @@ -39,6 +43,12 @@ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) } +export function setActionTimeout(timeout = setTimeout) { + clearTimeout(timer); + timer = null; + actionTimer = timeout(onTimeout, actionTimeout); +} + /** * 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 @@ -84,6 +94,7 @@ function lookupIabConsent({onSuccess, onError}) { processCmpData(tcfData, {onSuccess, onError}); } else { provisionalConsent = tcfData; + if (!isNaN(actionTimeout) && actionTimer === null && timer != null) setActionTimeout(); } } else { onError('CMP unable to register callback function. Please check CMP setup.'); @@ -162,14 +173,20 @@ function lookupIabConsent({onSuccess, onError}) { * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra * error arguments that will be undefined if there's no error. */ -function loadConsentData(cb) { +export function loadConsentData(cb, callMap = cmpCallMap, timeout = setTimeout) { let isDone = false; - let timer = null; function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { if (timer != null) { clearTimeout(timer); + timer = null; + } + + if (actionTimer != null) { + clearTimeout(actionTimer); + actionTimer = null; } + isDone = true; gdprDataHandler.setConsentData(consentData); if (typeof cb === 'function') { @@ -188,10 +205,11 @@ function loadConsentData(cb) { done(null, true, msg, ...extraArgs); } } - cmpCallMap[userCMP](callbacks); + + callMap[userCMP](callbacks); if (!isDone) { - const onTimeout = () => { + onTimeout = () => { const continueToAuction = (data) => { done(data, false, 'CMP did not load, continuing auction...'); } @@ -200,10 +218,16 @@ function loadConsentData(cb) { onError: () => continueToAuction(storeConsentData(undefined)) }) } + if (consentTimeout === 0) { onTimeout(); } else { - timer = setTimeout(onTimeout, consentTimeout); + if (timer != null) { + clearTimeout(timer); + timer = null; + } + + timer = timeout(onTimeout, consentTimeout); } } } @@ -328,6 +352,10 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); } + if (isNumber(config.actionTimeout)) { + actionTimeout = config.actionTimeout; + } + if (isNumber(config.timeout)) { consentTimeout = config.timeout; } else { diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 47a2e2ab3d9..506b88ad839 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,4 +1,15 @@ -import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData, gdprScope } from 'modules/consentManagement.js'; +import { + setConsentConfig, + requestBidsHook, + resetConsentData, + userCMP, + consentTimeout, + actionTimeout, + staticConsentData, + gdprScope, + loadConsentData, + setActionTimeout +} from 'modules/consentManagement.js'; import { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -726,4 +737,41 @@ describe('consentManagement', function () { }); }); }); + + describe('actionTimeout', function () { + afterEach(function () { + config.resetConfig(); + resetConsentData(); + }); + + it('should set actionTimeout if present', () => { + setConsentConfig({ + gdpr: { timeout: 5000, actionTimeout: 5500 } + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(5000); + expect(actionTimeout).to.be.equal(5500); + }); + + it('should utilize actionTimeout duration on initial user visit when user action is pending', () => { + const cb = () => {}; + const cmpCallMap = { + 'iab': () => {}, + 'static': () => {} + }; + const timeout = sinon.spy(); + + setConsentConfig({ + gdpr: { timeout: 5000, actionTimeout: 5500 } + }); + loadConsentData(cb, cmpCallMap, timeout); + + sinon.assert.calledWith(timeout, sinon.match.any, 5000); + + setActionTimeout(); + + timeout.lastCall.lastArg === 5500; + }); + }); }); From 04bc713d1aff97af7b1c4806d2daadc9d6fe6fa2 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:29:15 +0100 Subject: [PATCH 101/113] ZetaGlobalSsp bid adapter: bidfloor module (#9490) * ZetaGlobalSspBidAdapter: support bidfloors module * remove added space for linting * fix test --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Chris Huie --- modules/zeta_global_sspBidAdapter.js | 15 +++++++++++++++ .../modules/zeta_global_sspBidAdapter_spec.js | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 6c5b9783782..74ddfc0bd06 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -90,6 +90,21 @@ export const spec = { if (!impData.banner && !impData.video) { impData.banner = buildBanner(request); } + + if (typeof request.getFloor === 'function') { + const floorInfo = request.getFloor({ + currency: 'USD', + mediaType: impData.video ? 'video' : 'banner', + size: [ impData.video ? impData.video.w : impData.banner.w, impData.video ? impData.video.h : impData.banner.h ] + }); + if (floorInfo && floorInfo.floor) { + impData.bidfloor = floorInfo.floor; + } + } + if (!impData.bidfloor && params.bidfloor) { + impData.bidfloor = params.bidfloor; + } + return impData; }); diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index d6befa0fc78..f616403cfcd 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -41,6 +41,7 @@ describe('Zeta Ssp Bid Adapter', function () { app: { bundle: 'testBundle' }, + bidfloor: 0.2, test: 1 }; @@ -359,4 +360,11 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.tmax).to.be.undefined; }); + + it('Test provide bidfloor', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.eql(params.bidfloor); + }); }); From 3be964e4854bafbc1ef224407cb3a6227c7871cf Mon Sep 17 00:00:00 2001 From: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:34:50 +0100 Subject: [PATCH 102/113] OneTag Bid Adapter: add gppConsent fetch (#9487) Co-authored-by: federico --- modules/onetagBidAdapter.js | 13 ++++++- test/spec/modules/onetagBidAdapter_spec.js | 43 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index b5217e77cd6..70238294c38 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -61,6 +61,12 @@ function buildRequests(validBidRequests, bidderRequest) { consentRequired: bidderRequest.gdprConsent.gdprApplies }; } + if (bidderRequest && bidderRequest.gppConsent) { + payload.gppConsent = { + consentString: bidderRequest.gppConsent.gppString, + applicableSections: bidderRequest.gppConsent.applicableSections + } + } if (bidderRequest && bidderRequest.uspConsent) { payload.usPrivacy = bidderRequest.uspConsent; } @@ -340,7 +346,7 @@ function getSizes(sizes) { return ret; } -function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { let syncs = []; let params = ''; if (gdprConsent) { @@ -351,6 +357,11 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { params += '&gdpr_consent=' + gdprConsent.consentString; } } + if (gppConsent) { + if (typeof gppConsent.gppString === 'string') { + params += '&gpp_consent=' + gppConsent.gppString; + } + } if (uspConsent && typeof uspConsent === 'string') { params += '&us_privacy=' + uspConsent; } diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 71e897c7f9e..5bd65cf0fd5 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -266,6 +266,27 @@ describe('onetag', function () { expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; }); + it('Should send GPP consent data', function () { + let consentString = 'consentString'; + let applicableSections = [1, 2, 3]; + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + gppString: consentString, + applicableSections: applicableSections + } + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload).to.exist; + expect(payload.gppConsent).to.exist; + expect(payload.gppConsent.consentString).to.exist.and.to.equal(consentString); + expect(payload.gppConsent.applicableSections).to.have.same.members(applicableSections); + }); it('Should send us privacy string', function () { let consentString = 'us_foo'; let bidderRequest = { @@ -375,6 +396,28 @@ describe('onetag', function () { expect(syncs[0].url).to.include(sync_endpoint); expect(syncs[0].url).to.not.match(/(?:[?&](?:gdpr_consent=([^&]*)|gdpr=([^&]*)))+$/); }); + it('Must pass gpp consent string when gppConsent object is available', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: 'foo' + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:gpp_consent=foo([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is null', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: null + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is empty', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, {}); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); it('Should send us privacy string', function () { let usConsentString = 'us_foo'; const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, usConsentString); From 50e107faf3e49e67f7d8bb20eb04ccb5cbe42e5d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 2 Feb 2023 17:08:08 +0000 Subject: [PATCH 103/113] Prebid 7.35.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6249ef52c04..96f6a796918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.35.0-pre", + "version": "7.35.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6467045121b..9d3fbb66093 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.35.0-pre", + "version": "7.35.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ae21d87dbe50bb9f20fe8ecb29fadc32e80c41c8 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 2 Feb 2023 17:08:08 +0000 Subject: [PATCH 104/113] Increment version to 7.36.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 96f6a796918..d766178d188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.35.0", + "version": "7.36.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9d3fbb66093..cf3a7703aac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.35.0", + "version": "7.36.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 3e2b9fca2ab41eb8280925c27b5d970ca43cd6f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 12:38:37 -0500 Subject: [PATCH 105/113] Bump http-cache-semantics from 4.1.0 to 4.1.1 (#9502) Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/kornelski/http-cache-semantics/releases) - [Commits](https://github.com/kornelski/http-cache-semantics/commits) --- updated-dependencies: - dependency-name: http-cache-semantics dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index d766178d188..587b7897394 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.34.0-pre", + "version": "7.36.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -13818,9 +13818,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "node_modules/http-errors": { @@ -36045,9 +36045,9 @@ "dev": true }, "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "http-errors": { From dacbd1b6003000f8b0675f4aae1f9c35c69f5942 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Thu, 2 Feb 2023 12:41:51 -0500 Subject: [PATCH 106/113] Fix gpg Key Expiration for Debian Containers (#9497) Debian containers for yarn are having issues with key expirations. This change resolves that. Eventually the base images should be updated, but that timeline is unknown. There are a number of proposed solutions for the issue, but this one fixes ours. References to the issue: https://github.com/yarnpkg/yarn/issues/7866 Similar in AWS Builds: https://github.com/yarnpkg/yarn/issues/7866 --- .devcontainer/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cfb29ebdfa9..69e13850258 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,8 @@ ARG VARIANT="12" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg + # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends From 62fbcb491b6d40816e84a15ff182dbbe31446e07 Mon Sep 17 00:00:00 2001 From: Nitin Nimbalkar <96475150+nitin0610@users.noreply.github.com> Date: Fri, 3 Feb 2023 01:10:36 +0530 Subject: [PATCH 107/113] Topics Module: Mark Down file added (#9484) * Topics MD file added * Topics MD file changes --- modules/fpdModule/index.md | 7 +++-- modules/topicsFpdModule.md | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 modules/topicsFpdModule.md diff --git a/modules/fpdModule/index.md b/modules/fpdModule/index.md index 638c966883a..238881db96b 100644 --- a/modules/fpdModule/index.md +++ b/modules/fpdModule/index.md @@ -16,9 +16,11 @@ Validation Submodule: - verify that certain OpenRTB attributes are not specified - optionally suppress user FPD based on the existence of _pubcid_optout +Topic Submodule: +- populate first party/third party topics data onto user.data in bid stream. 1. Module initializes on first load and set bidRequestHook -2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations dependant on submodule +2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations/topics dependant on submodule 3. After hook complete, it is disabled - meaning module only runs on first auction 4. To reinitiate the module, run pbjs.refreshFPD(), which allows module to rerun as if initial load @@ -43,4 +45,5 @@ pbjs.setConfig({ At least one of the submodules must be included in order to successfully run the corresponding above operations. enrichmentFpdModule -validationFpdModule \ No newline at end of file +validationFpdModule +topicsFpdModule \ No newline at end of file diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md new file mode 100644 index 00000000000..b1bc3cb0d5b --- /dev/null +++ b/modules/topicsFpdModule.md @@ -0,0 +1,62 @@ +# Overview + +Module Name: topicsFpdModule + +# Description +Purpose of this module is to call the Topics API (document.browsingTopics()) which will fetch the first party domain as well third party domain(Iframe) topics data which will be sent onto user.data in bid stream. + +The intent of the Topics API is to provide callers (including third-party ad-tech or advertising providers on the page that run script) with coarse-grained advertising topics that the page visitor might currently be interested in. + +Topics Module(topicsFpdModule) should be included in prebid final package to call topics API. +Module topicsFpdModule helps to call the Topics API which will send topics data in bid stream (onto user.data) + +``` +try { + if ('browsingTopics' in document && document.featurePolicy.allowsFeature('browsing-topics')) { + topics = document.browsingTopics(); + } +} catch (e) { + console.error('Could not call topics API', e); +} +``` + +# Topics Iframe Configuration + +Topics iframe implementation is the enhancements of existing module under topicsFpdModule.js where different bidders will call the topic API under their domain to fetch the topics for respective domain and the segment data will be part of ORTB request under user.data object. Default config is maintained in the module itself. + +Below are the configuration which can be used to configure and override the default config maintained in the module. + +``` +pbjs.setConfig({ + userSync: { + ..., + topics: { + maxTopicCaller: 3, // SSP rotation + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html', + expiry: 7 // Configurable expiry days + },{ + bidder: 'rubicon', + iframeURL: 'https://rubicon.com:8080/topics/fpd/topic.html', // dummy URL + expiry: 7 // Configurable expiry days + },{ + bidder: 'appnexus', + iframeURL: 'https://appnexus.com:8080/topics/fpd/topic.html', // dummy URL + expiry: 7 // Configurable expiry days + }] + } + .... + } +}) +``` + +## Topics Config Descriptions + +| Field | Required? | Type | Description | +|---|---|---|---| +| topics.maxTopicCaller | no | integer | Defines the maximum numbers of Bidders Iframe which needs to be loaded on the publisher page. Default is 1 which is hardcoded in Module. Eg: topics.maxTopicCaller is set to 3. If there are 10 bidders configured along with their iframe URLS, random 3 bidders iframe URL is loaded which will call TOPICS API. If topics.maxTopicCaller is set to 0, it will load random 1(default) bidder iframe atleast. | +| topics.bidders | no | Array of objects | Array of topics callers with the iframe locations and other necessary informations like bidder(Bidder code) and expiry. Default Array of topics in the module itself.| +| topics.bidders[].bidder | yes | string | Bidder Code of the bidder(SSP). | +| topics.bidders[].iframeURL | yes | string | URL which is hosted on bidder/SSP/third-party domains which will call Topics API. | +| topics.bidders[].expiry | no | integer | Max number of days where Topics data will be persist. If Data is stored for more than mentioned expiry day, it will be deleted from storage. Default is 21 days which is hardcoded in Module. | \ No newline at end of file From e9cc90742d332f2c6211414acd88d8ea506994ab Mon Sep 17 00:00:00 2001 From: Alexandru Date: Sat, 4 Feb 2023 17:07:24 +0200 Subject: [PATCH 108/113] BrightcomSSP bid adapter: add new adapter (#9411) * BrightcomSSP: add new adapter * BrightcomSSP: add glvid * BrightcomSSP: add missing gvlid; update isBidRequestValidC --- modules/brightcomBidAdapter.js | 1 + modules/brightcomSSPBidAdapter.js | 323 ++++++++++++++ modules/brightcomSSPBidAdapter.md | 46 ++ .../modules/brightcomSSPBidAdapter_spec.js | 411 ++++++++++++++++++ 4 files changed, 781 insertions(+) create mode 100644 modules/brightcomSSPBidAdapter.js create mode 100644 modules/brightcomSSPBidAdapter.md create mode 100644 test/spec/modules/brightcomSSPBidAdapter_spec.js diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index bbe0203772a..15b4175b59a 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -9,6 +9,7 @@ const URL = 'https://brightcombid.marphezis.com/hb'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], + gvlid: 883, isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js new file mode 100644 index 00000000000..b7a9aa3fdc9 --- /dev/null +++ b/modules/brightcomSSPBidAdapter.js @@ -0,0 +1,323 @@ +import { + getBidIdParameter, + isArray, + getWindowTop, + getUniqueIdentifierStr, + deepSetValue, + logError, + logWarn, + createTrackPixelHtml, + getWindowSelf, + isFn, + isPlainObject, +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'bcmssp'; +const URL = 'https://rt.marphezis.com/hb'; +const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' + +export const spec = { + code: BIDDER_CODE, + gvlid: 883, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidderError, + onTimeout, + onBidWon, + getUserSyncs, +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + const impressions = bidReqs.map(bid => { + let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; + bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + + const bidFloor = _getBidFloor(bid); + + if (bidFloor) { + imp.bidfloor = bidFloor; + } + + return imp; + }) + + const referrer = bidderRequest?.refererInfo?.page || ''; + const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); + + const payload = { + id: getUniqueIdentifierStr(), + imp: impressions, + site: { + domain: bidderRequest?.refererInfo?.domain || '', + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + if (bidderRequest?.gdprConsent) { + deepSetValue(payload, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest?.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (config.getConfig('coppa') === true) { + deepSetValue(payload, 'regs.coppa', 1); + } + + if (bidReqs?.[0]?.schain) { + deepSetValue(payload, 'source.ext.schain', bidReqs[0].schain) + } + + if (bidReqs?.[0]?.userIdAsEids) { + deepSetValue(payload, 'user.ext.eids', bidReqs[0].userIdAsEids || []) + } + + if (bidReqs?.[0].userId) { + deepSetValue(payload, 'user.ext.ids', bidReqs[0].userId || []) + } + + return { + method: 'POST', + url: URL, + data: JSON.stringify(payload), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + let response = []; + if (!serverResponse.body || typeof serverResponse.body != 'object') { + logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return response; + } + + const {body: {id, seatbid}} = serverResponse; + + try { + if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { + response = seatbid[0].bid.map(bid => { + return { + requestId: bid.impid, + cpm: parseFloat(bid.price), + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(bid), + ttl: 60, + meta: { + advertiserDomains: bid?.adomain || [] + } + }; + }); + } + } catch (e) { + logError(e, {id, seatbid}); + } + + return response; +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function onTimeout(timeoutData) { + if (timeoutData === null) { + return; + } + + _trackEvent('timeout', timeoutData); +} + +function onBidderError(errorData) { + if (errorData === null || !errorData.bidderRequest) { + return; + } + + _trackEvent('error', errorData.bidderRequest) +} + +function onBidWon(bid) { + if (bid === null) { + return; + } + + _trackEvent('bidwon', bid) +} + +function _trackEvent(endpoint, data) { + ajax(`${TRACK_EVENT_URL}/${endpoint}`, null, JSON.stringify(data), { + method: 'POST', + withCredentials: false + }); +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, {w, h} = {}) { + return getWindowTop().document.visibilityState === 'visible' ? _getPercentInView(element, topWin, {w, h}) : 0; +} + +function _isIframe() { + try { + return getWindowSelf() !== getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBoundingBox(element, {w, h} = {}) { + let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return {width, height, left, top, right, bottom}; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, right: rects[0].right, top: rects[0].top, bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +function _getPercentInView(element, topWin, {w, h} = {}) { + const elementBoundingBox = _getBoundingBox(element, {w, h}); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([{ + left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight + }, elementBoundingBox]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +function _getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', mediaType: '*', size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +registerBidder(spec); diff --git a/modules/brightcomSSPBidAdapter.md b/modules/brightcomSSPBidAdapter.md new file mode 100644 index 00000000000..8d0e4ec70dc --- /dev/null +++ b/modules/brightcomSSPBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: Brightcom SSP Bid Adapter +Module Type: Bidder Adapter +Maintainer: alexandruc@brightcom.com +``` + +# Description + +Brightcom's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [{ + bidder: 'bcmssp', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'bcmssp', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/test/spec/modules/brightcomSSPBidAdapter_spec.js b/test/spec/modules/brightcomSSPBidAdapter_spec.js new file mode 100644 index 00000000000..6f35a7a290b --- /dev/null +++ b/test/spec/modules/brightcomSSPBidAdapter_spec.js @@ -0,0 +1,411 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/brightcomSSPBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config'; + +const URL = 'https://rt.marphezis.com/hb'; + +describe('brightcomSSPBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisherId not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].mediaTypes.banner.sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'bcmssp', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + page: 'http://example.com/page.html', + domain: 'example.com', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + + it('sends us_privacy', function () { + const bidderRequest = { + uspConsent: '1YYY' + }; + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) + + expect(data.regs).to.not.equal(null); + expect(data.regs.ext).to.not.equal(null); + expect(data.regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('sends coppa', function () { + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + + const data = JSON.parse(spec.buildRequests(bidRequests).data) + expect(data.regs).to.not.be.undefined; + expect(data.regs.coppa).to.equal(1); + }); + + it('sends schain', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data).to.not.be.undefined; + expect(data.source).to.not.be.undefined; + expect(data.source.ext).to.not.be.undefined; + expect(data.source.ext.schain).to.not.be.undefined; + expect(data.source.ext.schain.complete).to.equal(1); + expect(data.source.ext.schain.ver).to.equal('1.0'); + expect(data.source.ext.schain.nodes).to.not.be.undefined; + expect(data.source.ext.schain.nodes).to.lengthOf(1); + expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); + expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); + expect(data.source.ext.schain.nodes[0].hp).to.equal(1); + expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); + expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); + expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); + }); + + it('sends user eid parameters', function () { + bidRequests[0].userIdAsEids = [{ + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.eids).to.not.be.undefined; + expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); + }); + + it('sends user id parameters', function () { + const userId = { + sharedid: { + id: '01*******', + third: '01E*******' + } + }; + + bidRequests[0].userId = userId; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.ids).is.deep.equal(userId); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'] + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); From c8ac9db51d5ff92ba9da054daca2d8bc355d48bc Mon Sep 17 00:00:00 2001 From: AndreaC <67786179+darkstarac@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:36:09 +0100 Subject: [PATCH 109/113] AIDEM Bidder Adapter: changed required params and win notice payload (#9457) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object * PlacementId is now a required parameter Added optional rateLimit parameter Added publisherId, siteId, placementId in win notice payload Added unit tests * Revert to optional placementId parameter Added missing semicolons --------- Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar --- modules/aidemBidAdapter.js | 155 +++++++++++++--------- modules/aidemBidAdapter.md | 17 ++- test/spec/modules/aidemBidAdapter_spec.js | 87 ++++++++++-- 3 files changed, 177 insertions(+), 82 deletions(-) diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index 081a0324ddb..e4d5c618b77 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -1,4 +1,4 @@ -import {_each, contains, deepAccess, deepSetValue, getDNT, isBoolean, isStr, logError, logInfo} from '../src/utils.js'; +import {_each, contains, deepAccess, deepSetValue, getDNT, isBoolean, isStr, isNumber, logError, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -21,6 +21,8 @@ export const ERROR_CODES = { SITE_ID_INVALID_VALUE: 4, MEDIA_TYPE_NOT_SUPPORTED: 5, PUBLISHER_ID_INVALID_VALUE: 6, + INVALID_RATELIMIT: 7, + PLACEMENT_ID_INVALID_VALUE: 8, }; const endpoints = { @@ -30,29 +32,29 @@ const endpoints = { timeout: `${BASE_URL}/notice/timeout`, error: `${BASE_URL}/notice/error`, } -} +}; export function setEndPoints(env = null, path = '', mediaType = BANNER) { switch (env) { case 'local': - endpoints.request = mediaType === BANNER ? `${LOCAL_BASE_URL}${path}/bid/request` : `${LOCAL_BASE_URL}${path}/bid/videorequest` - endpoints.notice.win = `${LOCAL_BASE_URL}${path}/notice/win` - endpoints.notice.error = `${LOCAL_BASE_URL}${path}/notice/error` - endpoints.notice.timeout = `${LOCAL_BASE_URL}${path}/notice/timeout` + endpoints.request = mediaType === BANNER ? `${LOCAL_BASE_URL}${path}/bid/request` : `${LOCAL_BASE_URL}${path}/bid/videorequest`; + endpoints.notice.win = `${LOCAL_BASE_URL}${path}/notice/win`; + endpoints.notice.error = `${LOCAL_BASE_URL}${path}/notice/error`; + endpoints.notice.timeout = `${LOCAL_BASE_URL}${path}/notice/timeout`; break; case 'main': - endpoints.request = mediaType === BANNER ? `${BASE_URL}${path}/bid/request` : `${BASE_URL}${path}/bid/videorequest` - endpoints.notice.win = `${BASE_URL}${path}/notice/win` - endpoints.notice.error = `${BASE_URL}${path}/notice/error` - endpoints.notice.timeout = `${BASE_URL}${path}/notice/timeout` + endpoints.request = mediaType === BANNER ? `${BASE_URL}${path}/bid/request` : `${BASE_URL}${path}/bid/videorequest`; + endpoints.notice.win = `${BASE_URL}${path}/notice/win`; + endpoints.notice.error = `${BASE_URL}${path}/notice/error`; + endpoints.notice.timeout = `${BASE_URL}${path}/notice/timeout`; break; } - return endpoints + return endpoints; } config.getConfig('aidem', function (config) { - if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path, config.aidem.mediaType) } -}) + if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path, config.aidem.mediaType); } +}); // AIDEM Custom FN function recur(obj) { @@ -121,8 +123,8 @@ function getDevice() { function getRegs() { let regs = {}; - const consentManagement = config.getConfig('consentManagement') - const coppa = config.getConfig('coppa') + const consentManagement = config.getConfig('consentManagement'); + const coppa = config.getConfig('coppa'); if (consentManagement && !!(consentManagement.gdpr)) { deepSetValue(regs, 'gdpr_applies', !!consentManagement.gdpr); } else { @@ -145,11 +147,15 @@ function getRegs() { } function getPageUrl(bidderRequest) { - return bidderRequest?.refererInfo?.page + return bidderRequest?.refererInfo?.page; } function buildWinNotice(bid) { + const params = bid.params[0]; return { + publisherId: params.publisherId, + siteId: params.siteId, + placementId: params.placementId, burl: deepAccess(bid, 'meta.burl'), cpm: bid.cpm, currency: bid.currency, @@ -161,7 +167,7 @@ function buildWinNotice(bid) { ttl: bid.ttl, requestTimestamp: bid.requestTimestamp, responseTimestamp: bid.responseTimestamp, - } + }; } function buildErrorNotice(prebidErrorResponse) { @@ -171,29 +177,29 @@ function buildErrorNotice(prebidErrorResponse) { auctionId: prebidErrorResponse.auctionId, bidderRequestId: prebidErrorResponse.bidderRequestId, metrics: {} - } + }; } function hasValidFloor(obj) { - if (!obj) return false - const hasValue = !isNaN(Number(obj.value)) - const hasCurrency = contains(AVAILABLE_CURRENCIES, obj.currency) - return hasValue && hasCurrency + if (!obj) return false; + const hasValue = !isNaN(Number(obj.value)); + const hasCurrency = contains(AVAILABLE_CURRENCIES, obj.currency); + return hasValue && hasCurrency; } function getMediaType(bidRequest) { - if ((bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('video')) || bidRequest.params.hasOwnProperty('video')) { return VIDEO } - return BANNER + if ((bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('video')) || bidRequest.params.hasOwnProperty('video')) { return VIDEO; } + return BANNER; } function getPrebidRequestFields(bidderRequest, bidRequests) { - const payload = {} + const payload = {}; // Base Payload Data deepSetValue(payload, 'id', bidderRequest.auctionId); // Impressions - setPrebidImpressionObject(bidRequests, payload) + setPrebidImpressionObject(bidRequests, payload); // Device - deepSetValue(payload, 'device', getDevice()) + deepSetValue(payload, 'device', getDevice()); // Timeout deepSetValue(payload, 'tmax', bidderRequest.timeout); // Currency @@ -203,13 +209,13 @@ function getPrebidRequestFields(bidderRequest, bidRequests) { // Privacy Regs deepSetValue(payload, 'regs', getRegs()); // Site - setPrebidSiteObject(bidderRequest, payload) + setPrebidSiteObject(bidderRequest, payload); // Environment - setPrebidRequestEnvironment(payload) + setPrebidRequestEnvironment(payload); // AT auction type deepSetValue(payload, 'at', 1); - return payload + return payload; } function setPrebidImpressionObject(bidRequests, payload) { @@ -220,24 +226,24 @@ function setPrebidImpressionObject(bidRequests, payload) { deepSetValue(impressionObject, 'id', bidRequest.bidId); // Transaction id deepSetValue(impressionObject, 'tid', deepAccess(bidRequest, 'transactionId')); - // Placement id + // placement id deepSetValue(impressionObject, 'tagid', deepAccess(bidRequest, 'params.placementId', null)); // Publisher id deepSetValue(payload, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); // Site id deepSetValue(payload, 'site.id', deepAccess(bidRequest, 'params.siteId')); - const mediaType = getMediaType(bidRequest) + const mediaType = getMediaType(bidRequest); switch (mediaType) { case 'banner': - setPrebidImpressionObjectBanner(bidRequest, impressionObject) + setPrebidImpressionObjectBanner(bidRequest, impressionObject); break; case 'video': - setPrebidImpressionObjectVideo(bidRequest, impressionObject) + setPrebidImpressionObjectVideo(bidRequest, impressionObject); break; } // Floor (optional) - setPrebidImpressionObjectFloor(bidRequest, impressionObject) + setPrebidImpressionObjectFloor(bidRequest, impressionObject); impressionObject.imp_ext = {}; @@ -272,10 +278,10 @@ function setPrebidRequestEnvironment(payload) { } function setPrebidImpressionObjectFloor(bidRequest, impressionObject) { - const floor = deepAccess(bidRequest, 'params.floor') + const floor = deepAccess(bidRequest, 'params.floor'); if (hasValidFloor(floor)) { - deepSetValue(impressionObject, 'floor.value', floor.value) - deepSetValue(impressionObject, 'floor.currency', floor.currency) + deepSetValue(impressionObject, 'floor.value', floor.value); + deepSetValue(impressionObject, 'floor.currency', floor.currency); } } @@ -322,7 +328,7 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { deepSetValue(prebidResponseBidObject, 'currency', openRTBResponseBidObject.cur ? openRTBResponseBidObject.cur.toUpperCase() : DEFAULT_CURRENCY); deepSetValue(prebidResponseBidObject, 'width', openRTBResponseBidObject.w); deepSetValue(prebidResponseBidObject, 'height', openRTBResponseBidObject.h); - deepSetValue(prebidResponseBidObject, 'dealId', openRTBResponseBidObject.dealid) + deepSetValue(prebidResponseBidObject, 'dealId', openRTBResponseBidObject.dealid); deepSetValue(prebidResponseBidObject, 'netRevenue', true); deepSetValue(prebidResponseBidObject, 'ttl', 60000); @@ -335,13 +341,13 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { deepSetValue(prebidResponseBidObject, 'mediaType', BANNER); deepSetValue(prebidResponseBidObject, 'ad', openRTBResponseBidObject.adm); } - setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) - return prebidResponseBidObject + setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject); + return prebidResponseBidObject; } function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) { logInfo('AIDEM Bid Adapter meta', openRTBResponseBidObject); - deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', openRTBResponseBidObject.adomain); + deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', deepAccess(openRTBResponseBidObject, 'meta.advertiserDomains')); if (openRTBResponseBidObject.cat && Array.isArray(openRTBResponseBidObject.cat)) { const primaryCatId = openRTBResponseBidObject.cat.shift(); deepSetValue(prebidResponseBidObject, 'meta.primaryCatId', primaryCatId); @@ -357,28 +363,28 @@ function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponse } function hasValidMediaType(bidRequest) { - const supported = hasBannerMediaType(bidRequest) || hasVideoMediaType(bidRequest) + const supported = hasBannerMediaType(bidRequest) || hasVideoMediaType(bidRequest); if (!supported) { logError('AIDEM Bid Adapter: media type not supported', { bidder: BIDDER_CODE, code: ERROR_CODES.MEDIA_TYPE_NOT_SUPPORTED }); } - return supported + return supported; } function hasBannerMediaType(bidRequest) { - return !!deepAccess(bidRequest, 'mediaTypes.banner') + return !!deepAccess(bidRequest, 'mediaTypes.banner'); } function hasVideoMediaType(bidRequest) { - return !!deepAccess(bidRequest, 'mediaTypes.video') + return !!deepAccess(bidRequest, 'mediaTypes.video'); } function hasValidBannerMediaType(bidRequest) { - const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes') + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); if (!sizes) { logError('AIDEM Bid Adapter: media type sizes missing', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); return false; } - return true + return true; } function hasValidVideoMediaType(bidRequest) { @@ -387,23 +393,38 @@ function hasValidVideoMediaType(bidRequest) { logError('AIDEM Bid Adapter: media type playerSize missing', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); return false; } - return true + return true; } function hasValidVideoParameters(bidRequest) { - let valid = true + let valid = true; const adUnitsParameters = deepAccess(bidRequest, 'mediaTypes.video'); const bidderParameter = deepAccess(bidRequest, 'params.video'); for (let property of REQUIRED_VIDEO_PARAMS) { - const hasAdUnitParameter = adUnitsParameters.hasOwnProperty(property) - const hasBidderParameter = bidderParameter && bidderParameter.hasOwnProperty(property) + const hasAdUnitParameter = adUnitsParameters.hasOwnProperty(property); + const hasBidderParameter = bidderParameter && bidderParameter.hasOwnProperty(property); if (!hasAdUnitParameter && !hasBidderParameter) { logError(`AIDEM Bid Adapter: ${property} is not included in either the adunit or params level`, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); - valid = false + valid = false; } } - return valid + return valid; +} + +function passesRateLimit(bidRequest) { + const rateLimit = deepAccess(bidRequest, 'params.rateLimit', 1); + if (!isNumber(rateLimit) || rateLimit > 1 || rateLimit < 0) { + logError('AIDEM Bid Adapter: invalid rateLimit (must be a number between 0 and 1)', { bidder: BIDDER_CODE, code: ERROR_CODES.INVALID_RATELIMIT }); + return false; + } + if (rateLimit !== 1) { + const randomRateValue = Math.random(); + if (randomRateValue > rateLimit) { + return false; + } + } + return true; } function hasValidParameters(bidRequest) { @@ -421,7 +442,7 @@ function hasValidParameters(bidRequest) { return false; } - return true + return true; } export const spec = { @@ -431,28 +452,32 @@ export const spec = { logInfo('bid: ', bidRequest); // check if request has valid mediaTypes - if (!hasValidMediaType(bidRequest)) return false + if (!hasValidMediaType(bidRequest)) return false; // check if request has valid media type parameters at adUnit level if (hasBannerMediaType(bidRequest) && !hasValidBannerMediaType(bidRequest)) { - return false + return false; } if (hasVideoMediaType(bidRequest) && !hasValidVideoMediaType(bidRequest)) { - return false + return false; } if (hasVideoMediaType(bidRequest) && !hasValidVideoParameters(bidRequest)) { - return false + return false; } - return hasValidParameters(bidRequest) + if (!hasValidParameters(bidRequest)) { + return false; + } + + return passesRateLimit(bidRequest); }, buildRequests: function(validBidRequests, bidderRequest) { logInfo('validBidRequests: ', validBidRequests); logInfo('bidderRequest: ', bidderRequest); - const prebidRequest = getPrebidRequestFields(bidderRequest, validBidRequests) + const prebidRequest = getPrebidRequestFields(bidderRequest, validBidRequests); const payloadString = JSON.stringify(prebidRequest); return { @@ -474,7 +499,7 @@ export const spec = { return; } logInfo('CPM OK'); - const bid = getPrebidResponseBidObject(bidObject) + const bid = getPrebidResponseBidObject(bidObject); bids.push(bid); }); return bids; @@ -483,14 +508,14 @@ export const spec = { onBidWon: function(bid) { // Bidder specific code logInfo('onBidWon bid: ', bid); - const notice = buildWinNotice(bid) + const notice = buildWinNotice(bid); ajax(endpoints.notice.win, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); }, onBidderError: function({ bidderRequest }) { // Bidder specific code - const notice = buildErrorNotice(bidderRequest) + const notice = buildErrorNotice(bidderRequest); ajax(endpoints.notice.error, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); }, -} +}; registerBidder(spec); diff --git a/modules/aidemBidAdapter.md b/modules/aidemBidAdapter.md index 342a264da01..b59014c76ed 100644 --- a/modules/aidemBidAdapter.md +++ b/modules/aidemBidAdapter.md @@ -14,11 +14,12 @@ This module is GDPR and CCPA compliant, and no 3rd party userIds are allowed. ## Global Bid Params -| Name | Scope | Description | Example | Type | -|---------------|----------|---------------------|---------------|----------| -| `siteId` | required | Unique site ID | `'ABCDEF'` | `String` | -| `publisherId` | required | Unique publisher ID | `'ABCDEF'` | `String` | -| `placementId` | optional | Unique publisher tag ID | `'ABCDEF'` | `String` | +| Name | Scope | Description | Example | Type | +|---------------|----------|-------------------------|------------|----------| +| `siteId` | required | Unique site ID | `'ABCDEF'` | `String` | +| `publisherId` | required | Unique publisher ID | `'ABCDEF'` | `String` | +| `placementId` | optional | Unique publisher tag ID | `'ABCDEF'` | `String` | +| `rateLimit` | optional | Limit the volume sent to AIDEM. Must be between 0 and 1 | `0.6` | `Number` | ### Banner Bid Params @@ -67,7 +68,8 @@ var adUnits = [{ bids: [{ bidder: 'aidem', params: { - siteId: 'prebid-test-site', + siteId: 'prebid-test-siteId', + publisherId: 'prebid-test-publisherId', }, }] }]; @@ -90,7 +92,8 @@ var adUnits = [{ bids: [{ bidder: 'aidem', params: { - siteId: 'prebid-test-site', + siteId: 'prebid-test-siteId', + publisherId: 'prebid-test-publisherId', }, }] }]; diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 71edfcf82fb..f58e49eb364 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -13,7 +13,7 @@ const VALID_BIDS = [ params: { siteId: '301491', publisherId: '3021491', - placementId: 13144370, + placementId: '13144370', }, mediaTypes: { banner: { @@ -26,7 +26,7 @@ const VALID_BIDS = [ params: { siteId: '301491', publisherId: '3021491', - placementId: 13144370, + placementId: '13144370', }, mediaTypes: { video: { @@ -110,7 +110,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', }, }, { @@ -126,7 +126,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', }, }, { @@ -143,7 +143,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', video: { size: [480, 40] } @@ -167,8 +167,8 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ } }, params: { - siteId: 1, - placementId: 13144370 + siteId: '1', + placementId: '13144370' }, src: 'client', transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' @@ -192,8 +192,8 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ } }, params: { - siteId: 1, - placementId: 13144370 + siteId: '1', + placementId: '13144370' }, src: 'client', transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' @@ -289,14 +289,25 @@ const WIN_NOTICE = { 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', - 'hb_adomain': 'tenutabene.it' + 'hb_adomain': 'example.com' }, + 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', 'currency': [ 'USD' ], 'mediaType': 'banner', + 'advertiserDomains': [ + 'abc.com' + ], 'size': '300x250', + 'params': [ + { + 'placementId': '13144370', + 'siteId': '23434', + 'publisherId': '7689670753' + } + ], 'width': 300, 'height': 250, 'status': 'rendered', @@ -365,6 +376,62 @@ describe('Aidem adapter', () => { deepSetValue(validVideoRequest.params, 'video.size', [640, 480]) expect(spec.isBidRequestValid(validVideoRequest)).to.be.true }); + + it('BANNER: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.true + }); + + it('BANNER: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.true + }); + + it('VIDEO: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); }); describe('buildRequests', () => { From 3d3b0d7dcdd18e67ca6a168275bc49a276af1580 Mon Sep 17 00:00:00 2001 From: Maxim Schuwalow <16665913+mschuwalow@users.noreply.github.com> Date: Mon, 6 Feb 2023 15:43:08 +0100 Subject: [PATCH 110/113] LiveIntent Id module: Update live-connect-js version (#9505) * Update live-connect-js version * fix eslint comment --- modules/liveIntentIdSystem.js | 2 +- package-lock.json | 14 +++++++------- package.json | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index de70b0eaccd..9f45daeea29 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -7,7 +7,7 @@ import { triggerPixel, logError } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/esm/initializer.js'; +import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; diff --git a/package-lock.json b/package-lock.json index 587b7897394..dca60dbf37e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.5" + "live-connect-js": "^4.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16229,9 +16229,9 @@ "dev": true }, "node_modules/live-connect-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.5.tgz", - "integrity": "sha512-KqxE+V/050nK2tUx8PnAtQBOK4E29WVasQTrLkkCwSebmV5uqMu+VMcwhJSbnyh/g+GhDAE/LL9RB6X9vcmLrg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", + "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", "dependencies": { "tiny-hashes": "1.0.1" }, @@ -37865,9 +37865,9 @@ "dev": true }, "live-connect-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.5.tgz", - "integrity": "sha512-KqxE+V/050nK2tUx8PnAtQBOK4E29WVasQTrLkkCwSebmV5uqMu+VMcwhJSbnyh/g+GhDAE/LL9RB6X9vcmLrg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", + "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", "requires": { "tiny-hashes": "1.0.1" } diff --git a/package.json b/package.json index cf3a7703aac..7163c732634 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.5" + "live-connect-js": "^4.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" From d694fe01fe9781ceecb41d71508638a733d1f61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Mon, 6 Feb 2023 16:44:42 +0200 Subject: [PATCH 111/113] Vidazoo Bid Adapter - webSessionId request param (#9504) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * Vidazoo Bid Adapter - added webSessionId to request --------- Co-authored-by: roman Co-authored-by: Saar Amrani <89377180+saar120@users.noreply.github.com> Co-authored-by: Saar Amrani --- modules/vidazooBidAdapter.js | 4 +++- test/spec/modules/vidazooBidAdapter_spec.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 96dcc182436..3f3bb66d8ae 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -27,6 +27,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; +export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { @@ -131,7 +132,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { bidRequestsCount: bidRequestsCount, bidderRequestsCount: bidderRequestsCount, bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout + bidderTimeout: bidderTimeout, + webSessionId: webSessionId }; appendUserIdsToRequestPayload(data, userId); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 134e3e66256..24d565805d3 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -13,6 +13,7 @@ import { getUniqueDealId, getNextDealId, getVidazooSessionId, + webSessionId } from 'modules/vidazooBidAdapter.js'; import * as utils from 'src/utils.js'; import {version} from 'package.json'; @@ -283,6 +284,7 @@ describe('VidazooBidAdapter', function () { uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, + webSessionId: webSessionId, mediaTypes: { video: { api: [2], @@ -350,7 +352,8 @@ describe('VidazooBidAdapter', function () { isStorageAllowed: true, gpid: '1234567890', cat: ['IAB2'], - pagecat: ['IAB2-2'] + pagecat: ['IAB2-2'], + webSessionId: webSessionId } }); }); From 2d31a525ac1659a5d4ccd1aa1d5921e826ba70be Mon Sep 17 00:00:00 2001 From: anthonyjl92 Date: Mon, 6 Feb 2023 09:46:12 -0500 Subject: [PATCH 112/113] pass referer to ortb request (#9475) Co-authored-by: Anthony Lin --- modules/33acrossBidAdapter.js | 17 +++-- test/spec/modules/33acrossBidAdapter_spec.js | 69 +++++++++++++++----- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index d9524a281f8..e9901794ff9 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -167,7 +167,8 @@ function buildRequests(bidRequests, bidderRequest) { ttxSettings, gdprConsent, uspConsent, - pageUrl + pageUrl, + referer } = _buildRequestParams(bidRequests, bidderRequest); const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests); @@ -181,6 +182,7 @@ function buildRequests(bidRequests, bidderRequest) { gdprConsent, uspConsent, pageUrl, + referer, ttxSettings }) ) @@ -199,7 +201,9 @@ function _buildRequestParams(bidRequests, bidderRequest) { const uspConsent = bidderRequest && bidderRequest.uspConsent; - const pageUrl = bidderRequest?.refererInfo?.page + const pageUrl = bidderRequest?.refererInfo?.page; + + const referer = bidderRequest?.refererInfo?.ref; adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); @@ -207,7 +211,8 @@ function _buildRequestParams(bidRequests, bidderRequest) { ttxSettings, gdprConsent, uspConsent, - pageUrl + pageUrl, + referer } } @@ -241,7 +246,7 @@ function _getMRAKey(bidRequest) { } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, referer, ttxSettings }) { const ttxRequest = {}; const firstBidRequest = bidRequests[0]; const { siteId, test } = firstBidRequest.params; @@ -262,6 +267,10 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU ttxRequest.site.page = pageUrl; } + if (referer) { + ttxRequest.site.ref = referer; + } + ttxRequest.id = firstBidRequest.auctionId; if (gdprConsent.consentString) { diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 2680544d00b..3b3c05660df 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -218,6 +218,14 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withReferer = referer => { + Object.assign(ttxRequest.site, { + ref: referer + }); + + return this; + }; + this.withSchain = schain => { Object.assign(ttxRequest, { source: { @@ -1187,26 +1195,51 @@ describe('33acrossBidAdapter:', function () { }); }); - context('when referer value is available', function() { - it('returns corresponding server requests with site.page set', function() { - const bidderRequest = { - refererInfo: { - page: 'http://foo.com/bar' - } - }; + context('when refererInfo values are available', function() { + context('when refererInfo.page is defined', function() { + it('returns corresponding server requests with site.page set', function() { + const bidderRequest = { + refererInfo: { + page: 'http://foo.com/bar' + } + }; - const ttxRequest = new TtxRequestBuilder() - .withBanner() - .withProduct() - .withPageUrl('http://foo.com/bar') - .build(); - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withPageUrl('http://foo.com/bar') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when refererInfo.ref is defined', function() { + it('returns corresponding server requests with site.ref set', function() { + const bidderRequest = { + refererInfo: { + ref: 'google.com' + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withReferer('google.com') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); }); }); @@ -1246,7 +1279,7 @@ describe('33acrossBidAdapter:', function () { }); context('when referer value is not available', function() { - it('returns corresponding server requests without site.page set', function() { + it('returns corresponding server requests without site.page and site.ref set', function() { const bidderRequest = { refererInfo: {} }; From e5d1c92259d6af59d8b756cbd03312f3d14df245 Mon Sep 17 00:00:00 2001 From: Scott Floam Date: Mon, 6 Feb 2023 11:46:45 -0500 Subject: [PATCH 113/113] Freewheel SSP Bid Adapter: bugfix for schain (#9492) * freewheel-sspBidAdapter: Bug Fix for schain (#9471) * Fixed schain logic to parse schain as string * Updated schain test to check schain string * Update freewheel-sspBidAdapter.js * kickoff tests --------- Co-authored-by: Scott Floam Co-authored-by: Patrick McCann Co-authored-by: Chris Huie --- modules/freewheel-sspBidAdapter.js | 6 +++++- test/spec/modules/freewheel-sspBidAdapter_spec.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 764e3d6e6c0..b4d8f69d1b4 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -348,7 +348,11 @@ export const spec = { // Add schain object var schain = currentBidRequest.schain; if (schain) { - requestParams.schain = schain; + try { + requestParams.schain = JSON.stringify(schain); + } catch (error) { + logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the schain: ' + error); + } } var vastParams = currentBidRequest.params.vastUrlParams; diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 7ef576fc7ec..d1e0b055239 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -128,7 +128,7 @@ describe('freewheelSSP BidAdapter Test', () => { it('should return a properly formatted request with schain defined', function () { const request = spec.buildRequests(bidRequests); const payload = request[0].data; - expect(payload.schain).to.deep.equal(bidRequests[0].schain) + expect(payload.schain).to.deep.equal('{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"example.com\",\"sid\":\"0\",\"hp\":1,\"rid\":\"bidrequestid\",\"domain\":\"example.com\"}]}'); }); it('sends bid request to ENDPOINT via GET', () => {