diff --git a/modules/optableRtdProvider.js b/modules/optableRtdProvider.js index a8a69ce2345..2dee05b2f1e 100644 --- a/modules/optableRtdProvider.js +++ b/modules/optableRtdProvider.js @@ -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} - */ -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 @@ -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(); } @@ -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) { @@ -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) { diff --git a/modules/optableRtdProvider.md b/modules/optableRtdProvider.md index 1250437f6f0..9c621a417a2 100644 --- a/modules/optableRtdProvider.md +++ b/modules/optableRtdProvider.md @@ -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 `` as been provided by Optable. The `instanceName` is optional and defaults to `prebid_instance` if not provided. ```html ``` -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: '', + instanceName: '', adserverTargeting: '', }, }, @@ -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("test@example.com"), - 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