diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 53c58e8e87e..b416367efd9 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -140,7 +140,18 @@ name: "unifiedid", expires: 30 }, - }, { + },{ + name: "intentIqId", + params: { + partner: 0, //Set your real IntentIQ partner ID here for production. + }, + storage: { + type: "cookie", + name: "intentIqId", + expires: 30 + }, + }, + { name: "id5Id", params: { partner: 173 //Set your real ID5 partner ID here for production, please ask for one at http://id5.io/prebid diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js new file mode 100644 index 00000000000..7d497ea9b1a --- /dev/null +++ b/modules/intentIqIdSystem.js @@ -0,0 +1,68 @@ +/** + * This module adds IntentIqId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/intentIqIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js' + +const MODULE_NAME = 'intentIqId'; + +/** @type {Submodule} */ +export const intentIqIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{ctrid:string}} value + * @returns {{intentIqId:string}} + */ + decode(value) { + return (value && typeof value['ctrid'] === 'string') ? { 'intentIqId': value['ctrid'] } : undefined; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [configParams] + * @returns {IdResponse|undefined} + */ + getId(configParams) { + if (!configParams || typeof configParams.partner !== 'number') { + utils.logError('User ID - intentIqId submodule requires a valid partner to be defined'); + return; + } + + // use protocol relative urls for http or https + const url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; + const resp = function (callback) { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + } + } + callback(responseObj); + }, + error: error => { + utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + } +}; + +submodule('userId', intentIqIdSubmodule); diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 842737183a8..d78fe1e4c28 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -5,6 +5,12 @@ const USER_IDS_CONFIG = { // key-name : {config} + // intentIqId + 'intentIqId': { + source: 'intentiq.com', + atype: 1 + }, + // pubCommonId 'pubcid': { source: 'pubcid.org', diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 8ce81ea85b0..0ef678d31b7 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -20,6 +20,7 @@ import {id5IdSubmodule} from 'modules/id5IdSystem.js'; import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem.js'; import {netIdSubmodule} from 'modules/netIdSystem.js'; +import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {server} from 'test/mocks/xhr.js'; @@ -28,7 +29,7 @@ let expect = require('chai').expect; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; describe('User ID', function() { - function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5, configArr6, configArr7) { + function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5, configArr6, configArr7, configArr8) { return { userSync: { syncDelay: 0, @@ -39,7 +40,8 @@ describe('User ID', function() { (configArr4 && configArr4.length >= 3) ? getStorageMock.apply(null, configArr4) : null, (configArr5 && configArr5.length >= 3) ? getStorageMock.apply(null, configArr5) : null, (configArr6 && configArr6.length >= 3) ? getStorageMock.apply(null, configArr6) : null, - (configArr7 && configArr7.length >= 3) ? getStorageMock.apply(null, configArr7) : null + (configArr7 && configArr7.length >= 3) ? getStorageMock.apply(null, configArr7) : null, + (configArr8 && configArr8.length >= 3) ? getStorageMock.apply(null, configArr8) : null ].filter(i => i) } } @@ -338,22 +340,22 @@ describe('User ID', function() { }); it('handles config with no usersync object', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('handles config with empty usersync object', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); + it('handles config with empty usersync object', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('handles config with usersync and userIds that are empty objs', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); + it('handles config with usersync and userIds that are empty objs', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -363,8 +365,8 @@ describe('User ID', function() { expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); + it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -380,16 +382,16 @@ describe('User ID', function() { expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('config with 1 configurations should create 1 submodules', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); + it('config with 1 configurations should create 1 submodules', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); }); - it('config with 8 configurations should result in 8 submodules add', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule]); + it('config with 9 configurations should result in 9 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -417,14 +419,17 @@ describe('User ID', function() { }, { name: 'sharedId', storage: {name: 'sharedid', type: 'cookie'} + }, { + name: 'intentIqId', + storage: { name: 'intentIqId', type: 'cookie' } }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 8 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 9 submodules'); }); - it('config syncDelay updates module correctly', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); + it('config syncDelay updates module correctly', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -438,8 +443,8 @@ describe('User ID', function() { expect(syncDelay).to.equal(99); }); - it('config auctionDelay updates module correctly', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); + it('config auctionDelay updates module correctly', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -453,8 +458,8 @@ describe('User ID', function() { expect(auctionDelay).to.equal(100); }); - it('config auctionDelay defaults to 0 if not a number', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule]); + it('config auctionDelay defaults to 0 if not a number', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -1123,16 +1128,41 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, netId and sharedId have data to pass', function(done) { + it('test hook from intentIqId cookies', function(done) { + // simulate existing browser local storage values + coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'abcdefghijk'}), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([intentIqIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['intentIqId', 'intentIqId', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.intentIqId'); + expect(bid.userId.intentIqId).to.equal('abcdefghijk'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'intentiq.com', + uids: [{id: 'abcdefghijk', atype: 1}] + }); + }); + }); + coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, sharedId and netId have data to pass', function(done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1140,7 +1170,8 @@ describe('User ID', function() { ['identityLink', 'idl_env', 'cookie'], ['britepoolId', 'britepoolid', 'cookie'], ['netId', 'netId', 'cookie'], - ['sharedId', 'sharedid', 'cookie'])); + ['sharedId', 'sharedid', 'cookie'], + ['intentIqId', 'intentIqId', 'cookie'])); requestBidsHook(function() { adUnits.forEach(unit => { @@ -1168,7 +1199,10 @@ describe('User ID', function() { id: 'test_sharedId', third: 'test_sharedId' }); - expect(bid.userIdAsEids.length).to.equal(7); + // also check that intentIqId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.intentIqId'); + expect(bid.userId.intentIqId).to.equal('testintentIqId'); + expect(bid.userIdAsEids.length).to.equal(8); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1178,29 +1212,12 @@ describe('User ID', function() { coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when sharedId (opted out) have data to pass', function(done) { - coreStorage.setCookie('sharedid', JSON.stringify({'id': '00000000000000000000000000', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); - - setSubmoduleRegistry([sharedIdSubmodule]); - init(config); - config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); - - requestBidsHook(function() { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid.userIdAsEids).to.be.undefined; - }); - }); - coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, netId and sharedId have their modules added before and after init', function(done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, sharedId and netId have their modules added before and after init', function(done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1208,6 +1225,7 @@ describe('User ID', function() { coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -1223,6 +1241,7 @@ describe('User ID', function() { attachIdSystem(britepoolIdSubmodule); attachIdSystem(netIdSubmodule); attachIdSystem(sharedIdSubmodule); + attachIdSystem(intentIqIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1230,7 +1249,8 @@ describe('User ID', function() { ['identityLink', 'idl_env', 'cookie'], ['britepoolId', 'britepoolid', 'cookie'], ['netId', 'netId', 'cookie'], - ['sharedId', 'sharedid', 'cookie'])); + ['sharedId', 'sharedid', 'cookie'], + ['intentIqId', 'intentIqId', 'cookie'])); requestBidsHook(function() { adUnits.forEach(unit => { @@ -1258,7 +1278,10 @@ describe('User ID', function() { id: 'test_sharedId', third: 'test_sharedId' }); - expect(bid.userIdAsEids.length).to.equal(7); + // also check that intentIqId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.intentIqId'); + expect(bid.userId.intentIqId).to.equal('testintentIqId'); + expect(bid.userIdAsEids.length).to.equal(8); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1268,6 +1291,7 @@ describe('User ID', function() { coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -1301,9 +1325,10 @@ describe('User ID', function() { coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); init(config); config.setConfig({ @@ -1323,6 +1348,8 @@ describe('User ID', function() { name: 'netId', storage: {name: 'netId', type: 'cookie'} }, { name: 'sharedId', storage: {name: 'sharedid', type: 'cookie'} + }, { + name: 'intentIqId', storage: { name: 'intentIqId', type: 'cookie' } }, { name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] @@ -1373,7 +1400,10 @@ describe('User ID', function() { // check MockId data was copied to bid expect(bid).to.have.deep.nested.property('userId.mid'); expect(bid.userId.mid).to.equal('123456778'); - expect(bid.userIdAsEids.length).to.equal(7);// mid is unknown for eids.js + // also check that intentIqId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.intentIqId'); + expect(bid.userId.intentIqId).to.equal('testintentIqId'); + expect(bid.userIdAsEids.length).to.equal(8); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1383,6 +1413,7 @@ describe('User ID', function() { coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits});