Skip to content

Optable rtd provider update #4

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
109 changes: 86 additions & 23 deletions integrationExamples/gpt/optableRtdProvider_example.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
font-size: 1.5em;
line-height: 1.6;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0;
margin: 0 0 32px;
}

.container {
Expand All @@ -36,6 +36,27 @@
letter-spacing: -0.5px;
}

h2 {
font-size: 24px;
line-height: 1.35;
font-weight: 100;
letter-spacing: -0.5px;
}

p {
font-size: 15px;
line-height: 24px;
font-weight: 400;
/*letter-spacing: -0.5px;*/
}

ul > li {
font-size: 15px;
line-height: 24px;
font-weight: 400;
/*letter-spacing: -0.5px;*/
}

a {
color: #1eaedb;
text-decoration: underline;
Expand All @@ -47,8 +68,8 @@
}
}

#enriched-optable-data {
font-size: 12px;
pre {
font-size: 13px;
max-width: 100%;
overflow-x: scroll;
padding: 16px;
Expand Down Expand Up @@ -93,9 +114,9 @@
},
},
bids: [{
bidder: 'appnexus',
bidder: 'pubmatic',
params: {
placementId: 13232392,
publisherId: "156209",
}
}]
},
Expand All @@ -107,9 +128,9 @@
},
},
bids: [{
bidder: 'appnexus',
bidder: 'pubmatic',
params: {
placementId: 13232392,
publisherId: "156209",
}
}]
},
Expand All @@ -121,29 +142,23 @@
},
},
bids: [{
bidder: 'appnexus',
bidder: 'pubmatic',
params: {
placementId: 13232392,
publisherId: "156209",
}
}]
},
];

pbjs.setConfig({
optableRtdConfig: { // optional, check the doc for explanation
email: 'email-sha256-hash',
phone: 'phone-sha256-hash',
postal_code: 'postal_code',
},
debug: true, // use only for testing, remove in production
realTimeData: {
auctionDelay: 1000, // should be set lower in production use
auctionDelay: 50,
dataProviders: [
{
name: 'optable',
waitForIt: true,
params: {
// bundleUrl: "https://prebidtest.solutions.cdn.optable.co/public-assets/prebidtest-sdk.js?hello=world",
// adserverTargeting: false,
// handleRtd: async (reqBidsConfigObj, optableExtraData, mergeFn) => {
// const optableBundle = /** @type {Object} */ (window.optable);
Expand Down Expand Up @@ -176,7 +191,10 @@
try {
window.optable.cmd.push(() => {
document.getElementById('enriched-optable').style.display = 'block';
document.getElementById('enriched-optable-data').textContent = JSON.stringify(data.ortb2.user, null, 2);
const jsonContent = JSON.stringify(data.ortb2.user, null, 2);
if (jsonContent) {
document.getElementById('enriched-optable-data').textContent = jsonContent;
}
});
} catch (e) {
console.error('Exception while trying to display enriched data', e);
Expand Down Expand Up @@ -236,10 +254,54 @@
<hr class="delim" />
</div>
<div class="row">
<h1>Optable RTD module example</h1>
<h1>Prebid.js Integration with OptableRTD</h1>
<h2>Overview</h2>
<p>
This demo demonstrates how to integrate Optable's targeting capabilities with Prebid.js using the
<a href="https://docs.prebid.org/dev-docs/modules/optableRtdProvider.html" target="_blank">OptableRTD module</a>.
The implementation assumes Google Ad Manager (GAM) as the primary ad server, integrated via
<a href="https://developers.google.com/publisher-tag/guides/get-started" target="_blank">Google Publisher Tag (GPT)</a>.
</p>
<h2>Key Features</h2>
<ul>
<li>Loads active visitor cohorts and passes them to Prebid.js via OptableRTD</li>
<li>Automatically forwards matching cohorts to GAM for targeting</li>
<li>Supports local cache updates for improved targeting accuracy</li>
<li>Configurable GAM targeting override via localStorage</li>
</ul>
<h2>Implementation</h2>
<p>
The Prebid.js configuration requires minimal setup. Here's the essential configuration:
</p>
<pre><code>pbjs.mergeConfig({
debug: true, // use only for testing, remove in production
priceGranularity: "low",
userSync: {
iframeEnabled: true,
enabledBidders: ["pubmatic"]
},
realTimeData: {
auctionDelay: 50,
dataProviders: [{
name: 'optable',
waitForIt: true // Required to respect auctionDelay
}]
}
});</code></pre>
<h2>Important Notes</h2>
<ul>
<li>
<strong>Targeting Cache:</strong> For optimal targeting accuracy, call <code>targeting</code>
on page load to update the local cache, as the RTD module only uses cached values.
</li>
<li>
<strong>GAM Targeting Override:</strong> Set <code>localStorage.disableGamTargeting = "true"</code>
before page load to prevent the SDK from sending targeting data to GAM.
</li>
</ul>
</div>

<h3>web-sdk-demo-gam360/header-ad</h3>
<h2>web-sdk-demo-gam360/header-ad</h2>
<div id="div-gpt-ad-1598295788551-0">
<p>No response</p>
<script type="text/javascript">
Expand All @@ -249,7 +311,7 @@ <h3>web-sdk-demo-gam360/header-ad</h3>
</script>
</div>

<h3>web-sdk-demo-gam360/box-ad</h3>
<h2>web-sdk-demo-gam360/box-ad</h2>
<div id="div-gpt-ad-1598295897480-0">
<p>No response</p>
<script type="text/javascript">
Expand All @@ -259,7 +321,7 @@ <h3>web-sdk-demo-gam360/box-ad</h3>
</script>
</div>

<h3>web-sdk-demo-gam360/footer-ad</h3>
<h2>web-sdk-demo-gam360/footer-ad</h2>
<div id="div-gpt-ad-1598296001655-0">
<p>No response</p>
<script type="text/javascript">
Expand All @@ -270,8 +332,9 @@ <h3>web-sdk-demo-gam360/footer-ad</h3>
</div>

<div id="enriched-optable" style="display: none;">
<h3>Enriched ortb2.user data from Optable RTD module</h3>
<pre id="enriched-optable-data"></pre>
<h2>Enriched ortb2.user data from Optable RTD module</h2>
<pre id="enriched-optable-data">(this demo doesn't block the auction, so the data will be empty until the new auction is triggered)
(refresh the page)</pre>
</div>
</main>
</body>
Expand Down
96 changes: 26 additions & 70 deletions modules/optableRtdProvider.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,25 @@
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 { config } from '../src/config.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 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();
}

// 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 { adserverTargeting, handleRtd };
}

/**
Expand All @@ -46,14 +31,9 @@ export const parseConfig = (moduleConfig) => {
*/
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);
// Get targeting data from the cache
let targetingData = optableBundle?.rtd?.targetingFromCache();
logMessage('Original targeting data from targetingFromCache(): ', targetingData);

if (!targetingData || !targetingData.ortb2) {
logWarn('No targeting data found');
Expand All @@ -67,21 +47,6 @@ export const defaultHandleRtd = async (reqBidsConfigObj, optableExtraData, merge
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,34 +55,25 @@ 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);
// Extract custom handleRtd function from the module configuration
const { handleRtd } = parseConfig(moduleConfig);

const handleRtdFn = handleRtd || defaultHandleRtd;
// If no custom handleRtd function is provided, use the one from the Optable SDK or the default one
const handleRtdFn = handleRtd || window.optable?.rtd?.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);
});
}
// We assume that the Optable JS bundle is already present on the page.
// If it is, we can directly merge the data by calling the handleRtd function.
window.optable = window.optable || { cmd: [] };
window.optable.cmd.push(() => {
logMessage('Optable JS bundle found on the page');
try {
Promise.resolve(handleRtdFn(reqBidsConfigObj, optableExtraData, mergeDeep)).then(callback, callback);
} catch (error) {
logError('Error in handleRtd function: ', error);
callback();
}
});
} catch (error) {
// If an error occurs, log it and call the callback
// to continue with the auction
Expand All @@ -136,7 +92,7 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user
*/
export const getTargetingData = (adUnits, moduleConfig, userConsent, auction) => {
// Extract `adserverTargeting` from the module configuration
const {adserverTargeting} = parseConfig(moduleConfig);
const { adserverTargeting } = parseConfig(moduleConfig);
logMessage('Ad Server targeting: ', adserverTargeting);

if (!adserverTargeting) {
Expand All @@ -147,7 +103,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?.rtd?.targetingKeyValuesFromCache() || {};

// If no Optable targeting data is found, return an empty object
if (!Object.keys(optableTargetingData).length) {
Expand Down
Loading