diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 68cbb3b2412..724bf5ece6a 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -14,6 +14,7 @@ import { MinimalLiveConnect } from 'live-connect-js/esm/minimal-live-connect.js' const MODULE_NAME = 'liveIntentId'; export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +const defaultRequestedAttributes = {'nonId': true} const calls = { ajaxGet: (url, onSuccess, onError, timeout) => { ajaxBuilder(timeout)( @@ -57,6 +58,23 @@ function parseLiveIntentCollectorConfig(collectConfig) { return config; } +/** + * Create requestedAttributes array to pass to liveconnect + * @function + * @param {Object} overrides - object with boolean values that will override defaults { 'foo': true, 'bar': false } + * @returns {Array} + */ +function parseRequestedAttributes(overrides) { + function createParameterArray(config) { + return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [k] : []); + } + if (typeof overrides === 'object') { + return createParameterArray({...defaultRequestedAttributes, ...overrides}) + } else { + return createParameterArray(defaultRequestedAttributes); + } +} + function initializeLiveConnect(configParams) { configParams = configParams || {}; if (liveConnect) { @@ -66,7 +84,8 @@ function initializeLiveConnect(configParams) { const publisherId = configParams.publisherId || 'any'; const identityResolutionConfig = { source: 'prebid', - publisherId: publisherId + publisherId: publisherId, + requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides) }; if (configParams.url) { identityResolutionConfig.url = configParams.url @@ -136,9 +155,24 @@ export const liveIntentIdSubmodule = { decode(value, config) { const configParams = (config && config.params) || {}; function composeIdObject(value) { - const base = { 'lipbid': value.unifiedId }; - delete value.unifiedId; - return { 'lipb': { ...base, ...value } }; + const result = {}; + + // old versions stored lipbid in unifiedId. Ensure that we can still read the data. + const lipbid = value.nonId || value.unifiedId + if (lipbid) { + value.lipbid = lipbid + delete value.unifiedId + result.lipb = value + } + + // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. + // As adapters are applied in lexicographical order, we will always + // be overwritten by the 'proper' uid2 module if it is present. + if (value.uid2) { + result.uid2 = { 'id': value.uid2 } + } + + return result } if (!liveConnect) { @@ -146,7 +180,7 @@ export const liveIntentIdSubmodule = { } tryFireEvent(); - return (value && typeof value['unifiedId'] === 'string') ? composeIdObject(value) : undefined; + return composeIdObject(value); }, /** diff --git a/package-lock.json b/package-lock.json index 1665b80db6e..acdcfafc1cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.15.0-pre", + "version": "7.16.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "2.3.3" + "live-connect-js": "2.4.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -14416,9 +14416,9 @@ "dev": true }, "node_modules/live-connect-js": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.3.3.tgz", - "integrity": "sha512-WfY6v1jVutW/OGPvm0OaWAHc0MvB5MDdSuniZ+n9cpCltArWHTJtAsjWe8T+ACmdLAlZS9z7hzL3ntLnq+J0yQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.4.0.tgz", + "integrity": "sha512-MSBLKfnXoxH+pqwji/Mf8yZu3VZMq4tnNfwMw7NTWN5a+TBM6f0RWgwui1YMA3nHmMhX/nzxxsso0SkyKcF0fA==", "dependencies": { "tiny-hashes": "1.0.1" }, @@ -33706,9 +33706,9 @@ "dev": true }, "live-connect-js": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.3.3.tgz", - "integrity": "sha512-WfY6v1jVutW/OGPvm0OaWAHc0MvB5MDdSuniZ+n9cpCltArWHTJtAsjWe8T+ACmdLAlZS9z7hzL3ntLnq+J0yQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.4.0.tgz", + "integrity": "sha512-MSBLKfnXoxH+pqwji/Mf8yZu3VZMq4tnNfwMw7NTWN5a+TBM6f0RWgwui1YMA3nHmMhX/nzxxsso0SkyKcF0fA==", "requires": { "tiny-hashes": "1.0.1" } diff --git a/package.json b/package.json index f91ad0bab9e..dd31448fef9 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "2.3.3" + "live-connect-js": "2.4.0" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index b0f97ae0300..6a5afa58da2 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -47,7 +47,7 @@ describe('LiveIntentMinimalId', function() { 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; + expect(result).to.be.eql({}); }); it('should initialize LiveConnect and send no data', function() { @@ -64,7 +64,7 @@ describe('LiveIntentMinimalId', function() { 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'); + expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?resolve=nonId'); request.respond( 200, responseHeader, @@ -85,7 +85,7 @@ describe('LiveIntentMinimalId', function() { } }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899'); + expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?resolve=nonId'); request.respond( 200, responseHeader, @@ -100,7 +100,7 @@ describe('LiveIntentMinimalId', function() { 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'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 200, responseHeader, @@ -115,7 +115,7 @@ describe('LiveIntentMinimalId', function() { 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'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 503, responseHeader, @@ -132,7 +132,7 @@ describe('LiveIntentMinimalId', function() { 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}`); + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&resolve=nonId`); request.respond( 200, responseHeader, @@ -155,7 +155,7 @@ describe('LiveIntentMinimalId', function() { 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`); + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc&resolve=nonId`); request.respond( 200, responseHeader, @@ -177,7 +177,61 @@ describe('LiveIntentMinimalId', function() { 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'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should decode a unifiedId to lipbId and remove it', function() { + const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }); + expect(result).to.eql({'lipb': {'lipbid': 'data'}}); + }); + + it('should decode a nonId to lipbId', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'data' }); + expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); + }); + + it('should resolve extra attributes', function() { + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } + } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=nonId&resolve=foo`); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should decode a uid2 to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar'}}); + }); + + it('should decode values with uid2 but no nonId', function() { + const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); + expect(result).to.eql({'uid2': {'id': 'bar'}}); + }); + + it('should allow disabling nonId resolution', function() { + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } + } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=uid2`); request.respond( 200, responseHeader, diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 1ea7b5be4ad..3c22cda1154 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -111,7 +111,7 @@ describe('LiveIntentId', function() { 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; + expect(result).to.be.eql({}); }); it('should fire an event when decode', function() { @@ -133,7 +133,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); let request = server.requests[1]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); + expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?resolve=nonId'); request.respond( 204, responseHeader @@ -153,7 +153,7 @@ describe('LiveIntentId', function() { } }).callback; submoduleCallback(callBackSpy); let request = server.requests[1]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899'); + expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?resolve=nonId'); request.respond( 200, responseHeader, @@ -168,7 +168,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[1]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 200, responseHeader, @@ -183,7 +183,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[1]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 503, responseHeader, @@ -200,7 +200,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[1]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}`); + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&resolve=nonId`); request.respond( 200, responseHeader, @@ -223,7 +223,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); let request = server.requests[1]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc`); + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc&resolve=nonId`); request.respond( 200, responseHeader, @@ -245,7 +245,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); let request = server.requests[1]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); request.respond( 200, responseHeader, @@ -259,4 +259,58 @@ describe('LiveIntentId', function() { liveIntentIdSubmodule.getId(defaultConfigParams); expect(imgStub.getCall(0).args[0]).to.match(/.*ae=.+/); }); + + it('should decode a unifiedId to lipbId and remove it', function() { + const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }); + expect(result).to.eql({'lipb': {'lipbid': 'data'}}); + }); + + it('should decode a nonId to lipbId', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'data' }); + expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); + }); + + it('should resolve extra attributes', function() { + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } + } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[1]; + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=nonId&resolve=foo`); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should decode a uid2 to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar'}}); + }); + + it('should decode values with uid2 but no nonId', function() { + const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); + expect(result).to.eql({'uid2': {'id': 'bar'}}); + }); + + it('should allow disabling nonId resolution', function() { + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } + } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[1]; + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=uid2`); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); });