From 62517f66dbe849770310dea23afa7b4dbf8aacba Mon Sep 17 00:00:00 2001 From: Jason Snellbaker Date: Thu, 24 May 2018 10:39:43 -0400 Subject: [PATCH] update variable management to support gdpr in concurrent auctions --- modules/consentManagement.js | 84 +++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 2a47bb25899..56a42e06261 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -19,14 +19,6 @@ export let allowAuction; let consentData; -let context; -let args; -let nextFn; -let bidsBackHandler; - -let timer; -let haveExited; - // add new CMPs here, with their dedicated lookup function const cmpCallMap = { 'iab': lookupIabConsent @@ -38,9 +30,9 @@ const cmpCallMap = { * based on the appropriate result. * @param {function(string)} cmpSuccess acts as a success callback when CMP returns a value; pass along consentObject (string) from CMP * @param {function(string)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) - * @param {[objects]} adUnits used in the safeframe workflow to know what sizes to include in the $sf.ext.register call + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ -function lookupIabConsent(cmpSuccess, cmpError, adUnits) { +function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { let cmpCallbacks; // check if the CMP is located on the same window level as the prebid code. @@ -48,7 +40,9 @@ function lookupIabConsent(cmpSuccess, cmpError, adUnits) { // if it's not found, assume the prebid code may be inside an iframe and the CMP code is located in a higher parent window. // in this case, use the IAB's iframe locator sample code (which is slightly cutomized) to try to find the CMP and use postMessage() to communicate with the CMP. if (utils.isFn(window.__cmp)) { - window.__cmp('getVendorConsents', null, cmpSuccess); + window.__cmp('getVendorConsents', null, function (consentObject) { + cmpSuccess(consentObject, hookConfig); + }); } else if (inASafeFrame() && typeof window.$sf.ext.cmp === 'function') { callCmpWhileInSafeFrame(); } else { @@ -62,11 +56,12 @@ function lookupIabConsent(cmpSuccess, cmpError, adUnits) { function callCmpWhileInSafeFrame() { function sfCallback(msgName, data) { if (msgName === 'cmpReturn') { - cmpSuccess(data.vendorConsents); + cmpSuccess(data.vendorConsents, hookConfig); } } // find sizes from adUnits object + let adUnits = hookConfig.adUnits; let width = 1; let height = 1; @@ -106,7 +101,7 @@ function lookupIabConsent(cmpSuccess, cmpError, adUnits) { let errmsg = 'CMP not found'; // small customization to properly return error - return cmpError(errmsg); + return cmpError(errmsg, hookConfig); } let callId = Math.random() + ''; let msg = {__cmpCall: { @@ -146,7 +141,7 @@ function lookupIabConsent(cmpSuccess, cmpError, adUnits) { function cmpIframeCallback(consentObject) { removePostMessageListener(); - cmpSuccess(consentObject); + cmpSuccess(consentObject, hookConfig); } } @@ -159,31 +154,35 @@ function lookupIabConsent(cmpSuccess, cmpError, adUnits) { * @param {function} fn required; The next function in the chain, used by hook.js */ export function requestBidsHook(reqBidsConfigObj, fn) { - context = this; - args = arguments; - nextFn = fn; - haveExited = false; - let adUnits = reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits; - bidsBackHandler = reqBidsConfigObj.bidsBackHandler; + // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions) + const hookConfig = { + context: this, + args: arguments, + nextFn: fn, + adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits, + bidsBackHandler: reqBidsConfigObj.bidsBackHandler, + haveExited: false, + timer: null + }; // in case we already have consent (eg during bid refresh) if (consentData) { - return exitModule(); + return exitModule(null, hookConfig); } if (!includes(Object.keys(cmpCallMap), userCMP)) { utils.logWarn(`CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); - return nextFn.apply(context, args); + return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); } - cmpCallMap[userCMP].call(this, processCmpData, cmpFailed, adUnits); + cmpCallMap[userCMP].call(this, processCmpData, cmpFailed, hookConfig); // only let this code run if module is still active (ie if the callbacks used by CMPs haven't already finished) - if (!haveExited) { + if (!hookConfig.haveExited) { if (consentTimeout === 0) { processCmpData(undefined); } else { - timer = setTimeout(cmpTimedOut, consentTimeout); + hookConfig.timer = setTimeout(cmpTimedOut.bind(null, hookConfig), consentTimeout); } } } @@ -193,37 +192,39 @@ export function requestBidsHook(reqBidsConfigObj, fn) { * If it's bad, we exit the module depending on config settings. * If it's good, then we store the value and exits the module. * @param {object} consentObject required; object returned by CMP that contains user's consent choices + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ -function processCmpData(consentObject) { +function processCmpData(consentObject, hookConfig) { if (!utils.isPlainObject(consentObject) || !utils.isStr(consentObject.metadata) || consentObject.metadata === '') { - cmpFailed(`CMP returned unexpected value during lookup process; returned value was (${consentObject}).`); + cmpFailed(`CMP returned unexpected value during lookup process; returned value was (${consentObject}).`, hookConfig); } else { - clearTimeout(timer); + clearTimeout(hookConfig.timer); storeConsentData(consentObject); - exitModule(); + exitModule(null, hookConfig); } } /** * General timeout callback when interacting with CMP takes too long. */ -function cmpTimedOut() { - cmpFailed('CMP workflow exceeded timeout threshold.'); +function cmpTimedOut(hookConfig) { + cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig); } /** * This function contains the controlled steps to perform when there's a problem with CMP. * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened. + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ -function cmpFailed(errMsg) { - clearTimeout(timer); +function cmpFailed(errMsg, hookConfig) { + clearTimeout(hookConfig.timer); // still set the consentData to undefined when there is a problem as per config options if (allowAuction) { storeConsentData(undefined); } - exitModule(errMsg); + exitModule(errMsg, hookConfig); } /** @@ -253,10 +254,15 @@ function storeConsentData(cmpConsentObject) { * 2. bad exit but auction still continues (warning message is logged, CMP data is undefined and still passed along). * 3. bad exit with auction canceled (error message is logged). * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered. + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ -function exitModule(errMsg) { - if (haveExited === false) { - haveExited = true; +function exitModule(errMsg, hookConfig) { + if (hookConfig.haveExited === false) { + hookConfig.haveExited = true; + + let context = hookConfig.context; + let args = hookConfig.args; + let nextFn = hookConfig.nextFn; if (errMsg) { if (allowAuction) { @@ -264,8 +270,8 @@ function exitModule(errMsg) { nextFn.apply(context, args); } else { utils.logError(errMsg + ' Canceling auction as per consentManagement config.'); - if (typeof bidsBackHandler === 'function') { - bidsBackHandler(); + if (typeof hookConfig.bidsBackHandler === 'function') { + hookConfig.bidsBackHandler(); } else { utils.logError('Error executing bidsBackHandler'); }