Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new userId module - neustar's fabrick #5802

Merged
merged 7 commits into from
Oct 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"intentIqIdSystem",
"zeotapIdPlusIdSystem",
"haloIdSystem",
"quantcastIdSystem"
"quantcastIdSystem",
"fabrickIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
Expand Down
147 changes: 147 additions & 0 deletions modules/fabrickIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* 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 {SubmoduleConfig} [config]
* @param {ConsentData}
* @param {Object} cacheIdObj - existing id, if any consentData]
* @returns {IdResponse|undefined}
*/
getId(config, consentData, cacheIdObj) {
try {
const configParams = (config && config.params) || {};
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) {
utils.logError(`fabrickIdSystem encountered an error`, e);
}
} catch (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);
24 changes: 24 additions & 0 deletions modules/fabrickIdSystem.md
Original file line number Diff line number Diff line change
@@ -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)
}
}]
}
});
```
3 changes: 3 additions & 0 deletions test/mocks/fabrickId.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"fabrickId": 1980
}
106 changes: 106 additions & 0 deletions test/spec/modules/fabrickIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import * as utils from '../../../src/utils.js';
import {server} from '../../mocks/xhr.js';

import * as fabrickIdSystem from 'modules/fabrickIdSystem.js';

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({
name: 'fabrickId',
params: 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 r = '';
for (let i = 0; i < 300; i++) {
r += 'r';
}
let configParams = Object.assign({}, defaultConfigParams, {
refererInfo: {
referer: r,
stack: ['s-0'],
canonicalUrl: 'cu-0'
}
});
let submoduleCallback = fabrickIdSubmodule.getId({
name: 'fabrickId',
params: configParams
}).callback;
let callBackSpy = sinon.spy();
submoduleCallback(callBackSpy);
let request = server.requests[0];
r = '';
for (let i = 0; i < 200; i++) {
r += 'r';
}
expect(request.url).to.match(new RegExp(`r=${r}&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({
name: 'fabrickId',
params: 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,
JSON.stringify({})
);
expect(callBackSpy.calledOnce).to.be.true;
expect(logErrorStub.calledOnce).to.be.false;
});
});