Skip to content

Commit

Permalink
adds generic pair support
Browse files Browse the repository at this point in the history
  • Loading branch information
Miguel Morales committed Dec 23, 2024
1 parent 2f713dd commit 73e6c68
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 0 deletions.
123 changes: 123 additions & 0 deletions modules/openPairIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* This module adds Open PAIR Id to the User ID module
* The {@link module:modules/userId} module is required
* @module modules/openPairIdSystem
* @requires module:modules/userId
*/

import {submodule} from '../src/hook.js';
import {getStorageManager} from '../src/storageManager.js'
import {logInfo} from '../src/utils.js';
import {MODULE_TYPE_UID} from '../src/activities/modules.js';
import {VENDORLESS_GVLID} from '../src/consentHandler.js';

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

const MODULE_NAME = 'openPairId';
const DEFAULT_PUBLISHER_ID_KEY = 'pairId';

const DEFAULT_STORAGE_PUBLISHER_ID_KEYS = {
liveramp: '_lr_pairId'
};

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

function publisherIdFromLocalStorage(key) {
return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(key) : null;
}

function publisherIdFromCookie(key) {
return storage.cookiesAreEnabled() ? storage.getCookie(key) : null;
}

/** @type {Submodule} */
export const openPairIdSubmodule = {
/**
* used to link submodule with config
* @type {string}
*/
name: MODULE_NAME,
/**
* used to specify vendor id
* @type {number}
*/
gvlid: VENDORLESS_GVLID,
/**
* decode the stored id value for passing to bid requests
* @function
* @param { string | undefined } value
* @returns {{pairId:string} | undefined }
*/
decode(value) {
return value && Array.isArray(value) ? {'openPairId': value} : undefined;
},
/**
* Performs action to obtain ID and return a value in the callback's response argument.
* @function getId
* @param {Object} config - The configuration object.
* @param {Object} config.params - The parameters from the configuration.
* @returns {{id: string[] | undefined}} The obtained IDs or undefined if no IDs are found.
*/
getId(config) {
const publisherIdsString = publisherIdFromLocalStorage(DEFAULT_PUBLISHER_ID_KEY) || publisherIdFromCookie(DEFAULT_PUBLISHER_ID_KEY);
let ids = []

if (publisherIdsString && typeof publisherIdsString == 'string') {
try {
ids = ids.concat(JSON.parse(atob(publisherIdsString)))
} catch (error) {
logInfo(error)
}
}

const configParams = (config && config.params) ? config.params : {};
const cleanRooms = Object.keys(configParams);

for (let i = 0; i < cleanRooms.length; i++) {
const cleanRoom = cleanRooms[i];
const cleanRoomParams = configParams[cleanRoom];

const cleanRoomStorageLocation = cleanRoomParams.storageKey || DEFAULT_STORAGE_PUBLISHER_ID_KEYS[cleanRoom];
const cleanRoomValue = publisherIdFromLocalStorage(cleanRoomStorageLocation) || publisherIdFromCookie(cleanRoomStorageLocation);

if (cleanRoomValue) {
try {
const parsedValue = atob(cleanRoomValue);

if (parsedValue) {
const obj = JSON.parse(parsedValue);

if (obj && typeof obj === 'object' && obj.envelope) {
ids = ids.concat(obj.envelope);
} else {
logInfo('Open Pair ID: Parsed object is not valid or does not contain envelope');
}
} else {
logInfo('Open Pair ID: Decoded value is empty');
}
} catch (error) {
logInfo('Open Pair ID: Error parsing JSON: ', error);
}
} else {
logInfo('Open Pair ID: data clean room value for pairId from storage is empty or null');
}
}

if (ids.length == 0) {
logInfo('Open Pair ID: no ids found')
return undefined;
}

return {'id': ids};
},
eids: {
'openPairId': {
source: 'pair-protocol.com',
atype: 3
},
}
};

submodule('userId', openPairIdSubmodule);
114 changes: 114 additions & 0 deletions test/spec/modules/openPairIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { storage, submodule } from 'modules/openPairIdSystem.js';
import * as utils from 'src/utils.js';

describe('openPairId', function () {
let sandbox;
let logInfoStub;

beforeEach(() => {
sandbox = sinon.sandbox.create();
logInfoStub = sandbox.stub(utils, 'logInfo');
});
afterEach(() => {
sandbox.restore();
});

it('should read publisher id from specified clean room if configured with storageKey', function() {
let publisherIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3'];
sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('habu_pairId_custom').returns(btoa(JSON.stringify({'envelope': publisherIds})));

let id = submodule.getId({
params: {
habu: {
storageKey: 'habu_pairId_custom'
}
}})

expect(id).to.be.deep.equal({id: publisherIds});
});

xit('should read publisher id from liveramp with default storageKey and additional clean room with configured storageKey', function() {
let getDataStub = sandbox.stub(storage, 'getDataFromLocalStorage');
let liveRampPairIds = ['lr-test-pair-id1', 'lr-test-pair-id2', 'lr-test-pair-id3'];
getDataStub.withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': liveRampPairIds})));

let habuPairIds = ['habu-test-pair-id1', 'habu-test-pair-id2', 'habu-test-pair-id3'];
getDataStub.withArgs('habu_pairId_custom').returns(btoa(JSON.stringify({'envelope': habuPairIds})));

let id = submodule.getId({
params: {
habu: {
storageKey: 'habu_pairId_custom'
},
liveramp: {}
}})

expect(id).to.be.deep.equal({id: habuPairIds.concat(liveRampPairIds)});
});

xit('should log an error if no ID is found when getId', function() {
submodule.getId({ params: {} });
expect(logInfoStub.calledOnce).to.be.true;
});

xit('should read publisher id from local storage if exists', function() {
let pairIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3'];
sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('pairId').returns(btoa(JSON.stringify(pairIds)));

let id = submodule.getId({ params: {} });
expect(id).to.be.deep.equal({id: pairIds});
});

xit('should read publisher id from cookie if exists', function() {
let pairIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6'];
sandbox.stub(storage, 'getCookie').withArgs('pairId').returns(btoa(JSON.stringify(pairIds)));

let id = submodule.getId({ params: {} });
expect(id).to.be.deep.equal({id: pairIds});
});

xit('should read publisher id from default liveramp envelope local storage key if configured', function() {
let pairIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3'];
sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': pairIds})));
let id = submodule.getId({
params: {
liveramp: {}
}})
expect(id).to.be.deep.equal({id: pairIds})
});

xit('should read publisher id from default liveramp envelope cookie entry if configured', function() {
let pairIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6'];
sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': pairIds})));
let id = submodule.getId({
params: {
liveramp: {}
}})
expect(id).to.be.deep.equal({id: pairIds})
});

xit('should read publisher id from specified liveramp envelope cookie entry if configured with storageKey', function() {
let pairIds = ['test-pair-id7', 'test-pair-id8', 'test-pair-id9'];
sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('lr_pairId_custom').returns(btoa(JSON.stringify({'envelope': pairIds})));
let id = submodule.getId({
params: {
liveramp: {
storageKey: 'lr_pairId_custom'
}
}})
expect(id).to.be.deep.equal({id: pairIds})
});

xit('should not get data from storage if local storage and cookies are disabled', function () {
sandbox.stub(storage, 'localStorageIsEnabled').returns(false);
sandbox.stub(storage, 'cookiesAreEnabled').returns(false);
let id = submodule.getId({
params: {
liveramp: {
storageKey: 'lr_pairId_custom'
}
}
})
expect(id).to.equal(undefined)
});
});

0 comments on commit 73e6c68

Please sign in to comment.