diff --git a/integrationExamples/gpt/anonymised_segments_example.html b/integrationExamples/gpt/anonymised_segments_example.html new file mode 100644 index 00000000000..16f3f879636 --- /dev/null +++ b/integrationExamples/gpt/anonymised_segments_example.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
First Party Data (ortb2) Sent to Bidding Adapter
+
+ + diff --git a/modules/anonymisedRtdProvider.js b/modules/anonymisedRtdProvider.js new file mode 100644 index 00000000000..48ac649f002 --- /dev/null +++ b/modules/anonymisedRtdProvider.js @@ -0,0 +1,122 @@ +/** + * This module adds the Anonymised RTD provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will populate real-time data from Anonymised + * @module modules/anonymisedRtdProvider + * @requires module:modules/realTimeData + */ +import {getStorageManager} from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; + +export function createRtdProvider(moduleName) { + const MODULE_NAME = 'realTimeData'; + const SUBMODULE_NAME = moduleName; + + const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); + /** + * Add real-time data & merge segments. + * @param ortb2 object to merge into + * @param {Object} rtd + */ + function addRealTimeData(ortb2, rtd) { + if (isPlainObject(rtd.ortb2)) { + logMessage(`${SUBMODULE_NAME}RtdProvider: merging original: `, ortb2); + logMessage(`${SUBMODULE_NAME}RtdProvider: merging in: `, rtd.ortb2); + mergeDeep(ortb2, rtd.ortb2); + } + } + /** + * Try parsing stringified array of segment IDs. + * @param {String} data + */ + function tryParse(data) { + try { + return JSON.parse(data); + } catch (err) { + logError(`${SUBMODULE_NAME}RtdProvider: failed to parse json:`, data); + return null; + } + } + /** + * Real-time data retrieval from Anonymised + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} rtdConfig + * @param {Object} userConsent + */ + function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { + if (rtdConfig && isPlainObject(rtdConfig.params)) { + const cohortStorageKey = rtdConfig.params.cohortStorageKey; + const bidders = rtdConfig.params.bidders; + + if (cohortStorageKey !== 'cohort_ids') { + logError(`${SUBMODULE_NAME}RtdProvider: 'cohortStorageKey' should be 'cohort_ids'`) + return; + } + + const jsonData = storage.getDataFromLocalStorage(cohortStorageKey); + if (!jsonData) { + return; + } + + const segments = tryParse(jsonData); + + if (segments) { + const udSegment = { + name: 'anonymised.io', + ext: { + segtax: rtdConfig.params.segtax + }, + segment: segments.map(x => ({id: x})) + } + + logMessage(`${SUBMODULE_NAME}RtdProvider: user.data.segment: `, udSegment); + const data = { + rtd: { + ortb2: { + user: { + data: [ + udSegment + ] + } + } + } + }; + + if (bidders?.includes('appnexus')) { + data.rtd.ortb2.user.keywords = segments.map(x => `perid=${x}`).join(','); + } + + addRealTimeData(reqBidsConfigObj.ortb2Fragments?.global, data.rtd); + onDone(); + } + } + } + /** + * Module init + * @param {Object} provider + * @param {Object} userConsent + * @return {boolean} + */ + function init(provider, userConsent) { + return true; + } + /** @type {RtdSubmodule} */ + const rtdSubmodule = { + name: SUBMODULE_NAME, + getBidRequestData: getRealTimeData, + init: init + }; + + submodule(MODULE_NAME, rtdSubmodule); + + return { + getRealTimeData, + rtdSubmodule, + storage + }; +} + +export const { getRealTimeData, rtdSubmodule: anonymisedRtdSubmodule, storage } = createRtdProvider('anonymised'); diff --git a/modules/anonymisedRtdProvider.md b/modules/anonymisedRtdProvider.md new file mode 100644 index 00000000000..2ff2597690b --- /dev/null +++ b/modules/anonymisedRtdProvider.md @@ -0,0 +1,54 @@ +### Overview + +Anonymised is a data anonymization technology for privacy-preserving advertising. Publishers and advertisers are able to target and retarget custom audience segments covering 100% of consented audiences. +Anonymised’s Real-time Data Provider automatically obtains segment IDs from the Anonymised on-domain script (via localStorage) and passes them to the bid-stream. + +### Integration + + - Build the anonymisedRtd module into the Prebid.js package with: + + ```bash + gulp build --modules=anonymisedRtdProvider,... + ``` + + - Use `setConfig` to instruct Prebid.js to initilaize the anonymisedRtdProvider module, as specified below. + +### Configuration + +```javascript + pbjs.setConfig({ + realTimeData: { + dataProviders: [ + { + name: "anonymised", + waitForIt: true, + params: { + cohortStorageKey: "cohort_ids", + bidders: ["smartadserver", "appnexus"], + segtax: 1000 + } + } + ] + } + }); + ``` + + ### Config Syntax details +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Anonymised Rtd module name | 'anonymised' always| +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params.cohortStorageKey | String | the `localStorage` key, under which Anonymised Marketing Tag stores the segment IDs | 'cohort_ids' always | +| params.bidders | Array | Bidders with which to share segment information | Optional | +| params.segtax | Integer | The taxonomy for Anonymised | '1000' always | + +Please note that anonymisedRtdProvider should be integrated into the publisher website along with the [Anonymised Marketing Tag](https://support.anonymised.io/integrate/marketing-tag). +Please reach out to Anonymised [representative](mailto:support@anonymised.io) if you have any questions or need further help to integrate Prebid, anonymisedRtdProvider, and Anonymised Marketing Tag + +### Testing +To view an example of available segments returned by Anonymised: +```bash +gulp serve --modules=rtdModule,anonymisedRtdProvider,pubmaticBidAdapter +``` +And then point your browser at: +"http://localhost:9999/integrationExamples/gpt/anonymised_segments_example.html" diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js index dd08a132b2d..8e6e3c20a64 100644 --- a/modules/idWardRtdProvider.js +++ b/modules/idWardRtdProvider.js @@ -1,107 +1,10 @@ /** * This module adds the ID Ward RTD provider to the real time data module * The {@link module:modules/realTimeData} module is required - * The module will poulate real-time data from ID Ward + * The module will populate real-time data from ID Ward * @module modules/idWardRtdProvider * @requires module:modules/realTimeData */ -import {getStorageManager} from '../src/storageManager.js'; -import {submodule} from '../src/hook.js'; -import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.js'; -import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; -/** - * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule - */ - -const MODULE_NAME = 'realTimeData'; -const SUBMODULE_NAME = 'idWard'; - -export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); -/** - * Add real-time data & merge segments. - * @param ortb2 object to merge into - * @param {Object} rtd - */ -function addRealTimeData(ortb2, rtd) { - if (isPlainObject(rtd.ortb2)) { - logMessage('idWardRtdProvider: merging original: ', ortb2); - logMessage('idWardRtdProvider: merging in: ', rtd.ortb2); - mergeDeep(ortb2, rtd.ortb2); - } -} - -/** - * Try parsing stringified array of segment IDs. - * @param {String} data - */ -function tryParse(data) { - try { - return JSON.parse(data); - } catch (err) { - logError(`idWardRtdProvider: failed to parse json:`, data); - return null; - } -} - -/** - * Real-time data retrieval from ID Ward - * @param {Object} reqBidsConfigObj - * @param {function} onDone - * @param {Object} rtdConfig - * @param {Object} userConsent - */ -export function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { - if (rtdConfig && isPlainObject(rtdConfig.params)) { - const jsonData = storage.getDataFromLocalStorage(rtdConfig.params.cohortStorageKey) - - if (!jsonData) { - return; - } - - const segments = tryParse(jsonData); - - if (segments) { - const udSegment = { - name: 'id-ward.com', - ext: { - segtax: rtdConfig.params.segtax - }, - segment: segments.map(x => ({id: x})) - } - - logMessage('idWardRtdProvider: user.data.segment: ', udSegment); - const data = { - rtd: { - ortb2: { - user: { - data: [ - udSegment - ] - } - } - } - }; - addRealTimeData(reqBidsConfigObj.ortb2Fragments?.global, data.rtd); - onDone(); - } - } -} - -/** - * Module init - * @param {Object} provider - * @param {Object} userConsent - * @return {boolean} - */ -function init(provider, userConsent) { - return true; -} - -/** @type {RtdSubmodule} */ -export const idWardRtdSubmodule = { - name: SUBMODULE_NAME, - getBidRequestData: getRealTimeData, - init: init -}; +import { createRtdProvider } from './anonymisedRtdProvider.js';/* eslint prebid/validate-imports: "off" */ -submodule(MODULE_NAME, idWardRtdSubmodule); +export const { getRealTimeData, rtdSubmodule: idWardRtdSubmodule, storage } = createRtdProvider('idWard'); diff --git a/modules/idWardRtdProvider.md b/modules/idWardRtdProvider.md index 5a44bfa49f3..1c9f0654de6 100644 --- a/modules/idWardRtdProvider.md +++ b/modules/idWardRtdProvider.md @@ -1,3 +1,10 @@ +> **Warning!** +> +> The **idWardRtdProvider** module has been renamed to [anonymisedRtdProvider](anonymisedRtdProvider.md) in light of the company's rebranding. +> **idWardRtdProvider** module is maintained for backward compatibility until the next major Prebid release. +> +> Please use anonymisedRtdProvider instead of idWardRtdProvider in your Prebid integration. + ### Overview ID Ward is a data anonymization technology for privacy-preserving advertising. Publishers and advertisers are able to target and retarget custom audience segments covering 100% of consented audiences. @@ -41,4 +48,4 @@ To view an example of available segments returned by Id Ward: ‘gulp serve --modules=rtdModule,idWardRtdProvider,pubmaticBidAdapter ``` and then point your browser at: -"http://localhost:9999/integrationExamples/gpt/idward_segments_example.html" +"http://localhost:9999/integrationExamples/gpt/idward_segments_example.html" \ No newline at end of file diff --git a/test/spec/modules/anonymisedRtdProvider_spec.js b/test/spec/modules/anonymisedRtdProvider_spec.js new file mode 100644 index 00000000000..89115e5e740 --- /dev/null +++ b/test/spec/modules/anonymisedRtdProvider_spec.js @@ -0,0 +1,214 @@ +import {config} from 'src/config.js'; +import {getRealTimeData, anonymisedRtdSubmodule, storage} from 'modules/anonymisedRtdProvider.js'; + +describe('anonymisedRtdProvider', function() { + let getDataFromLocalStorageStub; + + const testReqBidsConfigObj = { + adUnits: [ + { + bids: ['bid1', 'bid2'] + } + ] + }; + + const onDone = function() { return true }; + + const cmoduleConfig = { + 'name': 'anonymised', + 'params': { + 'cohortStorageKey': 'cohort_ids' + } + } + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('anonymisedRtdSubmodule', function() { + it('successfully instantiates', function () { + expect(anonymisedRtdSubmodule.init()).to.equal(true); + }); + }); + + describe('Get Real-Time Data', function() { + it('gets rtd from local storage and set to ortb2.user.data', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + bidders: ['smartadserver'], + segtax: 503 + } + }; + + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; + + const rtdUserObj1 = { + name: 'anonymised.io', + ext: { + segtax: 503 + }, + segment: [ + { + id: 'TCZPQOWPEJG3MJOTUQUF793A' + }, + { + id: '93SUG3H540WBJMYNT03KX8N3' + } + ] + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.keywords).to.be.undefined; + }); + + it('gets rtd from local storage and set to ortb2.user.keywords for appnexus bidders parameter', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + bidders: ['smartadserver', 'appnexus'], + segtax: 503 + } + }; + + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; + + const rtdUserObj1 = { + name: 'anonymised.io', + ext: { + segtax: 503 + }, + segment: [ + { + id: 'TCZPQOWPEJG3MJOTUQUF793A' + }, + { + id: '93SUG3H540WBJMYNT03KX8N3' + } + ] + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.keywords).to.include('perid=TCZPQOWPEJG3MJOTUQUF793A'); + expect(bidConfig.ortb2Fragments.global.user.keywords).to.include('perid=93SUG3H540WBJMYNT03KX8N3'); + }); + + it('gets rtd from local storage and set to ortb2.user.data if `bidders` parameter undefined', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; + + const rtdUserObj1 = { + name: 'anonymised.io', + ext: { + segtax: 503 + }, + segment: [ + { + id: 'TCZPQOWPEJG3MJOTUQUF793A' + }, + { + id: '93SUG3H540WBJMYNT03KX8N3' + } + ] + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.keywords).to.be.undefined; + }); + + it('do not set rtd if `cohortStorageKey` parameter undefined', function() { + const rtdConfig = { + params: { + bidders: ['smartadserver'] + } + }; + + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['randomsegmentid'])); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user).to.be.undefined; + }); + + it('do not set rtd if local storage empty', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = {}; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(null); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2).to.be.undefined; + }); + + it('do not set rtd if local storage has incorrect value', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = {}; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns('wrong cohort ids value'); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2).to.be.undefined; + }); + + it('should initialize and return with config', function () { + expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) + }); + }); +}); diff --git a/test/spec/modules/idWardRtdProvider_spec.js b/test/spec/modules/idWardRtdProvider_spec.js index 924a3794c7b..d1601f058ff 100644 --- a/test/spec/modules/idWardRtdProvider_spec.js +++ b/test/spec/modules/idWardRtdProvider_spec.js @@ -52,7 +52,7 @@ describe('idWardRtdProvider', function() { }; const rtdUserObj1 = { - name: 'id-ward.com', + name: 'anonymised.io', ext: { segtax: 503 }, @@ -109,7 +109,7 @@ describe('idWardRtdProvider', function() { expect(config.getConfig().ortb2).to.be.undefined; }); - it('should initalise and return with config', function () { + it('should initialize and return with config', function () { expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) }); });