Skip to content

Commit

Permalink
GC-179 Simpliy the userId module, and added feature to allow customer…
Browse files Browse the repository at this point in the history
…s to provide custom EIDs (prebid#11004)
  • Loading branch information
southern-growthcode authored and Kevin Siow committed Mar 1, 2024
1 parent 4fd0428 commit cc85f1a
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 199 deletions.
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 + '"}'));
});
})

0 comments on commit cc85f1a

Please sign in to comment.