Skip to content

Commit

Permalink
parrableIdSystem: Read legacy ID and optout cookies and migrate to ne…
Browse files Browse the repository at this point in the history
…w cookie storage implementation (#5219)

* Add unit coverage for parrableIdSystem getId callback

* PBID-14: Pass uspString to Parrable as us_privacy query parameter

* PBID-14: Simplify parrableIdSystem us_privacy test

* PBID-14: Only send us_privacy to Parrable when a value exists

* PBID-11: Read new Parrable compound cookie _parrable_id

Migrating from legacy _parrable_eid cookie. The new cookie contains ibaOptout and ccpaOptout status fields

* Remove path check from parrableIdSystem url test

* PBID-11: Integrate Parrable compound cookie, consolidating old cookies

* PBID-11: Update parrableIdSystem requestBids hook test to support compound cookie value

* PBID-11: Small refactor to parrableIdSystem spec to support compound cookie

* PBID-11: Handle legacy ibaOptout as truthy value when migrating to compound cookie

* PBID-11: Add parrableIdSystem spec tests covering migration of legacy cookies

* PBID-11: Remove storage documentation from test pages and userId module docs

* PBID-11: Remove SUBMODULES_THAT_ALWAYS_REFRESH_ID feature from userId system

* PBID-11: Use better serialize implementation for Parrable compound cookie

* PBID-11: Update parrableIdSystem interface documentation

* Add missing extension to mock xhr import

* PBID-11: Try to access eid property only when parrableId object exists

* PBID-11: Construct parrableId from legacy cookies in same manner as compound cookie

* Use hardcoded expiration date for legacy cookies

* parrableIdSystem: Relocate new unit test from upstream

* PBID-39: Fallback to cookie values when backend response is missing components

Also handle another missed callback scenario if the response object parses to nothing
  • Loading branch information
icflournoy authored Jun 8, 2020
1 parent acdece8 commit cabbf4e
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 181 deletions.
5 changes: 0 additions & 5 deletions integrationExamples/gpt/audigentSegments_example.html
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,6 @@
params: {
// change to Parrable Partner Client ID(s) you received from the Parrable Partners you are using
partner: '30182847-e426-4ff9-b2b5-9ca1324ea09b'
},
storage: {
type: "cookie",
name: "_parrable_eid", // create a cookie with this name
expires: 365 // cookie can last for a year
}
}, {
name: "pubCommonId",
Expand Down
5 changes: 0 additions & 5 deletions integrationExamples/gpt/userId_example.html
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,6 @@
params: {
// change to Parrable Partner Client ID(s) you received from the Parrable Partners you are using
partner: '30182847-e426-4ff9-b2b5-9ca1324ea09b'
},
storage: {
type: "cookie",
name: "_parrable_eid", // create a cookie with this name
expires: 365 // cookie can last for a year
}
}, {
name: "pubCommonId",
Expand Down
138 changes: 127 additions & 11 deletions modules/parrableIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,50 @@ import { ajax } from '../src/ajax.js';
import { submodule } from '../src/hook.js';
import { getRefererInfo } from '../src/refererDetection.js';
import { uspDataHandler } from '../src/adapterManager.js';
import { getStorageManager } from '../src/storageManager.js';

const PARRABLE_URL = 'https://h.parrable.com/prebid';
const PARRABLE_COOKIE_NAME = '_parrable_id';
const LEGACY_ID_COOKIE_NAME = '_parrable_eid';
const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout';
const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000;
const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT';

const storage = getStorageManager();

function getExpirationDate() {
const oneYearFromNow = new Date(utils.timestamp() + ONE_YEAR_MS);
return oneYearFromNow.toGMTString();
}

function deserializeParrableId(parrableIdStr) {
const parrableId = {};
const values = parrableIdStr.split(',');

values.forEach(function(value) {
const pair = value.split(':');
// unpack a value of 1 as true
parrableId[pair[0]] = +pair[1] === 1 ? true : pair[1];
});

return parrableId;
}

function serializeParrableId(parrableId) {
let components = [];

if (parrableId.eid) {
components.push('eid:' + parrableId.eid);
}
if (parrableId.ibaOptout) {
components.push('ibaOptout:1');
}
if (parrableId.ccpaOptout) {
components.push('ccpaOptout:1');
}

return components.join(',');
}

function isValidConfig(configParams) {
if (!configParams) {
Expand All @@ -22,17 +64,70 @@ function isValidConfig(configParams) {
utils.logError('User ID - parrableId submodule requires partner list');
return false;
}
if (configParams.storage) {
utils.logWarn('User ID - parrableId submodule does not require a storage config');
}
return true;
}

function fetchId(configParams, currentStoredId) {
function readCookie() {
const parrableIdStr = storage.getCookie(PARRABLE_COOKIE_NAME);
if (parrableIdStr) {
return deserializeParrableId(decodeURIComponent(parrableIdStr));
}
return null;
}

function writeCookie(parrableId) {
if (parrableId) {
const parrableIdStr = encodeURIComponent(serializeParrableId(parrableId));
storage.setCookie(PARRABLE_COOKIE_NAME, parrableIdStr, getExpirationDate(), 'lax');
}
}

function readLegacyCookies() {
const eid = storage.getCookie(LEGACY_ID_COOKIE_NAME);
const ibaOptout = (storage.getCookie(LEGACY_OPTOUT_COOKIE_NAME) === 'true');
if (eid || ibaOptout) {
const parrableId = {};
if (eid) {
parrableId.eid = eid;
}
if (ibaOptout) {
parrableId.ibaOptout = ibaOptout;
}
return parrableId;
}
return null;
}

function migrateLegacyCookies(parrableId) {
if (parrableId) {
writeCookie(parrableId);
if (parrableId.eid) {
storage.setCookie(LEGACY_ID_COOKIE_NAME, '', EXPIRE_COOKIE_DATE);
}
if (parrableId.ibaOptout) {
storage.setCookie(LEGACY_OPTOUT_COOKIE_NAME, '', EXPIRE_COOKIE_DATE);
}
}
}

function fetchId(configParams) {
if (!isValidConfig(configParams)) return;

let parrableId = readCookie();
if (!parrableId) {
parrableId = readLegacyCookies();
migrateLegacyCookies(parrableId);
}

const eid = (parrableId) ? parrableId.eid : null;
const refererInfo = getRefererInfo();
const uspString = uspDataHandler.getConsentData();

const data = {
eid: currentStoredId || null,
eid,
trackers: configParams.partner.split(','),
url: refererInfo.referer
};
Expand All @@ -54,16 +149,31 @@ function fetchId(configParams, currentStoredId) {
const callback = function (cb) {
const callbacks = {
success: response => {
let eid;
let newParrableId = parrableId ? utils.deepClone(parrableId) : {};
if (response) {
try {
let responseObj = JSON.parse(response);
eid = responseObj ? responseObj.eid : undefined;
if (responseObj) {
if (responseObj.ccpaOptout !== true) {
newParrableId.eid = responseObj.eid;
} else {
newParrableId.eid = null;
newParrableId.ccpaOptout = true;
}
if (responseObj.ibaOptout === true) {
newParrableId.ibaOptout = true;
}
}
} catch (error) {
utils.logError(error);
cb();
}
writeCookie(newParrableId);
cb(newParrableId);
} else {
utils.logError('parrableId: ID fetch returned an empty result');
cb();
}
cb(eid);
},
error: error => {
utils.logError(`parrableId: ID fetch encountered an error`, error);
Expand All @@ -73,7 +183,10 @@ function fetchId(configParams, currentStoredId) {
ajax(PARRABLE_URL, callbacks, searchParams, options);
};

return { callback };
return {
callback,
id: parrableId
};
};

/** @type {Submodule} */
Expand All @@ -86,22 +199,25 @@ export const parrableIdSubmodule = {
/**
* decode the stored id value for passing to bid requests
* @function
* @param {Object|string} value
* @param {ParrableId} parrableId
* @return {(Object|undefined}
*/
decode(value) {
return (value && typeof value === 'string') ? { 'parrableid': value } : undefined;
decode(parrableId) {
if (parrableId && utils.isPlainObject(parrableId)) {
return { 'parrableid': parrableId.eid };
}
return undefined;
},

/**
* performs action to obtain id and return a value in the callback's response argument
* @function
* @param {SubmoduleParams} [configParams]
* @param {ConsentData} [consentData]
* @returns {function(callback:function)}
* @returns {function(callback:function), id:ParrableId}
*/
getId(configParams, gdprConsentData, currentStoredId) {
return fetchId(configParams, currentStoredId);
return fetchId(configParams);
}
};

Expand Down
4 changes: 0 additions & 4 deletions modules/userId/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,6 @@ function initSubmodules(submodules, consentData) {
refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > submodule.config.storage.refreshInSeconds * 1000);
}

if (CONSTANTS.SUBMODULES_THAT_ALWAYS_REFRESH_ID[submodule.config.name] === true) {
refreshNeeded = true;
}

if (!storedId || refreshNeeded) {
// No previously saved id. Request one from submodule.
response = submodule.submodule.getId(submodule.config.params, consentData, storedId);
Expand Down
5 changes: 0 additions & 5 deletions modules/userId/userId.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ pbjs.setConfig({
params: {
// Replace partner with comma-separated (if more than one) Parrable Partner Client ID(s) for Parrable-aware bid adapters in use
partner: "30182847-e426-4ff9-b2b5-9ca1324ea09b"
},
storage: {
type: 'cookie',
name: '_parrable_eid',
expires: 365
}
}, {
name: 'identityLink',
Expand Down
3 changes: 0 additions & 3 deletions src/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,5 @@
"BID_TARGETING_SET": "targetingSet",
"RENDERED": "rendered",
"BID_REJECTED": "bidRejected"
},
"SUBMODULES_THAT_ALWAYS_REFRESH_ID": {
"parrableId": true
}
}
Loading

0 comments on commit cabbf4e

Please sign in to comment.