From 80ff16b61fb86f23abc58d7a3526e65632e11dbc Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Mon, 16 May 2022 07:13:22 +0200 Subject: [PATCH] Weborama RTD submodule: specify list of bidders to share data (#8350) * update .submodules.json to include weborama rtd update .submodules.json to include weborama rtd submodule * add support to pubmatic * improve test format * improve test format ~ * add support to appnexus/xandr bidder * fix issue when split set target in two steps: site and user centric data * add support to rubicon old style * fix code, update docs * add support to global and bid ortb2 * remove unused code * refactor code * update bidder list * add global level parameters * change error to warning * update doc * refactor * add field accountId * update example * refactor js concat string * refactor js concat string 2 * update jsdoc * correct jsdoc * add support to pubmatic * improve test format * improve test format ~ * add support to appnexus/xandr bidder * fix issue when split set target in two steps: site and user centric data * add support to rubicon old style * fix code, update docs * add support to global and bid ortb2 * remove unused code * refactor code * update bidder list * add global level parameters * change error to warning * update doc * refactor * add field accountId * update example * refactor js concat string * refactor js concat string 2 * update jsdoc * correct jsdoc * fix log * update example * remove todo * refactor duplicated code in config normalization * fix jsdoc * add main feature * update jstag * improve callback * improve doc * improve doc about ortb2 * refactor code * refactor tests to use one adunit * fix unit tests * prepare to add support to webo lite * refactor code * reorder code to handle bid data * finish unit test * improve copy of data * improve unit test by checking callbacks that alter bid data * format source * fix doc * specify webo lite as site-centric data * add check for profile format * update doc and code * update sendToBidder callback signature * fix doc * fix doc 2 * fix js example * update doc * improve doc * fix doc for LiTE * update code and tests * update doc * update unit test * improve doc * fix sfbx lite code, add isDefault flag on metadata * remove unused imports in tests * refactor code using ?. operator * improve deep clone usage * keep code less dinamic * refactor thinking in the prebid 7 * format source * format tests * suppress mention to lite * Revert "suppress mention to lite" This reverts commit 43fa65804446c4a6acf18dd48897f971c5d85683. * small fix * refactor init submodule functions * add new constants * update example * simplify code * rename function, rewrite callbacks into () => notation * update sfbx lite name * fix doc --- .../gpt/weboramaRtdProvider_example.html | 76 +- modules/weboramaRtdProvider.js | 756 +++-- modules/weboramaRtdProvider.md | 539 +++- test/spec/modules/weboramaRtdProvider_spec.js | 2599 +++++++++++++++-- 4 files changed, 3401 insertions(+), 569 deletions(-) diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html index b81ec52b2c4..73843c49914 100644 --- a/integrationExamples/gpt/weboramaRtdProvider_example.html +++ b/integrationExamples/gpt/weboramaRtdProvider_example.html @@ -1,9 +1,10 @@ - + + weborama rtd submodule example @@ -26,9 +27,8 @@ params: { setPrebidTargeting: true, // optional sendToBidders: true, // optional - onData: function (data, site) { // optional - var kind = (site) ? 'site' : 'user'; - console.log('onData', kind, data); + onData: function (data, meta) { // optional + console.log('onData', data, meta); }, weboCtxConf: { token: "to-be-defined", // mandatory @@ -36,21 +36,30 @@ setPrebidTargeting: true, // override param.setPrebidTargeting or default true sendToBidders: true, // override param.sendToBidders or default true defaultProfile: { // optional - webo_ctx: ['moon'], + webo_ctx: ["Rugby_Renault_c11495", "Sport_c11893"], webo_ds: ['bar'] }, - //, onData: function (data, ...) { ...} + // enabled: false, + //, onData: function (data,...) { ...} }, weboUserDataConf: { - accountId: 12345, // optional + accountId: 12345, // recommended setPrebidTargeting: true, // override param.setPrebidTargeting or default true - sendToBidders: true, // override param.sendToBidders or default true + sendToBidders: ['smartadserver'], // specify the bidder to share data defaultProfile: { // optional - webo_cs: ['Red'], + webo_cs: ['red'], webo_audiences: ['bam'] }, localStorageProfileKey: 'webo_wam2gam_entry', // default + // enabled: false, //, onData: function (data,...) { ...} + }, + sfbxLiteDataConf: { + enabled: true, + defaultProfile: { // optional + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }, } } }] @@ -62,6 +71,9 @@ var div_1_sizes = [ [300, 300] ]; + var div_2_sizes = [ + [600, 100] + ]; var PREBID_TIMEOUT = 3000; var FAILSAFE_TIMEOUT = 5000; @@ -106,6 +118,46 @@ networkId: 456456, }, }] + }, + { + code: '/1056029/webo-wam-prebid', + mediaTypes: { + banner: { + sizes: div_2_sizes + } + }, + bids: [{ + bidder: 'smartadserver', + params: { + siteId: 1234, + pageId: 1234, + formatId: 1234, + } + }, { + bidder: 'pubmatic', + params: { + publisherId: '32572', + } + }, { + bidder: 'appnexus', + params: { + placementId: 234234, + } + }, { + bidder: 'rubicon', + params: { + accountId: '14062', + siteId: '70608', + zoneId: '335918', + userId: '12346', + } + }, { + bidder: 'criteo', + params: { + zoneId: 234234, + networkId: 456456, + }, + }] } ]; @@ -138,7 +190,6 @@ }); } - // in case PBJS doesn't load setTimeout(function () { initAdserver(); @@ -146,6 +197,7 @@ googletag.cmd.push(function () { googletag.defineSlot('/1056029/webo-ctx-prebid', div_1_sizes, 'div-gpt-ad-1620653642627-0').addService(googletag.pubads()); + googletag.defineSlot('/1056029/webo-wam-prebid', div_2_sizes, 'div-gpt-ad-1645023761875-0').addService(googletag.pubads()); googletag.pubads().disableInitialLoad(); googletag.enableServices(); }); @@ -154,11 +206,12 @@

- test webo ctx using prebid.js + test webo rtd submodule with prebid.js

Basic Prebid.js Example

Div-1
+
+ diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 75e52f753ad..64cdd6508bb 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -7,57 +7,96 @@ * @requires module:modules/realTimeData */ +/** + * @typedef dataCallbackMetadata + * @property {Boolean} user if true it is user-centric data + * @property {String} source describe the source of data, if "contextual" or "wam" + * @property {Boolean} isDefault if true it the default profile defined in the configuration + */ + /** onData callback type * @callback dataCallback * @param {Object} data profile data - * @param {Boolean} site true if site, else it is user + * @param {dataCallbackMetadata} meta metadata * @returns {void} */ +/** setPrebidTargeting callback type + * @callback setPrebidTargetingCallback + * @param {String} adUnitCode + * @param {Object} data + * @param {dataCallbackMetadata} metadata + * @returns {Boolean} + */ + +/** sendToBidders callback type + * @callback sendToBiddersCallback + * @param {Object} bid + * @param {String} adUnitCode + * @param {Object} data + * @param {dataCallbackMetadata} metadata + * @returns {Boolean} + */ + /** * @typedef {Object} ModuleParams - * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default undefined) - * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default undefined) + * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) * @property {?dataCallback} onData callback - * @property {?WeboCtxConf} weboCtxConf - * @property {?WeboUserDataConf} weboUserDataConf + * @property {?WeboCtxConf} weboCtxConf site-centric contextual configuration + * @property {?WeboUserDataConf} weboUserDataConf user-centric wam configuration + * @property {?SfbxLiteDataConf} sfbxLiteDataConf site-centric lite configuration */ /** * @typedef {Object} WeboCtxConf * @property {string} token required token to be used on bigsea contextual API requests * @property {?string} targetURL specify the target url instead use the referer - * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default params.setPrebidTargeting or true) - * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default params.sendToBidders or true) + * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) * @property {?dataCallback} onData callback * @property {?object} defaultProfile to be used if the profile is not found * @property {?Boolean} enabled if false, will ignore this configuration + * @property {?string} baseURLProfileAPI to be used to point to a different domain than ctx.weborama.com */ /** * @typedef {Object} WeboUserDataConf * @property {?number} accountId wam account id - * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default params.setPrebidTargeting or true) - * @property {?Boolean} sendToBidders if true, will send the user-centric profile to all bidders (default params.sendToBidders or true) + * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) * @property {?object} defaultProfile to be used if the profile is not found * @property {?dataCallback} onData callback * @property {?string} localStorageProfileKey can be used to customize the local storage key (default is 'webo_wam2gam_entry') * @property {?Boolean} enabled if false, will ignore this configuration */ +/** + * @typedef {Object} SfbxLiteDataConf + * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) + * @property {?object} defaultProfile to be used if the profile is not found + * @property {?dataCallback} onData callback + * @property {?string} localStorageProfileKey can be used to customize the local storage key (default is '_lite') + * @property {?Boolean} enabled if false, will ignore this configuration + */ import { getGlobal } from '../src/prebidGlobal.js'; import { deepSetValue, - deepAccess, isEmpty, mergeDeep, logError, logWarn, tryAppendQueryString, logMessage, - isFn + isFn, + isArray, + isStr, + isBoolean, + isPlainObject, + deepClone, } from '../src/utils.js'; import { submodule @@ -75,13 +114,29 @@ const MODULE_NAME = 'realTimeData'; /** @type {string} */ const SUBMODULE_NAME = 'weborama'; /** @type {string} */ +const BASE_URL_CONTEXTUAL_PROFILE_API = 'ctx.weborama.com'; +/** @type {string} */ export const DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY = 'webo_wam2gam_entry'; /** @type {string} */ const LOCAL_STORAGE_USER_TARGETING_SECTION = 'targeting'; +/** @type {string} */ +export const DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY = '_lite'; +/** @type {string} */ +const LOCAL_STORAGE_LITE_TARGETING_SECTION = 'webo'; +/** @type {string} */ +const WEBO_CTX_CONF_SECTION = 'weboCtxConf'; +/** @type {string} */ +const WEBO_USER_DATA_CONF_SECTION = 'weboUserDataConf'; +/** @type {string} */ +const SFBX_LITE_DATA_CONF_SECTION = 'sfbxLiteDataConf'; + /** @type {number} */ const GVLID = 284; -/** @type {object} */ -export const storage = getStorageManager({gvlid: GVLID, moduleName: SUBMODULE_NAME}); +/** @type {?Object} */ +export const storage = getStorageManager({ + gvlid: GVLID, + moduleName: SUBMODULE_NAME +}); /** @type {null|Object} */ let _weboContextualProfile = null; @@ -89,78 +144,67 @@ let _weboContextualProfile = null; /** @type {Boolean} */ let _weboCtxInitialized = false; -/** @type {null|Object} */ +/** @type {?Object} */ let _weboUserDataUserProfile = null; /** @type {Boolean} */ let _weboUserDataInitialized = false; +/** @type {?Object} */ +let _sfbxLiteDataProfile = null; + +/** @type {Boolean} */ +let _sfbxLiteDataInitialized = false; + /** Initialize module * @param {object} moduleConfig * @return {Boolean} true if module was initialized with success */ function init(moduleConfig) { - moduleConfig = moduleConfig || {}; - const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf; - const weboUserDataConf = moduleParams.weboUserDataConf; + const moduleParams = moduleConfig?.params || {}; - _weboCtxInitialized = initWeboCtx(moduleParams, weboCtxConf); - _weboUserDataInitialized = initWeboUserData(moduleParams, weboUserDataConf); - - return _weboCtxInitialized || _weboUserDataInitialized; -} - -/** Initialize contextual sub module - * @param {ModuleParams} moduleParams - * @param {WeboCtxConf} weboCtxConf - * @return {Boolean} true if sub module was initialized with success - */ -function initWeboCtx(moduleParams, weboCtxConf) { - if (!weboCtxConf || weboCtxConf.enabled === false) { - moduleParams.weboCtxConf = null; - - return false - } - - normalizeConf(moduleParams, weboCtxConf); - - _weboCtxInitialized = false; _weboContextualProfile = null; + _weboUserDataUserProfile = null; + _sfbxLiteDataProfile = null; - if (!weboCtxConf.token) { - logWarn('missing param "token" for weborama contextual sub module initialization'); - return false; - } - - logMessage('weborama contextual intialized with success'); + _weboCtxInitialized = initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, 'token'); + _weboUserDataInitialized = initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION); + _sfbxLiteDataInitialized = initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION); - return true; + return _weboCtxInitialized || _weboUserDataInitialized || _sfbxLiteDataInitialized; } -/** Initialize weboUserData sub module - * @param {ModuleParams} moduleParams - * @param {WeboUserDataConf} weboUserDataConf - * @return {Boolean} true if sub module was initialized with success +/** Initialize subsection module + * @param {Object} moduleParams + * @param {string} subSection subsection name to initialize + * @param {[]string} requiredFields + * @return {Boolean} true if module subsection was initialized with success */ -function initWeboUserData(moduleParams, weboUserDataConf) { - if (!weboUserDataConf || weboUserDataConf.enabled === false) { - moduleParams.weboUserDataConf = null; +function initSubSection(moduleParams, subSection, ...requiredFields) { + const weboSectionConf = moduleParams[subSection] || {enabled: false}; + + if (weboSectionConf.enabled === false) { + delete moduleParams[subSection]; return false; } - normalizeConf(moduleParams, weboUserDataConf); + requiredFields ||= []; - _weboUserDataInitialized = false; - _weboUserDataUserProfile = null; + try { + normalizeConf(moduleParams, weboSectionConf); - let message = 'weborama user-centric intialized with success'; - if (weboUserDataConf.hasOwnProperty('accountId')) { - message = `weborama user-centric intialized with success for account: ${weboUserDataConf.accountId}`; + requiredFields.forEach(field => { + if (!weboSectionConf[field]) { + throw `missing required field "{field}" on {section}`; + } + }); + } catch (e) { + logError(`unable to initialize: error on ${subSection} configuration: ${e}`); + return false } - logMessage(message); + logMessage(`weborama ${subSection} initialized with success`); return true; } @@ -169,21 +213,172 @@ function initWeboUserData(moduleParams, weboUserDataConf) { const globalDefaults = { setPrebidTargeting: true, sendToBidders: true, - onData: (data, kind, def) => logMessage('onData(data,kind,default)', data, kind, def), + onData: () => { + /* do nothing */ }, } /** normalize submodule configuration * @param {ModuleParams} moduleParams - * @param {WeboCtxConf|WeboUserDataConf} submoduleParams + * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams * @return {void} */ function normalizeConf(moduleParams, submoduleParams) { + // handle defaults Object.entries(globalDefaults).forEach(([propertyName, globalDefaultValue]) => { if (!submoduleParams.hasOwnProperty(propertyName)) { const hasModuleParam = moduleParams.hasOwnProperty(propertyName); submoduleParams[propertyName] = (hasModuleParam) ? moduleParams[propertyName] : globalDefaultValue; } }) + + // handle setPrebidTargeting + coerceSetPrebidTargeting(submoduleParams) + + // handle sendToBidders + coerceSendToBidders(submoduleParams) + + if (!isFn(submoduleParams.onData)) { + throw 'onData parameter should be a callback'; + } + + submoduleParams.defaultProfile = submoduleParams.defaultProfile || {}; + + if (!isValidProfile(submoduleParams.defaultProfile)) { + throw 'defaultProfile is not valid'; + } +} + +/** coerce set prebid targeting to function + * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams + * @return {void} + */ +function coerceSetPrebidTargeting(submoduleParams) { + const setPrebidTargeting = submoduleParams.setPrebidTargeting; + + if (isFn(setPrebidTargeting)) { + return + } + + if (isBoolean(setPrebidTargeting)) { + const shouldSetPrebidTargeting = setPrebidTargeting; + + submoduleParams.setPrebidTargeting = () => shouldSetPrebidTargeting; + + return + } + + if (isStr(setPrebidTargeting)) { + const allowedAdUnitCode = setPrebidTargeting; + + submoduleParams.setPrebidTargeting = (adUnitCode) => allowedAdUnitCode == adUnitCode; + + return + } + + if (isArray(setPrebidTargeting)) { + const allowedAdUnitCodes = setPrebidTargeting; + + submoduleParams.setPrebidTargeting = (adUnitCode) => allowedAdUnitCodes.includes(adUnitCode); + + return + } + + throw `unexpected format for setPrebidTargeting: ${typeof setPrebidTargeting}`; +} + +/** coerce send to bidders to function + * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams + * @return {void} + */ +function coerceSendToBidders(submoduleParams) { + const sendToBidders = submoduleParams.sendToBidders; + + if (isFn(sendToBidders)) { + return + } + + if (isBoolean(sendToBidders)) { + const shouldSendToBidders = sendToBidders; + + submoduleParams.sendToBidders = () => shouldSendToBidders; + + return + } + + if (isStr(sendToBidders)) { + const allowedBidder = sendToBidders; + + submoduleParams.sendToBidders = (bid) => allowedBidder == bid.bidder; + + return + } + + if (isArray(sendToBidders)) { + const allowedBidders = sendToBidders; + + submoduleParams.sendToBidders = (bid) => allowedBidders.includes(bid.bidder); + + return + } + + if (isPlainObject(sendToBidders)) { + const sendToBiddersMap = sendToBidders; + submoduleParams.sendToBidders = (bid, adUnitCode) => { + const bidder = bid.bidder; + if (!sendToBiddersMap.hasOwnProperty(bidder)) { + return false + } + + const value = sendToBiddersMap[bidder]; + + if (isBoolean(value)) { + return value + } + + if (isStr(value)) { + return value == adUnitCode + } + + if (isArray(value)) { + return value.includes(adUnitCode) + } + + throw `unexpected format for sendToBidders[${bidder}]: ${typeof value}`; + }; + + return + } + + throw `unexpected format for sendToBidders: ${typeof sendToBidders}`; +} +/** + * check if profile is valid + * @param {*} profile + * @returns {Boolean} + */ +function isValidProfile(profile) { + if (!isPlainObject(profile)) { + return false; + } + + const keys = Object.keys(profile); + + for (var i in keys) { + const key = keys[i]; + const value = profile[key]; + if (!isArray(value)) { + return false; + } + + for (var j in value) { + const elem = value[j] + if (!isStr(elem)) { + return false; + } + } + } + + return true; } /** function that provides ad server targeting data to RTD-core @@ -192,24 +387,27 @@ function normalizeConf(moduleParams, submoduleParams) { * @returns {Object} target data */ function getTargetingData(adUnitsCodes, moduleConfig) { - moduleConfig = moduleConfig || {}; - const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf || {}; - const weboUserDataConf = moduleParams.weboUserDataConf || {}; - const weboCtxConfTargeting = weboCtxConf.setPrebidTargeting; - const weboUserDataConfTargeting = weboUserDataConf.setPrebidTargeting; + const moduleParams = moduleConfig?.params || {}; - try { - const profile = getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting); + const profileHandlers = buildProfileHandlers(moduleParams); - if (isEmpty(profile)) { - return {}; - } + if (isEmpty(profileHandlers)) { + logMessage('no data to set targeting'); + return {}; + } + try { const td = adUnitsCodes.reduce((data, adUnitCode) => { - if (adUnitCode) { - data[adUnitCode] = profile; - } + data[adUnitCode] = profileHandlers.reduce((targeting, ph) => { + // logMessage(`check if should set targeting for adunit '${adUnitCode}'`); + const cph = copyProfileHandler(ph); + if (ph.setTargeting(adUnitCode, cph.data, cph.metadata)) { + // logMessage(`set targeting for adunit '${adUnitCode}', source '${ph.metadata.source}'`); + + mergeDeep(targeting, cph.data); + } + return targeting; + }, {}); return data; }, {}); @@ -220,57 +418,155 @@ function getTargetingData(adUnitsCodes, moduleConfig) { } } -/** function that provides complete profile formatted to be used +/** function that provides data handlers based on the configuration * @param {ModuleParams} moduleParams - * @param {Boolean} weboCtxConfTargeting - * @param {Boolean} weboUserDataConfTargeting - * @returns {Object} complete profile + * @returns {Array} handlers */ -function getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting) { - const profile = {}; +function buildProfileHandlers(moduleParams) { + const profileHandlers = []; + + if (_weboCtxInitialized && moduleParams?.weboCtxConf) { + const weboCtxConf = moduleParams.weboCtxConf; + const [data, isDefault] = getContextualProfile(weboCtxConf); + if (!isEmpty(data)) { + profileHandlers.push({ + data: data, + metadata: { + user: false, + source: 'contextual', + isDefault: !!isDefault, + }, + setTargeting: weboCtxConf.setPrebidTargeting, + sendToBidders: weboCtxConf.sendToBidders, + onData: weboCtxConf.onData, + }) + } else { + logMessage('skip contextual profile: no data'); + } + } - if (weboCtxConfTargeting) { - const contextualProfile = getContextualProfile(moduleParams.weboCtxConf || {}); - mergeDeep(profile, contextualProfile); + if (_weboUserDataInitialized && moduleParams?.weboUserDataConf) { + const weboUserDataConf = moduleParams.weboUserDataConf; + const [data, isDefault] = getWeboUserDataProfile(weboUserDataConf); + if (!isEmpty(data)) { + profileHandlers.push({ + data: data, + metadata: { + user: true, + source: 'wam', + isDefault: !!isDefault, + }, + setTargeting: weboUserDataConf.setPrebidTargeting, + sendToBidders: weboUserDataConf.sendToBidders, + onData: weboUserDataConf.onData, + }) + } else { + logMessage('skip wam profile: no data'); + } } - if (weboUserDataConfTargeting) { - const weboUserDataProfile = getWeboUserDataProfile(moduleParams.weboUserDataConf || {}); - mergeDeep(profile, weboUserDataProfile); + if (_sfbxLiteDataInitialized && moduleParams?.sfbxLiteDataConf) { + const sfbxLiteDataConf = moduleParams.sfbxLiteDataConf; + const [data, isDefault] = getSfbxLiteDataProfile(sfbxLiteDataConf); + if (!isEmpty(data)) { + profileHandlers.push({ + data: data, + metadata: { + user: false, + source: 'lite', + isDefault: !!isDefault, + }, + setTargeting: sfbxLiteDataConf.setPrebidTargeting, + sendToBidders: sfbxLiteDataConf.sendToBidders, + onData: sfbxLiteDataConf.onData, + }) + } else { + logMessage('skip sfbx lite profile: no data'); + } } - return profile; + return profileHandlers; } /** return contextual profile * @param {WeboCtxConf} weboCtxConf - * @returns {Object} contextual profile + * @returns {Array} contextual profile + isDefault boolean flag */ function getContextualProfile(weboCtxConf) { + if (_weboContextualProfile) { + return [_weboContextualProfile, false]; + } + const defaultContextualProfile = weboCtxConf.defaultProfile || {}; - return _weboContextualProfile || defaultContextualProfile; + + return [defaultContextualProfile, true]; } /** return weboUserData profile * @param {WeboUserDataConf} weboUserDataConf - * @returns {Object} weboUserData profile + * @returns {Array} weboUserData profile + isDefault boolean flag */ function getWeboUserDataProfile(weboUserDataConf) { - const weboUserDataDefaultUserProfile = weboUserDataConf.defaultProfile || {}; + return getDataFromLocalStorage(weboUserDataConf, + () => _weboUserDataUserProfile, + (data) => _weboUserDataUserProfile = data, + DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY, + LOCAL_STORAGE_USER_TARGETING_SECTION, + 'wam'); +} - if (storage.localStorageIsEnabled() && !_weboUserDataUserProfile) { - const localStorageProfileKey = weboUserDataConf.localStorageProfileKey || DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY; +/** return weboUserData profile + * @param {SfbxLiteDataConf} sfbxLiteDataConf + * @returns {Array} sfbxLiteData profile + isDefault boolean flag + */ +function getSfbxLiteDataProfile(sfbxLiteDataConf) { + return getDataFromLocalStorage(sfbxLiteDataConf, + () => _sfbxLiteDataProfile, + (data) => _sfbxLiteDataProfile = data, + DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY, + LOCAL_STORAGE_LITE_TARGETING_SECTION, + 'lite'); +} + +/** return generic webo data profile + * @param {WeboUserDataConf|SfbxLiteDataConf} weboDataConf + * @param {cacheGetCallback} cacheGet + * @param {cacheSetCallback} cacheSet + * @param {String} defaultLocalStorageProfileKey + * @param {String} targetingSection + * @param {String} source + * @returns {Array} webo (user|lite) data profile + isDefault boolean flag + */ +function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalStorageProfileKey, targetingSection, source) { + const defaultProfile = weboDataConf.defaultProfile || {}; + + if (storage.localStorageIsEnabled() && !cacheGet()) { + const localStorageProfileKey = weboDataConf.localStorageProfileKey || defaultLocalStorageProfileKey; const entry = storage.getDataFromLocalStorage(localStorageProfileKey); if (entry) { const data = JSON.parse(entry); - if (data && Object.keys(data).length > 0) { - _weboUserDataUserProfile = data[LOCAL_STORAGE_USER_TARGETING_SECTION]; + if (data && isPlainObject(data) && data.hasOwnProperty(targetingSection)) { + const profile = data[targetingSection]; + const valid = isValidProfile(profile); + if (!valid) { + logWarn(`found invalid ${source} profile on local storage key ${localStorageProfileKey}, section ${targetingSection}`); + } + + if (valid && !isEmpty(data)) { + cacheSet(profile); + } } } } - return _weboUserDataUserProfile || weboUserDataDefaultUserProfile; + const profile = cacheGet() + + if (profile) { + return [profile, false]; + } + + return [defaultProfile, true]; } /** function that will allow RTD sub-modules to modify the AdUnit object for each auction @@ -280,102 +576,83 @@ function getWeboUserDataProfile(weboUserDataConf) { * @returns {void} */ export function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig) { - moduleConfig = moduleConfig || {}; - const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf || {}; - - const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + const moduleParams = moduleConfig?.params || {}; if (!_weboCtxInitialized) { - handleBidRequestData(adUnits, moduleParams); + handleBidRequestData(reqBidsConfigObj, moduleParams); onDone(); return; } + const weboCtxConf = moduleParams.weboCtxConf || {}; + fetchContextualProfile(weboCtxConf, (data) => { logMessage('fetchContextualProfile on getBidRequestData is done'); setWeboContextualProfile(data); }, () => { - handleBidRequestData(adUnits, moduleParams); + handleBidRequestData(reqBidsConfigObj, moduleParams); onDone(); }); } /** function that handles bid request data - * @param {Object[]} adUnits + * @param {Object} reqBids * @param {ModuleParams} moduleParams * @returns {void} */ +function handleBidRequestData(reqBids, moduleParams) { + const profileHandlers = buildProfileHandlers(moduleParams); -function handleBidRequestData(adUnits, moduleParams) { - const weboCtxConf = moduleParams.weboCtxConf || {}; - const weboUserDataConf = moduleParams.weboUserDataConf || {}; - const weboCtxConfTargeting = weboCtxConf.sendToBidders; - const weboUserDataConfTargeting = weboUserDataConf.sendToBidders; - - if (weboCtxConfTargeting) { - const contextualProfile = getContextualProfile(weboCtxConf); - if (!isEmpty(contextualProfile)) { - setBidRequestProfile(adUnits, contextualProfile, true); - } - } - - if (weboUserDataConfTargeting) { - const weboUserDataProfile = getWeboUserDataProfile(weboUserDataConf); - if (!isEmpty(weboUserDataProfile)) { - setBidRequestProfile(adUnits, weboUserDataProfile, false); - } + if (isEmpty(profileHandlers)) { + logMessage('no data to send to bidders'); + return; } - handleOnData(weboCtxConf, weboUserDataConf); -} - -/** function that handle with onData callbacks - * @param {WeboCtxConf} weboCtxConf - * @param {WeboUserDataConf} weboUserDataConf - */ + const adUnits = reqBids.adUnits || getGlobal().adUnits; -function handleOnData(weboCtxConf, weboUserDataConf) { - const callbacks = [{ - onData: weboCtxConf.onData, - fetchData: () => getContextualProfile(weboCtxConf), - site: true, - }, { - onData: weboUserDataConf.onData, - fetchData: () => getWeboUserDataProfile(weboUserDataConf), - site: false, - }]; + try { + adUnits.filter( + adUnit => adUnit.hasOwnProperty('bids') + ).forEach( + adUnit => adUnit.bids.forEach( + bid => profileHandlers.forEach(ph => { + // logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`); + + const cph = copyProfileHandler(ph); + if (ph.sendToBidders(bid, adUnit.code, cph.data, cph.metadata)) { + // logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`); + + handleBid(bid, cph.data, ph.metadata); + } + }) + ) + ); + } catch (e) { + logError('unable to send data to bidders:', e); + } - callbacks.filter(obj => isFn(obj.onData)).forEach(obj => { + profileHandlers.forEach(ph => { try { - const data = obj.fetchData(); - obj.onData(data, obj.site); + const cph = copyProfileHandler(ph); + ph.onData(cph.data, cph.metadata); } catch (e) { - const kind = (obj.site) ? 'site' : 'user'; - logError(`error while executure onData callback with ${kind}-based data:`, e); + logError(`error while executure onData callback with ${ph.metadata.source}-based data:`, e); } }); } - -/** function that set bid request data on each segment (site or user centric) - * @param {Object[]} adUnits - * @param {Object} profile - * @param {Boolean} site true if site centric, else it is user centric - * @returns {void} +/** function that handles bid request data + * @param {Object} ph profile handler + *@returns {Object} of deeply copy data and metadata */ -function setBidRequestProfile(adUnits, profile, site) { - setGlobalOrtb2(profile, site); - - adUnits.forEach(adUnit => { - if (adUnit.hasOwnProperty('bids')) { - const adUnitCode = adUnit.code || 'no code'; - adUnit.bids.forEach(bid => handleBid(adUnitCode, profile, site, bid)); - } - }); +function copyProfileHandler(ph) { + return { + data: deepClone(ph.data), + metadata: deepClone(ph.metadata), + }; } /** @type {string} */ @@ -394,115 +671,61 @@ const SMARTADSERVER = 'smartadserver'; const bidderAliasRegistry = adapterManager.aliasRegistry || {}; /** handle individual bid - * @param {string} adUnitCode - * @param {Object} profile - * @param {Boolean} site true if site centric, else it is user centric * @param {Object} bid + * @param {Object} profile + * @param {Object} metadata * @returns {void} */ -function handleBid(adUnitCode, profile, site, bid) { +function handleBid(bid, profile, metadata) { const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; - logMessage(`handling on adunit '${adUnitCode}', bidder '${bidder}' and bid`, bid); - switch (bidder) { case APPNEXUS: - handleAppnexusBid(profile, bid); + handleAppnexusBid(bid, profile); break; case PUBMATIC: - handlePubmaticBid(profile, bid); + handlePubmaticBid(bid, profile); break; case SMARTADSERVER: - handleSmartadserverBid(profile, bid); + handleSmartadserverBid(bid, profile); break; case RUBICON: - handleRubiconBid(profile, site, bid); + handleRubiconBid(bid, profile, metadata); break; default: - logMessage(`unsupported bidder '${bidder}', trying via bidder ortb2 fpd`); - const section = ((site) ? 'site' : 'user'); - const base = `ortb2.${section}.ext.data`; - - assignProfileToObject(bid, base, profile); - } -} - -/** - * set ortb2 global data - * @param {Object} profile - * @param {Boolean} site - * @returns {void} - */ -function setGlobalOrtb2(profile, site) { - const section = ((site) ? 'site' : 'user'); - const base = `${section}.ext.data`; - const addOrtb2 = {}; - - assignProfileToObject(addOrtb2, base, profile); - - if (!isEmpty(addOrtb2)) { - const testGlobal = getGlobal().getConfig('ortb2') || {}; - const ortb2 = { - ortb2: mergeDeep({}, testGlobal, addOrtb2) - }; - getGlobal().setConfig(ortb2); + handleBidViaORTB2(bid, profile, metadata); } } -/** - * assign profile to object - * @param {Object} destination - * @param {string} base - * @param {Object} profile - * @returns {void} - */ -function assignProfileToObject(destination, base, profile) { - Object.keys(profile).forEach(key => { - const path = `${base}.${key}`; - deepSetValue(destination, path, profile[key]) - }) -} - -/** handle rubicon bid - * @param {Object} profile - * @param {Boolean} site - * @param {Object} bid - * @returns {void} - */ -function handleRubiconBid(profile, site, bid) { - const section = (site) ? 'inventory' : 'visitor'; - const base = `params.${section}`; - assignProfileToObject(bid, base, profile); -} - /** handle appnexus/xandr bid - * @param {Object} profile * @param {Object} bid + * @param {Object} profile * @returns {void} */ -function handleAppnexusBid(profile, bid) { +function handleAppnexusBid(bid, profile) { const base = 'params.keywords'; assignProfileToObject(bid, base, profile); } /** handle pubmatic bid - * @param {Object} profile * @param {Object} bid + * @param {Object} profile * @returns {void} */ -function handlePubmaticBid(profile, bid) { +function handlePubmaticBid(bid, profile) { const sep = '|'; const subsep = ','; - const bidKey = 'params.dctr'; const target = []; - const data = deepAccess(bid, bidKey); + bid.params ||= {}; + + const data = bid.params.dctr; if (data) { data.split(sep).forEach(t => target.push(t)); } @@ -515,20 +738,21 @@ function handlePubmaticBid(profile, bid) { } }); - deepSetValue(bid, bidKey, target.join(sep)); + bid.params.dctr = target.join(sep); } /** handle smartadserver bid - * @param {Object} profile * @param {Object} bid + * @param {Object} profile * @returns {void} */ -function handleSmartadserverBid(profile, bid) { +function handleSmartadserverBid(bid, profile) { const sep = ';'; - const bidKey = 'params.target'; const target = []; - const data = deepAccess(bid, bidKey); + bid.params ||= {}; + + const data = bid.params.target; if (data) { data.split(sep).forEach(t => target.push(t)); } @@ -541,7 +765,56 @@ function handleSmartadserverBid(profile, bid) { } }); }); - deepSetValue(bid, bidKey, target.join(sep)); + + bid.params.target = target.join(sep); +} + +/** handle rubicon bid + * @param {Object} bid + * @param {Object} profile + * @param {Object} metadata + * @returns {void} + */ +function handleRubiconBid(bid, profile, metadata) { + if (isBoolean(metadata.user)) { + const section = (metadata.user) ? 'visitor' : 'inventory'; + const base = `params.${section}`; + assignProfileToObject(bid, base, profile); + } else { + logMessage(`SKIP bidder '${bid.bidder}', data from '${metadata.source}' is not defined as user or site-centric`); + } +} + +/** handle generic bid via ortb2 arbitrary data + * @param {Object} bid + * @param {Object} profile + * @param {Object} metadata + * @returns {void} + */ +function handleBidViaORTB2(bid, profile, metadata) { + if (isBoolean(metadata.user)) { + logMessage(`bidder '${bid.bidder}' is not directly supported, trying set data via bidder ortb2 fpd`); + const section = ((metadata.user) ? 'user' : 'site'); + const base = `ortb2.${section}.ext.data`; + + assignProfileToObject(bid, base, profile); + } else { + logMessage(`SKIP unsupported bidder '${bid.bidder}', data from '${metadata.source}' is not defined as user or site-centric`); + } +} + +/** + * assign profile to object + * @param {Object} destination + * @param {string} base + * @param {Object} profile + * @returns {void} + */ +function assignProfileToObject(destination, base, profile) { + Object.keys(profile).forEach(key => { + const path = `${base}.${key}`; + deepSetValue(destination, path, profile[key]) + }) } /** set bigsea contextual profile on module state @@ -549,7 +822,7 @@ function handleSmartadserverBid(profile, bid) { * @returns {void} */ export function setWeboContextualProfile(data) { - if (data && Object.keys(data).length > 0) { + if (data && isPlainObject(data) && isValidProfile(data) && !isEmpty(data)) { _weboContextualProfile = data; } } @@ -574,15 +847,16 @@ export function setWeboContextualProfile(data) { function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { const targetURL = weboCtxConf.targetURL || document.URL; const token = weboCtxConf.token; + const baseURLProfileAPI = weboCtxConf.baseURLProfileAPI || BASE_URL_CONTEXTUAL_PROFILE_API; let queryString = ''; queryString = tryAppendQueryString(queryString, 'token', token); queryString = tryAppendQueryString(queryString, 'url', targetURL); - const url = `https://ctx.weborama.com/api/profile?${queryString}`; + const urlProfileAPI = `https://${baseURLProfileAPI}/api/profile?${queryString}`; - ajax(url, { - success: function(response, req) { + ajax(urlProfileAPI, { + success: (response, req) => { if (req.status === 200) { try { const data = JSON.parse(response); @@ -597,7 +871,7 @@ function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { onDone(); } }, - error: function() { + error: () => { onDone(); logError('unable to get weborama data'); } diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md index 732944c6e1c..88ec907c9a1 100644 --- a/modules/weboramaRtdProvider.md +++ b/modules/weboramaRtdProvider.md @@ -6,11 +6,17 @@ Module Type: Rtd Provider Maintainer: prebid-support@weborama.com ``` -# Description +## Description -Weborama provides a Semantic AI Contextual API that classifies in Real-time a web page seen by a web user within generic and custom topics. It enables publishers to better monetize their inventory and unlock it to programmatic. +Weborama provides a Real-Time Data Submodule for `Prebid.js`, allowing to easy integrate different products such as: -Contact prebid-support@weborama.com for information. +* Semantic AI Contextual API that classifies in Real-time a web page seen by a web user within generic and custom topics. It enables publishers to better monetize their inventory and unlock it to programmatic. + +* Weborama Audience Manager (WAM) is a DMP (Data Management Platform) used by over 60 companies in the world. This platform distinguishes itself particularly by a high level interconnexion with the adtech & martech ecosystem and a transparent access to the database intelligence. + +* LiTE by SFBX® (Local inApp Trust Engine) provides “Zero Party Data” given by users, stored and calculated only on the user’s device. Through a unique cohorting system, it enables better monetization in a consent/consentless and identity-less mode. + +Contact prebid-support@weborama.com for more information. ### Publisher Usage @@ -18,7 +24,7 @@ Compile the Weborama RTD module into your Prebid build: `gulp build --modules=rtdModule,weboramaRtdProvider` -Add the Weborama RTD provider to your Prebid config. +Add the Weborama RTD provider to your Prebid config, use the configuration template below: ```javascript var pbjs = pbjs || {}; @@ -26,94 +32,525 @@ pbjs.que = pbjs.que || []; pbjs.que.push(function () { pbjs.setConfig({ - debug: true, + debug: true, // Output debug messages to the web console, *should* be disabled in production realTimeData: { auctionDelay: 1000, dataProviders: [{ name: "weborama", waitForIt: true, params: { - setPrebidTargeting: true, // optional - sendToBidders: true, // optional - onData: function(data, site){ // optional - var kind = (site)? 'site' : 'user'; - console.log('onData', kind, data); - }, - weboCtxConf: { - token: "to-be-defined", // mandatory - targetURL: "https://prebid.org", // default is document.URL - setPrebidTargeting: true, // override param.setPrebidTargeting or default true - sendToBidders: true, // override param.sendToBidders or default true - defaultProfile: { // optional - webo_ctx: ['moon'], - webo_ds: ['bar'] - } - //, onData: function (data, ...) { ...} - }, - weboUserDataConf: { - accountId: 12345, // optional, used for logging - setPrebidTargeting: true, // override param.setPrebidTargeting or default true - sendToBidders: true, // override param.sendToBidders or default true - defaultProfile: { // optional - webo_cs: ['Red'], - webo_audiences: ['bam'] - }, - localStorageProfileKey: 'webo_wam2gam_entry' // default - //, onData: function (data, ...) { ...} - } - } - }] + /* add weborama rtd submodule configuration here */ + }, + }, + // other modules... + ] } }); }); ``` +The module configuration has 3 independent sections (`weboCtxConf`, `weboUserDataConf` and `sfbxLiteDataConf`), each one mapped to a single product (`contextual`, `wam` and `lite`). No section is enabled by default, we must be explicit like in the minimal example below: + +```javascript +pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { // contextual site-centric configuration, *omit if not needed* + token: "<>", // mandatory + }, + weboUserDataConf: { // wam user-centric configuration, *omit if not needed* + enabled: true, + }, + sfbxLiteDataConf: { // sfbx-lite site-centric configuration, *omit if not needed* + enabled: true, + }, + } + }, + // other modules... + ] + } +}); +``` + +Each module can perform two actions: + +* set targeting on [GPT](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForGPTAsync.html) / [AST](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForAst.html]) via `prebid.js` + +* send data to other `prebid.js` bidder modules (check the complete list at the end of this page) + ### Parameter Descriptions for the Weborama Configuration Section +This is the main configuration section + | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | | name | String | Real time data module name | Mandatory. Always 'Weborama' | | waitForIt | Boolean | Mandatory. Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false but recommended to true | | params | Object | | Optional | -| params.setPrebidTargeting | Boolean | If true, may use the profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | -| params.sendToBidders | Boolean | If true, may send the profile to all bidders | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | -| params.weboCtxConf | Object | Weborama Contextual Configuration | Optional -| params.weboUserDataConf | Object | Weborama User-Centric Configuration | Optional | -| params.onData | Callback | If set, will receive the profile and site flag | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | +| params.setPrebidTargeting | Boolean | If true, may use the profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js | Optional. Affects the `weboCtxConf`, `weboUserDataConf` and `sfbxLiteDataConf` sections | +| params.sendToBidders | Boolean or Array | If true, may send the profile to all bidders. If an array, will specify the bidders to send data | Optional. Affects the `weboCtxConf`, `weboUserDataConf` and `sfbxLiteDataConf` sections | +| params.weboCtxConf | Object | Weborama Contextual Site-Centric Configuration | Optional | +| params.weboUserDataConf | Object | Weborama WAM User-Centric Configuration | Optional | +| params.sfbxLiteDataConf | Object | Sfbx LiTE Site-Centric Configuration | Optional | +| params.onData | Callback | If set, will receive the profile and metadata | Optional. Affects the `weboCtxConf`, `weboUserDataConf` and `sfbxLiteDataConf` sections | + +#### Contextual Site-Centric Configuration -#### Contextual Configuration +To be possible use the integration with Weborama Contextual Service you must be a client with a valid API token. Please contact weborama if you don't have it. + +On this section we will explain the `params.weboCtxConf` subconfiguration: | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | | token | String | Security Token provided by Weborama, unique per client | Mandatory | | targetURL | String | Url to be profiled in the contextual api | Optional. Defaults to `document.URL` | -| setPrebidTargeting|Boolean|If true, will use the contextual profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or **true**.| -| sendToBidders|Boolean|If true, will send the contextual profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or **true**.| +| setPrebidTargeting|Various|If true, will use the contextual profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or `true`.| +| sendToBidders|Various|If true, will send the contextual profile to all bidders. If an array, will specify the bidders to send data| Optional. Default is `params.sendToBidders` (if any) or `true`.| | defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | +| onData | Callback | If set, will receive the profile and metadata | Optional. Default is `params.onData` (if any) or log via prebid debug | +| enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present| +| baseURLProfileAPI | String| if present, update the domain of the contextual api| Optional. Default is `ctx.weborama.com` | + +#### WAM User-Centric Configuration + +To be possible use the integration with Weborama Audience Manager (WAM) you must be a client with an account id and you lust include the `wamfactory` script in your pages with `wam2gam` feature activated. +Please contact weborama if you don't have it. + +On this section we will explain the `params.weboUserDataConf` subconfiguration: + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| accountId|Number|WAM account id. If you don't have it, please contact weborama. | Recommended.| +| setPrebidTargeting|Various|If true, will use the user profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or `true`.| +| sendToBidders|Various|If true, will send the user profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or `true`.| | onData | Callback | If set, will receive the profile and site flag | Optional. Default is `params.onData` (if any) or log via prebid debug | -| enabled | Boolean| if false, will ignore this configuration| default true| +| defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | +| localStorageProfileKey| String | can be used to customize the local storage key | Optional | +| enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present| + +#### Sfbx LiTE Site-Centric Configuration + +To be possible use the integration between Weborama and Sfbx LiTE you should also contact SFBX® to setup this product. -#### User-Centric Configuration +On this section we will explain the `params.sfbxLiteDataConf` subconfiguration: | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | -| accountId|Number|WAM account id. If present, will be used on logging and statistics| Optional.| -| setPrebidTargeting|Boolean|If true, will use the user profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or **true**.| -| sendToBidders|Boolean|If true, will send the user profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or **true**.| +| setPrebidTargeting|Various|If true, will use the user profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or `true`.| +| sendToBidders|Varios|If true, will send the user profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or `true`.| | onData | Callback | If set, will receive the profile and site flag | Optional. Default is `params.onData` (if any) or log via prebid debug | | defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | -| localStorageProfileKey| String | can be used to customize the local storage key | Optional | -| enabled | Boolean| if false, will ignore this configuration| default true| +| localStorageProfileKey| String | can be used to customize the local storage key | Optional | +| enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present| + +##### Property setPrebidTargeting supported types + +This property support the following types + +| Type | Description | Example | Notes | +| :------------ | :------------ | :------------ |:------------ | +| Boolean|If true, set prebid targeting for all adunits, or not in case of false| `true` | default value | +| String|Will set prebid targeting only for one adunit | `'adUnitCode1'` | | +| Array of Strings|Will set prebid targeting only for some adunits| `['adUnitCode1','adUnitCode2']` | | +| Callback |Will be executed for each adunit, expects return a true value to set prebid targeting or not| `function(adUnitCode){return adUnitCode == 'adUnitCode';}` | | + +The complete callback function signature is: + +```javascript +setPrebidTargeting: function(adUnitCode, data, metadata){ + return true; // or false, depending on the logic +} +``` + +This callback will be executed with the adUnitCode, profile and a metadata with the following fields + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| user | Boolean | If true, it contains user-centric data | | +| source | String | Represent the source of data | can be `contextual`, `wam` or `lite` | +| isDefault | Boolean | If true, it contains the default profile defined in the configuration | | + +It is possible customize the targeting based on the parameters: + +```javascript +setPrebidTargeting: function(adUnitCode, data, metadata){ + // check metadata.source can be omitted if defined in params.weboUserDataConf + if (adUnitCode == 'adUnitCode1' && metadata.source == 'wam'){ + data['foo']=['bar']; // add this section only for adUnitCode1 + delete data['other']; // remove this section + } + return true; +} +``` + +##### Property sendToBidders supported types + +This property support the following types + +| Type | Description | Example | Notes | +| :------------ | :------------ | :------------ |:------------ | +| Boolean|If true, send data to all bidders, or not in case of false| `true` | default value | +| String|Will send data to only one bidder | `'appnexus'` | | +| Array of Strings|Will send data to only some bidders | `['appnexus','pubmatic']` | | +| Object |Will send data to only some bidders and some ad units | `{appnexus: true, pubmatic:['adUnitCode1']}` | | +| Callback |Will be executed for each adunit, expects return a true value to set prebid targeting or not| `function(bid, adUnitCode){return bid.bidder == 'appnexus' && adUnitCode == 'adUnitCode';}` | | + +A better look on the `Object` type + +```javascript +sendToBidders: { + appnexus: true, // send profile to appnexus on all ad units + pubmatic: ['adUnitCode1'],// send profile to pubmatic on this ad units +} +``` + +The complete callback function signature is: + +```javascript +sendToBidders: function(bid, adUnitCode, data, metadata){ + return true; // or false, depending on the logic +} +``` + +This callback will be executed with the bid object (contains a field `bidder` with name), adUnitCode, profile and a metadata with the following fields + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| user | Boolean | If true, it contains user-centric data | | +| source | String | Represent the source of data | can be `contextual`, `wam` or `lite` | +| isDefault | Boolean | If true, it contains the default profile defined in the configuration | | + +It is possible customize the targeting based on the parameters: + +```javascript +sendToBidders: function(bid, adUnitCode, data, metadata){ + if (bid.bidder == 'appnexus' && adUnitCode == 'adUnitCode1'){ + data['foo']=['bar']; // add this section only for appnexus + adUnitCode1 + delete data['other']; // remove this section + } + return true; +} +``` + +To be possible customize the way we send data to bidders via this callback: + +```javascript +sendToBidders: function(bid, adUnitCode, data, metadata){ + if (bid.bidder == 'other'){ + /* use bid object to store data based on this specific logic, like in the example below */ + + bid.params = bid.params || {}; + bid.params['some_specific_key'] = data; + + return false; // will prevent the module to follow the pre-defined logic per bidder + } + // others + return true; +} +``` + +In case of using bid _aliases_, we should match the same string used in the adUnit configuration. + +```javascript +pbjs.aliasBidder('appnexus', 'foo'); +pbjs.aliasBidder('criteo', 'bar'); +pbjs.aliasBidder('pubmatic', 'baz'); +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { + token: "to-be-defined", // mandatory + sendToBidders: ['foo','bar'], // will share site-centric data with bidders foo and bar + }, + weboUserDataConf: { + accountId: 12345, // recommended, + sendToBidders: ['baz'], // will share user-centric data with only bidder baz + } + } + }] + } +}); +``` + +##### Using onData callback + +We can specify a callback to handle the profile data from site-centric or user-centric data. + +This callback will be executed with the profile and a metadata with the following fields + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| user | Boolean | If true, it contains user-centric data | | +| source | String | Represent the source of data | can be `contextual`, `wam` or `lite` | +| isDefault | Boolean | If true, it contains the default profile defined in the configuration | | + +The metadata maybe not useful if we define the callback on site-centric of user-centric configuration, but if defined in the global level: + +```javascript +params: { + onData: function(data, metadata){ + var hasUserCentricData = metadata.user; + var dataSource = metadata.source; + console.log('onData', data, hasUserCentricData, dataSource); + } +} +``` + +an interesting example is to set GAM targeting in global level instead in slot level only for contextual data: + +```javascript +params: { + weboCtxConf: { + token: 'to-be-defined', + setPrebidTargeting: false, + onData: function(data, metadata){ + var googletag = googletag || {}; + googletag.cmd = googletag.cmd || []; + googletag.cmd.push(function () { + for(var key in data){ + googletag.pubads().setTargeting(key, data[key]); + } + }); + }, + } +} +``` + +### More configuration examples + +A more complete example can be found below. We can define default profiles, for each section, to be used in case of no data are found. + +We can control if we will set prebid targeting or send data to bidders in a global level or on each section (`contextual`, `wam` or `lite`). + +By default we try to send the data to all destinations, always. To restrict we can have two choices: + +* Set `setPrebidTargeting` or `sendToBidders` explicity to `true` or `false` on each section; +* Set `setPrebidTargeting` or `sendToBidders` globally to `false` and only enable on the right sections; + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { + token: "<>", // mandatory + targetURL: "https://example.org", // default is document.URL + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + defaultProfile: { // optional, used if nothing is found + webo_ctx: [ ... ], // contextual segments + webo_ds: [ ...], // data science segments + }, + enabled: true, + }, + weboUserDataConf: { + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + defaultProfile: { // optional, used if nothing is found + webo_cs: [...], // wam custom segments + webo_audiences: [...], // wam audiences + }, + enabled: true, + }, + sfbxLiteDataConf: { + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + defaultProfile: { // optional, used if nothing is found + /* add specific lite segments here */ + }, + enabled: true, + }, + } + }] + } + }); +}); +``` + +Imagine we need to configure the following options using the previous example, we can write the configuration like the one below. + +||contextual|wam|lite| +| :------------ | :------------ | :------------ |:------------ | +|setPrebidTargeting|true|false|true| +|sendToBidders|false|true|true| + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + setPrebidTargeting: false, // optional. set the default value of each section. + sendToBidders: false, // optional. set the default value of each section. + weboCtxConf: { + token: "<>", // mandatory + targetURL: "https://example.org", // default is document.URL + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + enabled: true, + }, + weboUserDataConf: { + sendToBidders: true, // override param.sendToBidders. default is true + enabled: true, + }, + sfbxLiteDataConf: { + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + enabled: true, + }, + } + }] + } + }); +}); +``` + +We can also define a list of adunits / bidders that will receive data instead of using boolean values. + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { + token: "to-be-defined", // mandatory + setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits + sendToBidders: ['appnexus',...], // overide, send to only some bidders + enabled: true, + }, + weboUserDataConf: { + accountId: 12345, // recommended + setPrebidTargeting: ['adUnitCode2',...], // set target only on certain adunits + sendToBidders: ['rubicon',...], // overide, send to only some bidders + enabled: true, + }, + sfbxLiteDataConf: { + setPrebidTargeting: ['adUnitCode3',...], // set target only on certain adunits + sendToBidders: ['smartadserver',...], // overide, send to only some bidders + enabled: true, + } + } + }] + } + }); +}); +``` + +Finally, we can combine several styles in the same configuration if needed. Including the callback style. + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + setPrebidTargeting: true, // optional + sendToBidders: true, // optional + onData: function(data, meta){ // optional + var userCentricData = meta.user; // maybe undefined + var sourceOfData = meta.source; // contextual, wam or lite + + var isDefault = meta.isDefault; // true if uses default profile + + console.log('onData', data, meta); + }, + weboCtxConf: { + token: "to-be-defined", // mandatory + targetURL: "https://prebid.org", // default is document.URL + setPrebidTargeting: true, // override param.setPrebidTargeting or default true + sendToBidders: ['appnexus',...], // overide, send to only some bidders + defaultProfile: { // optional + webo_ctx: ['moon'], + webo_ds: ['bar'] + }, + enabled: true, + //, onData: function (data, ...) { ...} + }, + weboUserDataConf: { + accountId: 12345, // recommended + setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits + sendToBidders: { // send to only some bidders and adunits + 'appnexus': true, // all adunits for appnexus + 'pubmatic': ['adUnitCode1',...] // some adunits for pubmatic + // other bidders will be ignored + }, + defaultProfile: { // optional + webo_cs: ['Red'], + webo_audiences: ['bam'] + }, + localStorageProfileKey: 'webo_wam2gam_entry', // default + enabled: true, + //, onData: function (data, ...) { ...} + }, + sfbxLiteDataConf: { + setPrebidTargeting: function(adUnitCode){ // specify set target via callback + return adUnitCode == 'adUnitCode1'; + }, + sendToBidders: function(bid, adUnitCode){ // specify sendToBidders via callback + return bid.bidder == 'appnexus' && adUnitCode == 'adUnitCode1'; + } + defaultProfile: { // optional + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }, + localStorageProfileKey: '_lite', // default + enabled: true, + //, onData: function (data, ...) { ...} + } + } + }] + } + }); +}); +``` ### Supported Bidders We currently support the following bidder adapters: + * SmartADServer SSP * PubMatic SSP * AppNexus SSP * Rubicon SSP -We also set the bidder and global ortb2 `site` and `user` sections. The following bidders may support it, to be sure, check the `First Party Data Support` on the feature list for the particular bidder from here: https://docs.prebid.org/dev-docs/bidders +We also set the bidder (and global, if no specific bidders are set on `sendToBidders`) ortb2 `site.ext.data` and `user.ext.data` sections (as arbitrary data). The following bidders may support it, to be sure, check the `First Party Data Support` on the feature list for the particular bidder from [here](https://docs.prebid.org/dev-docs/bidders). * Adagio * AdformOpenRTB diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index 0f0af4efe2f..4dff3db5c18 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -6,23 +6,19 @@ import { } from 'test/mocks/xhr.js'; import { storage, - DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY + DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY, + DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY } from '../../../modules/weboramaRtdProvider.js'; -import { - config -} from 'src/config.js'; -import { - getGlobal -} from 'src/prebidGlobal.js'; + import 'src/prebid.js'; const responseHeader = { 'Content-Type': 'application/json' }; -describe('weboramaRtdProvider', function () { - describe('weboramaSubmodule', function () { - it('successfully instantiates and call contextual api', function () { +describe('weboramaRtdProvider', function() { + describe('weboramaSubmodule', function() { + it('successfully instantiates and call contextual api', function() { const moduleConfig = { params: { weboCtxConf: { @@ -35,7 +31,7 @@ describe('weboramaRtdProvider', function () { expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); }); - it('instantiate without contextual token should fail', function () { + it('instantiate without contextual token should fail', function() { const moduleConfig = { params: { weboCtxConf: {} @@ -44,7 +40,7 @@ describe('weboramaRtdProvider', function () { expect(weboramaSubmodule.init(moduleConfig)).to.equal(false); }); - it('instantiate with empty weboUserData conf should return true', function () { + it('instantiate with empty weboUserData conf should return true', function() { const moduleConfig = { params: { weboUserDataConf: {} @@ -54,35 +50,33 @@ describe('weboramaRtdProvider', function () { }); }); - describe('Handle Set Targeting', function () { + describe('Handle Set Targeting and Bid Request', function() { let sandbox; - beforeEach(function () { + beforeEach(function() { sandbox = sinon.sandbox.create(); - storage.removeDataFromLocalStorage('webo_wam2gam_entry'); + storage.removeDataFromLocalStorage(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY); - getGlobal().setConfig({ - ortb2: undefined - }); + storage.removeDataFromLocalStorage(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY); }); - afterEach(function () { + afterEach(function() { sandbox.restore(); }); - describe('Add Contextual Data', function () { - it('should set gam targeting and send to bidders by default', function () { + describe('Add site-centric data (contextual)', function() { + it('should set gam targeting and send to bidders by default', function() { let onDataResponse = {}; const moduleConfig = { params: { weboCtxConf: { token: 'foo', targetURL: 'https://prebid.org', - onData: (data, site) => { + onData: (data, meta) => { onDataResponse = { data: data, - site: site, + meta: meta, }; }, } @@ -92,9 +86,10 @@ describe('weboramaRtdProvider', function () { webo_ctx: ['foo', 'bar'], webo_ds: ['baz'], }; - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ bidder: 'smartadserver' }, { @@ -124,11 +119,10 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); expect(targeting).to.deep.equal({ 'adunit1': data, - 'adunit2': data, }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); @@ -145,119 +139,532 @@ describe('weboramaRtdProvider', function () { }, } }); - expect(getGlobal().getConfig('ortb2')).to.deep.equal({ - site: { - ext: { - data: data - }, - } - }); expect(onDataResponse).to.deep.equal({ data: data, - site: true, + meta: { + user: false, + source: 'contextual', + isDefault: false, + }, }); }); - it('should set gam targeting but not send to bidders with setPrebidTargeting=true/sendToBidders=false', function () { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setPrebidTargeting: true, - sendToBidders: false, - } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - webo_ds: ['baz'], + describe('should set gam targeting and send to one specific bidder and multiple adunits', function() { + const testcases = { + 'single string': 'appnexus', + 'array with one entry': ['appnexus'], + 'map with one entry': { + 'appnexus': true + }, + 'map complete': { + 'smartadserver': false, + 'pubmatic': false, + 'appnexus': true, + 'rubicon': false, + 'other': false, + }, + 'callback': (bid) => { + return bid.bidder == 'appnexus' + }, }; - const adUnitsCodes = ['adunit1', 'adunit2']; - const reqBidsConfigObj = { - adUnits: [{ - bids: [{ - bidder: 'smartadserver', + + Object.keys(testcases).forEach(label => { + const sendToBidders = testcases[label]; + it(`check sendToBidders as ${label}`, function() { + let onDataResponse = {}; + const moduleConfig = { params: { - target: 'foo=bar' + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + sendToBidders: sendToBidders, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } } - }, { - bidder: 'pubmatic', + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[2].params.keywords).to.deep.equal(data); + expect(adUnit.bids[3].params).to.be.undefined; + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: false, + source: 'contextual', + isDefault: false, + }, + }); + }); + }); + }); + + describe('should set gam targeting and send to one specific bidder and one adunit', function() { + const testcases = { + 'map with one entry': { + 'appnexus': ['adunit1'] + }, + 'callback': (bid, adUnitCode) => { + return bid.bidder == 'appnexus' && adUnitCode == 'adunit1'; + }, + }; + + Object.keys(testcases).forEach(label => { + const sendToBidders = testcases[label]; + it(`check sendToBidders as ${label}`, function() { + let onDataResponse = {}; + const moduleConfig = { params: { - dctr: 'foo=bar' + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + sendToBidders: sendToBidders, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } } - }, { - bidder: 'appnexus', + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[3].params).to.be.undefined; + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); + expect(reqBidsConfigObj.adUnits[1].bids[2].params).to.be.undefined; + + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: false, + source: 'contextual', + isDefault: false, + }, + }); + }); + }); + }); + + describe('should set gam targeting for multiple adunits but not send to bidders with setPrebidTargeting=/sendToBidders=false', function() { + const testcases = { + 'boolean': true, + 'array with both units': ['adunit1', 'adunit2'], + 'callback': () => { + return true; + }, + }; + + Object.keys(testcases).forEach(label => { + const setPrebidTargeting = testcases[label]; + it(`check setPrebidTargeting as ${label}`, function() { + const moduleConfig = { params: { - keywords: { - foo: ['bar'] + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: setPrebidTargeting, + sendToBidders: false, } } - }, { - bidder: 'rubicon', - params: { + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + } + }, { + bidder: 'other', + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + } + }, { + bidder: 'other', + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('foo=bar'); + expect(adUnit.bids[1].params.dctr).to.equal('foo=bar'); + expect(adUnit.bids[2].params.keywords).to.deep.equal({ + foo: ['bar'] + }); + expect(adUnit.bids[3].params).to.deep.equal({ inventory: { foo: 'bar', }, visitor: { baz: 'bam', } - } - }, { - bidder: 'other', - }] - }] - }; - const onDoneSpy = sinon.spy(); - - expect(weboramaSubmodule.init(moduleConfig)).to.be.true; - weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); - - let request = server.requests[0]; - - expect(request.method).to.equal('GET'); - expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); - expect(request.withCredentials).to.be.false; - - request.respond(200, responseHeader, JSON.stringify(data)); - - expect(onDoneSpy.calledOnce).to.be.true; + }); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + }); }); + }); - expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar'); - expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ - foo: ['bar'] - }); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: { - foo: 'bar', + describe('should set gam targeting for one adunit but not send to bidders with setPrebidTargeting=/sendToBidders=false', function() { + const testcases = { + 'array with one unit': ['adunit1'], + 'callback': (adUnitCode) => { + return adUnitCode == 'adunit1'; }, - visitor: { - baz: 'bam', - } + }; + + Object.keys(testcases).forEach(label => { + const setPrebidTargeting = testcases[label]; + it(`check setPrebidTargeting as ${label}`, function() { + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: setPrebidTargeting, + sendToBidders: false, + } + } + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + } + }, { + bidder: 'other', + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + } + }, { + bidder: 'other', + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': {}, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('foo=bar'); + expect(adUnit.bids[1].params.dctr).to.equal('foo=bar'); + expect(adUnit.bids[2].params.keywords).to.deep.equal({ + foo: ['bar'] + }); + expect(adUnit.bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + }); + + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + }); }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.be.undefined; - expect(getGlobal().getConfig('ortb2')).to.be.undefined; }); - it('should set gam targeting but not send to bidders with (submodule override) setPrebidTargeting=true/(global) sendToBidders=false', function () { + it('should set gam targeting but not send to bidders with (submodule override) setPrebidTargeting=true/(global) sendToBidders=false', function() { let onDataResponse = {}; const moduleConfig = { params: { setPrebidTargeting: false, sendToBidders: false, - onData: (data, site) => { + onData: (data, meta) => { onDataResponse = { data: data, - site: site, + meta: meta, }; }, weboCtxConf: { @@ -271,9 +678,10 @@ describe('weboramaRtdProvider', function () { webo_ctx: ['foo', 'bar'], webo_ds: ['baz'], }; - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ bidder: 'smartadserver', params: { @@ -297,23 +705,25 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); expect(targeting).to.deep.equal({ 'adunit1': data, - 'adunit2': data, }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(1); - expect(getGlobal().getConfig('ortb2')).to.be.undefined; expect(onDataResponse).to.deep.equal({ data: data, - site: true, + meta: { + user: false, + source: 'contextual', + isDefault: false, + }, }); }); - it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function () { + it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function() { const moduleConfig = { params: { weboCtxConf: { @@ -327,9 +737,10 @@ describe('weboramaRtdProvider', function () { webo_ctx: ['foo', 'bar'], webo_ds: ['baz'], }; - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ bidder: 'smartadserver', params: { @@ -377,9 +788,11 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); - expect(targeting).to.deep.equal({}); + expect(targeting).to.deep.equal({ + 'adunit1': {}, + }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); @@ -406,16 +819,9 @@ describe('weboramaRtdProvider', function () { }, } }); - expect(getGlobal().getConfig('ortb2')).to.deep.equal({ - site: { - ext: { - data: data - }, - } - }); }); - it('should use default profile in case of api error', function () { + it('should use default profile in case of api error', function() { const defaultProfile = { webo_ctx: ['baz'], }; @@ -427,19 +833,20 @@ describe('weboramaRtdProvider', function () { targetURL: 'https://prebid.org', setPrebidTargeting: true, defaultProfile: defaultProfile, - onData: (data, site) => { + onData: (data, meta) => { onDataResponse = { data: data, - site: site, + meta: meta, }; }, } } }; - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ bidder: 'smartadserver' }, { @@ -468,11 +875,10 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); expect(targeting).to.deep.equal({ 'adunit1': defaultProfile, - 'adunit2': defaultProfile, }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); @@ -489,53 +895,67 @@ describe('weboramaRtdProvider', function () { }, } }); - expect(getGlobal().getConfig('ortb2')).to.deep.equal({ - site: { - ext: { - data: defaultProfile - }, - } - }); expect(onDataResponse).to.deep.equal({ data: defaultProfile, - site: true, + meta: { + user: false, + source: 'contextual', + isDefault: true, + }, }); }); - }); - describe('Add user-centric data (WAM2GAM)', function () { - it('should set gam targeting from local storage and send to bidders by default', function () { + it('should be possible update profile from callbacks for a given bidder/adUnitCode', function() { let onDataResponse = {}; const moduleConfig = { params: { - weboUserDataConf: { - accountID: 12345, - onData: (data, site) => { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: (adUnitCode, data, meta) => { + if (adUnitCode == 'adunit1') { + data['webo_foo'] = ['bar']; + } + return true; + }, + sendToBidders: (bid, adUnitCode, data, meta) => { + if (bid.bidder == 'appnexus' && adUnitCode == 'adunit1') { + data['webo_bar'] = ['baz']; + } + return true; + }, + baseURLProfileAPI: 'ctx.test.weborama.com', + onData: (data, meta) => { onDataResponse = { data: data, - site: site, + meta: meta, }; }, } } }; const data = { - webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], - }; - - const entry = { - targeting: data, + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], }; - - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - sandbox.stub(storage, 'getDataFromLocalStorage') - .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) - .returns(JSON.stringify(entry)); - - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, bids: [{ bidder: 'smartadserver' }, { @@ -549,53 +969,80 @@ describe('weboramaRtdProvider', function () { }] }] }; + const onDoneSpy = sinon.spy(); expect(weboramaSubmodule.init(moduleConfig)).to.be.true; weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.test.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); expect(targeting).to.deep.equal({ - 'adunit1': data, + 'adunit1': { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + webo_foo: ['bar'], + }, 'adunit2': data, }); - expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_cs=foo;webo_cs=bar;webo_audiences=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_cs=foo,bar|webo_audiences=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - visitor: data - }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ - user: { - ext: { - data: data - }, - } + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); + expect(adUnit.bids[1].params.dctr).to.equal('webo_ctx=foo,bar|webo_ds=baz'); + expect(adUnit.bids[3].params).to.deep.equal({ + inventory: data + }); + expect(adUnit.bids[4].ortb2).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); }); - expect(getGlobal().getConfig('ortb2')).to.deep.equal({ - user: { - ext: { - data: data - }, - } + + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + webo_bar: ['baz'], }); + expect(reqBidsConfigObj.adUnits[1].bids[2].params.keywords).to.deep.equal(data); + expect(onDataResponse).to.deep.equal({ data: data, - site: false, + meta: { + user: false, + source: 'contextual', + isDefault: false, + }, }); }); + }); - it('should set gam targeting but not send to bidders with setPrebidTargeting=true/sendToBidders=false', function () { + describe('Add user-centric data (wam)', function() { + it('should set gam targeting from local storage and send to bidders by default', function() { + let onDataResponse = {}; const moduleConfig = { params: { weboUserDataConf: { - setPrebidTargeting: true, - sendToBidders: false + accoundId: 12345, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, } } }; @@ -613,36 +1060,18 @@ describe('weboramaRtdProvider', function () { .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) .returns(JSON.stringify(entry)); - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ - bidder: 'smartadserver', - params: { - target: 'foo=bar' - } + bidder: 'smartadserver' }, { - bidder: 'pubmatic', - params: { - dctr: 'foo=bar' - } + bidder: 'pubmatic' }, { - bidder: 'appnexus', - params: { - keywords: { - foo: ['bar'] - } - } + bidder: 'appnexus' }, { - bidder: 'rubicon', - params: { - inventory: { - foo: 'bar' - }, - visitor: { - baz: 'bam' - } - } + bidder: 'rubicon' }, { bidder: 'other' }] @@ -655,44 +1084,554 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); expect(targeting).to.deep.equal({ 'adunit1': data, - 'adunit2': data, }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar'); - expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ - foo: ['bar'] - }); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_cs=foo;webo_cs=bar;webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_cs=foo,bar|webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: { - foo: 'bar' - }, - visitor: { - baz: 'bam' + visitor: data + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + user: { + ext: { + data: data + }, } }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.be.undefined; - expect(getGlobal().getConfig('ortb2')).to.be.undefined; + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: true, + source: 'wam', + isDefault: false, + }, + }); }); - it('should set gam targeting but not send to bidders with (submodule override) setPrebidTargeting=true/(global) sendToBidders=false', function () { - let onDataResponse = {}; - const moduleConfig = { - params: { - setPrebidTargeting: false, - sendToBidders: false, - onData: (data, site) => { + describe('should set gam targeting from local storage and send to one specific bidder and multiple adunits', function() { + const testcases = { + 'single string': 'appnexus', + 'array with one entry': ['appnexus'], + 'map with one entry': { + 'appnexus': true + }, + 'map complete': { + 'smartadserver': false, + 'pubmatic': false, + 'appnexus': true, + 'rubicon': false, + 'other': false, + }, + 'callback': (bid) => { + return bid.bidder == 'appnexus' + }, + }; + + Object.keys(testcases).forEach(label => { + const sendToBidders = testcases[label]; + it(`check sendToBidders as ${label}`, function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboUserDataConf: { + accountId: 12345, + sendToBidders: sendToBidders, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[2].params.keywords).to.deep.equal(data); + expect(adUnit.bids[3].params).to.be.undefined; + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: true, + source: 'wam', + isDefault: false, + }, + }); + }); + }); + }); + + describe('should set gam targeting from local storage and send to one specific bidder and one adunit', function() { + const testcases = { + 'map with one entry': { + 'appnexus': ['adunit1'] + }, + 'callback': (bid, adUnitCode) => { + return bid.bidder == 'appnexus' && adUnitCode == 'adunit1'; + }, + }; + + Object.keys(testcases).forEach(label => { + const sendToBidders = testcases[label]; + it(`check sendToBidders as ${label}`, function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboUserDataConf: { + accountId: 12345, + sendToBidders: sendToBidders, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[3].params).to.be.undefined; + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); + expect(reqBidsConfigObj.adUnits[1].bids[2].params).to.be.undefined; + + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: true, + source: 'wam', + isDefault: false, + }, + }); + }); + }); + }); + + describe('should set gam targeting for multiple adunits but not send to bidders with setPrebidTargeting=/sendToBidders=false', function() { + const testcases = { + 'boolean': true, + 'array with both units': ['adunit1', 'adunit2'], + 'callback': () => { + return true; + }, + }; + + Object.keys(testcases).forEach(label => { + const setPrebidTargeting = testcases[label]; + it(`check setPrebidTargeting as ${label}`, function() { + const moduleConfig = { + params: { + weboUserDataConf: { + accoundId: 12345, + setPrebidTargeting: setPrebidTargeting, + sendToBidders: false + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('foo=bar'); + expect(adUnit.bids[1].params.dctr).to.equal('foo=bar'); + expect(adUnit.bids[2].params.keywords).to.deep.equal({ + foo: ['bar'] + }); + expect(adUnit.bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + }); + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + }); + }); + }); + + describe('should set gam targeting for one adunit but not send to bidders with setPrebidTargeting=/sendToBidders=false', function() { + const testcases = { + 'array with one unit': ['adunit1'], + 'callback': (adUnitCode) => { + return adUnitCode == 'adunit1'; + }, + }; + + Object.keys(testcases).forEach(label => { + const setPrebidTargeting = testcases[label]; + it(`check setPrebidTargeting as ${label}`, function() { + const moduleConfig = { + params: { + weboUserDataConf: { + accoundId: 12345, + setPrebidTargeting: setPrebidTargeting, + sendToBidders: false + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': {}, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('foo=bar'); + expect(adUnit.bids[1].params.dctr).to.equal('foo=bar'); + expect(adUnit.bids[2].params.keywords).to.deep.equal({ + foo: ['bar'] + }); + expect(adUnit.bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + }); + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + }); + }); + }); + + it('should set gam targeting but not send to bidders with (submodule override) setPrebidTargeting=true/(global) sendToBidders=false', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + setPrebidTargeting: false, + sendToBidders: false, + onData: (data, meta) => { onDataResponse = { data: data, - site: site, + meta: meta, }; }, weboUserDataConf: { + accoundId: 12345, setPrebidTargeting: true, // submodule parameter will override module parameter } } @@ -711,9 +1650,10 @@ describe('weboramaRtdProvider', function () { .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) .returns(JSON.stringify(entry)); - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ bidder: 'smartadserver', params: { @@ -729,26 +1669,29 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); expect(targeting).to.deep.equal({ 'adunit1': data, - 'adunit2': data, }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(1); expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); - expect(getGlobal().getConfig('ortb2')).to.be.undefined; expect(onDataResponse).to.deep.equal({ data: data, - site: false, + meta: { + user: true, + source: 'wam', + isDefault: false, + }, }); }); - it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function () { + it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function() { const moduleConfig = { params: { weboUserDataConf: { + accoundId: 12345, setPrebidTargeting: false, } } @@ -767,9 +1710,10 @@ describe('weboramaRtdProvider', function () { .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) .returns(JSON.stringify(entry)); - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ bidder: 'smartadserver', params: { @@ -809,9 +1753,11 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); - expect(targeting).to.deep.equal({}); + expect(targeting).to.deep.equal({ + 'adunit1': {}, + }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;webo_cs=foo;webo_cs=bar;webo_audiences=baz'); @@ -838,22 +1784,16 @@ describe('weboramaRtdProvider', function () { }, } }); - expect(getGlobal().getConfig('ortb2')).to.deep.equal({ - user: { - ext: { - data: data - }, - } - }); }); - it('should use default profile in case of nothing on local storage', function () { + it('should use default profile in case of nothing on local storage', function() { const defaultProfile = { webo_audiences: ['baz'] }; const moduleConfig = { params: { weboUserDataConf: { + accoundId: 12345, setPrebidTargeting: true, defaultProfile: defaultProfile, } @@ -862,9 +1802,10 @@ describe('weboramaRtdProvider', function () { sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ bidder: 'smartadserver' }, { @@ -885,11 +1826,10 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); expect(targeting).to.deep.equal({ 'adunit1': defaultProfile, - 'adunit2': defaultProfile, }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); @@ -906,16 +1846,9 @@ describe('weboramaRtdProvider', function () { }, } }); - expect(getGlobal().getConfig('ortb2')).to.deep.equal({ - user: { - ext: { - data: defaultProfile - }, - } - }); }); - it('should use default profile if cant read from local storage', function () { + it('should use default profile if cant read from local storage', function() { const defaultProfile = { webo_audiences: ['baz'] }; @@ -923,12 +1856,13 @@ describe('weboramaRtdProvider', function () { const moduleConfig = { params: { weboUserDataConf: { + accoundId: 12345, setPrebidTargeting: true, defaultProfile: defaultProfile, - onData: (data, site) => { + onData: (data, meta) => { onDataResponse = { data: data, - site: site, + meta: meta, }; }, } @@ -937,9 +1871,10 @@ describe('weboramaRtdProvider', function () { sandbox.stub(storage, 'localStorageIsEnabled').returns(false); - const adUnitsCodes = ['adunit1', 'adunit2']; + const adUnitCode = 'adunit1'; const reqBidsConfigObj = { adUnits: [{ + code: adUnitCode, bids: [{ bidder: 'smartadserver' }, { @@ -960,11 +1895,10 @@ describe('weboramaRtdProvider', function () { expect(onDoneSpy.calledOnce).to.be.true; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); expect(targeting).to.deep.equal({ 'adunit1': defaultProfile, - 'adunit2': defaultProfile, }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); @@ -981,16 +1915,1149 @@ describe('weboramaRtdProvider', function () { }, } }); - expect(getGlobal().getConfig('ortb2')).to.deep.equal({ - user: { + expect(onDataResponse).to.deep.equal({ + data: defaultProfile, + meta: { + user: true, + source: 'wam', + isDefault: true, + }, + }); + }); + + it('should be possible update profile from callbacks for a given bidder/adUnitCode', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboUserDataConf: { + accoundId: 12345, + targetURL: 'https://prebid.org', + setPrebidTargeting: (adUnitCode, data, meta) => { + if (adUnitCode == 'adunit1') { + data['webo_foo'] = ['bar']; + } + return true; + }, + sendToBidders: (bid, adUnitCode, data, meta) => { + if (bid.bidder == 'appnexus' && adUnitCode == 'adunit1') { + data['webo_bar'] = ['baz']; + } + return true; + }, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + webo_foo: ['bar'], + }, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('webo_cs=foo;webo_cs=bar;webo_audiences=baz'); + expect(adUnit.bids[1].params.dctr).to.equal('webo_cs=foo,bar|webo_audiences=baz'); + expect(adUnit.bids[3].params).to.deep.equal({ + visitor: data + }); + expect(adUnit.bids[4].ortb2).to.deep.equal({ + user: { + ext: { + data: data + }, + } + }); + }); + + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + webo_bar: ['baz'], + }); + expect(reqBidsConfigObj.adUnits[1].bids[2].params.keywords).to.deep.equal(data); + + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: true, + source: 'wam', + isDefault: false, + }, + }); + }); + }); + + describe('Add support to sfbx lite', function() { + it('should set gam targeting from local storage and send to bidders by default', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + sfbxLiteDataConf: { + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }; + + const entry = { + webo: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('lite_occupation=gérant;lite_occupation=bénévole;lite_hobbies=sport;lite_hobbies=cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('lite_occupation=gérant,bénévole|lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: data, + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + site: { ext: { - data: defaultProfile + data: data, }, - } + }, }); expect(onDataResponse).to.deep.equal({ - data: defaultProfile, - site: false, + data: data, + meta: { + user: false, + source: 'lite', + isDefault: false, + }, + }); + }); + + describe('should set gam targeting from local storage and send to one specific bidder and multiple adunits', function() { + const testcases = { + 'single string': 'appnexus', + 'array with one entry': ['appnexus'], + 'map with one entry': { + 'appnexus': true + }, + 'map complete': { + 'smartadserver': false, + 'pubmatic': false, + 'appnexus': true, + 'rubicon': false, + 'other': false, + }, + 'callback': (bid) => { + return bid.bidder == 'appnexus' + }, + }; + + Object.keys(testcases).forEach(label => { + const sendToBidders = testcases[label]; + it(`check sendToBidders as ${label}`, function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + sfbxLiteDataConf: { + sendToBidders: sendToBidders, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }; + + const entry = { + webo: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[2].params.keywords).to.deep.equal(data); + expect(adUnit.bids[3].params).to.be.undefined; + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: false, + source: 'lite', + isDefault: false, + }, + }); + }); + }); + }); + + describe('should set gam targeting from local storage and send to one specific bidder and one adunit', function() { + const testcases = { + 'map with one entry': { + 'appnexus': ['adunit1'] + }, + 'callback': (bid, adUnitCode) => { + return bid.bidder == 'appnexus' && adUnitCode == 'adunit1'; + }, + }; + + Object.keys(testcases).forEach(label => { + const sendToBidders = testcases[label]; + it(`check sendToBidders as ${label}`, function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + sfbxLiteDataConf: { + sendToBidders: sendToBidders, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }; + + const entry = { + webo: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[3].params).to.be.undefined; + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); + expect(reqBidsConfigObj.adUnits[1].bids[2].params).to.be.undefined; + + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: false, + source: 'lite', + isDefault: false, + }, + }); + }); + }); + }); + + describe('should set gam targeting for multiple adunits but not send to bidders with setPrebidTargeting=/sendToBidders=false', function() { + const testcases = { + 'boolean': true, + 'array with both units': ['adunit1', 'adunit2'], + 'callback': () => { + return true; + }, + }; + + Object.keys(testcases).forEach(label => { + const setPrebidTargeting = testcases[label]; + it(`check setPrebidTargeting as ${label}`, function() { + const moduleConfig = { + params: { + sfbxLiteDataConf: { + setPrebidTargeting: setPrebidTargeting, + sendToBidders: false + } + } + }; + const data = { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }; + + const entry = { + webo: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('foo=bar'); + expect(adUnit.bids[1].params.dctr).to.equal('foo=bar'); + expect(adUnit.bids[2].params.keywords).to.deep.equal({ + foo: ['bar'] + }); + expect(adUnit.bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + }); + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + }); + }); + }); + + describe('should set gam targeting for one adunit but not send to bidders with setPrebidTargeting=/sendToBidders=false', function() { + const testcases = { + 'array with one unit': ['adunit1'], + 'callback': (adUnitCode) => { + return adUnitCode == 'adunit1'; + }, + }; + + Object.keys(testcases).forEach(label => { + const setPrebidTargeting = testcases[label]; + it(`check setPrebidTargeting as ${label}`, function() { + const moduleConfig = { + params: { + sfbxLiteDataConf: { + setPrebidTargeting: setPrebidTargeting, + sendToBidders: false + } + } + }; + const data = { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }; + + const entry = { + webo: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': {}, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('foo=bar'); + expect(adUnit.bids[1].params.dctr).to.equal('foo=bar'); + expect(adUnit.bids[2].params.keywords).to.deep.equal({ + foo: ['bar'] + }); + expect(adUnit.bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + }); + expect(adUnit.bids[4].ortb2).to.be.undefined; + }); + }); + }); + }); + + it('should set gam targeting but not send to bidders with (submodule override) setPrebidTargeting=true/(global) sendToBidders=false', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + setPrebidTargeting: false, + sendToBidders: false, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + sfbxLiteDataConf: { + setPrebidTargeting: true, // submodule parameter will override module parameter + } + } + }; + const data = { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }; + + const entry = { + webo: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(1); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: false, + source: 'lite', + isDefault: false, + }, + }); + }); + + it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function() { + const moduleConfig = { + params: { + sfbxLiteDataConf: { + setPrebidTargeting: false, + } + } + }; + const data = { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }; + + const entry = { + webo: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + } + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': {}, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;lite_occupation=gérant;lite_occupation=bénévole;lite_hobbies=sport;lite_hobbies=cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar|lite_occupation=gérant,bénévole|lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ + foo: ['bar'], + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar', + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }, + visitor: { + baz: 'bam', + } + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + site: { + ext: { + data: data, + }, + }, + }); + }); + + it('should use default profile in case of nothing on local storage', function() { + const defaultProfile = { + lite_hobbies: ['sport', 'cinéma'], + }; + const moduleConfig = { + params: { + sfbxLiteDataConf: { + setPrebidTargeting: true, + defaultProfile: defaultProfile, + } + } + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('lite_hobbies=sport;lite_hobbies=cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: defaultProfile, + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + site: { + ext: { + data: defaultProfile, + }, + }, + }); + }); + + it('should use default profile if cant read from local storage', function() { + const defaultProfile = { + lite_hobbies: ['sport', 'cinéma'], + }; + let onDataResponse = {}; + const moduleConfig = { + params: { + sfbxLiteDataConf: { + setPrebidTargeting: true, + defaultProfile: defaultProfile, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('lite_hobbies=sport;lite_hobbies=cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: defaultProfile, + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + site: { + ext: { + data: defaultProfile, + }, + }, + }); + expect(onDataResponse).to.deep.equal({ + data: defaultProfile, + meta: { + user: false, + source: 'lite', + isDefault: true, + }, + }); + }); + + it('should be possible update profile from callbacks for a given bidder/adUnitCode', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + sfbxLiteDataConf: { + targetURL: 'https://prebid.org', + setPrebidTargeting: (adUnitCode, data, meta) => { + if (adUnitCode == 'adunit1') { + data['lito_foo'] = ['bar']; + } + return true; + }, + sendToBidders: (bid, adUnitCode, data, meta) => { + if (bid.bidder == 'appnexus' && adUnitCode == 'adunit1') { + data['lito_bar'] = ['baz']; + } + return true; + }, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }; + + const entry = { + webo: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitCode1 = 'adunit1'; + const adUnitCode2 = 'adunit2'; + const reqBidsConfigObj = { + adUnits: [{ + code: adUnitCode1, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }, { + code: adUnitCode2, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode1, adUnitCode2], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + lito_foo: ['bar'], + }, + 'adunit2': data, + }); + + reqBidsConfigObj.adUnits.forEach(adUnit => { + expect(adUnit.bids.length).to.equal(5); + expect(adUnit.bids[0].params.target).to.equal('lite_occupation=gérant;lite_occupation=bénévole;lite_hobbies=sport;lite_hobbies=cinéma'); + expect(adUnit.bids[1].params.dctr).to.equal('lite_occupation=gérant,bénévole|lite_hobbies=sport,cinéma'); + expect(adUnit.bids[3].params).to.deep.equal({ + inventory: data, + }); + expect(adUnit.bids[4].ortb2).to.deep.equal({ + site: { + ext: { + data: data, + }, + }, + }); + }); + + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + lito_bar: ['baz'], + }); + expect(reqBidsConfigObj.adUnits[1].bids[2].params.keywords).to.deep.equal(data); + + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: false, + source: 'lite', + isDefault: false, + }, }); }); });