Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OneKey RTD Module: initial release #8686

Merged
merged 3 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"iasRtdProvider",
"jwplayerRtdProvider",
"medianetRtdProvider",
"oneKeyRtdProvider",
"optimeraRtdProvider",
"permutiveRtdProvider",
"reconciliationRtdProvider",
Expand Down
98 changes: 98 additions & 0 deletions modules/oneKeyRtdProvider.js
Original file line number Diff line number Diff line change
@@ -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);
127 changes: 127 additions & 0 deletions modules/oneKeyRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
## 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:
- [OneKey-Network/addressability-framework](https://github.com/OneKey-Network/addressability-framework)
- [OneKey-Network/OneKey-implementation](https://github.com/OneKey-Network/OneKey-implementation)

## Implementation for Publishers

### Integration

1) Compile the OneKey RTD Provider and the OneKey UserID sub-module into your Prebid build.

```
gulp build --modules=rtdModule,oneKeyRtdProvider
```

2) Publishers must register OneKey RTD Provider as a Real Time Data provider by using `setConfig`
to load a Prebid Config containing a `realTimeData.dataProviders` array:

```javascript
pbjs.setConfig({
...,
realTimeData: {
auctionDelay: 100,
dataProviders: [
{
name: 'oneKey',
waitForIt: true
}
]
}
});
```

3) Configure the OneKey RTD Provider with the bidders that are part of the OneKey community. If there is no bidders specified, the RTD provider
will share OneKey data with all adapters.

⚠️ This module works in association with a RTD module. See [oneKeyIdSystem](oneKeyIdSystem.md).

### Parameters

| 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 | | Optional |
| params.bidders | Array | List of bidders to restrict the data to. | Optional |

## Implementation for Bidders

### Bidder Requests

The data will provided to the bidders using the `ortb2` object.
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
[OneKey-Network/addressability-framework]https://github.com/OneKey-Network/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: []
}
}
```
152 changes: 152 additions & 0 deletions test/spec/modules/oneKeyRtdProvider_spec.js
Original file line number Diff line number Diff line change
@@ -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();
}
}