From 1acd03e64d3588871edaf3999ecad595ee40f4e3 Mon Sep 17 00:00:00 2001 From: Renzo Toscani Date: Mon, 5 Oct 2020 17:16:40 -0300 Subject: [PATCH 1/2] add idx user id --- modules/.submodules.json | 3 +- modules/idxIdSystem.js | 61 ++++++++++++++ modules/idxIdSystem.md | 22 +++++ modules/userId/eids.js | 8 +- test/spec/modules/idxIdSystem_spec.js | 117 ++++++++++++++++++++++++++ 5 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 modules/idxIdSystem.js create mode 100644 modules/idxIdSystem.md create mode 100644 test/spec/modules/idxIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 18e75dd1794..b92ff26531b 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -15,7 +15,8 @@ "intentIqIdSystem", "zeotapIdPlusIdSystem", "haloIdSystem", - "quantcastIdSystem" + "quantcastIdSystem", + "idxIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js new file mode 100644 index 00000000000..fb0f39b7782 --- /dev/null +++ b/modules/idxIdSystem.js @@ -0,0 +1,61 @@ +/** + * This module adds IDx to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/idxIdSystem + * @requires module:modules/userId + */ +import * as utils from '../src/utils.js' +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const IDX_MODULE_NAME = 'idx'; +const IDX_COOKIE_NAME = '_idx'; +export const storage = getStorageManager(); + +function readIDxFromCookie() { + return storage.cookiesAreEnabled ? storage.getCookie(IDX_COOKIE_NAME) : null; +} + +function readIDxFromLocalStorage() { + return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(IDX_COOKIE_NAME) : null; +} + +/** @type {Submodule} */ +export const idxIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: IDX_MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param { Object | string | undefined } value + * @return { Object | string | undefined } + */ + decode(value) { + const idxVal = value ? utils.isStr(value) ? value : utils.isPlainObject(value) ? value.id : undefined : undefined; + return idxVal ? { + 'idx': idxVal + } : undefined; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} configParams + * @return {{id: string | undefined } | undefined} + */ + getId() { + const idxString = readIDxFromLocalStorage() || readIDxFromCookie(); + if (typeof idxString == 'string' && idxString) { + try { + const idxObj = JSON.parse(idxString); + return idxObj && idxObj.idx ? { id: idxObj.idx } : undefined; + } catch (error) { + utils.logError(error); + } + } + return undefined; + } +}; +submodule('userId', idxIdSubmodule); diff --git a/modules/idxIdSystem.md b/modules/idxIdSystem.md new file mode 100644 index 00000000000..363120899cb --- /dev/null +++ b/modules/idxIdSystem.md @@ -0,0 +1,22 @@ +## IDx User ID Submodule + +For assistance setting up your module please contact us at [prebid@idx.lat](prebid@idx.lat). + +### Prebid Params + +Individual params may be set for the IDx Submodule. +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'idx', + }] + } +}); +``` +## Parameter Descriptions for the `userSync` Configuration Section +The below parameters apply only to the IDx integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID of the module - `"idx"` | `"idx"` | diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 4e4576dbb14..f6c58a5a0bf 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -150,7 +150,13 @@ const USER_IDS_CONFIG = { 'quantcastId': { source: 'quantcast.com', atype: 1 - } + }, + + // IDx + 'idx': { + source: 'idx.lat', + atype: 1 + }, }; // this function will create an eid object for the given UserId sub-module diff --git a/test/spec/modules/idxIdSystem_spec.js b/test/spec/modules/idxIdSystem_spec.js new file mode 100644 index 00000000000..14cd9a88d13 --- /dev/null +++ b/test/spec/modules/idxIdSystem_spec.js @@ -0,0 +1,117 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { config } from 'src/config.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { storage, idxIdSubmodule } from 'modules/idxIdSystem.js'; + +const IDX_COOKIE_NAME = '_idx'; +const IDX_DUMMY_VALUE = 'idx value for testing'; +const IDX_COOKIE_STORED = '{ "idx": "' + IDX_DUMMY_VALUE + '" }'; +const ID_COOKIE_OBJECT = { id: IDX_DUMMY_VALUE }; +const IDX_COOKIE_OBJECT = { idx: IDX_DUMMY_VALUE }; + +function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [{ + name: 'idx' + }] + } + } +} + +function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; +} + +describe('IDx ID System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let getCookieStub, cookiesAreEnabledStub; + + beforeEach(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + cookiesAreEnabledStub.restore(); + }); + + describe('IDx: test "getId" method', () => { + it('provides the stored IDx if a cookie exists', () => { + getCookieStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + let idx = idxIdSubmodule.getId(); + expect(idx).to.deep.equal(ID_COOKIE_OBJECT); + }); + + it('provides the stored IDx if cookie is absent but present in local storage', () => { + getDataFromLocalStorageStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + let idx = idxIdSubmodule.getId(); + expect(idx).to.deep.equal(ID_COOKIE_OBJECT); + }); + + it('returns undefined if both cookie and local storage are empty', () => { + let idx = idxIdSubmodule.getId(); + expect(idx).to.be.undefined; + }) + }); + + describe('IDx: test "decode" method', () => { + it('provides the IDx from a stored object', () => { + expect(idxIdSubmodule.decode(ID_COOKIE_OBJECT)).to.deep.equal(IDX_COOKIE_OBJECT); + }); + + it('provides the IDx from a stored string', () => { + expect(idxIdSubmodule.decode(IDX_DUMMY_VALUE)).to.deep.equal(IDX_COOKIE_OBJECT); + }); + }); + + describe('requestBids hook', () => { + let adUnits; + + beforeEach(() => { + adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([idxIdSubmodule]); + init(config); + config.setConfig(getConfigMock()); + getCookieStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + }); + + it('when a stored IDx exists it is added to bids', (done) => { + requestBidsHook(() => { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.idx'); + expect(bid.userId.idx).to.equal(IDX_DUMMY_VALUE); + const idxIdAsEid = find(bid.userIdAsEids, e => e.source == 'idx.lat'); + expect(idxIdAsEid).to.deep.equal({ + source: 'idx.lat', + uids: [{ + id: IDX_DUMMY_VALUE, + atype: 1, + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + }); +}); From 7734ab64c8e746ec6e460f1ce8d72227b5ccb626 Mon Sep 17 00:00:00 2001 From: Renzo Toscani <7190938+rtoscani@users.noreply.github.com> Date: Tue, 6 Oct 2020 14:40:13 -0300 Subject: [PATCH 2/2] Update modules/idxIdSystem.js to match new SubmoduleConfig param Co-authored-by: Scott --- modules/idxIdSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js index fb0f39b7782..00e8a8bc5e5 100644 --- a/modules/idxIdSystem.js +++ b/modules/idxIdSystem.js @@ -42,7 +42,7 @@ export const idxIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} configParams + * @param {SubmoduleConfig} config * @return {{id: string | undefined } | undefined} */ getId() {