Skip to content

Commit

Permalink
GrowthCode ID Module: initial module release (prebid#9011)
Browse files Browse the repository at this point in the history
* Initial check-in ofthe GrowthCode Adaptor

* Growthcode ID System

* Working on test module

* Tests for the growthCode Id System

* Clean up tests for GrowthCode

* Fixed the default values for shareID

* Remove Test HTML Page

* Remove file

* Updated MD file to switch client ID to partner ID
  • Loading branch information
southern-growthcode authored and JacobKlein26 committed Feb 8, 2023
1 parent d2d09ab commit 93c2f7a
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 0 deletions.
164 changes: 164 additions & 0 deletions modules/growthCodeIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/**
* This module adds GrowthCodeId to the User ID module
* The {@link module:modules/userId} module is required
* @module modules/growthCodeIdSystem
* @requires module:modules/userId
*/

import {logError, logInfo, tryAppendQueryString} from '../src/utils.js';
import {ajax} from '../src/ajax.js';
import { submodule } from '../src/hook.js'
import { getStorageManager } from '../src/storageManager.js';

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

export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME });

/**
* Read GrowthCode data from cookie or local storage
* @param key
* @return {string}
*/
export function readData(key) {
try {
if (storage.hasLocalStorage()) {
return storage.getDataFromLocalStorage(key);
}
if (storage.cookiesAreEnabled()) {
return storage.getCookie(key);
}
} 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()) {
storage.setDataInLocalStorage(key, value);
}
const expiresStr = (new Date(Date.now() + (GCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString();
if (storage.cookiesAreEnabled()) {
storage.setCookie(key, value, expiresStr, 'LAX');
}
}
} catch (error) {
logError(error);
}
}

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

/** @type {Submodule} */
export const growthCodeIdSubmodule = {
/**
* used to link submodule with config
* @type {string}
*/
name: MODULE_NAME,
/**
* decode the stored id value for passing to bid requests
* @function
* @param {{string}} value
* @returns {{growthCodeId: {string}}|undefined}
*/
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) {
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 publisherId = configParams.publisher_id ? configParams.publisher_id : '_sharedID';

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.');
}

const resp = function(callback) {
let gcData = tryParse(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);

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))
callback(respJson);
} else {
callback();
}
},
error: error => {
logError(MODULE_NAME + ': ID fetch encountered an error', error);
callback();
}
}, undefined, {method: 'GET', withCredentials: true})
}
};
return { callback: resp };
}
};

submodule('userId', growthCodeIdSubmodule);
37 changes: 37 additions & 0 deletions modules/growthCodeIdSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## GrowthCode User ID Submodule

GrowthCode provides Id Enrichment for requests.

## Building Prebid with GrowthCode Support

First, make sure to add the GrowthCode submodule to your Prebid.js package with:

```
gulp build --modules=growthCodeIdSystem,userId
```

The following configuration parameters are available:

```javascript
pbjs.setConfig({
userSync: {
userIds: [{
name: 'growthCodeId',
params: {
pid: 'TEST01', // Set your Partner ID here for production (obtained from Growthcode)
publisher_id: '_sharedID',
publisher_id_storage: 'html5'
}
}]
}
});
```

| 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"` |
19 changes: 19 additions & 0 deletions modules/userId/eids.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ export const USER_IDS_CONFIG = {

// key-name : {config}

// GrowthCode
'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;
}
}
},

// trustpid
'trustpid': {
source: 'trustpid.com',
Expand Down
83 changes: 83 additions & 0 deletions test/spec/modules/growthCodeIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { growthCodeIdSubmodule } from 'modules/growthCodeIdSystem.js';
import * as utils from 'src/utils.js';
import { server } from 'test/mocks/xhr.js';
import { uspDataHandler } from 'src/adapterManager.js';
import {expect} from 'chai';
import {getStorageManager} from '../../../src/storageManager.js';

const GCID_EXPIRY = 45;
const MODULE_NAME = 'growthCodeId';
const SHAREDID = 'fe9c5c89-7d56-4666-976d-e07e73b3b664';

export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME });

const getIdParams = {params: {
pid: 'TEST01',
publisher_id: '_sharedid',
publisher_id_storage: 'html5',
}};

describe('growthCodeIdSystem', () => {
let logErrorStub;

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');
}
});

afterEach(function () {
logErrorStub.restore();
});

describe('name', () => {
it('should expose the name of the submodule', () => {
expect(growthCodeIdSubmodule.name).to.equal('growthCodeId');
});
});

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('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',
}});
expect(logErrorStub.callCount).to.be.equal(1);
});

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',
}});
expect(logErrorStub.callCount).to.be.equal(1);
});

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;
});
})

0 comments on commit 93c2f7a

Please sign in to comment.