diff --git a/allowedModules.js b/allowedModules.js
index e66b8e24098..142bb4b6c4d 100644
--- a/allowedModules.js
+++ b/allowedModules.js
@@ -1,4 +1,3 @@
-
const sharedWhiteList = [
"core-js/library/fn/array/find", // no ie11
"core-js/library/fn/array/includes", // no ie11
@@ -12,7 +11,8 @@ module.exports = {
'modules': [
...sharedWhiteList,
'jsencrypt',
- 'crypto-js'
+ 'crypto-js',
+ 'ulid'
],
'src': [
...sharedWhiteList,
diff --git a/integrationExamples/gpt/liveConnect_example.html b/integrationExamples/gpt/liveConnect_example.html
new file mode 100644
index 00000000000..84a57158c78
--- /dev/null
+++ b/integrationExamples/gpt/liveConnect_example.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+ LiveConnect
+
+
+
+Prebid.js LiveConnect
+
+
diff --git a/modules/consentManagement.js b/modules/consentManagement.js
index 1e2a6648145..a89dcce9673 100644
--- a/modules/consentManagement.js
+++ b/modules/consentManagement.js
@@ -376,6 +376,7 @@ export function setConsentConfig(config) {
}
if (!addedConsentHook) {
$$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50);
+ if ($$PREBID_GLOBAL$$.liveConnectHook) $$PREBID_GLOBAL$$.liveConnectHook.before(requestBidsHook, 50);
}
addedConsentHook = true;
}
diff --git a/modules/liveConnect.js b/modules/liveConnect.js
new file mode 100644
index 00000000000..c7a1a07e7cf
--- /dev/null
+++ b/modules/liveConnect.js
@@ -0,0 +1,395 @@
+/**
+ * This module adds tracking support for LiveIntent's LiveConnect
+ * @module modules/liveConnect
+ */
+
+/**
+ * @typedef {Object} LiveConnectStorageConfig
+ * @property {string} type - specifies where to store the liveConnect identifier. Allowed values: "cookie" or "html5" (local storage). Default: "cookie"
+ * @property {number} expires - number of days to store the liveConnect identifier. Default: 30
+ */
+
+/**
+ * @typedef {Object} LiveConnectConfig
+ * @property {(string[]|undefined)} scrapedIdentifiers - cookie names or local storage item names to be sent along with the tracking request
+ * @property {(string|undefined)} providedFirstPartyIdentifier - the cookie name or the local storage item name of the provided first party identifier
+ * @property {LiveConnectStorageConfig} storage - specifies way to store the liveConnect identifier
+ */
+
+/**
+ * @typedef {Object} NotValidatedLiveConnectStorageConfig
+ * @property {string|undefined} type - specifies where to store the liveConnect identifier. Allowed values: "cookie" or "html5" (local storage)
+ * @property {string|undefined} name - specifies the name of the liveConnect identifier
+ * @property {number|undefined} expires - number of days to store the liveConnect identifier
+ */
+
+/**
+ * @typedef {Object} NotValidatedLiveConnectConfig
+ * @property {(string[]|undefined)} scrapedIdentifiers - cookie names or local storage item names to be sent along with the tracking request
+ * @property {(string|undefined)} providedFirstPartyIdentifier - the cookie name or the local storage item name of the provided first party identifier
+ * @property {NotValidatedLiveConnectStorageConfig} storage - specifies way to store the liveConnect identifier
+ */
+
+import {hook} from '../src/hook';
+import {config} from '../src/config';
+import * as utils from '../src/utils';
+import {gdprDataHandler} from '../src/adapterManager';
+import $$PREBID_GLOBAL$$ from '../src/prebid';
+import {hasGDPRConsent} from './userId/gdprUtils';
+import {detectPrng, factory} from 'ulid/dist/index';
+
+const prng = detectPrng(true);
+/** Generator of ULID identifiers. Falls back to insecure `Math.random` identifiers when `window.crypto` is not present in browser. */
+const ulid = factory(prng);
+
+const COOKIE = 'cookie';
+const LOCAL_STORAGE = 'html5';
+const LIVE_PIXEL_URL = 'https://rp.liadm.com/p';
+const DUID_NAME = '_lc2_duid';
+const SHARED_DUID_NAME = '_li_duid';
+
+/** @type {LiveConnectConfig} */
+const CONFIG = {
+ ADDITIONAL_USER_IDENTIFIERS: 'additionalUserIdentifiers',
+ USER_IDENTIFIER: 'userIdentifier',
+ STORAGE: {
+ TYPE: {
+ KEY: 'type',
+ ALLOWED: [COOKIE, LOCAL_STORAGE],
+ DEFAULT: COOKIE
+ },
+ EXPIRES: {
+ KEY: 'expires',
+ DEFAULT: 730
+ }
+ }
+};
+
+// init the liveConnect hook to happen before the `requestBids` hook
+init();
+
+/**
+ * Adds the liveConnect hook to happen before the `requestBids` hook and after the `consentManagement` hook
+ */
+export function init() {
+ // priority value 40 will load after consentManagement with a priority of 50
+ $$PREBID_GLOBAL$$.requestBids.before(liveConnectHook, 40);
+}
+
+/**
+ * The global function to trigger the liveConnect pixel call.
+ */
+$$PREBID_GLOBAL$$.liveConnect = function () {
+ $$PREBID_GLOBAL$$.liveConnectHook({});
+};
+
+/**
+ * The liveConnect hook that triggers a pixel call. Happens either after `$$PREBID_GLOBAL$$.liveConnect` or `$$PREBID_GLOBAL$$.requestBids`
+ */
+$$PREBID_GLOBAL$$.liveConnectHook = hook('async', function () {
+ liveConnectHook();
+}, 'liveConnect');
+
+/**
+ * The flag that is used to ensure that pixel is called only once per script load.
+ * @type {boolean}
+ */
+let isPixelFired = false;
+
+/**
+ * Resets the flag to enable multiple calls to liveConnect pixel. This function is used in tests.
+ */
+export function resetPixel() {
+ isPixelFired = false;
+}
+
+/**
+ * This function sends the liveConnect pixel call.
+ * It does not send the pixel call if the pixel has already been triggered or the gdpr consent has not been given.
+ */
+function liveConnectHook() {
+ if (!isPixelFired) {
+ if (hasGDPRConsent(gdprDataHandler.getConsentData())) {
+ const duid = sendLiveConnectPixel();
+ trySettingSharedDuid(duid);
+ }
+ isPixelFired = true;
+ }
+}
+
+/**
+ * This function sends the liveConnect pixel call.
+ */
+function sendLiveConnectPixel() {
+ const validConfig = validateConfig(config.getConfig('liveConnect'));
+ const pageUrl = encodeURIComponent(getPageUrl());
+ const duid = getDuid(validConfig);
+ let pixelUri = `${LIVE_PIXEL_URL}?duid=${duid}&tna=$prebid.version$&pu=${pageUrl}`;
+
+ const providedFpiQueryParams = getProvidedFpiQueryParams(validConfig);
+ if (providedFpiQueryParams) {
+ pixelUri += `&${providedFpiQueryParams}`;
+ }
+
+ const scrapedIdentifiersQeuryParams = getScrapedIdentifiers(validConfig);
+ if (scrapedIdentifiersQeuryParams) {
+ pixelUri += `&${scrapedIdentifiersQeuryParams}`;
+ }
+
+ const legacyDuidQueryParam = getLegacyDuidQueryParam();
+ if (legacyDuidQueryParam) {
+ pixelUri += `&${legacyDuidQueryParam}`;
+ }
+
+ utils.triggerPixel(pixelUri);
+
+ return duid;
+}
+
+/**
+ * Validates the liveConnect config. Sets the config values to be default values when they are not provided or invalid.
+ * @param {NotValidatedLiveConnectConfig} config
+ * @returns {LiveConnectConfig}
+ */
+function validateConfig(config) {
+ const validConfig = {
+ storage: {}
+ };
+ validConfig.storage[CONFIG.STORAGE.TYPE.KEY] = CONFIG.STORAGE.TYPE.DEFAULT;
+ validConfig.storage[CONFIG.STORAGE.EXPIRES.KEY] = CONFIG.STORAGE.EXPIRES.DEFAULT;
+
+ if (!config) return validConfig;
+
+ validConfig[CONFIG.ADDITIONAL_USER_IDENTIFIERS] = validOrDefault(config[CONFIG.ADDITIONAL_USER_IDENTIFIERS], isArrayOfStrings, []);
+ validConfig[CONFIG.USER_IDENTIFIER] = validOrDefault(config[CONFIG.USER_IDENTIFIER], utils.isStr, null);
+
+ if (utils.isPlainObject(config.storage)) {
+ validConfig.storage[CONFIG.STORAGE.TYPE.KEY] = validOrDefault(
+ config.storage[CONFIG.STORAGE.TYPE.KEY],
+ v => utils.isStr(v) && CONFIG.STORAGE.TYPE.ALLOWED.includes(v),
+ CONFIG.STORAGE.TYPE.DEFAULT
+ );
+ validConfig.storage[CONFIG.STORAGE.EXPIRES.KEY] = validOrDefault(config.storage[CONFIG.STORAGE.EXPIRES.KEY], utils.isNumber, CONFIG.STORAGE.EXPIRES.DEFAULT);
+ }
+ return validConfig;
+}
+
+/**
+ * Validates the given value against the function.
+ * If the value is valid - the function returns this value.
+ * If the value is invalid - the function returns the default value.
+ * @param {*} val value to check
+ * @param {function} check function that validates the value
+ * @param {*} defaultVal the default value
+ * @returns {*} or the default value
+ */
+function validOrDefault(val, check, defaultVal) {
+ return check(val) ? val : defaultVal;
+}
+
+/**
+ * Checks if the argument is an array of strings.
+ * @param val value to be validated
+ * @returns {Boolean} true - if the value is an array of string. Else - otherwise
+ */
+function isArrayOfStrings(val) {
+ return (utils.isArray(val)) && (val.every(v => utils.isStr(v)));
+}
+
+/**
+ * The url of the current page
+ * @returns {string} The current page url or the referrer if the js is executed inside an iFrame
+ */
+function getPageUrl() {
+ return utils.inIframe() ? document.referrer : window.location.href;
+}
+
+/**
+ * Get the stored duid (liveConnect id) value from cookie or local storage.
+ * Stores the new duid if it has not been stored. Updates the expiration if the duid has been set.
+ * @param {LiveConnectConfig} validConfig
+ * @returns {string} duid
+ */
+function getDuid(validConfig) {
+ let duid = getStoredDuid(validConfig.storage);
+ if (!duid) {
+ duid = ulid();
+ }
+ storeDuid(validConfig.storage, duid);
+ return duid;
+}
+
+/**
+ * Get the stored duid (liveConnect id) value from cookie or local storage.
+ * @param {LiveConnectStorageConfig} storage
+ * @returns {string} duid
+ */
+function getStoredDuid(storage) {
+ let storedValue;
+ try {
+ if (storage[CONFIG.STORAGE.TYPE.KEY] === COOKIE) {
+ storedValue = utils.getCookie(DUID_NAME);
+ } else if (storage[CONFIG.STORAGE.TYPE.KEY] === LOCAL_STORAGE) {
+ storedValue = getFromLocalStorage(DUID_NAME);
+ }
+ } catch (e) {
+ utils.logError(e);
+ }
+ return storedValue;
+}
+
+/**
+ * Get a value from local storage by name if it is not expired
+ * @param {string} name local storage item name
+ * @returns {string}
+ */
+function getFromLocalStorage(name) {
+ const storedValueExp = localStorage.getItem(`${name}_exp`);
+ let storedValue;
+ // empty string means no expiration set
+ if (storedValueExp === '') {
+ storedValue = localStorage.getItem(name);
+ } else if (storedValueExp) {
+ if ((new Date(storedValueExp)).getTime() - Date.now() > 0) {
+ storedValue = decodeURIComponent(localStorage.getItem(name));
+ }
+ }
+ return storedValue;
+}
+
+/**
+ * Stores the duid into cookie or local storage
+ * @param {LiveConnectStorageConfig} storage
+ * @param {string} value duid value
+ */
+function storeDuid(storage, value) {
+ try {
+ const expiresStr = expiresString(storage[CONFIG.STORAGE.EXPIRES.KEY]);
+ if (storage[CONFIG.STORAGE.TYPE.KEY] === COOKIE) {
+ storeCookieOnEtldPlus1(DUID_NAME, value, expiresStr);
+ } else if (storage[CONFIG.STORAGE.TYPE.KEY] === LOCAL_STORAGE) {
+ localStorage.setItem(`${DUID_NAME}_exp`, expiresStr);
+ localStorage.setItem(DUID_NAME, encodeURIComponent(value));
+ }
+ } catch (error) {
+ utils.logError(error);
+ }
+}
+
+/**
+ * UTC formatted string of the expiration date
+ * @param {number} expirationDays ttl of the cookie/local storage item
+ * @returns {string} formatted date
+ */
+function expiresString(expirationDays) {
+ let expiration = new Date();
+ expiration.setDate(expiration.getDate() + expirationDays);
+ return expiration.toUTCString();
+}
+
+/**
+ * Stores the cookie on the apex domain.
+ * @param {string} name cookie name
+ * @param {string} value cookie value
+ * @param {string} expiresStr UTC formatted expiration date string
+ */
+function storeCookieOnEtldPlus1(name, value, expiresStr) {
+ const hostParts = window.location.hostname.split('.');
+ if (!utils.cookiesAreEnabled()) {
+ return;
+ }
+ for (let i = hostParts.length - 1; i >= 0; i--) {
+ let domain = '.' + hostParts.slice(i).join('.');
+ utils.setCookie(name, value, expiresStr, 'Lax', domain);
+ const storedCookie = utils.getCookie(name);
+ if (storedCookie === value) {
+ return;
+ }
+ }
+}
+
+/**
+ * Gets pixel query params that contains first party identifiers.
+ * @param {LiveConnectConfig} validConfig
+ * @returns {string|undefined} concatenated query params
+ */
+function getProvidedFpiQueryParams(validConfig) {
+ let fpi;
+ if (validConfig[CONFIG.USER_IDENTIFIER]) {
+ const providedFirstPartyIdentifier = getFromCookieOrLocalStorage(validConfig[CONFIG.USER_IDENTIFIER]);
+ if (providedFirstPartyIdentifier) {
+ fpi = `pfpi=${providedFirstPartyIdentifier}&fpn=${validConfig[CONFIG.USER_IDENTIFIER]}`;
+ }
+ }
+ return fpi;
+}
+
+/**
+ * Reads an identifier from cookie or local storage.
+ * @param {string} identifierName name of the identifier
+ * @returns {string | null} identifier value
+ */
+function getFromCookieOrLocalStorage(identifierName) {
+ let identifierValue = utils.getCookie(identifierName);
+ if (!identifierValue) {
+ identifierValue = localStorage.getItem(identifierName);
+ }
+ return identifierValue;
+}
+
+/**
+ * Gets pixel query params that contain scraped identifiers.
+ * @param {LiveConnectConfig} validConfig
+ * @returns {string|null} concatenated query params
+ */
+function getScrapedIdentifiers(validConfig) {
+ let identifiers;
+ if (validConfig[CONFIG.ADDITIONAL_USER_IDENTIFIERS]) {
+ identifiers = validConfig[CONFIG.ADDITIONAL_USER_IDENTIFIERS]
+ .map(identifierName => {
+ let identifierValue = getFromCookieOrLocalStorage(identifierName);
+ return identifierValue ? `ext_${identifierName}=${identifierValue}` : '';
+ })
+ .filter(param => param && param.length > 0)
+ .join('&');
+ }
+
+ return identifiers && identifiers.length > 0 ? identifiers : null;
+}
+
+/**
+ * Gets pixel query param that contains a legacy duid.
+ * @returns {string} query param
+ */
+function getLegacyDuidQueryParam() {
+ let queryParam = '';
+ const legacyDuid = getLegacyDuid();
+ if (legacyDuid) {
+ queryParam = `lduid=${legacyDuid}`;
+ }
+ return queryParam;
+}
+
+/**
+ * Gets a value from _li_duid local storage item. If it contains uuid, then this is the legacy duid
+ * @returns {string|undefined} Legacy duid
+ */
+function getLegacyDuid() {
+ const duid = localStorage.getItem(SHARED_DUID_NAME);
+ let legacyDuid;
+ if (duid && duid.includes('-')) {
+ legacyDuid = duid;
+ }
+ return legacyDuid;
+}
+
+/**
+ * Update _li_duid value with the ulid duid if the previous value is not a legacy duid
+ * @param {string} duid
+ */
+function trySettingSharedDuid(duid) {
+ const legacyDuid = getLegacyDuid();
+ if (!legacyDuid) {
+ localStorage.setItem(SHARED_DUID_NAME, duid);
+ }
+}
diff --git a/modules/liveConnect.md b/modules/liveConnect.md
new file mode 100644
index 00000000000..3116811f871
--- /dev/null
+++ b/modules/liveConnect.md
@@ -0,0 +1,53 @@
+# Overview
+
+**Module Name**: LiveConnect
+**Module Type**: Tracker
+**Maintainer**: dev-berlin@liveintent.com
+
+# Description
+
+Send a tracking pixel request to LiveConnect pixel service.
+
+# Usage
+To trigger the pixel, call `pbjs.liveConnect()`.
+Make sure that `pbjs.setConfig` is done before the pixel call.
+```javascript
+pbjs.que.push(function () {
+ pbjs.setConfig({});
+ pbjs.liveConnect();
+});
+```
+Alternatively, the pixel is also triggered upon `pbjs.requestBids`
+```javascript
+pbjs.que.push(function () {
+ pbjs.setConfig({});
+ pbjs.requestBids({});
+});
+```
+
+# Example configuration
+Example showing configuration for scraped identifiers and provided identifier.
+LiveConnect identifier has a changed name and expiration.
+```javascript
+pbjs.setConfig({
+ liveConnect: {
+ userIdentifier: "pubcid",
+ additionalUserIdentifiers: ["_parrable_eid"],
+ storage: {
+ type: "cookie",
+ expires: 60
+ }
+ }
+});
+```
+Example showing configuration to store identifiers in local storage.
+```javascript
+pbjs.setConfig({
+ liveConnect: {
+ storage: {
+ type: "html5",
+ expires: 60
+ }
+ }
+});
+```
diff --git a/modules/userId/gdprUtils.js b/modules/userId/gdprUtils.js
new file mode 100644
index 00000000000..b622d9e304b
--- /dev/null
+++ b/modules/userId/gdprUtils.js
@@ -0,0 +1,16 @@
+/**
+ * test if consent module is present, applies, and is valid for local storage or cookies (purpose 1)
+ * @param {ConsentData} consentData
+ * @returns {boolean}
+ */
+export function hasGDPRConsent(consentData) {
+ if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) {
+ if (!consentData.consentString) {
+ return false;
+ }
+ if (consentData.vendorData && consentData.vendorData.purposeConsents && consentData.vendorData.purposeConsents['1'] === false) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/modules/userId/index.js b/modules/userId/index.js
index ac96fd2cec8..5ec21fad973 100644
--- a/modules/userId/index.js
+++ b/modules/userId/index.js
@@ -106,6 +106,7 @@ import CONSTANTS from '../../src/constants.json';
import {module} from '../../src/hook';
import {unifiedIdSubmodule} from './unifiedIdSystem.js';
import {pubCommonIdSubmodule} from './pubCommonIdSystem.js';
+import {hasGDPRConsent} from './gdprUtils';
const MODULE_NAME = 'User ID';
const COOKIE = 'cookie';
@@ -199,23 +200,6 @@ function getStoredValue(storage, key = undefined) {
return storedValue;
}
-/**
- * test if consent module is present, applies, and is valid for local storage or cookies (purpose 1)
- * @param {ConsentData} consentData
- * @returns {boolean}
- */
-function hasGDPRConsent(consentData) {
- if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) {
- if (!consentData.consentString) {
- return false;
- }
- if (consentData.vendorData && consentData.vendorData.purposeConsents && consentData.vendorData.purposeConsents['1'] === false) {
- return false;
- }
- }
- return true;
-}
-
/**
* @param {SubmoduleContainer[]} submodules
* @param {function} cb - callback for after processing is done.
diff --git a/package-lock.json b/package-lock.json
index c7256dd8e2d..11c1b393311 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "prebid.js",
- "version": "2.35.0-pre",
+ "version": "2.38.0-pre",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3397,29 +3397,51 @@
"dev": true
},
"cliui": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
- "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
- "string-width": "^2.1.1",
- "strip-ansi": "^4.0.0",
- "wrap-ansi": "^2.0.0"
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
},
"dependencies": {
"ansi-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
"strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
- "ansi-regex": "^3.0.0"
+ "ansi-regex": "^4.1.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
}
}
}
@@ -6174,9 +6196,9 @@
},
"dependencies": {
"is-buffer": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
- "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+ "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
"dev": true
}
}
@@ -6411,7 +6433,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -6432,12 +6455,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -6452,17 +6477,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -6579,7 +6607,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -6591,6 +6620,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -6605,6 +6635,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -6612,12 +6643,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -6636,6 +6669,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -6716,7 +6750,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -6728,6 +6763,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -6813,7 +6849,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -6849,6 +6886,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -6868,6 +6906,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -6911,12 +6950,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -8624,12 +8665,6 @@
"loose-envify": "^1.0.0"
}
},
- "invert-kv": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
- "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
- "dev": true
- },
"ipaddr.js": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
@@ -9679,15 +9714,6 @@
"readable-stream": "^2.0.5"
}
},
- "lcid": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
- "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
- "dev": true,
- "requires": {
- "invert-kv": "^2.0.0"
- }
- },
"lcov-parse": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz",
@@ -10167,15 +10193,6 @@
"kind-of": "^6.0.2"
}
},
- "map-age-cleaner": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
- "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
- "dev": true,
- "requires": {
- "p-defer": "^1.0.0"
- }
- },
"map-cache": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
@@ -10366,25 +10383,6 @@
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
- "mem": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
- "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
- "dev": true,
- "requires": {
- "map-age-cleaner": "^0.1.1",
- "mimic-fn": "^2.0.0",
- "p-is-promise": "^2.0.0"
- },
- "dependencies": {
- "p-is-promise": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
- "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
- "dev": true
- }
- }
- },
"memoizee": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz",
@@ -10598,12 +10596,6 @@
"mime-db": "1.40.0"
}
},
- "mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true
- },
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
@@ -11414,17 +11406,6 @@
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
- "os-locale": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
- "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
- "dev": true,
- "requires": {
- "execa": "^1.0.0",
- "lcid": "^2.0.0",
- "mem": "^4.0.0"
- }
- },
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -11437,12 +11418,6 @@
"integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==",
"dev": true
},
- "p-defer": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
- "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
- "dev": true
- },
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
@@ -11566,9 +11541,9 @@
}
},
"mocha": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz",
- "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz",
+ "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==",
"dev": true,
"requires": {
"ansi-colors": "3.2.3",
@@ -11591,9 +11566,9 @@
"supports-color": "6.0.0",
"which": "1.3.1",
"wide-align": "1.1.3",
- "yargs": "13.2.2",
- "yargs-parser": "13.0.0",
- "yargs-unparser": "1.5.0"
+ "yargs": "13.3.0",
+ "yargs-parser": "13.1.1",
+ "yargs-unparser": "1.6.0"
}
},
"ms": {
@@ -11632,22 +11607,21 @@
}
},
"yargs": {
- "version": "13.2.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz",
- "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==",
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
+ "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
"dev": true,
"requires": {
- "cliui": "^4.0.0",
+ "cliui": "^5.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
- "os-locale": "^3.1.0",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
- "yargs-parser": "^13.0.0"
+ "yargs-parser": "^13.1.1"
}
}
}
@@ -14054,6 +14028,21 @@
}
}
},
+ "tldjs": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz",
+ "integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==",
+ "requires": {
+ "punycode": "^1.4.1"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+ }
+ }
+ },
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -14355,6 +14344,11 @@
}
}
},
+ "ulid": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz",
+ "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw=="
+ },
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
@@ -16226,9 +16220,9 @@
"dev": true
},
"yargs-parser": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz",
- "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==",
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+ "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
@@ -16236,56 +16230,58 @@
}
},
"yargs-unparser": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz",
- "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
+ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
"dev": true,
"requires": {
"flat": "^4.1.0",
- "lodash": "^4.17.11",
- "yargs": "^12.0.5"
+ "lodash": "^4.17.15",
+ "yargs": "^13.3.0"
},
"dependencies": {
- "get-caller-file": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
- "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
- "require-main-filename": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
- "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
- "dev": true
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
},
"yargs": {
- "version": "12.0.5",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
- "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
+ "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
"dev": true,
"requires": {
- "cliui": "^4.0.0",
- "decamelize": "^1.2.0",
+ "cliui": "^5.0.0",
"find-up": "^3.0.0",
- "get-caller-file": "^1.0.1",
- "os-locale": "^3.0.0",
+ "get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
- "require-main-filename": "^1.0.1",
+ "require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
- "string-width": "^2.0.0",
+ "string-width": "^3.0.0",
"which-module": "^2.0.0",
- "y18n": "^3.2.1 || ^4.0.0",
- "yargs-parser": "^11.1.1"
- }
- },
- "yargs-parser": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
- "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
- "dev": true,
- "requires": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.1"
}
}
}
diff --git a/package.json b/package.json
index 9e5effb8627..6bb412352a2 100755
--- a/package.json
+++ b/package.json
@@ -102,6 +102,7 @@
"dset": "2.0.1",
"fun-hooks": "^0.9.5",
"jsencrypt": "^3.0.0-rc.1",
- "just-clone": "^1.0.2"
+ "just-clone": "^1.0.2",
+ "ulid": "^2.3.0"
}
}
diff --git a/src/utils.js b/src/utils.js
index 2caaedc4164..1eff91dcc1d 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -918,8 +918,8 @@ export function getCookie(name) {
return m ? decodeURIComponent(m[2]) : null;
}
-export function setCookie(key, value, expires, sameSite) {
- document.cookie = `${key}=${encodeURIComponent(value)}${(expires !== '') ? `; expires=${expires}` : ''}; path=/${sameSite ? `; SameSite=${sameSite}` : ''}`;
+export function setCookie(key, value, expires, sameSite, domain) {
+ document.cookie = `${key}=${encodeURIComponent(value)}${(expires !== '') ? `; expires=${expires}` : ''}; path=/${sameSite ? `; SameSite=${sameSite}` : ''}${domain ? `; domain=${domain}` : ''}`;
}
/**
diff --git a/test/spec/modules/liveConnect_spec.js b/test/spec/modules/liveConnect_spec.js
new file mode 100644
index 00000000000..bd7d231f3e6
--- /dev/null
+++ b/test/spec/modules/liveConnect_spec.js
@@ -0,0 +1,275 @@
+import * as liveConnect from 'modules/liveConnect';
+import {config} from 'src/config';
+import * as utils from 'src/utils';
+import {resetConsentData} from 'modules/consentManagement';
+
+const expect = require('chai').expect;
+
+const USER_IDENTIFIER_NAME = 'testPfpi';
+const USER_IDENTIFIER_VALUE = 'testPfpiValue';
+const ADDITIONAL_IDENTIFIER_NAME = 'testIdentifier';
+const ADDITIONAL_IDENTIFIER_VALUE = 'testIdentifierValue';
+const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT';
+const BASIC_PIXEL_CALL = 'https://rp\\.liadm\\.com/p\\?duid=[0-9A-Z]{26}&tna=$prebid.version$&pu=http%3A%2F%2Flocalhost%3A[0-9]{4}%2F%3Fid%3D[0-9]+';
+
+function liveConnectConfig(config) {
+ return {
+ liveConnect: config
+ }
+}
+
+describe('LiveConnect', () => {
+ beforeEach(function () {
+ liveConnect.init();
+ sinon.stub(utils, 'triggerPixel');
+ sinon.spy(utils, 'setCookie');
+ sinon.spy(localStorage, 'setItem');
+ });
+
+ afterEach(function () {
+ $$PREBID_GLOBAL$$.requestBids.removeAll();
+ config.resetConfig();
+ utils.setCookie(USER_IDENTIFIER_NAME, '', EXPIRED_COOKIE_DATE);
+ utils.setCookie(ADDITIONAL_IDENTIFIER_NAME, '', EXPIRED_COOKIE_DATE);
+ localStorage.removeItem(USER_IDENTIFIER_NAME);
+ localStorage.removeItem(ADDITIONAL_IDENTIFIER_NAME);
+ localStorage.removeItem('_li_duid');
+ liveConnect.resetPixel();
+ utils.triggerPixel.restore();
+ utils.setCookie.restore();
+ localStorage.setItem.restore();
+ resetConsentData();
+ });
+
+ describe('identifier', () => {
+ it('should be set to cookie', () => {
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.setCookie.callCount).to.equal(1);
+ expect(utils.setCookie.getCall(0).args[0]).to.exist.and.to.equal('_lc2_duid');
+ });
+
+ it('should be set to cookie and shared local storage identifier', () => {
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.setCookie.callCount).to.equal(1);
+ expect(utils.setCookie.getCall(0).args[0]).to.exist.and.to.equal('_lc2_duid');
+ expect(localStorage.setItem.callCount).to.equal(1);
+ expect(localStorage.setItem.getCall(0).args[0]).to.exist.and.to.equal('_li_duid');
+ });
+
+ it('should be set to cookie but not set to shared local storage identifier when there is a legady duid in shared local storage item', () => {
+ localStorage.setItem.restore();
+ localStorage.setItem('_li_duid', 'a-1234--519253bc-469f-43f4-8100-592803e07dc3');
+ sinon.spy(localStorage, 'setItem');
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.setCookie.callCount).to.equal(1);
+ expect(utils.setCookie.getCall(0).args[0]).to.exist.and.to.equal('_lc2_duid');
+ expect(localStorage.setItem.callCount).to.equal(0);
+ });
+
+ it('should be reset to cookie when the duid has been already stored', () => {
+ utils.setCookie.restore();
+ utils.setCookie('_lc2_duid', '01DRV0Z3SYRKV68NFAB40TN3EE', 'Thu, 01 Jan 2030 00:00:01 GMT');
+ sinon.spy(utils, 'setCookie');
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.setCookie.callCount).to.equal(1);
+ expect(utils.setCookie.getCall(0).args[0]).to.exist.and.to.equal('_lc2_duid');
+ expect(utils.setCookie.getCall(0).args[1]).to.exist.and.to.equal('01DRV0Z3SYRKV68NFAB40TN3EE');
+ });
+
+ it('should be set to cookie when storage type is unknown', () => {
+ config.setConfig(liveConnectConfig({storage: {type: 'html7'}}));
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.setCookie.callCount).to.equal(1);
+ expect(utils.setCookie.getCall(0).args[0]).to.exist.and.to.equal('_lc2_duid');
+ });
+
+ it('should be set to local storage identifier', () => {
+ config.setConfig(liveConnectConfig({storage: {type: 'html5', expires: 23}}));
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.setCookie.callCount).to.equal(0);
+ expect(localStorage.setItem.callCount).to.equal(3);
+ expect(localStorage.setItem.getCall(0).args[0]).to.exist.and.to.equal('_lc2_duid_exp');
+ expect(localStorage.setItem.getCall(1).args[0]).to.exist.and.to.equal('_lc2_duid');
+ expect(localStorage.setItem.getCall(2).args[0]).to.exist.and.to.equal('_li_duid');
+ });
+
+ it('should be reset to local storage when the duid has been already stored', () => {
+ localStorage.setItem.restore();
+ localStorage.setItem('_lc2_duid', '01DRV0Z3SYRKV68NFAB40TN3EE');
+ sinon.spy(localStorage, 'setItem');
+ config.setConfig(liveConnectConfig({storage: {type: 'html5'}}));
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.setCookie.callCount).to.equal(0);
+ expect(localStorage.setItem.callCount).to.equal(3);
+ expect(localStorage.setItem.getCall(0).args[0]).to.exist.and.to.equal('_lc2_duid_exp');
+ expect(localStorage.setItem.getCall(1).args[0]).to.exist.and.to.equal('_lc2_duid');
+ expect(localStorage.setItem.getCall(1).args[1]).to.exist.and.to.equal('01DRV0Z3SYRKV68NFAB40TN3EE');
+ expect(localStorage.setItem.getCall(2).args[0]).to.exist.and.to.equal('_li_duid');
+ });
+
+ it('should be set only once', () => {
+ $$PREBID_GLOBAL$$.liveConnect();
+ $$PREBID_GLOBAL$$.requestBids({});
+
+ expect(utils.setCookie.callCount).to.equal(1);
+ });
+
+ it('should not be set when consent is not given', () => {
+ config.setConfig({
+ consentManagement: {
+ cmpApi: 'static',
+ timeout: 7500,
+ allowAuctionWithoutConsent: false,
+ consentData: {
+ getConsentData: {
+ 'gdprApplies': true,
+ 'consentData': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA'
+ },
+ getVendorConsents: {
+ 'purposeConsents': {
+ '1': false
+ }
+ }
+ }
+ }
+ });
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.setCookie.callCount).to.equal(0);
+ expect(localStorage.setItem.callCount).to.equal(0);
+ });
+ });
+
+ describe('pixel call', () => {
+ it('should be sent to pixel endpoint', () => {
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.triggerPixel.callCount).to.equal(1);
+ expect(utils.triggerPixel.getCall(0).args[0]).to.exist.and.to.match(new RegExp(BASIC_PIXEL_CALL));
+ });
+
+ it('should be sent to pixel endpoint when bid is performed', () => {
+ $$PREBID_GLOBAL$$.requestBids({});
+
+ expect(utils.triggerPixel.callCount).to.equal(1);
+ expect(utils.triggerPixel.getCall(0).args[0]).to.exist.and.to.match(new RegExp(BASIC_PIXEL_CALL));
+ });
+
+ it('should be sent to pixel endpoint only once', () => {
+ $$PREBID_GLOBAL$$.liveConnect();
+ $$PREBID_GLOBAL$$.requestBids({});
+
+ expect(utils.setCookie.callCount).to.equal(1);
+ });
+
+ it('should be sent to pixel endpoint when the legacy duid is set into local storage', () => {
+ localStorage.setItem('_li_duid', 'a-1234--519253bc-469f-43f4-8100-592803e07dc3');
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.triggerPixel.callCount).to.equal(1);
+ expect(utils.triggerPixel.getCall(0).args[0]).to.exist.and.to.match(new RegExp(
+ BASIC_PIXEL_CALL + '&lduid=a-1234--519253bc-469f-43f4-8100-592803e07dc3'
+ ));
+ });
+
+ it('should be sent to pixel endpoint when the user identifier is set into cookie', () => {
+ utils.setCookie(USER_IDENTIFIER_NAME, USER_IDENTIFIER_VALUE, (new Date(Date.now() + 60000).toUTCString()));
+ config.setConfig(liveConnectConfig({userIdentifier: USER_IDENTIFIER_NAME}));
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.triggerPixel.callCount).to.equal(1);
+ expect(utils.triggerPixel.getCall(0).args[0]).to.exist.and.to.match(new RegExp(
+ BASIC_PIXEL_CALL + `&pfpi=${USER_IDENTIFIER_VALUE}&fpn=${USER_IDENTIFIER_NAME}`
+ ));
+ });
+
+ it('should be sent to pixel endpoint when the user identifier is set into local storage', () => {
+ localStorage.setItem(USER_IDENTIFIER_NAME, USER_IDENTIFIER_VALUE);
+ config.setConfig(liveConnectConfig({userIdentifier: USER_IDENTIFIER_NAME}));
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.triggerPixel.callCount).to.equal(1);
+ expect(utils.triggerPixel.getCall(0).args[0]).to.exist.and.to.match(new RegExp(
+ BASIC_PIXEL_CALL + `&pfpi=${USER_IDENTIFIER_VALUE}&fpn=${USER_IDENTIFIER_NAME}`
+ ));
+ });
+
+ it('should be sent to pixel endpoint when the additional identifier is set into cookie', () => {
+ utils.setCookie(ADDITIONAL_IDENTIFIER_NAME, ADDITIONAL_IDENTIFIER_VALUE, (new Date(Date.now() + 60000).toUTCString()));
+ config.setConfig(liveConnectConfig({additionalUserIdentifiers: [ADDITIONAL_IDENTIFIER_NAME]}));
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.triggerPixel.callCount).to.equal(1);
+ expect(utils.triggerPixel.getCall(0).args[0]).to.exist.and.to.match(new RegExp(
+ BASIC_PIXEL_CALL + `&ext_${ADDITIONAL_IDENTIFIER_NAME}=${ADDITIONAL_IDENTIFIER_VALUE}`
+ ));
+ });
+
+ it('should be sent to pixel endpoint when the additional identifier is set into local storage', () => {
+ localStorage.setItem(ADDITIONAL_IDENTIFIER_NAME, ADDITIONAL_IDENTIFIER_VALUE);
+ config.setConfig(liveConnectConfig({additionalUserIdentifiers: [ADDITIONAL_IDENTIFIER_NAME]}));
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.triggerPixel.callCount).to.equal(1);
+ expect(utils.triggerPixel.getCall(0).args[0]).to.exist.and.to.match(new RegExp(
+ BASIC_PIXEL_CALL + `&ext_${ADDITIONAL_IDENTIFIER_NAME}=${ADDITIONAL_IDENTIFIER_VALUE}`
+ ));
+ });
+
+ it('should be sent to pixel endpoint when the additional identifier and user identifier are set', () => {
+ localStorage.setItem(ADDITIONAL_IDENTIFIER_NAME, ADDITIONAL_IDENTIFIER_VALUE);
+ localStorage.setItem(USER_IDENTIFIER_NAME, USER_IDENTIFIER_VALUE);
+ config.setConfig(liveConnectConfig({userIdentifier: USER_IDENTIFIER_NAME, additionalUserIdentifiers: [ADDITIONAL_IDENTIFIER_NAME]}));
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.triggerPixel.callCount).to.equal(1);
+ expect(utils.triggerPixel.getCall(0).args[0]).to.exist.and.to.match(new RegExp(
+ BASIC_PIXEL_CALL + `&pfpi=${USER_IDENTIFIER_VALUE}&fpn=${USER_IDENTIFIER_NAME}&ext_${ADDITIONAL_IDENTIFIER_NAME}=${ADDITIONAL_IDENTIFIER_VALUE}`
+ ));
+ });
+
+ it('should not be sent to pixel endpoint when consent is not given', () => {
+ config.setConfig({
+ consentManagement: {
+ cmpApi: 'static',
+ timeout: 7500,
+ allowAuctionWithoutConsent: false,
+ consentData: {
+ getConsentData: {
+ 'gdprApplies': true,
+ 'consentData': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA'
+ },
+ getVendorConsents: {
+ 'purposeConsents': {
+ '1': false
+ }
+ }
+ }
+ }
+ });
+
+ $$PREBID_GLOBAL$$.liveConnect();
+
+ expect(utils.triggerPixel.callCount).to.equal(0);
+ });
+ });
+});