Skip to content

'Module Change' Optable RTD module: remove unneeded logique. #13114

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
118 changes: 18 additions & 100 deletions modules/optableRtdProvider.js
Original file line number Diff line number Diff line change
@@ -1,87 +1,26 @@
import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
import {loadExternalScript} from '../src/adloader.js';
import {config} from '../src/config.js';
import {submodule} from '../src/hook.js';
import {deepAccess, mergeDeep, prefixLog} from '../src/utils.js';
import { submodule } from '../src/hook.js';
import { deepAccess, mergeDeep, prefixLog } from '../src/utils.js';

const MODULE_NAME = 'optable';
export const LOG_PREFIX = `[${MODULE_NAME} RTD]:`;
const optableLog = prefixLog(LOG_PREFIX);
const {logMessage, logWarn, logError} = optableLog;
const { logMessage, logWarn, logError } = optableLog;

/**
* Extracts the parameters for Optable RTD module from the config object passed at instantiation
* @param {Object} moduleConfig Configuration object for the module
*/
export const parseConfig = (moduleConfig) => {
let bundleUrl = deepAccess(moduleConfig, 'params.bundleUrl', null);
let instanceName = deepAccess(moduleConfig, 'params.instanceName', "prebid_instance");
let adserverTargeting = deepAccess(moduleConfig, 'params.adserverTargeting', true);
let handleRtd = deepAccess(moduleConfig, 'params.handleRtd', null);

// If present, trim the bundle URL
if (typeof bundleUrl === 'string') {
bundleUrl = bundleUrl.trim();
if (typeof instanceName === 'string') {
instanceName = instanceName.trim();
}

// Verify that bundleUrl is a valid URL: only secure (HTTPS) URLs are allowed
if (typeof bundleUrl === 'string' && bundleUrl.length && !bundleUrl.startsWith('https://')) {
throw new Error(
LOG_PREFIX + ' Invalid URL format for bundleUrl in moduleConfig. Only HTTPS URLs are allowed.'
);
}

if (handleRtd && typeof handleRtd !== 'function') {
throw new Error(LOG_PREFIX + ' handleRtd must be a function');
}

return {bundleUrl, adserverTargeting, handleRtd};
return { instanceName, adserverTargeting };
}

/**
* Default function to handle/enrich RTD data
* @param reqBidsConfigObj Bid request configuration object
* @param optableExtraData Additional data to be used by the Optable SDK
* @param mergeFn Function to merge data
* @returns {Promise<void>}
*/
export const defaultHandleRtd = async (reqBidsConfigObj, optableExtraData, mergeFn) => {
const optableBundle = /** @type {Object} */ (window.optable);
// Get targeting data from cache, if available
let targetingData = optableBundle?.instance?.targetingFromCache();
// If no targeting data is found in the cache, call the targeting function
if (!targetingData) {
// Call Optable DCN for targeting data and return the ORTB2 object
targetingData = await optableBundle?.instance?.targeting();
}
logMessage('Original targeting data from targeting(): ', targetingData);

if (!targetingData || !targetingData.ortb2) {
logWarn('No targeting data found');
return;
}

mergeFn(
reqBidsConfigObj.ortb2Fragments.global,
targetingData.ortb2,
);
logMessage('Prebid\'s global ORTB2 object after merge: ', reqBidsConfigObj.ortb2Fragments.global);
};

/**
* Get data from Optable and merge it into the global ORTB2 object
* @param {Function} handleRtdFn Function to handle RTD data
* @param {Object} reqBidsConfigObj Bid request configuration object
* @param {Object} optableExtraData Additional data to be used by the Optable SDK
* @param {Function} mergeFn Function to merge data
*/
export const mergeOptableData = async (handleRtdFn, reqBidsConfigObj, optableExtraData, mergeFn) => {
if (handleRtdFn.constructor.name === 'AsyncFunction') {
await handleRtdFn(reqBidsConfigObj, optableExtraData, mergeFn);
} else {
handleRtdFn(reqBidsConfigObj, optableExtraData, mergeFn);
}
};

/**
* @param {Object} reqBidsConfigObj Bid request configuration object
* @param {Function} callback Called on completion
Expand All @@ -90,37 +29,17 @@ export const mergeOptableData = async (handleRtdFn, reqBidsConfigObj, optableExt
*/
export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => {
try {
// Extract the bundle URL from the module configuration
const {bundleUrl, handleRtd} = parseConfig(moduleConfig);

const handleRtdFn = handleRtd || defaultHandleRtd;
const optableExtraData = config.getConfig('optableRtdConfig') || {};

if (bundleUrl) {
// If bundleUrl is present, load the Optable JS bundle
// by using the loadExternalScript function
logMessage('Custom bundle URL found in config: ', bundleUrl);

// Load Optable JS bundle and merge the data
loadExternalScript(bundleUrl, MODULE_TYPE_RTD, MODULE_NAME, () => {
logMessage('Successfully loaded Optable JS bundle');
mergeOptableData(handleRtdFn, reqBidsConfigObj, optableExtraData, mergeDeep).then(callback, callback);
}, document);
} else {
// At this point, we assume that the Optable JS bundle is already
// present on the page. If it is, we can directly merge the data
// by passing the callback to the optable.cmd.push function.
logMessage('Custom bundle URL not found in config. ' +
'Assuming Optable JS bundle is already present on the page');
window.optable = window.optable || { cmd: [] };
window.optable.cmd.push(() => {
logMessage('Optable JS bundle found on the page');
mergeOptableData(handleRtdFn, reqBidsConfigObj, optableExtraData, mergeDeep).then(callback, callback);
});
const { instanceName } = parseConfig(moduleConfig);

let targetingData = window?.optable?.[instanceName]?.targetingFromCache();
if (!targetingData || !targetingData.ortb2) {
logWarn('No targeting data found');
return;
}

mergeDeep(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2);
logMessage("Merged targeting data into global ORTB2 object");
} catch (error) {
// If an error occurs, log it and call the callback
// to continue with the auction
logError(error);
callback();
}
Expand All @@ -135,8 +54,7 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user
* @returns {Object} Targeting data
*/
export const getTargetingData = (adUnits, moduleConfig, userConsent, auction) => {
// Extract `adserverTargeting` from the module configuration
const {adserverTargeting} = parseConfig(moduleConfig);
const { instanceName, adserverTargeting } = parseConfig(moduleConfig);
logMessage('Ad Server targeting: ', adserverTargeting);

if (!adserverTargeting) {
Expand All @@ -147,7 +65,7 @@ export const getTargetingData = (adUnits, moduleConfig, userConsent, auction) =>
const targetingData = {};

// Get the Optable targeting data from the cache
const optableTargetingData = window?.optable?.instance?.targetingKeyValuesFromCache() || {};
const optableTargetingData = window?.optable?.[instanceName]?.targetingKeyValuesFromCache() || {};

// If no Optable targeting data is found, return an empty object
if (!Object.keys(optableTargetingData).length) {
Expand Down
71 changes: 7 additions & 64 deletions modules/optableRtdProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,27 @@ gulp build --modules="rtdModule,optableRtdProvider,appnexusBidAdapter,..."

### Preloading Optable SDK bundle

In order to use the module you first need to register with Optable and obtain a bundle URL. The bundle URL may be specified as a `bundleUrl` parameter to the script, or otherwise it can be added directly to the page source as:
In order to use the module you first need to load the Optable SDK in your page. You can do this by adding the following script tag to your page where `<bundleURL>` as been provided by Optable. The `instanceName` is optional and defaults to `prebid_instance` if not provided.

```html
<script async src="<bundleURL>"></script>
```

In this case bundleUrl parameter is not needed and the script will await bundle loading before delegating to it.

### Configuration

This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to 1000 ms and make sure `waitForIt` is set to `true` for the `Optable` RTD provider.
This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to 400 ms and make sure `waitForIt` is set to `true` for the `Optable` RTD provider.

```javascript
pbjs.setConfig({
debug: true, // we recommend turning this on for testing as it adds more logging
realTimeData: {
auctionDelay: 1000,
auctionDelay: 400,
dataProviders: [
{
name: 'optable',
waitForIt: true, // should be true, otherwise the auctionDelay will be ignored
params: {
bundleUrl: '<optional, your bundle url>',
instanceName: '<optional, prebid_instance by default, name of the Optable SDK instance to use>',
adserverTargeting: '<optional, true by default, set to true to also set GAM targeting keywords to ad slots>',
},
},
Expand All @@ -55,70 +53,15 @@ pbjs.setConfig({
});
```

### Additional input to the module

Optable bundle may use PPIDs (publisher provided IDs) from the `user.ext.eids` as input.

In addition, other arbitrary keys can be used as input, f.e. the following:

- `optableRtdConfig.email` - a SHA256-hashed user email
- `optableRtdConfig.phone` - a SHA256-hashed [E.164 normalized phone](https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization) (meaning a phone number consisting of digits and leading plus sign without spaces or any additional characters, f.e. a US number would be: `+12345678999`)
- `optableRtdConfig.postal_code` - a ZIP postal code string

Each of these identifiers is completely optional and can be provided through `pbjs.mergeConfig(...)` like so:

```javascript
pbjs.mergeConfig({
optableRtdConfig: {
email: await sha256("[email protected]"),
phone: await sha256("12345678999"),
postal_code: "61054"
}
})
```

Where `sha256` function can be defined as:

```javascript
async function sha256(input) {
return [...new Uint8Array(
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input))
)].map(b => b.toString(16).padStart(2, "0")).join("");
}
```

To handle PPIDs and the above input - a custom `handleRtd` function may need to be provided.

### Parameters

| Name | Type | Description | Default | Notes |
|--------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|----------|
| name | String | Real time data module name | Always `optable` | |
| waitForIt | Boolean | Should be set `true` together with `auctionDelay: 1000` | `false` | |
| params | Object | | | |
| params.bundleUrl | String | Optable bundle URL | `null` | Optional |
| waitForIt | Boolean | Should be set `true` together with `auctionDelay` to ensure proper timing of data enrichment | `false` | Required |
| params | Object | Configuration parameters for the Optable RTD provider | | |
| params.instanceName | String | Name of the Optable SDK instance to use | `"prebid_instance"` | Optional |
| params.adserverTargeting | Boolean | If set to `true`, targeting keywords will be passed to the ad server upon auction completion | `true` | Optional |
| params.handleRtd | Function | An optional function that uses Optable data to enrich `reqBidsConfigObj` with the real-time data. If not provided, the module will do a default call to Optable bundle. The function signature is `[async] (reqBidsConfigObj, optableExtraData, mergeFn) => {}` | `null` | Optional |

## Publisher Customized RTD Handler Function

When there is more pre-processing or post-processing needed prior/post calling Optable bundle - a custom `handleRtd`
function can be supplied to do that.
This function will also be responsible for the `reqBidsConfigObj` enrichment.
It will also receive the `optableExtraData` object, which can contain the extra data required for the enrichment and
shouldn't be shared with other RTD providers/bidders.
`mergeFn` parameter taken by `handleRtd` is a standard Prebid.js utility function that take an object to be enriched and
an object to enrich with: the second object's fields will be merged into the first one (also see the code of an example
mentioned below):

```javascript
mergeFn(
reqBidsConfigObj.ortb2Fragments.global, // or other nested object as needed
rtdData,
);
```

A `handleRtd` function implementation has access to its surrounding context including capturing a `pbjs` object, calling `pbjs.getConfig()` and f.e. reading off the `consentManagement` config to make the appropriate decision based on it.

## Example

Expand Down