From 170e0662b4e6570223e0eb088d483900094b4ee2 Mon Sep 17 00:00:00 2001 From: "Anderson, Ben" Date: Wed, 30 Sep 2020 09:37:38 -0400 Subject: [PATCH 1/6] submitting userId module for neustar's fabrick - https://www.home.neustar/fabrick --- modules/.submodules.json | 3 +- modules/fabrickIdSystem.js | 148 ++++++++++++++++++++++ modules/fabrickIdSystem.md | 24 ++++ test/mocks/fabrickId.json | 3 + test/spec/modules/fabrickIdSystem_spec.js | 90 +++++++++++++ 5 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 modules/fabrickIdSystem.js create mode 100644 modules/fabrickIdSystem.md create mode 100644 test/mocks/fabrickId.json create mode 100644 test/spec/modules/fabrickIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 18e75dd1794..0e5fdba4807 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -15,7 +15,8 @@ "intentIqIdSystem", "zeotapIdPlusIdSystem", "haloIdSystem", - "quantcastIdSystem" + "quantcastIdSystem", + "fabrickIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js new file mode 100644 index 00000000000..ee35fe2b860 --- /dev/null +++ b/modules/fabrickIdSystem.js @@ -0,0 +1,148 @@ +/** + * This module adds neustar's fabrickId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/fabrickIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; + +/** @type {Submodule} */ +export const fabrickIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'fabrickId', + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode(value) { + if (value && value.fabrickId) { + return { 'fabrickId': value.fabrickId }; + } else { + return undefined; + } + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function getId + * @param {SubmoduleParams} [configParams] + * @param {ConsentData} + * @param {Object} cacheIdObj - existing id, if any consentData] + * @returns {IdResponse|undefined} + */ + getId(configParams, consentData, cacheIdObj) { + try { + if (window.fabrickMod1) { + window.fabrickMod1(configParams, consentData, cacheIdObj); + } + if (!configParams || typeof configParams.apiKey !== 'string') { + utils.logError('fabrick submodule requires an apiKey.'); + return; + } + try { + let url = _getBaseUrl(configParams); + let keysArr = Object.keys(configParams); + for (let i in keysArr) { + let k = keysArr[i]; + if (k === 'url' || k === 'refererInfo') { + continue; + } + let v = configParams[k]; + if (Array.isArray(v)) { + for (let j in v) { + url += `${k}=${v[j]}&`; + } + } else { + url += `${k}=${v}&`; + } + } + // pull off the trailing & + url = url.slice(0, -1) + const referer = _getRefererInfo(configParams); + const urls = new Set(); + url = truncateAndAppend(urls, url, 'r', referer.referer); + if (referer.stack && referer.stack[0]) { + url = truncateAndAppend(urls, url, 'r', referer.stack[0]); + } + url = truncateAndAppend(urls, url, 'r', referer.canonicalUrl); + url = truncateAndAppend(urls, url, 'r', window.location.href); + + const resp = function (callback) { + const callbacks = { + success: response => { + if (window.fabrickMod2) { + return window.fabrickMod2( + callback, response, configParams, consentData, cacheIdObj); + } else { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + responseObj = {}; + } + } + callback(responseObj); + } + }, + error: error => { + utils.logError(`fabrickId fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, null, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + } catch (e) { + console.error(e); + utils.logError(`fabrickIdSystem encountered an error`, e); + } + } catch (e) { + console.error(e) + utils.logError(`fabrickIdSystem encountered an error`, e); + } + } +}; + +function _getRefererInfo(configParams) { + if (configParams.refererInfo) { + return configParams.refererInfo; + } else { + return getRefererInfo(); + } +} + +function _getBaseUrl(configParams) { + if (configParams.url) { + return configParams.url; + } else { + return `https://fid.agkn.com/f?`; + } +} + +function truncateAndAppend(urls, url, paramName, s) { + if (s && url.length < 2000) { + if (s.length > 200) { + s = s.substring(0, 200); + } + // Don't send the same url in multiple params + if (!urls.has(s)) { + urls.add(s); + return `${url}&${paramName}=${s}` + } + } + return url; +} + +submodule('userId', fabrickIdSubmodule); diff --git a/modules/fabrickIdSystem.md b/modules/fabrickIdSystem.md new file mode 100644 index 00000000000..268c861710a --- /dev/null +++ b/modules/fabrickIdSystem.md @@ -0,0 +1,24 @@ +## Neustar Fabrick User ID Submodule + +Fabrick ID Module - https://www.home.neustar/fabrick +Product and Sales Inquiries: 1-855-898-0036 + +## Example configuration for publishers: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'fabrickId', + storage: { + name: 'pbjs_fabrickId', + type: 'cookie', + expires: 7 + }, + params: { + apiKey: 'your apiKey', // provided to you by Neustar + e: '31c5543c1734d25c7206f5fd591525d0295bec6fe84ff82f946a34fe970a1e66' // example hash identifier (sha256) + } + }] + } +}); +``` diff --git a/test/mocks/fabrickId.json b/test/mocks/fabrickId.json new file mode 100644 index 00000000000..a8723ec88ec --- /dev/null +++ b/test/mocks/fabrickId.json @@ -0,0 +1,3 @@ +{ + "fabrickId": 1980 +} diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js new file mode 100644 index 00000000000..cf3309d30ea --- /dev/null +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -0,0 +1,90 @@ +import * as utils from '../../../src/utils'; +import {server} from '../../mocks/xhr'; + +import * as fabrickIdSystem from 'modules/fabrickIdSystem'; + +const defaultConfigParams = { + apiKey: '123', + e: 'abc', + p: ['def', 'hij'], + url: 'http://localhost:9999/test/mocks/fabrickId.json?' +}; +const responseHeader = {'Content-Type': 'application/json'} +const fabrickIdSubmodule = fabrickIdSystem.fabrickIdSubmodule; + +describe('Fabrick ID System', function() { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + fabrickIdSubmodule.getRefererInfoOverride = null; + }); + + it('should log an error if no configParams were passed into getId', function () { + fabrickIdSubmodule.getId(); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should error on json parsing', function() { + let submoduleCallback = fabrickIdSubmodule.getId(defaultConfigParams).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + request.respond( + 200, + responseHeader, + '] this is not json {' + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should truncate the params', function() { + let configParams = Object.assign({}, defaultConfigParams, { + refererInfo: { + referer: 'r'.repeat(300), + stack: ['s-0'], + canonicalUrl: 'cu-0' + } + }); + let submoduleCallback = fabrickIdSubmodule.getId(configParams).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.match(new RegExp(`r=${'r'.repeat(200)}&r=`)); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(logErrorStub.calledOnce).to.be.false; + }); + + it('should complete successfully', function() { + let configParams = Object.assign({}, defaultConfigParams, { + refererInfo: { + referer: 'r-0', + stack: ['s-0'], + canonicalUrl: 'cu-0' + } + }); + let submoduleCallback = fabrickIdSubmodule.getId(configParams).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.match(/r=r-0&r=s-0&r=cu-0&r=http/); + request.respond( + 200, + responseHeader, + // TODO - actually check the value + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(logErrorStub.calledOnce).to.be.false; + }); +}); From 5215a6082c4dd6c606e7f190dab91e2b5be6a3bf Mon Sep 17 00:00:00 2001 From: "Anderson, Ben" Date: Fri, 2 Oct 2020 09:45:39 -0400 Subject: [PATCH 2/6] fixing 'gulp test' errors --- modules/fabrickIdSystem.js | 2 -- test/spec/modules/fabrickIdSystem_spec.js | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js index ee35fe2b860..a2eaad9966c 100644 --- a/modules/fabrickIdSystem.js +++ b/modules/fabrickIdSystem.js @@ -105,11 +105,9 @@ export const fabrickIdSubmodule = { }; return {callback: resp}; } catch (e) { - console.error(e); utils.logError(`fabrickIdSystem encountered an error`, e); } } catch (e) { - console.error(e) utils.logError(`fabrickIdSystem encountered an error`, e); } } diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js index cf3309d30ea..eae82ab107d 100644 --- a/test/spec/modules/fabrickIdSystem_spec.js +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -1,7 +1,7 @@ -import * as utils from '../../../src/utils'; -import {server} from '../../mocks/xhr'; +import * as utils from '../../../src/utils.js'; +import {server} from '../../mocks/xhr.js'; -import * as fabrickIdSystem from 'modules/fabrickIdSystem'; +import * as fabrickIdSystem from 'modules/fabrickIdSystem.js'; const defaultConfigParams = { apiKey: '123', From 6a5e35191ab68382b54b3f0096271536538e8a63 Mon Sep 17 00:00:00 2001 From: "Anderson, Ben" Date: Fri, 2 Oct 2020 10:54:00 -0400 Subject: [PATCH 3/6] fixing another test issue (related to ie) --- test/spec/modules/fabrickIdSystem_spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js index eae82ab107d..e641ff389f4 100644 --- a/test/spec/modules/fabrickIdSystem_spec.js +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -44,9 +44,13 @@ describe('Fabrick ID System', function() { }); it('should truncate the params', function() { + let r = ''; + for (let i = 0; i < 300; i++) { + r += 'r'; + } let configParams = Object.assign({}, defaultConfigParams, { refererInfo: { - referer: 'r'.repeat(300), + referer: r, stack: ['s-0'], canonicalUrl: 'cu-0' } From 14c03ac82aaeacc3b93a953e4307aaa0432fb874 Mon Sep 17 00:00:00 2001 From: "Anderson, Ben" Date: Fri, 2 Oct 2020 11:21:24 -0400 Subject: [PATCH 4/6] removing another (last) repeat --- test/spec/modules/fabrickIdSystem_spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js index e641ff389f4..12119c51c81 100644 --- a/test/spec/modules/fabrickIdSystem_spec.js +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -59,7 +59,11 @@ describe('Fabrick ID System', function() { let callBackSpy = sinon.spy(); submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.match(new RegExp(`r=${'r'.repeat(200)}&r=`)); + r = ''; + for (let i = 0; i < 200; i++) { + r += 'r'; + } + expect(request.url).to.match(new RegExp(`r=${r}&r=`)); request.respond( 200, responseHeader, From 76ebe0d860ac180850d23696a41ed0b1b1448fe4 Mon Sep 17 00:00:00 2001 From: "Anderson, Ben" Date: Tue, 6 Oct 2020 15:29:55 -0400 Subject: [PATCH 5/6] - expose full user id config (including storage) to user id modules (#5803 - removing TODO from test --- modules/fabrickIdSystem.js | 5 +++-- test/spec/modules/fabrickIdSystem_spec.js | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js index a2eaad9966c..e61b377eefa 100644 --- a/modules/fabrickIdSystem.js +++ b/modules/fabrickIdSystem.js @@ -35,13 +35,14 @@ export const fabrickIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function getId - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} * @param {Object} cacheIdObj - existing id, if any consentData] * @returns {IdResponse|undefined} */ - getId(configParams, consentData, cacheIdObj) { + getId(config, consentData, cacheIdObj) { try { + const configParams = (config && config.params) || {}; if (window.fabrickMod1) { window.fabrickMod1(configParams, consentData, cacheIdObj); } diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js index 12119c51c81..c36e1a8dce3 100644 --- a/test/spec/modules/fabrickIdSystem_spec.js +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -89,7 +89,6 @@ describe('Fabrick ID System', function() { request.respond( 200, responseHeader, - // TODO - actually check the value JSON.stringify({}) ); expect(callBackSpy.calledOnce).to.be.true; From 4847d7544372ab1ee0530fe435f8bb13e0ff675a Mon Sep 17 00:00:00 2001 From: "Anderson, Ben" Date: Wed, 7 Oct 2020 08:50:33 -0400 Subject: [PATCH 6/6] - updates to test --- test/spec/modules/fabrickIdSystem_spec.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js index c36e1a8dce3..cbd538816ab 100644 --- a/test/spec/modules/fabrickIdSystem_spec.js +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -30,7 +30,10 @@ describe('Fabrick ID System', function() { }); it('should error on json parsing', function() { - let submoduleCallback = fabrickIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: defaultConfigParams + }).callback; let callBackSpy = sinon.spy(); submoduleCallback(callBackSpy); let request = server.requests[0]; @@ -55,7 +58,10 @@ describe('Fabrick ID System', function() { canonicalUrl: 'cu-0' } }); - let submoduleCallback = fabrickIdSubmodule.getId(configParams).callback; + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: configParams + }).callback; let callBackSpy = sinon.spy(); submoduleCallback(callBackSpy); let request = server.requests[0]; @@ -81,7 +87,10 @@ describe('Fabrick ID System', function() { canonicalUrl: 'cu-0' } }); - let submoduleCallback = fabrickIdSubmodule.getId(configParams).callback; + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: configParams + }).callback; let callBackSpy = sinon.spy(); submoduleCallback(callBackSpy); let request = server.requests[0];