From 720f200faf522ffa4a53b75061a0337d16d1a7f2 Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Thu, 17 Sep 2020 12:00:01 +0100 Subject: [PATCH 01/11] Initial work on Verizon Media User ID module --- modules/.submodules.json | 3 +- modules/userId/eids.js | 6 +++ modules/verizonMediaIdSystem.js | 79 +++++++++++++++++++++++++++++++++ modules/verizonMediaSystemId.md | 36 +++++++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 modules/verizonMediaIdSystem.js create mode 100644 modules/verizonMediaSystemId.md diff --git a/modules/.submodules.json b/modules/.submodules.json index 58ab8798fa5..0ca4043606a 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -10,7 +10,8 @@ "criteoIdSystem", "netIdSystem", "identityLinkIdSystem", - "sharedIdSystem" + "sharedIdSystem", + "verizonMediaIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 72be98eca50..8ad4e1c688f 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -117,6 +117,12 @@ const USER_IDS_CONFIG = { third: data.third } : undefined; } + }, + + // Verizon Media + 'vmuid': { + source: 'verizonmedia.com', + atype: 1 } }; diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js new file mode 100644 index 00000000000..2f28ec96a81 --- /dev/null +++ b/modules/verizonMediaIdSystem.js @@ -0,0 +1,79 @@ +/** + * This module adds verizonMediaId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/verizonMediaIdSystem + * @requires module:modules/userId + */ + +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; + +const MODULE_NAME = 'verizonMedia'; +const VMUID_ENDPOINT = 'https://ups.analytics.yahoo.com/ups/58300/fed'; + +function isEUConsentRequired(consentData) { + return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); +} + +/** @type {Submodule} */ +export const verizonMediaIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{vmuid: string} | undefined} + */ + decode(value) { + return (value && typeof value.vmuid === 'string') ? {vmuid: value.vmuid} : undefined; + }, + /** + * get the VerizonMedia Id + * @function + * @param {SubmoduleParams} [configParams] + * @param {ConsentData} [consentData] + * @returns {IdResponse|undefined} + */ + getId(configParams, consentData) { + if (!configParams || typeof configParams.he !== 'string') { + utils.logError('The verizonMediaId submodule requires the \'he\' parameter to be defined.'); + return; + } + + const data = { + '1p': configParams['1p'] ? configParams['1p'] : '0', + he: configParams.he, + gdpr: isEUConsentRequired(consentData) ? '1' : '0', + euconsent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', + us_privacy: consentData.uspConsent + }; + + 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(configParams.endpoint || VMUID_ENDPOINT, callbacks, JSON.stringify(data), {method: 'POST', withCredentials: true}); + }; + return {callback: resp}; + } +}; + +submodule('userId', verizonMediaIdSubmodule); diff --git a/modules/verizonMediaSystemId.md b/modules/verizonMediaSystemId.md new file mode 100644 index 00000000000..fc0fe4d0317 --- /dev/null +++ b/modules/verizonMediaSystemId.md @@ -0,0 +1,36 @@ +## Verizon Media User ID Submodule + +Verizon Media User ID Module. For assistance setting up your module please contact us at [prebid@britepool.com](prebid@britepool.com). + +### Prebid Params + +At least one identifier must be set in the params object. +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'verizonMedia', + storage: { + name: 'vmuid', + type: 'cookie', + expires: 30 + }, + params: { + he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a' + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Verizon Media User ID Module integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Verizon Media module - `"verizonMedia"` | `"verizonMedia"` | +| params | Required | Object | Data for Verizon Media ID initialization. | | +| params.he | Required | String | The SHA-256 hashed user email address | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | +| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"verizonmediaid"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | From 55f45c10dadbce5844db9f340135db3d96e1b704 Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Fri, 18 Sep 2020 09:16:20 +0100 Subject: [PATCH 02/11] Submodule tests --- modules/verizonMediaIdSystem.js | 15 +- .../spec/modules/verizonMediaIdSystem_spec.js | 146 ++++++++++++++++++ 2 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 test/spec/modules/verizonMediaIdSystem_spec.js diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index 2f28ec96a81..aaa01b425ff 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -45,11 +45,11 @@ export const verizonMediaIdSubmodule = { } const data = { - '1p': configParams['1p'] ? configParams['1p'] : '0', + '1p': [1, '1', true].includes(configParams['1p']) ? '1' : '0', he: configParams.he, gdpr: isEUConsentRequired(consentData) ? '1' : '0', euconsent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', - us_privacy: consentData.uspConsent + us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' }; const resp = function (callback) { @@ -70,9 +70,18 @@ export const verizonMediaIdSubmodule = { callback(); } }; - ajax(configParams.endpoint || VMUID_ENDPOINT, callbacks, JSON.stringify(data), {method: 'POST', withCredentials: true}); + verizonMediaIdSubmodule.getAjaxFn()(configParams.endpoint || VMUID_ENDPOINT, callbacks, JSON.stringify(data), {method: 'POST', withCredentials: true}); }; return {callback: resp}; + }, + + /** + * Return the function used to perform XHR calls. + * Utilised for each of testing. + * @returns {Function} + */ + getAjaxFn() { + return ajax; } }; diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js new file mode 100644 index 00000000000..14d807b51e0 --- /dev/null +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -0,0 +1,146 @@ +import {expect} from 'chai'; + +import {verizonMediaIdSubmodule} from 'modules/verizonMediaIdSystem.js'; + +describe('Verizon Media ID Submodule', () => { + const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; + const API_ENDPOINT_OVERRIDE = 'https://test-override'; + const PROD_ENDPOINT = 'https://ups.analytics.yahoo.com/ups/58300/fed'; + const OVERRIDE_ENDPOINT = 'https://foo/bar'; + + it('should have the correct module name declared', () => { + expect(verizonMediaIdSubmodule.name).to.equal('verizonMedia'); + }); + + describe('getId()', () => { + let ajaxStub; + let getAjaxFnStub; + let consentData; + beforeEach(() => { + ajaxStub = sinon.stub(); + getAjaxFnStub = sinon.stub(verizonMediaIdSubmodule, 'getAjaxFn'); + getAjaxFnStub.returns(ajaxStub); + + consentData = { + gdpr: { + gdprApplies: 1, + consentString: 'GDPR_CONSENT_STRING' + }, + uspConsent: 'USP_CONSENT_STRING' + }; + }); + + afterEach(() => { + getAjaxFnStub.restore(); + }); + + function invokeGetIdAPI(configParams, consentData) { + let result = verizonMediaIdSubmodule.getId(configParams, consentData); + result.callback(sinon.stub()); + return result; + } + + it('returns undefined if the hashed email address is not passed', () => { + expect(verizonMediaIdSubmodule.getId({}, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + + it('returns an object with the callback function if the correct params are passed', () => { + let result = verizonMediaIdSubmodule.getId({ + he: HASHED_EMAIL + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('Makes an ajax POST request to the production API endpoint', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + }, consentData); + + expect(ajaxStub.firstCall.args[0]).to.equal(PROD_ENDPOINT); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'POST', withCredentials: true}); + }); + + it('Makes an ajax POST request to the specified override API endpoint', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + + expect(ajaxStub.firstCall.args[0]).to.equal(OVERRIDE_ENDPOINT); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'POST', withCredentials: true}); + }); + + it('sets the callbacks param of the ajax function call correctly', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL + }, consentData); + + expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); + }); + + it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL + }, consentData); + + const EXPECTED_PAYLOAD = { + '1p': '0', + he: HASHED_EMAIL, + gdpr: '1', + euconsent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent + }; + + expect(ajaxStub.firstCall.args[0]).to.equal(PROD_ENDPOINT); + expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); + expect(JSON.parse(ajaxStub.firstCall.args[2])).to.deep.equal(EXPECTED_PAYLOAD); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'POST', withCredentials: true}); + }); + + it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { + consentData.gdpr.gdprApplies = false; + + invokeGetIdAPI({ + he: HASHED_EMAIL + }, consentData); + + const EXPECTED_PAYLOAD = { + '1p': '0', + he: HASHED_EMAIL, + gdpr: '0', + euconsent: '', + us_privacy: consentData.uspConsent + }; + expect(JSON.parse(ajaxStub.firstCall.args[2])).to.deep.equal(EXPECTED_PAYLOAD); + }); + + [1, '1', true].forEach(firstPartyParamValue => { + it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { + invokeGetIdAPI({ + '1p': firstPartyParamValue, + he: HASHED_EMAIL + }, consentData); + + expect(JSON.parse(ajaxStub.firstCall.args[2])['1p']).to.equal('1'); + }); + }); + }); + + describe('decode()', () => { + const VALID_API_RESPONSE = { + vmuid: '1234' + }; + it('should return a newly constructed object with the vmuid property', () => { + expect(verizonMediaIdSubmodule.decode(VALID_API_RESPONSE)).to.deep.equal(VALID_API_RESPONSE); + expect(verizonMediaIdSubmodule.decode(VALID_API_RESPONSE)).to.not.equal(VALID_API_RESPONSE); + }); + + [{}, '', {foo: 'bar'}].forEach((response) => { + it(`should return undefined for an invalid response "${JSON.stringify(response)}"`, () => { + expect(verizonMediaIdSubmodule.decode(response)).to.be.undefined; + }); + }); + }); +}); From 0c1550243813af2cb5f31122885bf38329583b2d Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Wed, 23 Sep 2020 13:38:33 +0100 Subject: [PATCH 03/11] Add sample eid object for Verizon Media --- modules/userId/eids.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 03aec46cf48..fc0cfc02823 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -86,6 +86,7 @@ userIdAsEids = [ atype: 1 }] }, + { source: 'sharedid.org', uids: [{ @@ -96,6 +97,7 @@ userIdAsEids = [ } }] }, + { source: 'zeotap.com', uids: [{ @@ -103,6 +105,7 @@ userIdAsEids = [ atype: 1 }] }, + { source: 'audigent.com', uids: [{ @@ -110,12 +113,21 @@ userIdAsEids = [ atype: 1 }] }, + { source: 'quantcast.com', uids: [{ id: 'some-random-id-value', atype: 1 }] + }, + + { + source: 'verizonmedia.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] } ] ``` From b121c411a18b1032ac4a97f2124dbe67563761ef Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Wed, 23 Sep 2020 14:37:22 +0100 Subject: [PATCH 04/11] Documentation update --- modules/verizonMediaSystemId.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/verizonMediaSystemId.md b/modules/verizonMediaSystemId.md index fc0fe4d0317..0ad9bd09547 100644 --- a/modules/verizonMediaSystemId.md +++ b/modules/verizonMediaSystemId.md @@ -1,6 +1,6 @@ ## Verizon Media User ID Submodule -Verizon Media User ID Module. For assistance setting up your module please contact us at [prebid@britepool.com](prebid@britepool.com). +Verizon Media User ID Module. ### Prebid Params From 2bd06306371c8f15b7d910a4e27a8677cb635973 Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Wed, 23 Sep 2020 19:06:33 +0100 Subject: [PATCH 05/11] Switch to HTTP GET, update tests. --- modules/verizonMediaIdSystem.js | 3 +- .../spec/modules/verizonMediaIdSystem_spec.js | 66 ++++++++++--------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index aaa01b425ff..4cf58313866 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -70,7 +70,8 @@ export const verizonMediaIdSubmodule = { callback(); } }; - verizonMediaIdSubmodule.getAjaxFn()(configParams.endpoint || VMUID_ENDPOINT, callbacks, JSON.stringify(data), {method: 'POST', withCredentials: true}); + let url = `${configParams.endpoint || VMUID_ENDPOINT}?${utils.formatQS(data)}`; + verizonMediaIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true}); }; return {callback: resp}; }, diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index 14d807b51e0..93671088153 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -1,10 +1,9 @@ import {expect} from 'chai'; - +import * as utils from 'src/utils.js'; import {verizonMediaIdSubmodule} from 'modules/verizonMediaIdSystem.js'; -describe('Verizon Media ID Submodule', () => { +describe.only('Verizon Media ID Submodule', () => { const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; - const API_ENDPOINT_OVERRIDE = 'https://test-override'; const PROD_ENDPOINT = 'https://ups.analytics.yahoo.com/ups/58300/fed'; const OVERRIDE_ENDPOINT = 'https://foo/bar'; @@ -53,23 +52,43 @@ describe('Verizon Media ID Submodule', () => { expect(result.callback).to.be.a('function'); }); - it('Makes an ajax POST request to the production API endpoint', () => { + it('Makes an ajax GET request to the production API endpoint with query params', () => { invokeGetIdAPI({ he: HASHED_EMAIL, }, consentData); - expect(ajaxStub.firstCall.args[0]).to.equal(PROD_ENDPOINT); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'POST', withCredentials: true}); + const expectedParams = { + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + euconsent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent + }; + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(PROD_ENDPOINT)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); }); - it('Makes an ajax POST request to the specified override API endpoint', () => { + it('Makes an ajax GET request to the specified override API endpoint with query params', () => { invokeGetIdAPI({ he: HASHED_EMAIL, endpoint: OVERRIDE_ENDPOINT }, consentData); - expect(ajaxStub.firstCall.args[0]).to.equal(OVERRIDE_ENDPOINT); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'POST', withCredentials: true}); + const expectedParams = { + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + euconsent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent + }; + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(OVERRIDE_ENDPOINT)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); }); it('sets the callbacks param of the ajax function call correctly', () => { @@ -85,18 +104,9 @@ describe('Verizon Media ID Submodule', () => { he: HASHED_EMAIL }, consentData); - const EXPECTED_PAYLOAD = { - '1p': '0', - he: HASHED_EMAIL, - gdpr: '1', - euconsent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent - }; - - expect(ajaxStub.firstCall.args[0]).to.equal(PROD_ENDPOINT); - expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); - expect(JSON.parse(ajaxStub.firstCall.args[2])).to.deep.equal(EXPECTED_PAYLOAD); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'POST', withCredentials: true}); + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('1'); + expect(requestQueryParams.euconsent).to.equal(consentData.gdpr.consentString); }); it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { @@ -106,14 +116,9 @@ describe('Verizon Media ID Submodule', () => { he: HASHED_EMAIL }, consentData); - const EXPECTED_PAYLOAD = { - '1p': '0', - he: HASHED_EMAIL, - gdpr: '0', - euconsent: '', - us_privacy: consentData.uspConsent - }; - expect(JSON.parse(ajaxStub.firstCall.args[2])).to.deep.equal(EXPECTED_PAYLOAD); + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('0'); + expect(requestQueryParams.euconsent).to.equal(''); }); [1, '1', true].forEach(firstPartyParamValue => { @@ -123,7 +128,8 @@ describe('Verizon Media ID Submodule', () => { he: HASHED_EMAIL }, consentData); - expect(JSON.parse(ajaxStub.firstCall.args[2])['1p']).to.equal('1'); + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams['1p']).to.equal('1'); }); }); }); From 33e70b1062baaac4cefbef267f5688c6a3c17735 Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Wed, 23 Sep 2020 19:10:39 +0100 Subject: [PATCH 06/11] Remove single test restriction. --- test/spec/modules/verizonMediaIdSystem_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index 93671088153..a61eb0bfd06 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -2,7 +2,7 @@ import {expect} from 'chai'; import * as utils from 'src/utils.js'; import {verizonMediaIdSubmodule} from 'modules/verizonMediaIdSystem.js'; -describe.only('Verizon Media ID Submodule', () => { +describe('Verizon Media ID Submodule', () => { const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; const PROD_ENDPOINT = 'https://ups.analytics.yahoo.com/ups/58300/fed'; const OVERRIDE_ENDPOINT = 'https://foo/bar'; From 2754e4b91640ce551b350cd59528465b9427349f Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Thu, 24 Sep 2020 15:35:50 +0100 Subject: [PATCH 07/11] Documentation update --- modules/verizonMediaSystemId.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/verizonMediaSystemId.md b/modules/verizonMediaSystemId.md index 0ad9bd09547..dbd825a5075 100644 --- a/modules/verizonMediaSystemId.md +++ b/modules/verizonMediaSystemId.md @@ -4,7 +4,6 @@ Verizon Media User ID Module. ### Prebid Params -At least one identifier must be set in the params object. ``` pbjs.setConfig({ userSync: { @@ -29,8 +28,8 @@ The below parameters apply only to the Verizon Media User ID Module integration. | --- | --- | --- | --- | --- | | name | Required | String | ID value for the Verizon Media module - `"verizonMedia"` | `"verizonMedia"` | | params | Required | Object | Data for Verizon Media ID initialization. | | -| params.he | Required | String | The SHA-256 hashed user email address | +| params.he | Required | String | The SHA-256 hashed user email address | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | | storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | | storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | -| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"verizonmediaid"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"vmuid"` | | storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | From 3c2d709152284b9ea2c4f918ee634a6fbfc14ff1 Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Sun, 27 Sep 2020 13:14:26 +0100 Subject: [PATCH 08/11] Addressing initial PR feedback. --- modules/verizonMediaIdSystem.js | 2 +- modules/verizonMediaSystemId.md | 10 +++------- test/spec/modules/verizonMediaIdSystem_spec.js | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index 4cf58313866..9e98f924e66 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -9,7 +9,7 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import * as utils from '../src/utils.js'; -const MODULE_NAME = 'verizonMedia'; +const MODULE_NAME = 'verizonMediaId'; const VMUID_ENDPOINT = 'https://ups.analytics.yahoo.com/ups/58300/fed'; function isEUConsentRequired(consentData) { diff --git a/modules/verizonMediaSystemId.md b/modules/verizonMediaSystemId.md index dbd825a5075..8b00684f8cf 100644 --- a/modules/verizonMediaSystemId.md +++ b/modules/verizonMediaSystemId.md @@ -8,10 +8,10 @@ Verizon Media User ID Module. pbjs.setConfig({ userSync: { userIds: [{ - name: 'verizonMedia', + name: 'verizonMediaId', storage: { name: 'vmuid', - type: 'cookie', + type: 'html5', expires: 30 }, params: { @@ -26,10 +26,6 @@ The below parameters apply only to the Verizon Media User ID Module integration. | Param under usersync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | -| name | Required | String | ID value for the Verizon Media module - `"verizonMedia"` | `"verizonMedia"` | +| name | Required | String | ID value for the Verizon Media module - `"verizonMediaId"` | `"verizonMediaId"` | | params | Required | Object | Data for Verizon Media ID initialization. | | | params.he | Required | String | The SHA-256 hashed user email address | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | -| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | -| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | -| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"vmuid"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index a61eb0bfd06..bfefaf5a68e 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -8,7 +8,7 @@ describe('Verizon Media ID Submodule', () => { const OVERRIDE_ENDPOINT = 'https://foo/bar'; it('should have the correct module name declared', () => { - expect(verizonMediaIdSubmodule.name).to.equal('verizonMedia'); + expect(verizonMediaIdSubmodule.name).to.equal('verizonMediaId'); }); describe('getId()', () => { From 4775c7b338f742e996bd17d71f54d0769f17bc69 Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Sun, 18 Oct 2020 21:26:23 +0100 Subject: [PATCH 09/11] Accept pixelId parameter to construct VMUID URL --- modules/verizonMediaIdSystem.js | 15 +++++-- modules/verizonMediaSystemId.md | 2 + .../spec/modules/verizonMediaIdSystem_spec.js | 40 ++++++++++++++----- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index 9e98f924e66..0ec0b0cd598 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -10,7 +10,8 @@ import {submodule} from '../src/hook.js'; import * as utils from '../src/utils.js'; const MODULE_NAME = 'verizonMediaId'; -const VMUID_ENDPOINT = 'https://ups.analytics.yahoo.com/ups/58300/fed'; +const PLACEHOLDER = '__PIXEL_ID__'; +const VMUID_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; function isEUConsentRequired(consentData) { return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); @@ -39,8 +40,9 @@ export const verizonMediaIdSubmodule = { * @returns {IdResponse|undefined} */ getId(configParams, consentData) { - if (!configParams || typeof configParams.he !== 'string') { - utils.logError('The verizonMediaId submodule requires the \'he\' parameter to be defined.'); + if (!configParams || typeof configParams.he !== 'string' || + (typeof configParams.pixelId === 'undefined' && typeof configParams.endpoint === 'undefined')) { + utils.logError('The verizonMediaId submodule requires the \'he\' and \'pixelId\' parameters to be defined.'); return; } @@ -52,6 +54,10 @@ export const verizonMediaIdSubmodule = { us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' }; + if (configParams.pixelId) { + data.pixelId = configParams.pixelId + } + const resp = function (callback) { const callbacks = { success: response => { @@ -70,7 +76,8 @@ export const verizonMediaIdSubmodule = { callback(); } }; - let url = `${configParams.endpoint || VMUID_ENDPOINT}?${utils.formatQS(data)}`; + const endpoint = VMUID_ENDPOINT.replace(PLACEHOLDER, configParams.pixelId); + let url = `${configParams.endpoint || endpoint}?${utils.formatQS(data)}`; verizonMediaIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true}); }; return {callback: resp}; diff --git a/modules/verizonMediaSystemId.md b/modules/verizonMediaSystemId.md index 8b00684f8cf..8d0e0bddaa9 100644 --- a/modules/verizonMediaSystemId.md +++ b/modules/verizonMediaSystemId.md @@ -15,6 +15,7 @@ pbjs.setConfig({ expires: 30 }, params: { + pixelId: 58776, he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a' } }] @@ -28,4 +29,5 @@ The below parameters apply only to the Verizon Media User ID Module integration. | --- | --- | --- | --- | --- | | name | Required | String | ID value for the Verizon Media module - `"verizonMediaId"` | `"verizonMediaId"` | | params | Required | Object | Data for Verizon Media ID initialization. | | +| params.pixelId | Required | Number | The Verizon Media supplied publisher specific pixel Id | `8976` | | params.he | Required | String | The SHA-256 hashed user email address | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index bfefaf5a68e..e3341a64a92 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -4,7 +4,8 @@ import {verizonMediaIdSubmodule} from 'modules/verizonMediaIdSystem.js'; describe('Verizon Media ID Submodule', () => { const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; - const PROD_ENDPOINT = 'https://ups.analytics.yahoo.com/ups/58300/fed'; + const PIXEL_ID = '1234'; + const PROD_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PIXEL_ID}/fed`; const OVERRIDE_ENDPOINT = 'https://foo/bar'; it('should have the correct module name declared', () => { @@ -39,14 +40,29 @@ describe('Verizon Media ID Submodule', () => { return result; } - it('returns undefined if the hashed email address is not passed', () => { + it('returns undefined if he and pixelId params are not passed', () => { expect(verizonMediaIdSubmodule.getId({}, consentData)).to.be.undefined; expect(ajaxStub.callCount).to.equal(0); }); + it('returns undefined if the pixelId param is not passed', () => { + expect(verizonMediaIdSubmodule.getId({ + he: HASHED_EMAIL + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + + it('returns undefined if the he param is not passed', () => { + expect(verizonMediaIdSubmodule.getId({ + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + it('returns an object with the callback function if the correct params are passed', () => { let result = verizonMediaIdSubmodule.getId({ - he: HASHED_EMAIL + he: HASHED_EMAIL, + pixelId: PIXEL_ID }, consentData); expect(result).to.be.an('object').that.has.all.keys('callback'); expect(result.callback).to.be.a('function'); @@ -55,10 +71,12 @@ describe('Verizon Media ID Submodule', () => { it('Makes an ajax GET request to the production API endpoint with query params', () => { invokeGetIdAPI({ he: HASHED_EMAIL, + pixelId: PIXEL_ID }, consentData); const expectedParams = { he: HASHED_EMAIL, + pixelId: PIXEL_ID, '1p': '0', gdpr: '1', euconsent: consentData.gdpr.consentString, @@ -66,7 +84,7 @@ describe('Verizon Media ID Submodule', () => { }; const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(ajaxStub.firstCall.args[0].indexOf(PROD_ENDPOINT)).to.equal(0); + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); expect(requestQueryParams).to.deep.equal(expectedParams); expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); }); @@ -86,14 +104,15 @@ describe('Verizon Media ID Submodule', () => { }; const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(ajaxStub.firstCall.args[0].indexOf(OVERRIDE_ENDPOINT)).to.equal(0); + expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); expect(requestQueryParams).to.deep.equal(expectedParams); expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); }); it('sets the callbacks param of the ajax function call correctly', () => { invokeGetIdAPI({ - he: HASHED_EMAIL + he: HASHED_EMAIL, + pixelId: PIXEL_ID, }, consentData); expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); @@ -101,7 +120,8 @@ describe('Verizon Media ID Submodule', () => { it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { invokeGetIdAPI({ - he: HASHED_EMAIL + he: HASHED_EMAIL, + pixelId: PIXEL_ID, }, consentData); const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -113,7 +133,8 @@ describe('Verizon Media ID Submodule', () => { consentData.gdpr.gdprApplies = false; invokeGetIdAPI({ - he: HASHED_EMAIL + he: HASHED_EMAIL, + pixelId: PIXEL_ID, }, consentData); const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -125,7 +146,8 @@ describe('Verizon Media ID Submodule', () => { it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { invokeGetIdAPI({ '1p': firstPartyParamValue, - he: HASHED_EMAIL + he: HASHED_EMAIL, + pixelId: PIXEL_ID, }, consentData); const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); From 91d306b75f9ffc7fc6b2f191f54cb65b7a39b318 Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Sun, 18 Oct 2020 22:28:51 +0100 Subject: [PATCH 10/11] Fix tests following API signature change --- test/spec/modules/verizonMediaIdSystem_spec.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index e3341a64a92..24150ac3d7e 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -35,32 +35,36 @@ describe('Verizon Media ID Submodule', () => { }); function invokeGetIdAPI(configParams, consentData) { - let result = verizonMediaIdSubmodule.getId(configParams, consentData); - result.callback(sinon.stub()); + let result = verizonMediaIdSubmodule.getId({ + params: configParams + }, consentData); + if (typeof result === 'object') { + result.callback(sinon.stub()); + } return result; } it('returns undefined if he and pixelId params are not passed', () => { - expect(verizonMediaIdSubmodule.getId({}, consentData)).to.be.undefined; + expect(invokeGetIdAPI({}, consentData)).to.be.undefined; expect(ajaxStub.callCount).to.equal(0); }); it('returns undefined if the pixelId param is not passed', () => { - expect(verizonMediaIdSubmodule.getId({ + expect(invokeGetIdAPI({ he: HASHED_EMAIL }, consentData)).to.be.undefined; expect(ajaxStub.callCount).to.equal(0); }); it('returns undefined if the he param is not passed', () => { - expect(verizonMediaIdSubmodule.getId({ + expect(invokeGetIdAPI({ pixelId: PIXEL_ID }, consentData)).to.be.undefined; expect(ajaxStub.callCount).to.equal(0); }); it('returns an object with the callback function if the correct params are passed', () => { - let result = verizonMediaIdSubmodule.getId({ + let result = invokeGetIdAPI({ he: HASHED_EMAIL, pixelId: PIXEL_ID }, consentData); From 0c7592dc02cf68c6470834c49a86a1e7de51523e Mon Sep 17 00:00:00 2001 From: slimkrazy Date: Wed, 21 Oct 2020 09:27:45 +0100 Subject: [PATCH 11/11] Add IAB vendor ID --- modules/verizonMediaIdSystem.js | 6 ++++++ test/spec/modules/verizonMediaIdSystem_spec.js | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index f06409442e7..617561765cc 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -10,6 +10,7 @@ import {submodule} from '../src/hook.js'; import * as utils from '../src/utils.js'; const MODULE_NAME = 'verizonMediaId'; +const VENDOR_ID = 25; const PLACEHOLDER = '__PIXEL_ID__'; const VMUID_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; @@ -24,6 +25,11 @@ export const verizonMediaIdSubmodule = { * @type {string} */ name: MODULE_NAME, + /** + * Vendor id of Verizon Media EMEA Limited + * @type {Number} + */ + gvlid: VENDOR_ID, /** * decode the stored id value for passing to bid requests * @function diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index 24150ac3d7e..a30be5a2569 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -12,6 +12,10 @@ describe('Verizon Media ID Submodule', () => { expect(verizonMediaIdSubmodule.name).to.equal('verizonMediaId'); }); + it('should have the correct TCFv2 Vendor ID declared', () => { + expect(verizonMediaIdSubmodule.gvlid).to.equal(25); + }); + describe('getId()', () => { let ajaxStub; let getAjaxFnStub;