diff --git a/modules/.submodules.json b/modules/.submodules.json index 861c3c75dafa..9bb8f3c5d747 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -61,6 +61,7 @@ "iasRtdProvider", "jwplayerRtdProvider", "medianetRtdProvider", + "oneKeyRtdProvider", "optimeraRtdProvider", "permutiveRtdProvider", "reconciliationRtdProvider", diff --git a/modules/oneKeyRtdProvider.js b/modules/oneKeyRtdProvider.js new file mode 100644 index 000000000000..27511017676d --- /dev/null +++ b/modules/oneKeyRtdProvider.js @@ -0,0 +1,98 @@ + +import { submodule } from '../src/hook.js'; +import { mergeDeep, logError, logMessage, deepSetValue, generateUUID } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +const SUBMODULE_NAME = 'oneKey'; +const prefixLog = 'OneKey.RTD-module' + +// Pre-init OneKey if it has not load yet. +window.OneKey = window.OneKey || {}; +window.OneKey.queue = window.OneKey.queue || []; + +/** + * Generate the OneKey transmission and include it in the Bid Request. + * + * Modify the AdUnit object for each auction. + * It’s called as part of the requestBids hook. + * https://docs.prebid.org/dev-docs/add-rtd-submodule.html#getbidrequestdata + * + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {Object} rtdConfig + * @param {Object} userConsent + */ +const getTransmissionInBidRequest = (reqBidsConfigObj, done, rtdConfig) => { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + const transactionIds = adUnits.map(() => generateUUID()); + + logMessage(prefixLog, 'Queue seed generation.'); + window.OneKey.queue.push(() => { + logMessage(prefixLog, 'Generate a seed.'); + window.OneKey.generateSeed(transactionIds) + .then(onGetSeed(reqBidsConfigObj, rtdConfig, adUnits, transactionIds)) + .catch((err) => { logError(SUBMODULE_NAME, err.message); }) + .finally(done); + }); +} + +const onGetSeed = (reqBidsConfigObj, rtdConfig, adUnits, transactionIds) => { + return (seed) => { + if (!seed) { + logMessage(prefixLog, 'No seed generated.'); + return; + } + + logMessage(prefixLog, 'Has retrieved a seed:', seed); + addTransactionIdsToAdUnits(adUnits, transactionIds); + addTransmissionToOrtb2(reqBidsConfigObj, rtdConfig, seed); + }; +}; + +const addTransactionIdsToAdUnits = (adUnits, transactionIds) => { + adUnits.forEach((unit, index) => { + deepSetValue(unit, `ortb2Imp.ext.data.paf.transaction_id`, transactionIds[index]); + }); +}; + +const addTransmissionToOrtb2 = (reqBidsConfigObj, rtdConfig, seed) => { + const okOrtb2 = { + ortb2: { + user: { + ext: { + paf: { + transmission: { + seed + } + } + } + } + } + } + + const shareSeedWithAllBidders = !rtdConfig.params || !rtdConfig.params.bidders; + if (shareSeedWithAllBidders) { + // Change global first party data with OneKey + logMessage(prefixLog, 'set ortb2:', okOrtb2); + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, okOrtb2.ortb2); + } else { + // Change bidder-specific first party data with OneKey + logMessage(prefixLog, `set ortb2 for: ${rtdConfig.params.bidders.join(',')}`, okOrtb2); + rtdConfig.params.bidders.forEach(bidder => { + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { [bidder]: okOrtb2.ortb2 }); + }); + } +}; + +/** @type {RtdSubmodule} */ +export const oneKeyDataSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + init: () => true, + getBidRequestData: getTransmissionInBidRequest, +}; + +submodule('realTimeData', oneKeyDataSubmodule); diff --git a/modules/oneKeyRtdProvider.md b/modules/oneKeyRtdProvider.md new file mode 100644 index 000000000000..5be3bd66f16e --- /dev/null +++ b/modules/oneKeyRtdProvider.md @@ -0,0 +1,126 @@ +## OneKey Real-time Data Submodule + +The OneKey real-time data module in Prebid has been built so that publishers +can quickly and easily setup the OneKey Addressability Framework. +This module is used along with the oneKeyIdSystem to pass OneKey data to your partners. +Both modules are required. This module will pass transmission requests to your partners +while the oneKeyIdSystem will pass the oneKeyData. + +Background information: +- [prebid/addressability-framework](https://github.com/prebid/addressability-framework) +- [prebid/paf-mvp-implementation](https://github.com/prebid/paf-mvp-implementation) + +### Publisher Usage + +The OneKey RTD module depends on paf-lib.js existing in the page. + +Compile the OneKey RTD module into your Prebid build: + +`gulp build --modules=userId,oneKeyIdSystem,rtdModule,oneKeyRtdProvider,appnexusBidAdapter` + +Add the OneKey RTD provider to your Prebid config. In this example we will configure +a sample proxyHostName. See the "Parameter Descriptions" below for more detailed information +of the configuration parameters. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 5000, + dataProviders: [ + { + name: "paf", + waitForIt: true, + params: { + proxyHostName: "cmp.pafdemopublisher.com" + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the OneKey Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Always 'oneKey' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.proxyHostName | String | servername of the OneKey Proxy which will generate seeds. | Required | +| params.bidders | Array | List of bidders to restrict the data to. | Optional | + +### Data for bidders + +The data will provided to the bidders using the `ortb2` object. You can find the +format of the data at https://github.com/prebid/addressability-framework. +The following is an example of the format of the data: + +```json +"user": { + "ext": { + "paf": { + "transmission": { + "seed": { + "version": "0.1", + "transaction_ids": ["06df6992-691c-4342-bbb0-66d2a005d5b1", "d2cd0aa7-8810-478c-bd15-fb5bfa8138b8"], + "publisher": "cmp.pafdemopublisher.com", + "source": { + "domain": "cmp.pafdemopublisher.com", + "timestamp": 1649712888, + "signature": "turzZlXh9IqD5Rjwh4vWR78pKLrVsmwQrGr6fgw8TPgQVJSC8K3HvkypTV7lm3UaCi+Zzjl+9sd7Hrv87gdI8w==" + } + } + } + } + } +} +``` + + +```json +"ortb2Imp": { + "ext": { + "data": { + "paf": { + "transaction_id": "52d23fed-4f50-4c17-b07a-c458143e9d09" + } + } + } +} +``` + +### Bidder Responses + +Bidders who are part of the OneKey Addressability Framework and receive OneKey +transmissions are required to return transmission responses as outlined in +[prebid/addressability-framework](https://github.com/prebid/addressability-framework/blob/main/mvp-spec/ad-auction.md). Transmission responses should be appended to bids +along with the releveant content_id using the meta.paf field. The paf-lib will +be responsible for collecting all of the transmission responses. + +Below is an example of setting a transmission response: +```javascript +bid.meta.paf = { + "content_id": "90141190-26fe-497c-acee-4d2b649c2112", + "transmission": { + "version": "0.1", + "contents": [ + { + "transaction_id": "f55a401d-e8bb-4de1-a3d2-fa95619393e8", + "content_id": "90141190-26fe-497c-acee-4d2b649c2112" + } + ], + "status": "success", + "details": "", + "receiver": "dsp1.com", + "source": { + "domain": "dsp1.com", + "timestamp": 1639589531, + "signature": "d01c6e83f14b4f057c2a2a86d320e2454fc0c60df4645518d993b5f40019d24c" + }, + "children": [] + } +} +``` + diff --git a/test/spec/modules/oneKeyRtdProvider_spec.js b/test/spec/modules/oneKeyRtdProvider_spec.js new file mode 100644 index 000000000000..70023e35196e --- /dev/null +++ b/test/spec/modules/oneKeyRtdProvider_spec.js @@ -0,0 +1,152 @@ +import {oneKeyDataSubmodule} from 'modules/oneKeyRtdProvider.js'; +import {getAdUnits} from '../../fixtures/fixtures.js'; + +const defaultSeed = { + version: '0.1', + transaction_ids: [ + 'd566b02a-a6e2-4c87-98dc-f5623cd9e828', + 'f7ffe3cc-0d58-4ec4-b687-1d3d410a48fe' + ], + publisher: 'cmp.pafdemopublisher.com', + source: { + domain: 'cmp.pafdemopublisher.com', + timestamp: 1657116880, + signature: '6OmdrSGwagPpugGFuQ4VGjzqYadHxWIXPaLItk0vA1lmi/EQyRvNF5seXStfwKWRnC7HZlOIGSjA6g7HAuofWw==' + + } +}; + +const defaultOrb2WithTransmission = { + user: { + ext: { + paf: { + transmission: { + seed: defaultSeed + } + } + } + } +}; + +const defaultRtdConfig = { + params: { + proxyHostName: 'host' + } +}; + +describe('oneKeyDataSubmodule', () => { + var bidsConfig; + beforeEach(() => { + // Fresh bidsConfig because it can be altered + // during the tests. + bidsConfig = getReqBidsConfig(); + setUpOneKey(); + }); + + it('successfully instantiates', () => { + expect(oneKeyDataSubmodule.init()).to.equal(true); + }); + + it('call OneKey API once it is loaded', () => { + const done = sinon.spy(); + + oneKeyDataSubmodule.getBidRequestData(bidsConfig, done, defaultRtdConfig); + + expect(bidsConfig).to.eql(getReqBidsConfig()); + expect(done.callCount).to.equal(0); + expect(window.OneKey.queue.length).to.equal(1); + }); + + it('don\'t change anything without a seed', () => { + window.OneKey.generateSeed = (_transactionIds) => { + return Promise.resolve(undefined); + }; + + // Act + return new Promise(resolve => { + oneKeyDataSubmodule.getBidRequestData(bidsConfig, resolve, defaultRtdConfig); + executeOneKeyQueue(); + }) + + // Assert + .then(() => { + expect(bidsConfig).to.eql(getReqBidsConfig()); + }); + }); + + [ // Test cases + { + description: 'global orb2', + rtdConfig: defaultRtdConfig, + expectedFragment: { + global: { + ...defaultOrb2WithTransmission + }, + bidder: {} + } + }, + + { + description: 'bidder-specific orb2', + rtdConfig: { + params: { + proxyHostName: 'host', + bidders: [ 'bidder42', 'bidder24' ] + } + }, + expectedFragment: { + global: { }, + bidder: { + bidder42: { + ...defaultOrb2WithTransmission + }, + bidder24: { + ...defaultOrb2WithTransmission + } + } + } + } + ].forEach(testCase => { + it(`update adUnits with transaction-ids and transmission in ${testCase.description}`, () => { + // Act + return new Promise(resolve => { + oneKeyDataSubmodule.getBidRequestData(bidsConfig, resolve, testCase.rtdConfig); + executeOneKeyQueue(); + }) + + // Assert + .then(() => { + // Verify transaction-ids without equality + // because they are generated UUID. + bidsConfig.adUnits.forEach((adUnit) => { + expect(adUnit.ortb2Imp.ext.data.paf.transaction_id).to.not.be.undefined; + }); + expect(bidsConfig.ortb2Fragments).to.eql(testCase.expectedFragment); + }); + }); + }); +}); + +const getReqBidsConfig = () => { + return { + adUnits: getAdUnits(), + ortb2Fragments: { + global: {}, + bidder: {} + } + } +} + +const setUpOneKey = () => { + window.OneKey.queue = []; + OneKey.generateSeed = (_transactionIds) => { + return Promise.resolve(defaultSeed); + }; +} + +const executeOneKeyQueue = () => { + while (window.OneKey.queue.length > 0) { + window.OneKey.queue[0](); + window.OneKey.queue.shift(); + } +}