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

Publink Id System (Conversant): add new user id module #7322

Merged
merged 4 commits into from
Sep 8, 2021
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 @@ -32,7 +32,8 @@
"flocIdSystem",
"amxIdSystem",
"naveggId",
"imuIdSystem"
"imuIdSystem",
"publinkIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
Expand Down
123 changes: 123 additions & 0 deletions modules/publinkIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* This module adds the PublinkId to the User ID module
* The {@link module:modules/userId} module is required
* @module modules/publinkIdSystem
* @requires module:modules/userId
*/

import {submodule} from '../src/hook.js';
import {getStorageManager} from '../src/storageManager.js';
import {ajax} from '../src/ajax.js';
import * as utils from '../src/utils.js';
import {uspDataHandler} from '../src/adapterManager.js';

const MODULE_NAME = 'publinkId';
const GVLID = 24;
const PUBLINK_COOKIE = '_publink';
const PUBLINK_S2S_COOKIE = '_publink_srv';

export const storage = getStorageManager(GVLID);

function publinkIdUrl(params, consentData) {
let url = utils.parseUrl('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink');
url.search = {
deh: params.e,
mpn: 'Prebid.js',
mpv: '$prebid.version$',
};
if (consentData) {
url.search.gdpr = (consentData.gdprApplies) ? 1 : 0;
url.search.gdpr_consent = consentData.consentString;
}

const usPrivacyString = uspDataHandler.getConsentData();
if (usPrivacyString && typeof usPrivacyString === 'string') {
url.search.us_privacy = usPrivacyString;
}

return utils.buildUrl(url);
}

function makeCallback(config = {}, consentData) {
return function(prebidCallback) {
const options = {method: 'GET', withCredentials: true};
let handleResponse = function(responseText, xhr) {
if (xhr.status === 200) {
let response = JSON.parse(responseText);
if (response) {
prebidCallback(response.publink);
}
}
};
if (config.params && config.params.e) {
ajax(publinkIdUrl(config.params, consentData), handleResponse, undefined, options);
}
};
}

function getlocalValue() {
let result;
function getData(key) {
let value;
if (storage.hasLocalStorage()) {
value = storage.getDataFromLocalStorage(key);
}
if (!value) {
value = storage.getCookie(key);
}

if (typeof value === 'string') {
try {
const obj = JSON.parse(value);
if (obj && obj.exp && obj.exp > Date.now()) {
return obj.publink;
}
} catch (e) {
utils.logError(e);
}
}
}
result = getData(PUBLINK_S2S_COOKIE);
if (!result) {
result = getData(PUBLINK_COOKIE);
}
return result;
}

/** @type {Submodule} */
export const publinkIdSubmodule = {
/**
* used to link submodule with config
* @type {string}
*/
name: MODULE_NAME,
gvlid: GVLID,

/**
* decode the stored id value for passing to bid requests
* @function
* @param {string} id encrypted userid
* @returns {{publinkId: string} | undefined}
*/
decode(publinkId) {
return {publink: publinkId};
},

/**
* performs action to obtain id
* Use a publink cookie first if it is present, otherwise use prebids copy, if neither are available callout to get a new id
* @function
* @param {SubmoduleConfig} [config] Config object with params and storage properties
* @returns {IdResponse}
*/
getId: function(config, consentData, storedId) {
const localValue = getlocalValue();
if (localValue) {
return {id: localValue};
}
if (!storedId) {
return {callback: makeCallback(config, consentData)};
}
}
};
submodule('userId', publinkIdSubmodule);
28 changes: 28 additions & 0 deletions modules/publinkIdSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Publink User ID Submodule

Publink user id module

## Configuration Descriptions for the `userId` Configuration Section

| Param Name | Required | Type | Description | Example |
| --- | --- | --- | --- | --- |
| name | Yes | String | module identifier | `"publinkId"` |
| params.e | Yes | String | hashed email address | `"e80b5017098950fc58aad83c8c14978e"` |

### Example configuration for Publink
```
pbjs.setConfig({
userSync: {
userIds: [{
name: "publinkId",
storage: {
name: "pbjs_publink",
type: "html5"
},
params: {
e: "e80b5017098950fc58aad83c8c14978e", // example hashed email (md5)
}
}],
}
});
```
4 changes: 4 additions & 0 deletions modules/userId/eids.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ const USER_IDS_CONFIG = {
source: 'amxrtb.com',
atype: 1,
},
'publinkId': {
source: 'epsilon.com',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems incorrect; how is epsilon a source, they appear to be a consumer of the id

atype: 3
},
'kpuid': {
source: 'kpuid.com',
atype: 3
Expand Down
136 changes: 136 additions & 0 deletions test/spec/modules/publinkIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {publinkIdSubmodule} from 'modules/publinkIdSystem.js';
import {getStorageManager} from '../../../src/storageManager';
import {server} from 'test/mocks/xhr.js';
import sinon from 'sinon';
import {uspDataHandler} from '../../../src/adapterManager';

export const storage = getStorageManager(24);
const TEST_COOKIE_VALUE = 'cookievalue';
describe('PublinkIdSystem', () => {
describe('decode', () => {
it('decode', () => {
const result = publinkIdSubmodule.decode(TEST_COOKIE_VALUE);
expect(result).deep.equals({publink: TEST_COOKIE_VALUE});
});
});

describe('Fetch Local Cookies', () => {
const PUBLINK_COOKIE = '_publink';
const PUBLINK_SRV_COOKIE = '_publink_srv';
const EXP = Date.now() + 60 * 60 * 24 * 7 * 1000;
const COOKIE_VALUE = {publink: 'publinkCookieValue', exp: EXP};
const LOCAL_VALUE = {publink: 'publinkLocalStorageValue', exp: EXP};
const COOKIE_EXPIRATION = (new Date(Date.now() + 60 * 60 * 24 * 1000)).toUTCString();
const DELETE_COOKIE = 'Thu, 01 Jan 1970 00:00:01 GMT';
it('publink srv cookie', () => {
storage.setCookie(PUBLINK_SRV_COOKIE, JSON.stringify(COOKIE_VALUE), COOKIE_EXPIRATION);
const result = publinkIdSubmodule.getId();
expect(result.id).to.equal(COOKIE_VALUE.publink);
storage.setCookie(PUBLINK_SRV_COOKIE, '', DELETE_COOKIE);
});
it('publink srv local storage', () => {
storage.setDataInLocalStorage(PUBLINK_SRV_COOKIE, JSON.stringify(LOCAL_VALUE));
const result = publinkIdSubmodule.getId();
expect(result.id).to.equal(LOCAL_VALUE.publink);
storage.removeDataFromLocalStorage(PUBLINK_SRV_COOKIE);
});
it('publink cookie', () => {
storage.setCookie(PUBLINK_COOKIE, JSON.stringify(COOKIE_VALUE), COOKIE_EXPIRATION);
const result = publinkIdSubmodule.getId();
expect(result.id).to.equal(COOKIE_VALUE.publink);
storage.setCookie(PUBLINK_COOKIE, '', DELETE_COOKIE);
});
it('publink local storage', () => {
storage.setDataInLocalStorage(PUBLINK_COOKIE, JSON.stringify(LOCAL_VALUE));
const result = publinkIdSubmodule.getId();
expect(result.id).to.equal(LOCAL_VALUE.publink);
storage.removeDataFromLocalStorage(PUBLINK_COOKIE);
});
it('ignore expired cookie', () => {
storage.setDataInLocalStorage(PUBLINK_COOKIE, JSON.stringify({publink: 'value', exp: Date.now() - 60 * 60 * 24 * 1000}));
const result = publinkIdSubmodule.getId();
expect(result.id).to.be.undefined;
storage.removeDataFromLocalStorage(PUBLINK_COOKIE);
});
it('priority goes to publink_srv cookie', () => {
storage.setCookie(PUBLINK_SRV_COOKIE, JSON.stringify(COOKIE_VALUE), COOKIE_EXPIRATION);
storage.setDataInLocalStorage(PUBLINK_COOKIE, JSON.stringify(LOCAL_VALUE));
const result = publinkIdSubmodule.getId();
expect(result.id).to.equal(COOKIE_VALUE.publink);
storage.setCookie(PUBLINK_SRV_COOKIE, '', DELETE_COOKIE);
storage.removeDataFromLocalStorage(PUBLINK_COOKIE);
});
});

describe('getId', () => {
const serverResponse = {publink: 'ec0xHT3yfAOnykP64Qf0ORSi7LjNT1wju04ZSCsoPBekOJdBwK-0Zl_lXKDNnzhauC4iszBc-PvA1Be6IMlh1QocA'};
it('no config', () => {
const result = publinkIdSubmodule.getId();
expect(result).to.exist;
expect(result.callback).to.be.a('function');
});
it('Use local copy', () => {
const result = publinkIdSubmodule.getId({}, undefined, TEST_COOKIE_VALUE);
expect(result).to.be.undefined;
});

describe('callout for id', () => {
let callbackSpy = sinon.spy();

beforeEach(() => {
callbackSpy.resetHistory();
});

it('Fetch with consent data', () => {
const config = {storage: {type: 'cookie'}, params: {e: 'hashedemailvalue'}};
const consentData = {gdprApplies: 1, consentString: 'myconsentstring'};
let submoduleCallback = publinkIdSubmodule.getId(config, consentData).callback;
submoduleCallback(callbackSpy);

let request = server.requests[0];
expect(request.url).to.equal('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink?deh=hashedemailvalue&mpn=Prebid.js&mpv=$prebid.version$&gdpr=1&gdpr_consent=myconsentstring');

request.respond(200, {}, JSON.stringify(serverResponse));
expect(callbackSpy.calledOnce).to.be.true;
expect(callbackSpy.lastCall.lastArg).to.equal(serverResponse.publink);
});

it('server doesnt respond', () => {
const config = {storage: {type: 'cookie'}, params: {e: 'hashedemailvalue'}};
let submoduleCallback = publinkIdSubmodule.getId(config).callback;
submoduleCallback(callbackSpy);

let request = server.requests[0];
expect(request.url).to.equal('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink?deh=hashedemailvalue&mpn=Prebid.js&mpv=$prebid.version$');

request.respond(204, {}, JSON.stringify(serverResponse));
expect(callbackSpy.calledOnce).to.be.false;
});
});

describe('usPrivacy', () => {
let callbackSpy = sinon.spy();
const oldPrivacy = uspDataHandler.getConsentData();
before(() => {
uspDataHandler.setConsentData('1YNN');
});
after(() => {
uspDataHandler.setConsentData(oldPrivacy);
callbackSpy.resetHistory();
});

it('Fetch with usprivacy data', () => {
const config = {storage: {type: 'cookie'}, params: {e: 'hashedemailvalue'}};
let submoduleCallback = publinkIdSubmodule.getId(config).callback;
submoduleCallback(callbackSpy);

let request = server.requests[0];
expect(request.url).to.equal('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink?deh=hashedemailvalue&mpn=Prebid.js&mpv=$prebid.version$&us_privacy=1YNN');

request.respond(200, {}, JSON.stringify(serverResponse));
expect(callbackSpy.calledOnce).to.be.true;
expect(callbackSpy.lastCall.lastArg).to.equal(serverResponse.publink);
});
});
});
});