From 7bd17e4f49ef50c6e6308c4bd7a4b0de7bd9e449 Mon Sep 17 00:00:00 2001 From: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Date: Tue, 16 Jun 2020 07:17:23 -0700 Subject: [PATCH] UserId SharedId submodule (#5315) * Adding sharedid submodule * Updating with Shared ID Module * SharedID test and sharedid eids * Shared ID md changes * Shared ID md changes * Shared ID changes * Apply suggestions from code review Co-Authored-By: Brad Rodriguez * Applying review suggestions * Apply suggestions from code review Co-Authored-By: Brad Rodriguez * Reformatting and reorganizing sharedId submodule * Reformatting and reorganizing sharedId submodule * Shared Id generation changes * Adding cookie Sync * Decode and sync cookie * Updating endpoint * Updaitng eids.md * Configured sync * Refactor and md update * Refactoring * Refactoring * Updating sync to seconds * Updating configuration * Reformatting * Reformatting * Reformatting * Fixing review comments * Changes to id value * Updating documentation * Documentation update * Resolving merge conflicts * updating userid_example.html * Fixing review comments on test to separate sharedid opt out tests * Moving sharedID generation within sharedId module * Moving sharedID generation within sharedId module Co-authored-by: skocheri Co-authored-by: Brad Rodriguez --- integrationExamples/gpt/userId_example.html | 12 +- modules/.submodules.json | 3 +- modules/sharedIdSystem.js | 333 ++++++++++++++++ modules/sharedIdSystem.md | 43 +++ modules/userId/eids.js | 13 + modules/userId/eids.md | 12 +- modules/userId/userId.md | 20 + test/spec/modules/eids_spec.js | 36 ++ test/spec/modules/userId_spec.js | 408 +++++++++++++++----- 9 files changed, 787 insertions(+), 93 deletions(-) create mode 100644 modules/sharedIdSystem.js create mode 100644 modules/sharedIdSystem.md diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 3bb8ce2df66..7886e5b13ca 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -179,9 +179,19 @@ name: 'idl_env', expires: 30 } + }, { + name: "sharedId", + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + type: "cookie", + name: "sharedid", + expires: 28 + } }], syncDelay: 5000, - auctionDelay: 1000 + auctionDelay: 1000 } }); pbjs.addAdUnits(adUnits); diff --git a/modules/.submodules.json b/modules/.submodules.json index ce3eb8fa137..25ae3c3884b 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -9,7 +9,8 @@ "liveIntentIdSystem", "criteoIdSystem", "netIdSystem", - "identityLinkIdSystem" + "identityLinkIdSystem", + "sharedIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js new file mode 100644 index 00000000000..5c2a3df0595 --- /dev/null +++ b/modules/sharedIdSystem.js @@ -0,0 +1,333 @@ +/** + * This module adds Shared ID support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/sharedIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; + +const MODULE_NAME = 'sharedId'; +const ID_SVC = 'https://id.sharedid.org/id'; +const DEFAULT_24_HOURS = 86400; +const OPT_OUT_VALUE = '00000000000000000000000000'; +// These values should NEVER change. If +// they do, we're no longer making ulids! +const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; // Crockford's Base32 +const ENCODING_LEN = ENCODING.length; +const TIME_MAX = Math.pow(2, 48) - 1; +const TIME_LEN = 10; +const RANDOM_LEN = 16; +const id = factory(); +/** + * Constructs cookie value + * @param value + * @param needsSync + * @returns {string} + */ +function constructCookieValue(value, needsSync) { + const cookieValue = {}; + cookieValue.id = value; + cookieValue.ts = utils.timestamp(); + if (needsSync) { + cookieValue.ns = true; + } + utils.logInfo('SharedId: cookie Value: ' + JSON.stringify(cookieValue)); + return cookieValue; +} + +/** + * Checks if id needs to be synced + * @param configParams + * @param storedId + * @returns {boolean} + */ +function isIdSynced(configParams, storedId) { + const needSync = storedId.ns; + if (needSync) { + return true; + } + if (!configParams || typeof configParams.syncTime !== 'number') { + utils.logInfo('SharedId: Sync time is not configured or is not a number'); + } + let syncTime = (!configParams || typeof configParams.syncTime !== 'number') ? DEFAULT_24_HOURS : configParams.syncTime; + if (syncTime > DEFAULT_24_HOURS) { + syncTime = DEFAULT_24_HOURS; + } + const cookieTimestamp = storedId.ts; + if (cookieTimestamp) { + var secondBetweenTwoDate = timeDifferenceInSeconds(utils.timestamp(), cookieTimestamp); + return secondBetweenTwoDate >= syncTime; + } + return false; +} + +/** + * Gets time difference in secounds + * @param date1 + * @param date2 + * @returns {number} + */ +function timeDifferenceInSeconds(date1, date2) { + const diff = (date1 - date2) / 1000; + return Math.abs(Math.round(diff)); +} + +/** + * id generation call back + * @param result + * @param callback + * @returns {{success: success, error: error}} + */ +function idGenerationCallback(callback) { + return { + success: function (responseBody) { + let value = {}; + if (responseBody) { + try { + let responseObj = JSON.parse(responseBody); + utils.logInfo('SharedId: Generated SharedId: ' + responseObj.sharedId); + value = constructCookieValue(responseObj.sharedId, false); + } catch (error) { + utils.logError(error); + } + } + callback(value); + }, + error: function (statusText, responseBody) { + const value = constructCookieValue(id(), true); + utils.logInfo('SharedId: Ulid Generated SharedId: ' + value.id); + callback(value); + } + } +} + +/** + * existing id generation call back + * @param result + * @param callback + * @returns {{success: success, error: error}} + */ +function existingIdCallback(storedId, callback) { + return { + success: function (responseBody) { + utils.logInfo('SharedId: id to be synced: ' + storedId.id); + if (responseBody) { + try { + let responseObj = JSON.parse(responseBody); + storedId = constructCookieValue(responseObj.sharedId, false); + utils.logInfo('SharedId: Older SharedId: ' + storedId.id); + } catch (error) { + utils.logError(error); + } + } + callback(storedId); + }, + error: function () { + utils.logInfo('SharedId: Sync error for id : ' + storedId.id); + callback(storedId); + } + } +} + +/** + * Encode the id + * @param value + * @returns {string|*} + */ +function encodeId(value) { + const result = {}; + const sharedId = (value && typeof value['id'] === 'string') ? value['id'] : undefined; + if (sharedId == OPT_OUT_VALUE) { + return undefined; + } + if (sharedId) { + const bidIds = { + id: sharedId, + } + const ns = (value && typeof value['ns'] === 'boolean') ? value['ns'] : undefined; + if (ns == undefined) { + bidIds.third = sharedId; + } + result.sharedid = bidIds; + utils.logInfo('SharedId: Decoded value ' + JSON.stringify(result)); + return result; + } + return sharedId; +} + +/** + * the factory to generate unique identifier based on time and current pseudorandom number + * @param {string} the current pseudorandom number generator + * @returns {function(*=): *} + */ +function factory(currPrng) { + if (!currPrng) { + currPrng = detectPrng(); + } + return function ulid(seedTime) { + if (isNaN(seedTime)) { + seedTime = Date.now(); + } + return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN, currPrng); + }; +} + +/** + * creates and logs the error message + * @function + * @param {string} error message + * @returns {Error} + */ +function createError(message) { + utils.logError(message); + const err = new Error(message); + err.source = 'sharedId'; + return err; +} + +/** + * gets a a random charcter from generated pseudorandom number + * @param {string} the generated pseudorandom number + * @returns {string} + */ +function randomChar(prng) { + let rand = Math.floor(prng() * ENCODING_LEN); + if (rand === ENCODING_LEN) { + rand = ENCODING_LEN - 1; + } + return ENCODING.charAt(rand); +} + +/** + * encodes the time based on the length + * @param now + * @param len + * @returns {string} encoded time. + */ +function encodeTime (now, len) { + if (isNaN(now)) { + throw new Error(now + ' must be a number'); + } + + if (Number.isInteger(now) === false) { + throw createError('time must be an integer'); + } + + if (now > TIME_MAX) { + throw createError('cannot encode time greater than ' + TIME_MAX); + } + if (now < 0) { + throw createError('time must be positive'); + } + + if (Number.isInteger(len) === false) { + throw createError('length must be an integer'); + } + if (len < 0) { + throw createError('length must be positive'); + } + + let mod; + let str = ''; + for (; len > 0; len--) { + mod = now % ENCODING_LEN; + str = ENCODING.charAt(mod) + str; + now = (now - mod) / ENCODING_LEN; + } + return str; +} + +/** + * encodes random character + * @param len + * @param prng + * @returns {string} + */ +function encodeRandom (len, prng) { + let str = ''; + for (; len > 0; len--) { + str = randomChar(prng) + str; + } + return str; +} + +/** + * detects the pseudorandom number generator and generates the random number + * @function + * @param {string} error message + * @returns {string} a random number + */ +function detectPrng(root) { + if (!root) { + root = typeof window !== 'undefined' ? window : null; + } + const browserCrypto = root && (root.crypto || root.msCrypto); + if (browserCrypto) { + return () => { + const buffer = new Uint8Array(1); + browserCrypto.getRandomValues(buffer); + return buffer[0] / 0xff; + }; + } + return () => Math.random(); +} + +/** @type {Submodule} */ +export const sharedIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{sharedid:{ id: string, third:string}} or undefined if value doesn't exists + */ + decode(value) { + return (value) ? encodeId(value) : undefined; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleParams} [configParams] + * @returns {sharedId} + */ + getId(configParams) { + const resp = function (callback) { + utils.logInfo('SharedId: Sharedid doesnt exists, new cookie creation'); + ajax(ID_SVC, idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + }, + + /** + * performs actions even if the id exists and returns a value + * @param configParams + * @param storedId + * @returns {{callback: *}} + */ + extendId(configParams, storedId) { + utils.logInfo('SharedId: Existing shared id ' + storedId.id); + const resp = function (callback) { + const needSync = isIdSynced(configParams, storedId); + if (needSync) { + utils.logInfo('SharedId: Existing shared id ' + storedId + ' is not synced'); + const sharedIdPayload = {}; + sharedIdPayload.sharedId = storedId.id; + const payloadString = JSON.stringify(sharedIdPayload); + ajax(ID_SVC, existingIdCallback(storedId, callback), payloadString, {method: 'POST', withCredentials: true}); + } + }; + return {callback: resp}; + } +}; + +// Register submodule for userId +submodule('userId', sharedIdSubmodule); diff --git a/modules/sharedIdSystem.md b/modules/sharedIdSystem.md new file mode 100644 index 00000000000..acb076ed97f --- /dev/null +++ b/modules/sharedIdSystem.md @@ -0,0 +1,43 @@ +## Shared ID User ID Submodule + +Shared ID User ID Module generates a UUID that can be utilized to improve user matching.This module enables timely synchronization which handles sharedId.org optout. This module does not require any registration. + +### Building Prebid with Shared Id Support +Your Prebid build must include the modules for both **userId** and **sharedId** submodule. Follow the build instructions for Prebid as +explained in the top level README.md file of the Prebid source tree. + +ex: $ gulp build --modules=userId,sharedIdSystem + +### Prebid Params + +Individual params may be set for the Shared ID User ID Submodule. +``` +pbjs.setConfig({ + usersync: { + userIds: [{ + name: 'sharedId', + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + name: 'sharedid', + type: 'cookie', + expires: 28 + }, + }] + } +}); +``` + +### Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Shared ID User ID Module integration. + +| Params under usersync.userIds[]| Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Shared ID module - `"sharedId"` | `"sharedId"` | +| params | Optional | Object | Details for sharedId syncing. | | +| params.syncTime | Optional | Object | Configuration to define the frequency(in seconds) of id synchronization. By default id is synchronized every 24 hours | 60 | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | +| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"sharedid"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `28` | diff --git a/modules/userId/eids.js b/modules/userId/eids.js index c088e51c74b..842737183a8 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -85,6 +85,19 @@ const USER_IDS_CONFIG = { 'netId': { source: 'netid.de', atype: 1 + }, + // sharedid + 'sharedid': { + source: 'sharedid.org', + atype: 1, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + return (data && data.third) ? { + third: data.third + } : undefined; + } } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index baface1ab6f..60f450e9328 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -85,6 +85,16 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 1 }] + }, + { + source: 'sharedid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + third: 'some-random-id-value' + } + }] } ] -``` \ No newline at end of file +``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 5ce31a4e3a9..eb9e985a1d6 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -59,6 +59,16 @@ pbjs.setConfig({ name: '_li_pbid', expires: 60 } + }, { + name: 'sharedId', + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } }], syncDelay: 5000, auctionDelay: 1000 @@ -108,6 +118,16 @@ pbjs.setConfig({ name: '_li_pbid', expires: 60 } + }, { + name: 'sharedId', + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } }], syncDelay: 5000 } diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index ed32ecc51d2..160277204df 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -146,6 +146,42 @@ describe('eids array generation for known sub-modules', function() { uids: [{id: 'some-random-id-value', atype: 1}] }); }); + it('Sharedid', function() { + const userId = { + sharedid: { + id: 'test_sharedId', + third: 'test_sharedId' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [{ + id: 'test_sharedId', + atype: 1, + ext: { + third: 'test_sharedId' + } + }] + }); + }); + it('Sharedid: Not Synched', function() { + const userId = { + sharedid: { + id: 'test_sharedId' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [{ + id: 'test_sharedId', + atype: 1 + }] + }); + }); }); describe('Negative case', function() { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index e909c53b60f..8ce81ea85b0 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -20,6 +20,7 @@ import {id5IdSubmodule} from 'modules/id5IdSystem.js'; import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem.js'; import {netIdSubmodule} from 'modules/netIdSystem.js'; +import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {server} from 'test/mocks/xhr.js'; let assert = require('chai').assert; @@ -27,7 +28,7 @@ let expect = require('chai').expect; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; describe('User ID', function() { - function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5, configArr6) { + function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5, configArr6, configArr7) { return { userSync: { syncDelay: 0, @@ -37,16 +38,20 @@ describe('User ID', function() { (configArr3 && configArr3.length >= 3) ? getStorageMock.apply(null, configArr3) : null, (configArr4 && configArr4.length >= 3) ? getStorageMock.apply(null, configArr4) : null, (configArr5 && configArr5.length >= 3) ? getStorageMock.apply(null, configArr5) : null, - (configArr6 && configArr6.length >= 3) ? getStorageMock.apply(null, configArr6) : null - ].filter(i => i)} + (configArr6 && configArr6.length >= 3) ? getStorageMock.apply(null, configArr6) : null, + (configArr7 && configArr7.length >= 3) ? getStorageMock.apply(null, configArr7) : null + ].filter(i => i) + } } } + function getStorageMock(name = 'pubCommonId', key = 'pubcid', type = 'cookie', expires = 30, refreshInSeconds) { - return { name: name, storage: { name: key, type: type, expires: expires, refreshInSeconds: refreshInSeconds } } + return {name: name, storage: {name: key, type: type, expires: expires, refreshInSeconds: refreshInSeconds}} } + function getConfigValueMock(name, value) { return { - userSync: { syncDelay: 0, userIds: [{ name: name, value: value }] } + userSync: {syncDelay: 0, userIds: [{name: name, value: value}]} } } @@ -62,7 +67,11 @@ describe('User ID', function() { function addConfig(cfg, name, value) { if (cfg && cfg.userSync && cfg.userSync.userIds) { cfg.userSync.userIds.forEach(element => { - if (element[name] !== undefined) { element[name] = Object.assign(element[name], value); } else { element[name] = value; } + if (element[name] !== undefined) { + element[name] = Object.assign(element[name], value); + } else { + element[name] = value; + } }); } @@ -82,7 +91,7 @@ describe('User ID', function() { sinon.spy(coreStorage, 'setCookie'); }); - afterEach(function () { + afterEach(function() { $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); coreStorage.setCookie.restore(); @@ -93,7 +102,7 @@ describe('User ID', function() { coreStorage.setCookie('pubcid_alt', '', EXPIRED_COOKIE_DATE); }); - it('Check same cookie behavior', function () { + it('Check same cookie behavior', function() { let adUnits1 = [getAdUnitMock()]; let adUnits2 = [getAdUnitMock()]; let innerAdUnits1; @@ -106,7 +115,9 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - requestBidsHook(config => { innerAdUnits1 = config.adUnits }, {adUnits: adUnits1}); + requestBidsHook(config => { + innerAdUnits1 = config.adUnits + }, {adUnits: adUnits1}); pubcid = coreStorage.getCookie('pubcid'); // cookies is created after requestbidHook innerAdUnits1.forEach(unit => { @@ -120,11 +131,13 @@ describe('User ID', function() { }); }); - requestBidsHook(config => { innerAdUnits2 = config.adUnits }, {adUnits: adUnits2}); + requestBidsHook(config => { + innerAdUnits2 = config.adUnits + }, {adUnits: adUnits2}); assert.deepEqual(innerAdUnits1, innerAdUnits2); }); - it('Check different cookies', function () { + it('Check different cookies', function() { let adUnits1 = [getAdUnitMock()]; let adUnits2 = [getAdUnitMock()]; let innerAdUnits1; @@ -135,7 +148,9 @@ describe('User ID', function() { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - requestBidsHook((config) => { innerAdUnits1 = config.adUnits }, {adUnits: adUnits1}); + requestBidsHook((config) => { + innerAdUnits1 = config.adUnits + }, {adUnits: adUnits1}); pubcid1 = coreStorage.getCookie('pubcid'); // get first cookie coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); // erase cookie @@ -153,7 +168,9 @@ describe('User ID', function() { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - requestBidsHook((config) => { innerAdUnits2 = config.adUnits }, {adUnits: adUnits2}); + requestBidsHook((config) => { + innerAdUnits2 = config.adUnits + }, {adUnits: adUnits2}); pubcid2 = coreStorage.getCookie('pubcid'); // get second cookie @@ -171,14 +188,16 @@ describe('User ID', function() { expect(pubcid1).to.not.equal(pubcid2); }); - it('Use existing cookie', function () { + it('Use existing cookie', function() { let adUnits = [getAdUnitMock()]; let innerAdUnits; setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie'])); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); innerAdUnits.forEach((unit) => { unit.bids.forEach((bid) => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -193,7 +212,7 @@ describe('User ID', function() { expect(coreStorage.setCookie.callCount).to.equal(0); }); - it('Extend cookie', function () { + it('Extend cookie', function() { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customConfig = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); @@ -202,7 +221,9 @@ describe('User ID', function() { setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); init(config); config.setConfig(customConfig); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); innerAdUnits.forEach((unit) => { unit.bids.forEach((bid) => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -217,7 +238,7 @@ describe('User ID', function() { expect(coreStorage.setCookie.callCount).to.equal(1); }); - it('Disable auto create', function () { + it('Disable auto create', function() { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customConfig = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); @@ -226,7 +247,9 @@ describe('User ID', function() { setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); init(config); config.setConfig(customConfig); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); innerAdUnits.forEach((unit) => { unit.bids.forEach((bid) => { expect(bid).to.not.have.deep.nested.property('userId.pubcid'); @@ -267,16 +290,16 @@ describe('User ID', function() { }); }); - describe('Opt out', function () { - before(function () { + describe('Opt out', function() { + before(function() { coreStorage.setCookie('_pbjs_id_optout', '1', (new Date(Date.now() + 5000).toUTCString())); }); - beforeEach(function () { + beforeEach(function() { sinon.stub(utils, 'logInfo'); }); - afterEach(function () { + afterEach(function() { // removed cookie coreStorage.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); $$PREBID_GLOBAL$$.requestBids.removeAll(); @@ -284,18 +307,18 @@ describe('User ID', function() { config.resetConfig(); }); - after(function () { + after(function() { coreStorage.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); }); - it('fails initialization if opt out cookie exists', function () { + it('fails initialization if opt out cookie exists', function() { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - opt-out cookie found, exit module'); }); - it('initializes if no opt out cookie exists', function () { + it('initializes if no opt out cookie exists', function() { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); @@ -303,34 +326,34 @@ describe('User ID', function() { }); }); - describe('Handle variations of config values', function () { - beforeEach(function () { + describe('Handle variations of config values', function() { + beforeEach(function() { sinon.stub(utils, 'logInfo'); }); - afterEach(function () { + afterEach(function() { $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); config.resetConfig(); }); - it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule]); + it('handles config with no usersync object', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule]); + it('handles config with empty usersync object', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); - config.setConfig({ userSync: {} }); + config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule]); + it('handles config with usersync and userIds that are empty objs', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -340,33 +363,33 @@ describe('User ID', function() { expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule]); + it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig({ userSync: { userIds: [{ name: '', - value: { test: '1' } + value: {test: '1'} }, { name: 'foo', - value: { test: '1' } + value: {test: '1'} }] } }); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule]); + it('config with 1 configurations should create 1 submodules', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); }); - it('config with 7 configurations should result in 7 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule]); + it('config with 8 configurations should result in 8 submodules add', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -375,67 +398,70 @@ describe('User ID', function() { name: 'pubCommonId', value: {'pubcid': '11111'} }, { name: 'unifiedId', - storage: { name: 'unifiedid', type: 'cookie' } + storage: {name: 'unifiedid', type: 'cookie'} }, { name: 'id5Id', - storage: { name: 'id5id', type: 'cookie' } + storage: {name: 'id5id', type: 'cookie'} }, { name: 'identityLink', - storage: { name: 'idl_env', type: 'cookie' } + storage: {name: 'idl_env', type: 'cookie'} }, { name: 'liveIntentId', - storage: { name: '_li_pbid', type: 'cookie' } + storage: {name: '_li_pbid', type: 'cookie'} }, { name: 'britepoolId', - value: { 'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd' } + value: {'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd'} }, { name: 'netId', - storage: { name: 'netId', type: 'cookie' } + storage: {name: 'netId', type: 'cookie'} + }, { + name: 'sharedId', + storage: {name: 'sharedid', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 7 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 8 submodules'); }); - it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule]); + it('config syncDelay updates module correctly', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig({ userSync: { syncDelay: 99, userIds: [{ name: 'unifiedId', - storage: { name: 'unifiedid', type: 'cookie' } + storage: {name: 'unifiedid', type: 'cookie'} }] } }); expect(syncDelay).to.equal(99); }); - it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule]); + it('config auctionDelay updates module correctly', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig({ userSync: { auctionDelay: 100, userIds: [{ name: 'unifiedId', - storage: { name: 'unifiedid', type: 'cookie' } + storage: {name: 'unifiedid', type: 'cookie'} }] } }); expect(auctionDelay).to.equal(100); }); - it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule]); + it('config auctionDelay defaults to 0 if not a number', function() { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig({ userSync: { auctionDelay: '', userIds: [{ name: 'unifiedId', - storage: { name: 'unifiedid', type: 'cookie' } + storage: {name: 'unifiedid', type: 'cookie'} }] } }); @@ -483,7 +509,7 @@ describe('User ID', function() { attachIdSystem(mockIdSystem, true); }); - afterEach(function () { + afterEach(function() { $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); sandbox.restore(); @@ -495,7 +521,7 @@ describe('User ID', function() { auctionDelay: 33, syncDelay: 77, userIds: [{ - name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] } }); @@ -528,7 +554,7 @@ describe('User ID', function() { auctionDelay: 33, syncDelay: 77, userIds: [{ - name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] } }); @@ -566,7 +592,7 @@ describe('User ID', function() { userSync: { syncDelay: 77, userIds: [{ - name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] } }); @@ -602,7 +628,7 @@ describe('User ID', function() { userSync: { syncDelay: 0, userIds: [{ - name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] } }); @@ -633,7 +659,7 @@ describe('User ID', function() { auctionDelay: 33, syncDelay: 77, userIds: [{ - name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] } }); @@ -712,7 +738,7 @@ describe('User ID', function() { expect(bid.userId.tdid).to.equal('testunifiedid_alt'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'adserver.org', - uids: [{id: 'testunifiedid_alt', atype: 1, ext: { rtiPartner: 'TDID' }}] + uids: [{id: 'testunifiedid_alt', atype: 1, ext: {rtiPartner: 'TDID'}}] }); }); }); @@ -818,6 +844,133 @@ describe('User ID', function() { }, {adUnits}); }); + it('test hook from sharedId html5', function(done) { + // simulate existing browser local storage values + localStorage.setItem('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611})); + localStorage.setItem('sharedid_exp', ''); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['sharedId', 'sharedid', 'html5'])); + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.have.deep.nested.property('id'); + expect(bid.userId.sharedid).to.have.deep.nested.property('third'); + expect(bid.userId.sharedid).to.deep.equal({ + id: 'test_sharedId', + third: 'test_sharedId' + }); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [{ + id: 'test_sharedId', + atype: 1, + ext: { + third: 'test_sharedId' + } + }] + }); + }); + }); + localStorage.removeItem('sharedid'); + localStorage.removeItem('sharedid_exp'); + done(); + }, {adUnits}); + }); + + it('test hook from sharedId html5 (id not synced)', function(done) { + // simulate existing browser local storage values + localStorage.setItem('sharedid', JSON.stringify({'id': 'test_sharedId', 'ns': true, 'ts': 1590525289611})); + localStorage.setItem('sharedid_exp', ''); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['sharedId', 'sharedid', 'html5'])); + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.have.deep.nested.property('id'); + expect(bid.userId.sharedid).to.deep.equal({ + id: 'test_sharedId' + }); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [{ + id: 'test_sharedId', + atype: 1 + }] + }); + }); + }); + localStorage.removeItem('sharedid'); + localStorage.removeItem('sharedid_exp'); + done(); + }, {adUnits}); + }); + it('test hook from sharedId cookie', function(done) { + coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.have.deep.nested.property('id'); + expect(bid.userId.sharedid).to.have.deep.nested.property('third'); + expect(bid.userId.sharedid).to.deep.equal({ + id: 'test_sharedId', + third: 'test_sharedId' + }); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [{ + id: 'test_sharedId', + atype: 1, + ext: { + third: 'test_sharedId' + } + }] + }); + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + it('test hook from sharedId cookie (id not synced) ', function(done) { + coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ns': true, 'ts': 1590525289611}), (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.have.deep.nested.property('id'); + expect(bid.userId.sharedid).to.deep.equal({ + id: 'test_sharedId' + }); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [{ + id: 'test_sharedId', + atype: 1 + }] + }); + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); it('test hook from id5id cookies when refresh needed', function(done) { // simulate existing browser local storage values coreStorage.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -895,7 +1048,10 @@ describe('User ID', function() { }); it('test hook from liveIntentId cookie', function(done) { - coreStorage.setCookie('_li_pbid', JSON.stringify({'unifiedId': 'random-cookie-identifier', 'segments': ['123']}), (new Date(Date.now() + 100000).toUTCString())); + coreStorage.setCookie('_li_pbid', JSON.stringify({ + 'unifiedId': 'random-cookie-identifier', + 'segments': ['123'] + }), (new Date(Date.now() + 100000).toUTCString())); setSubmoduleRegistry([liveIntentIdSubmodule]); init(config); @@ -967,22 +1123,24 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId and netId have data to pass', function(done) { + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, netId and sharedId have data to pass', function(done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], ['id5Id', 'id5id', 'cookie'], ['identityLink', 'idl_env', 'cookie'], ['britepoolId', 'britepoolid', 'cookie'], - ['netId', 'netId', 'cookie'])); + ['netId', 'netId', 'cookie'], + ['sharedId', 'sharedid', 'cookie'])); requestBidsHook(function() { adUnits.forEach(unit => { @@ -1005,7 +1163,12 @@ describe('User ID', function() { // also check that netId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.netId'); expect(bid.userId.netId).to.equal('testnetId'); - expect(bid.userIdAsEids.length).to.equal(6); + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.deep.equal({ + id: 'test_sharedId', + third: 'test_sharedId' + }); + expect(bid.userIdAsEids.length).to.equal(7); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1014,17 +1177,37 @@ describe('User ID', function() { coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when sharedId (opted out) have data to pass', function(done) { + coreStorage.setCookie('sharedid', JSON.stringify({'id': '00000000000000000000000000', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid.userIdAsEids).to.be.undefined; + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId and netId have their modules added before and after init', function(done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, netId and sharedId have their modules added before and after init', function(done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -1039,13 +1222,15 @@ describe('User ID', function() { attachIdSystem(identityLinkSubmodule); attachIdSystem(britepoolIdSubmodule); attachIdSystem(netIdSubmodule); + attachIdSystem(sharedIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], ['id5Id', 'id5id', 'cookie'], ['identityLink', 'idl_env', 'cookie'], ['britepoolId', 'britepoolid', 'cookie'], - ['netId', 'netId', 'cookie'])); + ['netId', 'netId', 'cookie'], + ['sharedId', 'sharedid', 'cookie'])); requestBidsHook(function() { adUnits.forEach(unit => { @@ -1068,7 +1253,12 @@ describe('User ID', function() { // also check that britepoolId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.netId'); expect(bid.userId.netId).to.equal('testnetId'); - expect(bid.userIdAsEids.length).to.equal(6); + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.deep.equal({ + id: 'test_sharedId', + third: 'test_sharedId' + }); + expect(bid.userIdAsEids.length).to.equal(7); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1077,6 +1267,28 @@ describe('User ID', function() { coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when sharedId(opted out) have their modules added before and after init', function(done) { + coreStorage.setCookie('sharedid', JSON.stringify({'id': '00000000000000000000000000', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([]); + init(config); + + attachIdSystem(sharedIdSubmodule); + + config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid.userIdAsEids).to.be.undefined; + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -1088,28 +1300,31 @@ describe('User ID', function() { coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule]); init(config); config.setConfig({ userSync: { syncDelay: 0, userIds: [{ - name: 'pubCommonId', storage: { name: 'pubcid', type: 'cookie' } + name: 'pubCommonId', storage: {name: 'pubcid', type: 'cookie'} + }, { + name: 'unifiedId', storage: {name: 'unifiedid', type: 'cookie'} }, { - name: 'unifiedId', storage: { name: 'unifiedid', type: 'cookie' } + name: 'id5Id', storage: {name: 'id5id', type: 'cookie'} }, { - name: 'id5Id', storage: { name: 'id5id', type: 'cookie' } + name: 'identityLink', storage: {name: 'idl_env', type: 'cookie'} }, { - name: 'identityLink', storage: { name: 'idl_env', type: 'cookie' } + name: 'britepoolId', storage: {name: 'britepoolid', type: 'cookie'} }, { - name: 'britepoolId', storage: { name: 'britepoolid', type: 'cookie' } + name: 'netId', storage: {name: 'netId', type: 'cookie'} }, { - name: 'netId', storage: { name: 'netId', type: 'cookie' } + name: 'sharedId', storage: {name: 'sharedid', type: 'cookie'} }, { - name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] } }); @@ -1149,10 +1364,16 @@ describe('User ID', function() { // check MockId data was copied to bid expect(bid).to.have.deep.nested.property('userId.netId'); expect(bid.userId.netId).to.equal('testnetId'); + // also check that sharedId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.deep.equal({ + id: 'test_sharedId', + third: 'test_sharedId' + }); // check MockId data was copied to bid expect(bid).to.have.deep.nested.property('userId.mid'); expect(bid.userId.mid).to.equal('123456778'); - expect(bid.userIdAsEids.length).to.equal(6);// mid is unknown for eids.js + expect(bid.userIdAsEids.length).to.equal(7);// mid is unknown for eids.js }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1161,6 +1382,7 @@ describe('User ID', function() { coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); @@ -1184,7 +1406,7 @@ describe('User ID', function() { coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); }); - it('pubcid callback with url', function () { + it('pubcid callback with url', function() { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customCfg = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); @@ -1193,14 +1415,16 @@ describe('User ID', function() { setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); init(config); config.setConfig(customCfg); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); expect(utils.triggerPixel.called).to.be.false; events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); }); - it('unifiedid callback with url', function () { + it('unifiedid callback with url', function() { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); @@ -1209,14 +1433,16 @@ describe('User ID', function() { setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); init(config); config.setConfig(customCfg); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); expect(server.requests).to.be.empty; events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); expect(server.requests[0].url).to.equal('/any/unifiedid/url'); }); - it('unifiedid callback with partner', function () { + it('unifiedid callback with partner', function() { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); @@ -1225,7 +1451,9 @@ describe('User ID', function() { setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); init(config); config.setConfig(customCfg); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); expect(server.requests).to.be.empty; events.emit(CONSTANTS.EVENTS.AUCTION_END, {});