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

GrowthCode User ID Module: Simplify Code base, added custom EID option #11004

Merged
merged 1 commit into from
Jan 30, 2024
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
169 changes: 20 additions & 149 deletions modules/growthCodeIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,88 +5,15 @@
* @requires module:modules/userId
*/

import {logError, logInfo, pick} from '../src/utils.js';
import {ajax} from '../src/ajax.js';
import { submodule } from '../src/hook.js'
import {getStorageManager} from '../src/storageManager.js';
import {MODULE_TYPE_UID} from '../src/activities/modules.js';
import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js';

/**
* @typedef {import('../modules/userId/index.js').Submodule} Submodule
* @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig
* @typedef {import('../modules/userId/index.js').IdResponse} IdResponse
*/

const MODULE_NAME = 'growthCodeId';
const GC_DATA_KEY = '_gc_data';
const GCID_KEY = 'gcid';
const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb?'

export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME });

/**
* Read GrowthCode data from cookie or local storage
* @param key
* @return {string}
*/
export function readData(key) {
try {
let payload
if (storage.cookiesAreEnabled(null)) {
payload = tryParse(storage.getCookie(key, null))
}
if (storage.hasLocalStorage()) {
payload = tryParse(storage.getDataFromLocalStorage(key, null))
}
if (payload !== undefined) {
if (payload.expire_at > (Date.now() / 1000)) {
return payload
}
}
} catch (error) {
logError(error);
}
}

/**
* Store GrowthCode data in either cookie or local storage
* expiration date: 45 days
* @param key
* @param {string} value
*/
function storeData(key, value) {
try {
logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value);

if (value) {
if (storage.hasLocalStorage(null)) {
storage.setDataInLocalStorage(key, value, null);
}
}
} catch (error) {
logError(error);
}
}

/**
* Parse json if possible, else return null
* @param data
* @param {object|null}
*/
function tryParse(data) {
let payload;
try {
payload = JSON.parse(data);
if (payload == null) {
return undefined
}
return payload
} catch (err) {
return undefined;
}
}

/** @type {Submodule} */
export const growthCodeIdSubmodule = {
/**
Expand All @@ -103,96 +30,40 @@ export const growthCodeIdSubmodule = {
decode(value) {
return value && value !== '' ? { 'growthCodeId': value } : undefined;
},

/**
* performs action to obtain id and return a value in the callback's response argument
* @function
* @param {SubmoduleConfig} [config]
* @returns {IdResponse|undefined}
*/
getId(config, consentData) {
getId(config) {
const configParams = (config && config.params) || {};
if (!configParams || typeof configParams.pid !== 'string') {
logError('User ID - GrowthCodeID submodule requires a valid Partner ID to be defined');
return;
}

const gdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0;
const consentString = gdpr ? consentData.consentString : '';
if (gdpr && !consentString) {
logInfo('Consent string is required to call GrowthCode id.');
return;
}
let ids = [];
let gcid = storage.getDataFromLocalStorage(GCID_KEY, null)

let publisherId = configParams.publisher_id ? configParams.publisher_id : '_sharedID';
if (gcid !== null) {
const gcEid = {
source: 'growthcode.io',
uids: [{
id: gcid,
atype: 3,
}]
}

let sharedId;
if (configParams.publisher_id_storage === 'html5') {
sharedId = storage.getDataFromLocalStorage(publisherId, null) ? (storage.getDataFromLocalStorage(publisherId, null)) : null;
} else {
sharedId = storage.getCookie(publisherId, null) ? (storage.getCookie(publisherId, null)) : null;
}
if (!sharedId) {
logError('User ID - Publisher ID is not correctly setup.');
ids = ids.concat(gcEid)
}

const resp = function(callback) {
let gcData = readData(GC_DATA_KEY);
if (gcData) {
callback(gcData);
} else {
let segment = window.location.pathname.substr(1).replace(/\/+$/, '');
if (segment === '') {
segment = 'home';
}

let url = configParams.url ? configParams.url : ENDPOINT_URL;
url = tryAppendQueryString(url, 'pid', configParams.pid);
url = tryAppendQueryString(url, 'uid', sharedId);
url = tryAppendQueryString(url, 'u', window.location.href);
url = tryAppendQueryString(url, 'h', window.location.hostname);
url = tryAppendQueryString(url, 's', segment);
url = tryAppendQueryString(url, 'r', document.referrer);
let additionalEids = storage.getDataFromLocalStorage(configParams.customerEids, null)
if (additionalEids !== null) {
let data = JSON.parse(additionalEids)
ids = ids.concat(data)
}

ajax(url, {
success: response => {
let respJson = tryParse(response);
// If response is a valid json and should save is true
if (respJson) {
storeData(GC_DATA_KEY, JSON.stringify(respJson))
storeData(GCID_KEY, respJson.gc_id);
callback(respJson);
} else {
callback();
}
},
error: error => {
logError(MODULE_NAME + ': ID fetch encountered an error', error);
callback();
}
}, undefined, {method: 'GET', withCredentials: true})
}
};
return { callback: resp };
return {id: ids}
},
eids: {
'growthCodeId': {
getValue: function(data) {
return data.gc_id
},
source: 'growthcode.io',
atype: 1,
getUidExt: function(data) {
const extendedData = pick(data, [
'h1',
'h2',
'h3',
]);
if (Object.keys(extendedData).length) {
return extendedData;
}
}
},
}

};

submodule('userId', growthCodeIdSubmodule);
40 changes: 29 additions & 11 deletions modules/growthCodeIdSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,38 @@ pbjs.setConfig({
userIds: [{
name: 'growthCodeId',
params: {
pid: 'TEST01', // Set your Partner ID here for production (obtained from Growthcode)
publisher_id: '_sharedID',
publisher_id_storage: 'html5'
customerEids: 'customerEids',
}
}]
}
});
```

| Param under userSync.userIds[] | Scope | Type | Description | Example |
|--------------------------------|----------|--------| --- |-----------------|
| name | Required | String | The name of this module. | `"growthCodeId"` |
| params | Required | Object | Details of module params. | |
| params.pid | Required | String | This is the Parter ID value obtained from GrowthCode | `"TEST01"` |
| params.url | Optional | String | Custom URL for server | |
| params.publisher_id | Optional | String | Name if the variable that holds your publisher ID | `"_sharedID"` |
| params.publisher_id_storage | Optional | String | Publisher ID storage (cookie, html5) | `"html5"` |
### Sample Eids
Below is an example of the EIDs stored in Local Store (customerEids)
```json
[
{
"source":"domain.com",
"uids":[
{
"id":"8212212191539393121",
"ext":{
"stype":"ppuid"
}
}
]
},
{
"source":"example.com",
"uids":[
{
"id":"e06e9e5a-273c-46f8-aace-6f62cf13ea71",
"ext":{
"stype":"ppuid"
}
}
]
}
]
```
66 changes: 27 additions & 39 deletions test/spec/modules/growthCodeIdSystem_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import {expect} from 'chai';
import {getStorageManager} from '../../../src/storageManager.js';
import {MODULE_TYPE_UID} from '../../../src/activities/modules.js';

const GCID_EXPIRY = 45;
const MODULE_NAME = 'growthCodeId';
const SHAREDID = 'fe9c5c89-7d56-4666-976d-e07e73b3b664';
const EIDS = '[{"source":"domain.com","uids":[{"id":"8212212191539393121","ext":{"stype":"ppuid"}}]}]';
const GCID = 'e06e9e5a-273c-46f8-aace-6f62cf13ea71'

const GCID_EID = '{"id": [{"source": "growthcode.io", "uids": [{"atype": 3,"id": "e06e9e5a-273c-46f8-aace-6f62cf13ea71"}]}]}'
const GCID_EID_EID = '{"id": [{"source": "growthcode.io", "uids": [{"atype": 3,"id": "e06e9e5a-273c-46f8-aace-6f62cf13ea71"}]},{"source": "domain.com", "uids": [{"id": "8212212191539393121", "ext": {"stype":"ppuid"}}]}]}'

const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME });

Expand All @@ -23,11 +26,8 @@ describe('growthCodeIdSystem', () => {

beforeEach(function () {
logErrorStub = sinon.stub(utils, 'logError');
storage.setDataInLocalStorage('_sharedid', SHAREDID);
const expiresStr = (new Date(Date.now() + (GCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString();
if (storage.cookiesAreEnabled()) {
storage.setCookie('_sharedid', SHAREDID, expiresStr, 'LAX');
}
storage.setDataInLocalStorage('gcid', GCID, null);
storage.setDataInLocalStorage('customerEids', EIDS, null);
});

afterEach(function () {
Expand All @@ -40,45 +40,33 @@ describe('growthCodeIdSystem', () => {
});
});

it('should NOT call the growthcode id endpoint if gdpr applies but consent string is missing', function () {
let submoduleCallback = growthCodeIdSubmodule.getId(getIdParams, { gdprApplies: true }, undefined);
expect(submoduleCallback).to.be.undefined;
});

it('should log an error if pid configParam was not passed when getId', function () {
growthCodeIdSubmodule.getId();
expect(logErrorStub.callCount).to.be.equal(1);
it('test return of GCID', function () {
let ids;
ids = growthCodeIdSubmodule.getId();
expect(ids).to.deep.equal(JSON.parse(GCID_EID));
});

it('should log an error if sharedId (LocalStore) is not setup correctly', function () {
growthCodeIdSubmodule.getId({params: {
pid: 'TEST01',
publisher_id: '_sharedid_bad',
publisher_id_storage: 'html5',
it('test return of the GCID and an additional EID', function () {
let ids;
ids = growthCodeIdSubmodule.getId({params: {
customerEids: 'customerEids',
}});
expect(logErrorStub.callCount).to.be.equal(1);
expect(ids).to.deep.equal(JSON.parse(GCID_EID_EID));
});

it('should log an error if sharedId (LocalStore) is not setup correctly', function () {
growthCodeIdSubmodule.getId({params: {
pid: 'TEST01',
publisher_id: '_sharedid_bad',
publisher_id_storage: 'cookie',
it('test return of the GCID and an additional EID (bad Local Store name)', function () {
let ids;
ids = growthCodeIdSubmodule.getId({params: {
customerEids: 'customerEidsBad',
}});
expect(logErrorStub.callCount).to.be.equal(1);
expect(ids).to.deep.equal(JSON.parse(GCID_EID));
});

it('should call the growthcode id endpoint', function () {
let callBackSpy = sinon.spy();
let submoduleCallback = growthCodeIdSubmodule.getId(getIdParams).callback;
submoduleCallback(callBackSpy);
let request = server.requests[0];
expect(request.url.substr(0, 85)).to.be.eq('https://p2.gcprivacy.com/v1/pb?pid=TEST01&uid=' + SHAREDID + '&u=');
request.respond(
200,
{},
JSON.stringify({})
);
expect(callBackSpy.calledOnce).to.be.true;
it('test decode function)', function () {
let ids;
ids = growthCodeIdSubmodule.decode(GCID, {params: {
customerEids: 'customerEids',
}});
expect(ids).to.deep.equal(JSON.parse('{"growthCodeId":"' + GCID + '"}'));
});
})