Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue with GDPR module in concurrent auctions #2612

Merged
merged 2 commits into from
May 24, 2018
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 45 additions & 39 deletions modules/consentManagement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,17 +30,19 @@ 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.
// if it's found, directly call the CMP via it's API and call the cmpSuccess callback.
// 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 {
Expand All @@ -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;

Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -146,7 +141,7 @@ function lookupIabConsent(cmpSuccess, cmpError, adUnits) {

function cmpIframeCallback(consentObject) {
removePostMessageListener();
cmpSuccess(consentObject);
cmpSuccess(consentObject, hookConfig);
}
}

Expand All @@ -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);
}
}
}
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -253,19 +254,24 @@ 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) {
utils.logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.');
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');
}
Expand Down