From 0c6c3800b5cf2e79649a6fffe4bda25a7051217a Mon Sep 17 00:00:00 2001
From: Bohdan V <25197509+BohdanVV@users.noreply.github.com>
Date: Fri, 20 Jun 2025 16:06:03 +0200
Subject: [PATCH 1/3] Optable RTD submodule: remove `bundleUrl` param, simplify
the module, update usage of Optable SDK
---
.../gpt/optableRtdProvider_example.html | 292 +++++++++++-------
modules/optableRtdProvider.js | 96 ++----
modules/optableRtdProvider.md | 52 +---
src/adloader.js | 1 -
test/spec/modules/optableRtdProvider_spec.js | 125 ++------
5 files changed, 254 insertions(+), 312 deletions(-)
diff --git a/integrationExamples/gpt/optableRtdProvider_example.html b/integrationExamples/gpt/optableRtdProvider_example.html
index 5e1c8a77cb9..17a634794b7 100644
--- a/integrationExamples/gpt/optableRtdProvider_example.html
+++ b/integrationExamples/gpt/optableRtdProvider_example.html
@@ -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 {
@@ -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;
@@ -47,8 +68,8 @@
}
}
- #enriched-optable-data {
- font-size: 12px;
+ pre {
+ font-size: 13px;
max-width: 100%;
overflow-x: scroll;
padding: 16px;
@@ -83,114 +104,132 @@
pbjs.initAdserverSet = true;
}
- pbjs.que.push(function () {
- var adUnits = [
- {
- code: '/22081946781/web-sdk-demo-gam360/header-ad',
- mediaTypes: {
- banner: {
- sizes: [[728, 90]],
+ function startBidding() {
+ pbjs.que.push(function () {
+ var adUnits = [
+ {
+ code: '/22081946781/web-sdk-demo-gam360/header-ad',
+ mediaTypes: {
+ banner: {
+ sizes: [[728, 90]],
+ },
},
+ bids: [{
+ bidder: 'pubmatic',
+ params: {
+ publisherId: "156209",
+ }
+ }]
},
- bids: [{
- bidder: 'appnexus',
- params: {
- placementId: 13232392,
- }
- }]
- },
- {
- code: '/22081946781/web-sdk-demo-gam360/box-ad',
- mediaTypes: {
- banner: {
- sizes: [[250, 250], [300, 250], [200, 200]],
+ {
+ code: '/22081946781/web-sdk-demo-gam360/box-ad',
+ mediaTypes: {
+ banner: {
+ sizes: [[250, 250], [300, 250], [200, 200]],
+ },
},
+ bids: [{
+ bidder: 'pubmatic',
+ params: {
+ publisherId: "156209",
+ }
+ }]
},
- bids: [{
- bidder: 'appnexus',
- params: {
- placementId: 13232392,
- }
- }]
- },
- {
- code: '/22081946781/web-sdk-demo-gam360/footer-ad',
- mediaTypes: {
- banner: {
- sizes: [[728, 90]],
+ {
+ code: '/22081946781/web-sdk-demo-gam360/footer-ad',
+ mediaTypes: {
+ banner: {
+ sizes: [[728, 90]],
+ },
},
+ bids: [{
+ bidder: 'pubmatic',
+ params: {
+ publisherId: "156209",
+ }
+ }]
},
- bids: [{
- bidder: 'appnexus',
- params: {
- placementId: 13232392,
- }
- }]
- },
- ];
+ ];
- 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
- 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);
- // console.warn('Entering custom RTD handler');
- // console.warn('reqBidsConfigObj', reqBidsConfigObj);
- // console.warn('optableExtraData', optableExtraData);
- // console.warn('mergeFn', mergeFn);
- //
- // // Call Optable DCN for targeting data and return the ORTB2 object
- // const targetingData = await optableBundle.instance.targeting();
- //
- // if (!targetingData || !targetingData.ortb2) {
- // return;
- // }
- //
- // mergeFn(
- // reqBidsConfigObj.ortb2Fragments.global,
- // targetingData.ortb2,
- // );
- // }
+ 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: 50, // should be set lower in production use
+ dataProviders: [
+ {
+ name: 'optable',
+ waitForIt: true,
+ params: {
+ // adserverTargeting: false,
+ // handleRtd: async (reqBidsConfigObj, optableExtraData, mergeFn) => {
+ // const optableBundle = /** @type {Object} */ (window.optable);
+ // console.warn('Entering custom RTD handler');
+ // console.warn('reqBidsConfigObj', reqBidsConfigObj);
+ // console.warn('optableExtraData', optableExtraData);
+ // console.warn('mergeFn', mergeFn);
+ //
+ // // Call Optable DCN for targeting data and return the ORTB2 object
+ // const targetingData = await optableBundle.instance.targeting();
+ //
+ // if (!targetingData || !targetingData.ortb2) {
+ // return;
+ // }
+ //
+ // mergeFn(
+ // reqBidsConfigObj.ortb2Fragments.global,
+ // targetingData.ortb2,
+ // );
+ // }
+ }
}
- }
- ]
- },
- });
+ ]
+ },
+ });
- pbjs.addAdUnits(adUnits);
+ pbjs.addAdUnits(adUnits);
- pbjs.onEvent('bidRequested', function (data) {
- 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);
- });
- } catch (e) {
- console.error('Exception while trying to display enriched data', e);
- }
- });
+ pbjs.onEvent('bidRequested', function (data) {
+ try {
+ window.optable.cmd.push(() => {
+ document.getElementById('enriched-optable').style.display = 'block';
+ 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);
+ }
+ });
- pbjs.requestBids({
- timeout: PREBID_TIMEOUT,
- bidsBackHandler: function (bidResponses) {
- initAdserver();
- }
+ pbjs.requestBids({
+ timeout: PREBID_TIMEOUT,
+ bidsBackHandler: function (bidResponses) {
+ initAdserver();
+ }
+ });
});
+ setTimeout(initAdserver, FAILSAFE_TIMEOUT);
+ }
+
+ const OPTABLE_INSTANCE_NAME = 'instance'; // This should match the instance name in the Optable bundle
+
+ window.optable = window.optable || { cmd: [] };
+ window.optable.cmd.push(() => {
+ console.log('Optable:', window.optable);
+ console.log('Optable instance ready:', window.optable?.[OPTABLE_INSTANCE_NAME]);
+ const targetingIsCached = window.optable?.[OPTABLE_INSTANCE_NAME]?.targetingFromCache();
+ if (!targetingIsCached || !targetingIsCached.ortb2) {
+ window.optable?.[OPTABLE_INSTANCE_NAME]?.targeting();
+ }
+
+ startBidding();
});
- setTimeout(initAdserver, FAILSAFE_TIMEOUT);
-
web-sdk-demo-gam360/box-ad
+ web-sdk-demo-gam360/box-ad
- web-sdk-demo-gam360/footer-ad
+ web-sdk-demo-gam360/footer-ad
No response
```
-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 50 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: 50,
dataProviders: [
{
name: 'optable',
waitForIt: true, // should be true, otherwise the auctionDelay will be ignored
params: {
- bundleUrl: '
',
adserverTargeting: '',
},
},
@@ -55,48 +52,13 @@ 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` | |
+| waitForIt | Boolean | Should be set `true` together with `auctionDelay: 50` | `false` | |
| params | Object | | | |
-| params.bundleUrl | String | Optable bundle URL | `null` | 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 |
@@ -125,7 +87,7 @@ A `handleRtd` function implementation has access to its surrounding context incl
If you want to see an example of how the optable RTD module works, run the following command:
```bash
-gulp serve --modules=optableRtdProvider,consentManagementGpp,consentManagementTcf,appnexusBidAdapter
+gulp serve --modules=optableRtdProvider,pubmaticBidAdapter
```
and then open the following URL in your browser:
@@ -138,4 +100,4 @@ Open the browser console to see the logs.
Any suggestions or questions can be directed to [prebid@optable.co](mailto:prebid@optable.co).
-Alternatively please open a new [issue](https://github.com/prebid/prebid-server-java/issues/new) or [pull request](https://github.com/prebid/prebid-server-java/pulls) in this repository.
+Alternatively, please open a new [issue](https://github.com/prebid/Prebid.js/issues/new) or [pull request](https://github.com/prebid/Prebid.js/pulls) in this repository.
diff --git a/src/adloader.js b/src/adloader.js
index a170af11f68..5e5932f99ea 100644
--- a/src/adloader.js
+++ b/src/adloader.js
@@ -36,7 +36,6 @@ const _approvedLoadExternalJSList = [
'wurfl',
'nodalsAi',
'anonymised',
- 'optable',
// UserId Submodules
'justtag',
'tncId',
diff --git a/test/spec/modules/optableRtdProvider_spec.js b/test/spec/modules/optableRtdProvider_spec.js
index 7aa4be3c8b2..536766eb393 100644
--- a/test/spec/modules/optableRtdProvider_spec.js
+++ b/test/spec/modules/optableRtdProvider_spec.js
@@ -1,7 +1,6 @@
import {
parseConfig,
defaultHandleRtd,
- mergeOptableData,
getBidRequestData,
getTargetingData,
optableSubmodule,
@@ -12,42 +11,22 @@ describe('Optable RTD Submodule', function () {
it('parses valid config correctly', function () {
const config = {
params: {
- bundleUrl: 'https://cdn.optable.co/bundle.js',
adserverTargeting: true,
handleRtd: () => {}
}
};
expect(parseConfig(config)).to.deep.equal({
- bundleUrl: 'https://cdn.optable.co/bundle.js',
adserverTargeting: true,
handleRtd: config.params.handleRtd,
});
});
- it('trims bundleUrl if it contains extra spaces', function () {
- const config = {params: {bundleUrl: ' https://cdn.optable.co/bundle.js '}};
- expect(parseConfig(config).bundleUrl).to.equal('https://cdn.optable.co/bundle.js');
- });
-
- it('throws an error for invalid bundleUrl format', function () {
- expect(() => parseConfig({params: {bundleUrl: 'invalidURL'}})).to.throw();
- expect(() => parseConfig({params: {bundleUrl: 'www.invalid.com'}})).to.throw();
- });
-
- it('throws an error for non-HTTPS bundleUrl', function () {
- expect(() => parseConfig({params: {bundleUrl: 'http://cdn.optable.co/bundle.js'}})).to.throw();
- expect(() => parseConfig({params: {bundleUrl: '//cdn.optable.co/bundle.js'}})).to.throw();
- expect(() => parseConfig({params: {bundleUrl: '/bundle.js'}})).to.throw();
- });
-
it('defaults adserverTargeting to true if missing', function () {
- expect(parseConfig(
- {params: {bundleUrl: 'https://cdn.optable.co/bundle.js'}}
- ).adserverTargeting).to.be.true;
+ expect(parseConfig({ params: {} }).adserverTargeting).to.be.true;
});
it('throws an error if handleRtd is not a function', function () {
- expect(() => parseConfig({params: {handleRtd: 'notAFunction'}})).to.throw();
+ expect(() => parseConfig({ params: { handleRtd: 'notAFunction' } })).to.throw();
});
});
@@ -56,10 +35,10 @@ describe('Optable RTD Submodule', function () {
beforeEach(() => {
sandbox = sinon.createSandbox();
- reqBidsConfigObj = {ortb2Fragments: {global: {}}};
+ reqBidsConfigObj = { ortb2Fragments: { global: {} } };
mergeFn = sinon.spy();
window.optable = {
- instance: {
+ rtd: {
targeting: sandbox.stub(),
targetingFromCache: sandbox.stub(),
},
@@ -71,63 +50,38 @@ describe('Optable RTD Submodule', function () {
});
it('merges valid targeting data into the global ORTB2 object', async function () {
- const targetingData = {ortb2: {user: {ext: {optable: 'testData'}}}};
- window.optable.instance.targetingFromCache.returns(targetingData);
- window.optable.instance.targeting.resolves(targetingData);
+ const targetingData = { ortb2: { user: { ext: { optable: 'testData' } } } };
+ window.optable.rtd.targetingFromCache.returns(targetingData);
+ window.optable.rtd.targeting.resolves(targetingData);
await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn);
expect(mergeFn.calledWith(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2)).to.be.true;
});
it('does nothing if targeting data is missing the ortb2 property', async function () {
- window.optable.instance.targetingFromCache.returns({});
- window.optable.instance.targeting.resolves({});
+ window.optable.rtd.targetingFromCache.returns({});
+ window.optable.rtd.targeting.resolves({});
await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn);
expect(mergeFn.called).to.be.false;
});
it('uses targeting data from cache if available', async function () {
- const targetingData = {ortb2: {user: {ext: {optable: 'testData'}}}};
- window.optable.instance.targetingFromCache.returns(targetingData);
+ const targetingData = { ortb2: { user: { ext: { optable: 'testData' } } } };
+ window.optable.rtd.targetingFromCache.returns(targetingData);
await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn);
expect(mergeFn.calledWith(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2)).to.be.true;
});
- it('calls targeting function if no data is found in cache', async function () {
- const targetingData = {ortb2: {user: {ext: {optable: 'testData'}}}};
- window.optable.instance.targetingFromCache.returns(null);
- window.optable.instance.targeting.resolves(targetingData);
+ it("doesn't call targeting function if no data is found in cache", async function () {
+ const targetingData = { ortb2: { user: { ext: { optable: 'testData' } } } };
+ window.optable.rtd.targetingFromCache.returns(null);
+ window.optable.rtd.targeting.resolves(targetingData);
await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn);
- expect(mergeFn.calledWith(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2)).to.be.true;
- });
- });
-
- describe('mergeOptableData', function () {
- let sandbox, mergeFn, handleRtdFn, reqBidsConfigObj;
-
- beforeEach(() => {
- sandbox = sinon.createSandbox();
- mergeFn = sinon.spy();
- reqBidsConfigObj = {ortb2Fragments: {global: {}}};
- });
-
- afterEach(() => {
- sandbox.restore();
- });
-
- it('calls handleRtdFn synchronously if it is a regular function', async function () {
- handleRtdFn = sinon.spy();
- await mergeOptableData(handleRtdFn, reqBidsConfigObj, {}, mergeFn);
- expect(handleRtdFn.calledOnceWith(reqBidsConfigObj, {}, mergeFn)).to.be.true;
- });
-
- it('calls handleRtdFn asynchronously if it is an async function', async function () {
- handleRtdFn = sinon.stub().resolves();
- await mergeOptableData(handleRtdFn, reqBidsConfigObj, {}, mergeFn);
- expect(handleRtdFn.calledOnceWith(reqBidsConfigObj, {}, mergeFn)).to.be.true;
+ expect(mergeFn.calledWith(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2)).to.be.false;
+ expect(window.optable.rtd.targeting.called).to.be.false;
});
});
@@ -136,12 +90,11 @@ describe('Optable RTD Submodule', function () {
beforeEach(() => {
sandbox = sinon.createSandbox();
- reqBidsConfigObj = {ortb2Fragments: {global: {}}};
+ reqBidsConfigObj = { ortb2Fragments: { global: {} } };
callback = sinon.spy();
- moduleConfig = {params: {bundleUrl: 'https://cdn.optable.co/bundle.js'}};
+ moduleConfig = { params: {} };
- sandbox.stub(window, 'optable').value({cmd: []});
- sandbox.stub(window.document, 'createElement');
+ sandbox.stub(window, 'optable').value({ cmd: [] });
sandbox.stub(window.document, 'head');
});
@@ -149,20 +102,12 @@ describe('Optable RTD Submodule', function () {
sandbox.restore();
});
- it('loads Optable JS bundle if bundleUrl is provided', function () {
- getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {});
- expect(window.document.createElement.called).to.be.true;
- });
-
- it('uses existing Optable instance if no bundleUrl is provided', function () {
- moduleConfig.params.bundleUrl = null;
+ it('uses existing Optable instance', function () {
getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {});
expect(window.optable.cmd.length).to.equal(1);
});
it('calls callback when assuming the bundle is present', function (done) {
- moduleConfig.params.bundleUrl = null;
-
getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {});
// Check that the function is queued
@@ -176,9 +121,10 @@ describe('Optable RTD Submodule', function () {
}, 50);
});
- it('mergeOptableData catches error and executes callback when something goes wrong', function (done) {
- moduleConfig.params.bundleUrl = null;
- moduleConfig.params.handleRtd = () => { throw new Error('Test error'); };
+ it('getBidRequestData catches error and executes callback handleRtd throws an error', function (done) {
+ moduleConfig.params.handleRtd = () => {
+ throw new Error('Test error');
+ };
getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {});
@@ -192,7 +138,6 @@ describe('Optable RTD Submodule', function () {
});
it('getBidRequestData catches error and executes callback when something goes wrong', function (done) {
- moduleConfig.params.bundleUrl = null;
moduleConfig.params.handleRtd = 'not a function';
getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {});
@@ -205,15 +150,11 @@ describe('Optable RTD Submodule', function () {
}, 50);
});
- it("doesn't fail when optable is not available", function (done) {
+ it("doesn't fail when optable is not available", function () {
delete window.optable;
getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {});
- expect(window?.optable?.cmd?.length).to.be.undefined;
- setTimeout(() => {
- expect(callback.calledOnce).to.be.true;
- done();
- }, 50);
+ expect(window.optable.cmd.length).to.equal(1);
});
});
@@ -222,8 +163,8 @@ describe('Optable RTD Submodule', function () {
beforeEach(() => {
sandbox = sinon.createSandbox();
- moduleConfig = {params: {adserverTargeting: true}};
- window.optable = {instance: {targetingKeyValuesFromCache: sandbox.stub().returns({key1: 'value1'})}};
+ moduleConfig = { params: { adserverTargeting: true } };
+ window.optable = { rtd: { targetingKeyValuesFromCache: sandbox.stub().returns({ key1: 'value1' }) } };
});
afterEach(() => {
@@ -232,11 +173,11 @@ describe('Optable RTD Submodule', function () {
it('returns correct targeting data when Optable data is available', function () {
const result = getTargetingData(['adUnit1'], moduleConfig, {}, {});
- expect(result).to.deep.equal({adUnit1: {key1: 'value1'}});
+ expect(result).to.deep.equal({ adUnit1: { key1: 'value1' } });
});
it('returns empty object when no Optable data is found', function () {
- window.optable.instance.targetingKeyValuesFromCache.returns({});
+ window.optable.rtd.targetingKeyValuesFromCache.returns({});
expect(getTargetingData(['adUnit1'], moduleConfig, {}, {})).to.deep.equal({});
});
@@ -246,10 +187,10 @@ describe('Optable RTD Submodule', function () {
});
it('returns empty object when provided keys contain no data', function () {
- window.optable.instance.targetingKeyValuesFromCache.returns({key1: []});
+ window.optable.rtd.targetingKeyValuesFromCache.returns({ key1: [] });
expect(getTargetingData(['adUnit1'], moduleConfig, {}, {})).to.deep.equal({});
- window.optable.instance.targetingKeyValuesFromCache.returns({key1: [], key2: [], key3: []});
+ window.optable.rtd.targetingKeyValuesFromCache.returns({ key1: [], key2: [], key3: [] });
expect(getTargetingData(['adUnit1'], moduleConfig, {}, {})).to.deep.equal({});
});
});
From a11a606fd7352d77f2dad8993309151bf00bc8fa Mon Sep 17 00:00:00 2001
From: Eugene Dorfman
Date: Mon, 23 Jun 2025 15:18:07 +0200
Subject: [PATCH 2/3] minor correction to the doc
---
modules/optableRtdProvider.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/optableRtdProvider.md b/modules/optableRtdProvider.md
index 7ee7837cc81..6cc18da430e 100644
--- a/modules/optableRtdProvider.md
+++ b/modules/optableRtdProvider.md
@@ -17,7 +17,7 @@ Optable RTD submodule enriches the OpenRTB request by populating `user.ext.eids`
Compile the Optable RTD Module with other modules and adapters into your Prebid.js build:
```bash
-gulp build --modules="rtdModule,optableRtdProvider,pubmaticBidAdapter,..."
+gulp build --modules="rtdModule,optableRtdProvider,..."
```
> Note that Optable RTD module is dependent on the global real-time data module, `rtdModule`.
@@ -32,7 +32,7 @@ To use the module, you first need to load the Optable SDK on your page. You can
### Configuration
-This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to 50 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 at least 50 ms and make sure `waitForIt` is set to `true` for the `Optable` RTD provider.
```javascript
pbjs.setConfig({
From 9efb2753181891ca25c754807fcf057ebcca3f28 Mon Sep 17 00:00:00 2001
From: Bohdan V <25197509+BohdanVV@users.noreply.github.com>
Date: Wed, 25 Jun 2025 22:45:31 +0200
Subject: [PATCH 3/3] Optable RTD submodule: simplify example
---
.../gpt/optableRtdProvider_example.html | 205 ++++++++----------
1 file changed, 92 insertions(+), 113 deletions(-)
diff --git a/integrationExamples/gpt/optableRtdProvider_example.html b/integrationExamples/gpt/optableRtdProvider_example.html
index 17a634794b7..1074b84996e 100644
--- a/integrationExamples/gpt/optableRtdProvider_example.html
+++ b/integrationExamples/gpt/optableRtdProvider_example.html
@@ -104,132 +104,111 @@
pbjs.initAdserverSet = true;
}
- function startBidding() {
- pbjs.que.push(function () {
- var adUnits = [
- {
- code: '/22081946781/web-sdk-demo-gam360/header-ad',
- mediaTypes: {
- banner: {
- sizes: [[728, 90]],
- },
+ pbjs.que.push(function () {
+ var adUnits = [
+ {
+ code: '/22081946781/web-sdk-demo-gam360/header-ad',
+ mediaTypes: {
+ banner: {
+ sizes: [[728, 90]],
},
- bids: [{
- bidder: 'pubmatic',
- params: {
- publisherId: "156209",
- }
- }]
},
- {
- code: '/22081946781/web-sdk-demo-gam360/box-ad',
- mediaTypes: {
- banner: {
- sizes: [[250, 250], [300, 250], [200, 200]],
- },
+ bids: [{
+ bidder: 'pubmatic',
+ params: {
+ publisherId: "156209",
+ }
+ }]
+ },
+ {
+ code: '/22081946781/web-sdk-demo-gam360/box-ad',
+ mediaTypes: {
+ banner: {
+ sizes: [[250, 250], [300, 250], [200, 200]],
},
- bids: [{
- bidder: 'pubmatic',
- params: {
- publisherId: "156209",
- }
- }]
},
- {
- code: '/22081946781/web-sdk-demo-gam360/footer-ad',
- mediaTypes: {
- banner: {
- sizes: [[728, 90]],
- },
+ bids: [{
+ bidder: 'pubmatic',
+ params: {
+ publisherId: "156209",
+ }
+ }]
+ },
+ {
+ code: '/22081946781/web-sdk-demo-gam360/footer-ad',
+ mediaTypes: {
+ banner: {
+ sizes: [[728, 90]],
},
- bids: [{
- bidder: 'pubmatic',
- params: {
- 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: 50, // should be set lower in production use
- dataProviders: [
- {
- name: 'optable',
- waitForIt: true,
- params: {
- // adserverTargeting: false,
- // handleRtd: async (reqBidsConfigObj, optableExtraData, mergeFn) => {
- // const optableBundle = /** @type {Object} */ (window.optable);
- // console.warn('Entering custom RTD handler');
- // console.warn('reqBidsConfigObj', reqBidsConfigObj);
- // console.warn('optableExtraData', optableExtraData);
- // console.warn('mergeFn', mergeFn);
- //
- // // Call Optable DCN for targeting data and return the ORTB2 object
- // const targetingData = await optableBundle.instance.targeting();
- //
- // if (!targetingData || !targetingData.ortb2) {
- // return;
- // }
- //
- // mergeFn(
- // reqBidsConfigObj.ortb2Fragments.global,
- // targetingData.ortb2,
- // );
- // }
- }
- }
- ]
- },
- });
-
- pbjs.addAdUnits(adUnits);
+ bids: [{
+ bidder: 'pubmatic',
+ params: {
+ publisherId: "156209",
+ }
+ }]
+ },
+ ];
- pbjs.onEvent('bidRequested', function (data) {
- try {
- window.optable.cmd.push(() => {
- document.getElementById('enriched-optable').style.display = 'block';
- const jsonContent = JSON.stringify(data.ortb2.user, null, 2);
- if (jsonContent) {
- document.getElementById('enriched-optable-data').textContent = jsonContent;
+ pbjs.setConfig({
+ debug: true, // use only for testing, remove in production
+ realTimeData: {
+ auctionDelay: 50,
+ dataProviders: [
+ {
+ name: 'optable',
+ waitForIt: true,
+ params: {
+ // adserverTargeting: false,
+ // handleRtd: async (reqBidsConfigObj, optableExtraData, mergeFn) => {
+ // const optableBundle = /** @type {Object} */ (window.optable);
+ // console.warn('Entering custom RTD handler');
+ // console.warn('reqBidsConfigObj', reqBidsConfigObj);
+ // console.warn('optableExtraData', optableExtraData);
+ // console.warn('mergeFn', mergeFn);
+ //
+ // // Call Optable DCN for targeting data and return the ORTB2 object
+ // const targetingData = await optableBundle.instance.targeting();
+ //
+ // if (!targetingData || !targetingData.ortb2) {
+ // return;
+ // }
+ //
+ // mergeFn(
+ // reqBidsConfigObj.ortb2Fragments.global,
+ // targetingData.ortb2,
+ // );
+ // }
}
- });
- } catch (e) {
- console.error('Exception while trying to display enriched data', e);
- }
- });
-
- pbjs.requestBids({
- timeout: PREBID_TIMEOUT,
- bidsBackHandler: function (bidResponses) {
- initAdserver();
- }
- });
+ }
+ ]
+ },
});
- setTimeout(initAdserver, FAILSAFE_TIMEOUT);
- }
- const OPTABLE_INSTANCE_NAME = 'instance'; // This should match the instance name in the Optable bundle
+ pbjs.addAdUnits(adUnits);
- window.optable = window.optable || { cmd: [] };
- window.optable.cmd.push(() => {
- console.log('Optable:', window.optable);
- console.log('Optable instance ready:', window.optable?.[OPTABLE_INSTANCE_NAME]);
- const targetingIsCached = window.optable?.[OPTABLE_INSTANCE_NAME]?.targetingFromCache();
- if (!targetingIsCached || !targetingIsCached.ortb2) {
- window.optable?.[OPTABLE_INSTANCE_NAME]?.targeting();
- }
+ pbjs.onEvent('bidRequested', function (data) {
+ try {
+ window.optable.cmd.push(() => {
+ document.getElementById('enriched-optable').style.display = 'block';
+ 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);
+ }
+ });
- startBidding();
+ pbjs.requestBids({
+ timeout: PREBID_TIMEOUT,
+ bidsBackHandler: function (bidResponses) {
+ initAdserver();
+ }
+ });
});
+ setTimeout(initAdserver, FAILSAFE_TIMEOUT);