diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 4f18c73ad2ab..f87d67aae8ec 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -8,9 +8,10 @@ import * as utils from '../src/utils.js'; import { triggerPixel } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/cjs/live-connect.js'; +import { LiveConnect } from 'live-connect-js/esm/initializer.js'; import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; +import { MinimalLiveConnect } from 'live-connect-js/esm/minimal-live-connect.js'; const MODULE_NAME = 'liveIntentId'; export const storage = getStorageManager(null, MODULE_NAME); @@ -42,26 +43,18 @@ export function reset() { if (window && window.liQ) { window.liQ = []; } + liveIntentIdSubmodule.setModuleMode(null) eventFired = false; liveConnect = null; } function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; - if (collectConfig) { - if (collectConfig.appId) { - config.appId = collectConfig.appId; - } - if (collectConfig.fpiStorageStrategy) { - config.storageStrategy = collectConfig.fpiStorageStrategy; - } - if (collectConfig.fpiExpirationDays) { - config.expirationDays = collectConfig.fpiExpirationDays; - } - if (collectConfig.collectorUrl) { - config.collectorUrl = collectConfig.collectorUrl; - } - } + collectConfig = collectConfig || {} + collectConfig.appId && (config.appId = collectConfig.appId); + collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); + collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); + collectConfig.collectorUrl && (config.collectorUrl = collectConfig.collectorUrl); return config; } @@ -90,9 +83,6 @@ function initializeLiveConnect(configParams) { liveConnectConfig.wrapperName = 'prebid'; liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; - if (configParams.emailHash) { - liveConnectConfig.eventSource = { hash: configParams.emailHash } - } const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; @@ -105,7 +95,10 @@ function initializeLiveConnect(configParams) { // The second param is the storage object, LS & Cookie manipulation uses PBJS utils. // The third param is the ajax and pixel object, the ajax and pixel use PBJS utils. - liveConnect = LiveConnect(liveConnectConfig, storage, calls); + liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); + if (configParams.emailHash) { + liveConnect.push({ hash: configParams.emailHash }) + } return liveConnect; } @@ -118,12 +111,20 @@ function tryFireEvent() { /** @type {Submodule} */ export const liveIntentIdSubmodule = { + moduleMode: process.env.LiveConnectMode, /** * used to link submodule with config * @type {string} */ name: MODULE_NAME, + setModuleMode(mode) { + this.moduleMode = mode + }, + getInitializer() { + return this.moduleMode === 'minimal' ? MinimalLiveConnect : LiveConnect + }, + /** * decode the stored id value for passing to bid requests. Note that lipb object is a wrapper for everything, and * internally it could contain more data other than `lipbid`(e.g. `segments`) depending on the `partner` and @@ -136,7 +137,7 @@ export const liveIntentIdSubmodule = { decode(value, config) { const configParams = (config && config.params) || {}; function composeIdObject(value) { - const base = { 'lipbid': value['unifiedId'] }; + const base = { 'lipbid': value.unifiedId }; delete value.unifiedId; return { 'lipb': { ...base, ...value } }; } diff --git a/package-lock.json b/package-lock.json index bf95487ed9d3..330298cbaac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.14.0-pre", + "version": "4.15.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1920,11 +1920,6 @@ "schema-utils": "^2.7.0" } }, - "@kiosked/ulid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@kiosked/ulid/-/ulid-3.0.0.tgz", - "integrity": "sha512-ZKt2KIgGHDaGfKt6FjYvCpDvBXZRRoE8b+wDOlAV76aXKpq6ITiSUnPYevR4y55NKDnwCvwOrjWe+aVOCAK8kQ==" - }, "@sindresorhus/is": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.0.0.tgz", @@ -2771,7 +2766,8 @@ "abab": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", + "dev": true }, "abbrev": { "version": "1.0.9", @@ -5069,11 +5065,6 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, - "browser-cookies": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browser-cookies/-/browser-cookies-1.2.0.tgz", - "integrity": "sha1-/KP/ubamOq3E2MCZnGtX0Pp9KbU=" - }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -15727,13 +15718,10 @@ "dev": true }, "live-connect-js": { - "version": "1.1.23", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-1.1.23.tgz", - "integrity": "sha512-alOXlYyDdMXt8zzCIs3+iCrdi6r/69c7YRN3sMETa3b2cCOxep3i9j2O0iepk2hxT5JxiR1MvqlqdWAL9d2Hcg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.0.0.tgz", + "integrity": "sha512-Xhrj1JU5LoLjJuujjTlvDfc/n3Shzk2hPlYmLdCx/lsltFFVuCFa9uM8u5mcHlmOUKP5pu9I54bAITxZBMHoXg==", "requires": { - "@kiosked/ulid": "^3.0.0", - "abab": "^2.0.3", - "browser-cookies": "^1.2.0", "tiny-hashes": "1.0.1" } }, diff --git a/package.json b/package.json index f65b996a3935..9688d57c4494 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,6 @@ "fun-hooks": "^0.9.9", "jsencrypt": "^3.0.0-rc.1", "just-clone": "^1.0.2", - "live-connect-js": "^1.1.23" + "live-connect-js": "2.0.0" } } diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js new file mode 100644 index 000000000000..b0f97ae0300c --- /dev/null +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -0,0 +1,188 @@ +import * as utils from 'src/utils.js'; +import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; +import { server } from 'test/mocks/xhr.js'; +import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; + +const PUBLISHER_ID = '89899'; +const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; +const responseHeader = {'Content-Type': 'application/json'}; + +describe('LiveIntentMinimalId', function() { + let logErrorStub; + let uspConsentDataStub; + let gdprConsentDataStub; + let getCookieStub; + let getDataFromLocalStorageStub; + let imgStub; + + beforeEach(function() { + liveIntentIdSubmodule.setModuleMode('minimal'); + imgStub = sinon.stub(utils, 'triggerPixel'); + getCookieStub = sinon.stub(storage, 'getCookie'); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + logErrorStub = sinon.stub(utils, 'logError'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + }); + + afterEach(function() { + imgStub.restore(); + getCookieStub.restore(); + getDataFromLocalStorageStub.restore(); + logErrorStub.restore(); + uspConsentDataStub.restore(); + gdprConsentDataStub.restore(); + liveIntentIdSubmodule.setModuleMode('minimal'); + resetLiveIntentIdSubmodule(); + }); + it('should not fire an event when getId', function() { + uspConsentDataStub.returns('1YNY'); + gdprConsentDataStub.returns({ + gdprApplies: true, + consentString: 'consentDataString' + }) + liveIntentIdSubmodule.getId(defaultConfigParams); + expect(server.requests[0]).to.eql(undefined) + }); + + it('should not return a decoded identifier when the unifiedId is not present in the value', function() { + const result = liveIntentIdSubmodule.decode({ additionalData: 'data' }); + expect(result).to.be.undefined; + }); + + it('should initialize LiveConnect and send no data', function() { + liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.decode({}, defaultConfigParams); + expect(server.requests.length).to.be.eq(0); + }); + + it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).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({ params: { + ...defaultConfigParams.params, + ...{ + 'url': 'https://dummy.liveintent.com/idex', + '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 log an error and continue to callback if ajax request errors', 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( + 503, + responseHeader, + 'Unavailable' + ); + expect(logErrorStub.calledOnce).to.be.true; + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should include the LiveConnect identifier when calling the LiveIntent Identity Exchange endpoint', function() { + const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' + getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); + 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=${oldCookie}`); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should include the LiveConnect identifier and additional Identifiers to resolve', function() { + const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' + getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); + getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); + const configParams = { params: { + ...defaultConfigParams.params, + ...{ + 'identifiersToResolve': ['_thirdPC'] + } + }}; + 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?duid=${oldCookie}&_thirdPC=third-pc`); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + 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'}); + const configParams = { params: { + ...defaultConfigParams.params, + ...{ + 'identifiersToResolve': ['_thirdPC'] + } + }}; + 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; + }); +}); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index aae60cbcd190..3b4e4b9d9a77 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -2,7 +2,8 @@ import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } f import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; - +resetLiveIntentIdSubmodule(); +liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; const responseHeader = {'Content-Type': 'application/json'} @@ -16,6 +17,7 @@ describe('LiveIntentId', function() { let imgStub; beforeEach(function() { + liveIntentIdSubmodule.setModuleMode('standard'); imgStub = sinon.stub(utils, 'triggerPixel'); getCookieStub = sinon.stub(storage, 'getCookie'); getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); diff --git a/webpack.conf.js b/webpack.conf.js index a5c75fa8a1a6..6f0841757b08 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -13,7 +13,8 @@ var neverBundle = [ ]; var plugins = [ - new RequireEnsureWithoutJsonp() + new RequireEnsureWithoutJsonp(), + new webpack.EnvironmentPlugin(['LiveConnectMode']) ]; if (argv.analyze) {