diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 9d170254084..87e6c08bb6b 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -188,7 +188,7 @@ partner: 173 //Set your real ID5 partner ID here for production, please ask for one at http://id5.io/prebid }, storage: { - type: "cookie", + type: "html5", name: "id5id", expires: 90, refreshInSeconds: 8*3600 // Refresh frequency of cookies, defaulting to 'expires' diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 201fa6ce812..8a8cd25479f 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -19,10 +19,11 @@ export const ID5_STORAGE_NAME = 'id5id'; export const ID5_PRIVACY_STORAGE_NAME = `${ID5_STORAGE_NAME}_privacy`; const LOCAL_STORAGE = 'html5'; const ABTEST_RESOLUTION = 10000; +const LOG_PREFIX = 'User ID - ID5 submodule: '; // order the legacy cookie names in reverse priority order so the last // cookie in the array is the most preferred to use -const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st' ]; +const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st', 'id5id' ]; const storage = getStorageManager(GVLID, MODULE_NAME); @@ -63,15 +64,15 @@ export const id5IdSubmodule = { const controlGroup = isInControlGroup(universalUid, abConfig.controlGroupPct); if (abConfig.enabled === true && typeof controlGroup === 'undefined') { // A/B Testing is enabled, but configured improperly, so skip A/B testing - utils.logError('User ID - ID5 submodule: A/B Testing controlGroupPct must be a number >= 0 and <= 1! Skipping A/B Testing'); + utils.logError(LOG_PREFIX + 'A/B Testing controlGroupPct must be a number >= 0 and <= 1! Skipping A/B Testing'); } else if (abConfig.enabled === true && controlGroup === true) { // A/B Testing is enabled and user is in the Control Group, so do not share the ID5 ID - utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - user is in the Control Group, so the ID5 ID is NOT exposed'); + utils.logInfo(LOG_PREFIX + 'A/B Testing Enabled - user is in the Control Group, so the ID5 ID is NOT exposed'); universalUid = ''; linkType = 0; } else if (abConfig.enabled === true) { // A/B Testing is enabled but user is not in the Control Group, so ID5 ID is shared - utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - user is NOT in the Control Group, so the ID5 ID is exposed'); + utils.logInfo(LOG_PREFIX + 'A/B Testing Enabled - user is NOT in the Control Group, so the ID5 ID is exposed'); } let responseObj = { @@ -87,6 +88,8 @@ export const id5IdSubmodule = { utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', (typeof controlGroup === 'undefined' ? false : controlGroup)); } + utils.logInfo(LOG_PREFIX + 'Decoded ID', responseObj); + return responseObj; }, @@ -105,24 +108,38 @@ export const id5IdSubmodule = { const url = `https://id5-sync.com/g/v2/${config.params.partner}.json`; const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; + const usp = uspDataHandler.getConsentData(); const referer = getRefererInfo(); const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : getLegacyCookieSignature(); const data = { - 'gdpr': hasGdpr, - 'gdpr_consent': hasGdpr ? consentData.consentString : '', 'partner': config.params.partner, + 'gdpr': hasGdpr, 'nbPage': incrementNb(config.params.partner), 'o': 'pbjs', - 'pd': config.params.pd || '', - 'provider': config.params.provider || '', 'rf': referer.referer, - 's': signature, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, - 'us_privacy': uspDataHandler.getConsentData() || '', 'v': '$prebid.version$' }; + // pass in optional data, but only if populated + if (hasGdpr && typeof consentData.consentString !== 'undefined' && !utils.isEmpty(consentData.consentString) && !utils.isEmptyStr(consentData.consentString)) { + data.gdpr_consent = consentData.consentString; + } + if (typeof usp !== 'undefined' && !utils.isEmpty(usp) && !utils.isEmptyStr(usp)) { + data.us_privacy = usp; + } + if (typeof signature !== 'undefined' && !utils.isEmptyStr(signature)) { + data.s = signature; + } + if (typeof config.params.pd !== 'undefined' && !utils.isEmptyStr(config.params.pd)) { + data.pd = config.params.pd; + } + if (typeof config.params.provider !== 'undefined' && !utils.isEmptyStr(config.params.provider)) { + data.provider = config.params.provider; + } + + // pass in feature flags, if applicable if (getAbTestingConfig(config).enabled === true) { utils.deepSetValue(data, 'features.ab', 1); } @@ -134,7 +151,10 @@ export const id5IdSubmodule = { if (response) { try { responseObj = JSON.parse(response); + utils.logInfo(LOG_PREFIX + 'response received from the server', responseObj); + resetNb(config.params.partner); + if (responseObj.privacy) { storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(responseObj.privacy), NB_EXP_DAYS); } @@ -145,16 +165,17 @@ export const id5IdSubmodule = { removeLegacyCookies(config.params.partner); } } catch (error) { - utils.logError(error); + utils.logError(LOG_PREFIX + error); } } callback(responseObj); }, error: error => { - utils.logError(`User ID - ID5 submodule getId fetch encountered an error`, error); + utils.logError(LOG_PREFIX + 'getId fetch encountered an error', error); callback(); } }; + utils.logInfo(LOG_PREFIX + 'requesting an ID from the server', data); ajax(url, callbacks, JSON.stringify(data), { method: 'POST', withCredentials: true }); }; return {callback: resp}; @@ -172,30 +193,34 @@ export const id5IdSubmodule = { * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ extendId(config, consentData, cacheIdObj) { + hasRequiredConfig(config); + const partnerId = (config && config.params && config.params.partner) || 0; incrementNb(partnerId); + + utils.logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj); return cacheIdObj; } }; function hasRequiredConfig(config) { if (!config || !config.params || !config.params.partner || typeof config.params.partner !== 'number') { - utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); + utils.logError(LOG_PREFIX + 'partner required to be defined as a number'); return false; } if (!config.storage || !config.storage.type || !config.storage.name) { - utils.logError(`User ID - ID5 submodule requires storage to be set`); + utils.logError(LOG_PREFIX + 'storage required to be set'); return false; } - // TODO: in a future release, return false if storage type or name are not set as required + // in a future release, we may return false if storage type or name are not set as required if (config.storage.type !== LOCAL_STORAGE) { - utils.logWarn(`User ID - ID5 submodule recommends storage type to be '${LOCAL_STORAGE}'. In a future release this will become a strict requirement`); + utils.logWarn(LOG_PREFIX + `storage type recommended to be '${LOCAL_STORAGE}'. In a future release this may become a strict requirement`); } - // TODO: in a future release, return false if storage type or name are not set as required + // in a future release, we may return false if storage type or name are not set as required if (config.storage.name !== ID5_STORAGE_NAME) { - utils.logWarn(`User ID - ID5 submodule recommends storage name to be '${ID5_STORAGE_NAME}'. In a future release this will become a strict requirement`); + utils.logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'. In a future release this may become a strict requirement`); } return true; @@ -240,11 +265,12 @@ function getLegacyCookieSignature() { * @param {integer} partnerId */ function removeLegacyCookies(partnerId) { + utils.logInfo(LOG_PREFIX + 'removing legacy cookies'); LEGACY_COOKIE_NAMES.forEach(function(cookie) { - storage.setCookie(`${cookie}`, '', expDaysStr(-1)); - storage.setCookie(`${cookie}_nb`, '', expDaysStr(-1)); - storage.setCookie(`${cookie}_${partnerId}_nb`, '', expDaysStr(-1)); - storage.setCookie(`${cookie}_last`, '', expDaysStr(-1)); + storage.setCookie(`${cookie}`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_nb`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_${partnerId}_nb`, ' ', expDaysStr(-1)); + storage.setCookie(`${cookie}_last`, ' ', expDaysStr(-1)); }); } diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index aa09454d978..9ba9aee9c63 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -144,26 +144,26 @@ describe('ID5 ID System', function() { expect(request.withCredentials).to.be.true; expect(requestBody.partner).to.eq(ID5_TEST_PARTNER_ID); expect(requestBody.o).to.eq('pbjs'); - expect(requestBody.pd).to.eq(''); - expect(requestBody.s).to.eq(''); - expect(requestBody.provider).to.eq(''); + expect(requestBody.pd).to.be.undefined; + expect(requestBody.s).to.be.undefined; + expect(requestBody.provider).to.be.undefined expect(requestBody.v).to.eq('$prebid.version$'); expect(requestBody.gdpr).to.exist; - expect(requestBody.gdpr_consent).to.exist - expect(requestBody.us_privacy).to.exist; + expect(requestBody.gdpr_consent).to.be.undefined; + expect(requestBody.us_privacy).to.be.undefined; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); expect(callbackSpy.calledOnce).to.be.true; expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with empty signature field when no stored object', function () { + it('should call the ID5 server with no signature field when no stored object', function () { let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, undefined).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(requestBody.s).to.eq(''); + expect(requestBody.s).to.be.undefined; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); }); @@ -195,7 +195,7 @@ describe('ID5 ID System', function() { request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); }); - it('should call the ID5 server with empty pd field when pd config is not set', function () { + it('should call the ID5 server with no pd field when pd config is not set', function () { let id5Config = getId5FetchConfig(); id5Config.params.pd = undefined; @@ -204,7 +204,7 @@ describe('ID5 ID System', function() { let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(requestBody.pd).to.eq(''); + expect(requestBody.pd).to.be.undefined; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); }); @@ -505,7 +505,6 @@ describe('ID5 ID System', function() { let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); sinon.assert.notCalled(logErrorSpy); - sinon.assert.notCalled(logInfoSpy); }); }); @@ -523,7 +522,6 @@ describe('ID5 ID System', function() { testConfig.params.abTesting = testAbTestingConfig; id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); sinon.assert.notCalled(logErrorSpy); - sinon.assert.calledOnce(logInfoSpy); }); }); });