diff --git a/allowedModules.js b/allowedModules.js
index 0d4a36308c3..171ebebede2 100644
--- a/allowedModules.js
+++ b/allowedModules.js
@@ -13,7 +13,8 @@ module.exports = {
...sharedWhiteList,
'criteo-direct-rsa-validate',
'jsencrypt',
- 'crypto-js'
+ 'crypto-js',
+ 'live-connect'
],
'src': [
...sharedWhiteList,
diff --git a/integrationExamples/gpt/liveIntentId_example.html b/integrationExamples/gpt/liveIntentId_example.html
new file mode 100755
index 00000000000..8484b99d1c4
--- /dev/null
+++ b/integrationExamples/gpt/liveIntentId_example.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+Prebid.js LiveIntent Id Test
+
+
diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js
index 5652357569d..60e262f87f5 100644
--- a/modules/liveIntentIdSystem.js
+++ b/modules/liveIntentIdSystem.js
@@ -4,15 +4,72 @@
* @module modules/liveIntentIdSystem
* @requires module:modules/userId
*/
-import * as utils from '../src/utils'
-import {ajax} from '../src/ajax';
-import {submodule} from '../src/hook';
+import * as utils from '../src/utils';
+import { submodule } from '../src/hook';
+import { LiveConnect } from 'live-connect-js/cjs/live-connect';
+import { uspDataHandler } from '../src/adapterManager';
const MODULE_NAME = 'liveIntentId';
-const LIVE_CONNECT_DUID_KEY = '_li_duid';
-const DOMAIN_USER_ID_QUERY_PARAM_KEY = 'duid';
-const DEFAULT_LIVEINTENT_IDENTITY_URL = 'https://idx.liadm.com';
-const DEFAULT_PREBID_SOURCE = 'prebid';
+
+let eventFired = false;
+let liveConnect = null;
+
+/**
+ * This function is used in tests
+ */
+export function reset() {
+ eventFired = false;
+ liveConnect = null;
+}
+
+function initializeLiveConnect(configParams) {
+ if (liveConnect) {
+ return liveConnect;
+ }
+
+ const publisherId = configParams && configParams.publisherId;
+ if (!publisherId && typeof publisherId !== 'string') {
+ utils.logError(`${MODULE_NAME} - publisherId must be defined, not a '${publisherId}'`);
+ return;
+ }
+
+ const identityResolutionConfig = {
+ source: 'prebid',
+ publisherId: publisherId
+ };
+ if (configParams.url) {
+ identityResolutionConfig.url = configParams.url
+ }
+ if (configParams.partner) {
+ identityResolutionConfig.source = configParams.partner
+ }
+ if (configParams.storage && configParams.storage.expires) {
+ identityResolutionConfig.expirationDays = configParams.storage.expires;
+ }
+
+ const liveConnectConfig = {
+ identifiersToResolve: configParams.identifiersToResolve || [],
+ wrapperName: 'prebid',
+ identityResolutionConfig: identityResolutionConfig
+ };
+ const usPrivacyString = uspDataHandler.getConsentData();
+ if (usPrivacyString) {
+ liveConnectConfig.usPrivacyString = usPrivacyString;
+ }
+ if (configParams.appId) {
+ liveConnectConfig.appId = configParams.appId;
+ }
+
+ liveConnect = LiveConnect(liveConnectConfig);
+ return liveConnect;
+}
+
+function tryFireEvent() {
+ if (!eventFired && liveConnect) {
+ liveConnect.fire();
+ eventFired = true;
+ }
+}
/** @type {Submodule} */
export const liveIntentIdSubmodule = {
@@ -28,14 +85,21 @@ export const liveIntentIdSubmodule = {
* `publisherId` params.
* @function
* @param {{unifiedId:string}} value
+ * @param {SubmoduleParams|undefined} [configParams]
* @returns {{lipb:Object}}
*/
- decode(value) {
+ decode(value, configParams) {
function composeIdObject(value) {
- const base = {'lipbid': value['unifiedId']};
+ const base = { 'lipbid': value['unifiedId'] };
delete value.unifiedId;
- return {'lipb': {...base, ...value}};
+ return { 'lipb': { ...base, ...value } };
}
+
+ if (configParams) {
+ initializeLiveConnect(configParams);
+ tryFireEvent();
+ }
+
return (value && typeof value['unifiedId'] === 'string') ? composeIdObject(value) : undefined;
},
@@ -46,52 +110,12 @@ export const liveIntentIdSubmodule = {
* @returns {IdResponse|undefined}
*/
getId(configParams) {
- const publisherId = configParams && configParams.publisherId;
- if (!publisherId && typeof publisherId !== 'string') {
- utils.logError(`${MODULE_NAME} - publisherId must be defined, not a '${publisherId}'`);
+ const liveConnect = initializeLiveConnect(configParams);
+ if (!liveConnect) {
return;
}
- let baseUrl = DEFAULT_LIVEINTENT_IDENTITY_URL;
- let source = DEFAULT_PREBID_SOURCE;
- if (configParams.url) {
- baseUrl = configParams.url
- }
- if (configParams.partner) {
- source = configParams.partner
- }
-
- const additionalIdentifierNames = configParams.identifiersToResolve || [];
-
- const additionalIdentifiers = additionalIdentifierNames.concat([LIVE_CONNECT_DUID_KEY]).reduce((obj, identifier) => {
- const value = utils.getCookie(identifier) || utils.getDataFromLocalStorage(identifier);
- const key = identifier.replace(LIVE_CONNECT_DUID_KEY, DOMAIN_USER_ID_QUERY_PARAM_KEY);
- if (value) {
- if (typeof value === 'object') {
- obj[key] = JSON.stringify(value);
- } else {
- obj[key] = value;
- }
- }
- return obj
- }, {});
-
- const queryString = utils.parseQueryStringParameters(additionalIdentifiers)
- const url = `${baseUrl}/idex/${source}/${publisherId}?${queryString}`;
-
- const result = function (callback) {
- ajax(url, response => {
- let responseObj = {};
- if (response) {
- try {
- responseObj = JSON.parse(response);
- } catch (error) {
- utils.logError(error);
- }
- }
- callback(responseObj);
- }, undefined, { method: 'GET', withCredentials: true });
- };
- return {callback: result};
+ tryFireEvent();
+ return { callback: liveConnect.resolve };
}
};
diff --git a/modules/userId/index.js b/modules/userId/index.js
index cbe5e4de55f..f3db23e77bf 100644
--- a/modules/userId/index.js
+++ b/modules/userId/index.js
@@ -37,6 +37,7 @@
* @summary decode a stored value for passing to bid requests
* @name Submodule#decode
* @param {Object|string} value
+ * @param {SubmoduleParams|undefined} configParams
* @return {(Object|undefined)}
*/
@@ -73,6 +74,7 @@
* @property {(string|undefined)} pid - placement id url param value
* @property {(string|undefined)} publisherId - the unique identifier of the publisher in question
* @property {(array|undefined)} identifiersToResolve - the identifiers from either ls|cookie to be attached to the getId query
+ * @property {(string|undefined)} appId - the unique identifier of the application in question
*/
/**
@@ -411,7 +413,7 @@ function initSubmodules(submodules, consentData) {
if (storedId) {
// cache decoded value (this is copied to every adUnit bid)
- submodule.idObj = submodule.submodule.decode(storedId);
+ submodule.idObj = submodule.submodule.decode(storedId, submodule.config);
}
} else if (submodule.config.value) {
// cache decoded value (this is copied to every adUnit bid)
@@ -420,7 +422,7 @@ function initSubmodules(submodules, consentData) {
const response = submodule.submodule.getId(submodule.config.params, consentData, undefined);
if (utils.isPlainObject(response)) {
if (typeof response.callback === 'function') { submodule.callback = response.callback; }
- if (response.id) { submodule.idObj = submodule.submodule.decode(response.id); }
+ if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); }
}
}
carry.push(submodule);
diff --git a/package.json b/package.json
index cc5b4e4e9f7..8aa01ce93ca 100755
--- a/package.json
+++ b/package.json
@@ -104,6 +104,7 @@
"express": "^4.15.4",
"fun-hooks": "^0.9.5",
"jsencrypt": "^3.0.0-rc.1",
- "just-clone": "^1.0.2"
+ "just-clone": "^1.0.2",
+ "live-connect-js": "^1.0.11"
}
}
diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js
index 4c0e2bb25e6..6ef556fd8ea 100644
--- a/test/spec/modules/liveIntentIdSystem_spec.js
+++ b/test/spec/modules/liveIntentIdSystem_spec.js
@@ -1,140 +1,214 @@
-import { liveIntentIdSubmodule } from 'modules/liveIntentIdSystem';
+import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule } from 'modules/liveIntentIdSystem';
import * as utils from 'src/utils';
-import { server } from 'test/mocks/xhr';
+import * as liveConnect from 'live-connect-js/cjs/live-connect';
+import { uspDataHandler } from '../../../src/adapterManager';
+
+const assert = require('assert');
+
+const PUBLISHER_ID = '89899';
describe('LiveIntentId', function() {
- let getCookieStub;
- let getDataFromLocalStorageStub;
let logErrorStub;
+ let lcStub;
+ let consentDataStub;
+ const fireStub = sinon.stub();
+ const resolveStub = sinon.stub();
+ const lcClient = { fire: fireStub, resolve: resolveStub };
- const defaultConfigParams = {'publisherId': '89899'};
- const responseHeader = { 'Content-Type': 'application/json' }
+ const defaultConfigParams = { publisherId: PUBLISHER_ID };
- beforeEach(function () {
- getCookieStub = sinon.stub(utils, 'getCookie');
- getDataFromLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage');
+ beforeEach(function() {
logErrorStub = sinon.stub(utils, 'logError');
+ lcStub = sinon.stub(liveConnect, 'LiveConnect');
+ lcStub.returns(lcClient);
+ consentDataStub = sinon.stub(uspDataHandler, 'getConsentData');
});
- afterEach(function () {
- getCookieStub.restore();
- getDataFromLocalStorageStub.restore();
+ afterEach(function() {
logErrorStub.restore();
+ lcStub.restore();
+ consentDataStub.restore();
+ fireStub.resetHistory();
+ resolveStub.resetHistory();
+ resetLiveIntentIdSubmodule();
});
- it('should log an error if no configParams were passed', function() {
+ it('should log an error if no configParams were passed when getId', function() {
liveIntentIdSubmodule.getId();
expect(logErrorStub.calledOnce).to.be.true;
});
- it('should log an error if publisherId configParam was not passed', function() {
+ it('should log an error if publisherId configParam was not passed when getId', function() {
liveIntentIdSubmodule.getId({});
expect(logErrorStub.calledOnce).to.be.true;
});
- it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function() {
- getCookieStub.returns(null);
- let callBackSpy = sinon.spy();
- let submoduleCallback = liveIntentIdSubmodule.getId({...defaultConfigParams, ...{'url': 'https://dummy.liveintent.com'}}).callback;
- submoduleCallback(callBackSpy);
- let request = server.requests[0];
- expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?');
- request.respond(
- 200,
- responseHeader,
- JSON.stringify({})
- );
- expect(callBackSpy.calledOnce).to.be.true;
- });
-
- it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() {
- getCookieStub.returns(null);
- let callBackSpy = sinon.spy();
- let submoduleCallback = liveIntentIdSubmodule.getId({...defaultConfigParams, ...{'url': 'https://dummy.liveintent.com', 'partner': 'rubicon'}}).callback;
- submoduleCallback(callBackSpy);
- let request = server.requests[0];
- expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?');
- request.respond(
- 200,
- responseHeader,
- JSON.stringify({})
- );
- expect(callBackSpy.calledOnce).to.be.true;
- });
-
- it('should call the LiveIntent Identity Exchange endpoint, with no additional query params', function() {
- getCookieStub.returns(null);
- let callBackSpy = sinon.spy();
- let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback;
- submoduleCallback(callBackSpy);
- let request = server.requests[0];
- expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?');
- request.respond(
- 200,
- responseHeader,
- JSON.stringify({})
- );
- expect(callBackSpy.calledOnce).to.be.true;
- });
-
- it('should include the LiveConnect identifier when calling the LiveIntent Identity Exchange endpoint', function() {
- getCookieStub.withArgs('_li_duid').returns('li-fpc');
- let callBackSpy = sinon.spy();
- let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback;
- submoduleCallback(callBackSpy);
- let request = server.requests[0];
- expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?duid=li-fpc&');
- request.respond(
- 200,
- responseHeader,
- JSON.stringify({})
- );
- expect(callBackSpy.calledOnce).to.be.true;
+ it('should initialize LiveConnect with the config params when getId', function() {
+ liveIntentIdSubmodule.getId({
+ ...defaultConfigParams,
+ ...{
+ appId: 'a-0001',
+ identifiersToResolve: ['id1', 'id2'],
+ url: 'https://dummy.liveintent.com',
+ storage: {
+ expires: 3
+ }
+ }
+ });
+
+ expect(lcStub.calledOnce).to.be.true;
+ assert.deepEqual(lcStub.getCall(0).args[0], {
+ wrapperName: 'prebid',
+ appId: 'a-0001',
+ identifiersToResolve: ['id1', 'id2'],
+ identityResolutionConfig: {
+ source: 'prebid',
+ publisherId: PUBLISHER_ID,
+ url: 'https://dummy.liveintent.com',
+ expirationDays: 3
+ }
+ });
});
- it('should include the LiveConnect identifier and additional Identifiers to resolve', function() {
- getCookieStub.withArgs('_li_duid').returns('li-fpc');
- getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc');
- let configParams = {
+ it('should initialize LiveConnect with a source if it is passed in params when getId', function() {
+ liveIntentIdSubmodule.getId({
...defaultConfigParams,
...{
- 'identifiersToResolve': ['_thirdPC']
+ partner: 'test-partner'
}
- };
-
- let callBackSpy = sinon.spy();
- let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback;
- submoduleCallback(callBackSpy);
- let request = server.requests[0];
- expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=third-pc&duid=li-fpc&');
- request.respond(
- 200,
- responseHeader,
- JSON.stringify({})
- );
- expect(callBackSpy.calledOnce).to.be.true;
+ });
+
+ expect(lcStub.calledOnce).to.be.true;
+ expect(lcStub.getCall(0).args[0].identityResolutionConfig.source).to.be.eq('test-partner');
+ });
+
+ it('should initialize LiveConnect with a us privacy string when getId', function() {
+ consentDataStub.returns('1YNY');
+
+ liveIntentIdSubmodule.getId(defaultConfigParams);
+
+ expect(lcStub.calledOnce).to.be.true;
+ expect(lcStub.getCall(0).args[0].usPrivacyString).to.be.eq('1YNY');
+ });
+
+ it('should return function that resolves an identifier when getId', function() {
+ const result = liveIntentIdSubmodule.getId(defaultConfigParams);
+
+ assert.deepEqual(result, { callback: resolveStub });
+ });
+
+ it('should fire an event when getId', function() {
+ liveIntentIdSubmodule.getId(defaultConfigParams);
+
+ expect(fireStub.calledOnce).to.be.true;
+ });
+
+ it('should log an error if publisherId configParam was not passed when decode', function() {
+ liveIntentIdSubmodule.decode({}, {});
+ expect(logErrorStub.calledOnce).to.be.true;
+ });
+
+ it('should initialize LiveConnect with the config params when decode', function() {
+ liveIntentIdSubmodule.decode({}, {
+ ...defaultConfigParams,
+ ...{
+ appId: 'a-0001',
+ identifiersToResolve: ['id1', 'id2'],
+ url: 'https://dummy.liveintent.com',
+ storage: {
+ expires: 3
+ }
+ }
+ });
+
+ expect(lcStub.calledOnce).to.be.true;
+ assert.deepEqual(lcStub.getCall(0).args[0], {
+ wrapperName: 'prebid',
+ appId: 'a-0001',
+ identifiersToResolve: ['id1', 'id2'],
+ identityResolutionConfig: {
+ source: 'prebid',
+ publisherId: PUBLISHER_ID,
+ url: 'https://dummy.liveintent.com',
+ expirationDays: 3
+ }
+ });
});
- it('should include an additional identifier value to resolve even if it is an object', function() {
- getCookieStub.returns(null);
- getDataFromLocalStorageStub.withArgs('_thirdPC').returns({'key': 'value'});
- let configParams = {
+ it('should initialize LiveConnect with a source if it is passed in params when decode', function() {
+ liveIntentIdSubmodule.decode({}, {
...defaultConfigParams,
...{
- 'identifiersToResolve': ['_thirdPC']
+ partner: 'test-partner'
+ }
+ });
+
+ expect(lcStub.calledOnce).to.be.true;
+ expect(lcStub.getCall(0).args[0].identityResolutionConfig.source).to.be.eq('test-partner');
+ });
+
+ it('should initialize LiveConnect with a us privacy string when decode', function() {
+ consentDataStub.returns('1YNY');
+
+ liveIntentIdSubmodule.decode({}, defaultConfigParams);
+
+ expect(lcStub.calledOnce).to.be.true;
+ expect(lcStub.getCall(0).args[0].usPrivacyString).to.be.eq('1YNY');
+ });
+
+ it('should not initialize LiveConnect when decode and the config is not set', function() {
+ liveIntentIdSubmodule.decode({});
+
+ expect(lcStub.called).to.be.false;
+ });
+
+ it('should return a decoded identifier', function() {
+ const result = liveIntentIdSubmodule.decode(
+ {
+ unifiedId: 'id123',
+ additionalData: 'data'
+ }
+ );
+
+ assert.deepEqual(result, {
+ lipb: {
+ lipbid: 'id123',
+ additionalData: 'data'
+ }
+ });
+ });
+
+ it('should not return a decoded identifier when the unifiedId is not present in the value', function() {
+ const result = liveIntentIdSubmodule.decode(
+ {
+ additionalData: 'data'
}
- };
-
- let callBackSpy = sinon.spy();
- let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback;
- submoduleCallback(callBackSpy);
- let request = server.requests[0];
- expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D&');
- request.respond(
- 200,
- responseHeader,
- JSON.stringify({})
);
- expect(callBackSpy.calledOnce).to.be.true;
+
+ expect(result).to.be.undefined;
+ });
+
+ it('should fire an event when decode', function() {
+ liveIntentIdSubmodule.decode({}, defaultConfigParams);
+
+ expect(fireStub.calledOnce).to.be.true;
+ });
+
+ it('should fire an event only once', function() {
+ liveIntentIdSubmodule.getId(defaultConfigParams);
+ liveIntentIdSubmodule.decode({}, defaultConfigParams);
+ liveIntentIdSubmodule.getId(defaultConfigParams);
+ liveIntentIdSubmodule.decode({}, defaultConfigParams);
+
+ expect(fireStub.calledOnce).to.be.true;
+ });
+
+ it('should initialize LiveConnect only once', function() {
+ liveIntentIdSubmodule.getId(defaultConfigParams);
+ liveIntentIdSubmodule.decode({}, defaultConfigParams);
+ liveIntentIdSubmodule.getId(defaultConfigParams);
+ liveIntentIdSubmodule.decode({}, defaultConfigParams);
+
+ expect(lcStub.calledOnce).to.be.true;
});
});