Skip to content
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
213 changes: 213 additions & 0 deletions integrationExamples/realTimeData/datamageRtdProvider_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>OpsMage Prebid Test Page</title>

<!-- GPT -->
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js" crossorigin="anonymous"></script>
<script>
window.googletag = window.googletag || { cmd: [] };
window.gptSlot = null;

googletag.cmd.push(function () {
googletag.pubads().disableInitialLoad();

// ✅ Save slot reference so we can refresh JUST this slot
window.gptSlot = googletag.defineSlot(
'/50536250/designcon',
[[300, 250]],
'div-gpt-ad-1771335974713-0'
).addService(googletag.pubads());

googletag.pubads().enableSingleRequest();
googletag.enableServices();

// ✅ Display once (no request yet because disableInitialLoad)
googletag.display('div-gpt-ad-1771335974713-0');
});

function dumpGptTargeting(label) {
googletag.cmd.push(function () {
const pubads = googletag.pubads();
const keys = pubads.getTargetingKeys();
const pubadsMap = {};
keys.forEach(k => { pubadsMap[k] = pubads.getTargeting(k); });

console.log(label, 'PUBADS targeting:', pubadsMap);

if (window.gptSlot && typeof window.gptSlot.getTargetingMap === 'function') {
console.log(label, 'SLOT targeting:', window.gptSlot.getTargetingMap());
}
});
}
</script>

<!-- Prebid -->
<script async src="build/dev/prebid.js"></script>

<script>
var pbjs = window.pbjs = window.pbjs || {};
pbjs.que = pbjs.que || [];

var PREBID_TIMEOUT = 1500;
var DATAMAGE_WAIT_MS = 1200; // bump a bit so dm arrives more often

var adUnits = [{
code: 'div-gpt-ad-1771335974713-0',
mediaTypes: { banner: { sizes: [[300, 250]] } },
bids: [{
bidder: 'appnexus',
params: { placementId: 1234567 }
}]
}];

function looksLikeQueryStringBlob(s) {
// Detect "om_x=y&om_a=b" style blobs (these cause %3D and %26 in the request)
return typeof s === 'string' && /(^|&)om_[^=]+=/.test(s);
}

function normalizeValues(values) {
if (values == null) return [];
const arr = Array.isArray(values) ? values : [values];
return arr
.map(v => (v == null ? '' : String(v)))
.map(v => v.trim())
.filter(v => v.length);
}

function applyDatamageToGpt(gptMap) {
if (!gptMap || !window.googletag) return;

googletag.cmd.push(function () {
const pubads = googletag.pubads();

Object.keys(gptMap).forEach(function (key) {
let values = gptMap[key];

// ✅ Guard: never allow a whole querystring to be set as a value
if (looksLikeQueryStringBlob(values)) {
console.warn('Refusing to set querystring blob as targeting value for', key, values);
return;
}

const outVals = normalizeValues(values);
if (!outVals.length) return;

pubads.setTargeting(key, outVals);
});

// Useful log
dumpGptTargeting('[after applyDatamageToGpt]');
});
}

function waitForDatamage(maxWaitMs) {
return new Promise(function (resolve) {
if (window.__DATAMAGE_GPT_TARGETING__) {
resolve(window.__DATAMAGE_GPT_TARGETING__);
return;
}

let done = false;
function finish(val) {
if (done) return;
done = true;
window.removeEventListener('datamage:gptTargeting', onEvt);
resolve(val || null);
}

function onEvt(e) {
finish(e && e.detail);
}

window.addEventListener('datamage:gptTargeting', onEvt);
setTimeout(function () { finish(null); }, maxWaitMs);
});
}

pbjs.que.push(function () {
pbjs.setConfig({
debug: true,

consentManagement: {
allowAuctionWithoutConsent: true,
gdpr: {
cmpApi: 'static',
timeout: 0,
consentData: {
getTCData: {
tcString: 'CPAa+2APAa+2AAOACBENC1CoAP_AAH_AAAAAAwwxgAAAAA',
gdprApplies: true,
purpose: { consents: { 1: true, 2: true, 3: true, 4: true, 5: true } },
vendor: { consents: { 1: true }, legitimateInterests: { 1: true } }
}
}
},
usp: { cmpApi: 'static', timeout: 0, consentData: { getUSPData: { uspString: '1---' } } }
},

allowActivities: {
accessDevice: { rules: [{ action: 'allow' }] },
enrichUfpd: { rules: [{ action: 'allow' }] },
enrichEids: { rules: [{ action: 'allow' }] },
transmitTid: { rules: [{ action: 'allow' }] }
},

realTimeData: {
auctionDelay: 500,
dataProviders: [{
name: "datamage",
params: {
api_key: '8328309832',
selector: 'article',
auction_timeout_ms: 0,
fetch_timeout_ms: 2500
}
}]
}
});

pbjs.addAdUnits(adUnits);

pbjs.requestBids({
bidsBackHandler: async function () {
// HB keys (if bids)
pbjs.setTargetingForGPTAsync();

// OpsMage keys (even if no bids)
var dm = await waitForDatamage(DATAMAGE_WAIT_MS);
console.log('DATAMAGE map:', dm);
if (dm) applyDatamageToGpt(dm);
// Dump before refresh (this is the truth source)
dumpGptTargeting('[before refresh]');

// ✅ Refresh only our slot (safer + easier to inspect)
googletag.cmd.push(function () {
if (window.gptSlot) {
googletag.pubads().refresh([window.gptSlot]);
} else {
// fallback
googletag.pubads().refresh();
}
});
},
timeout: PREBID_TIMEOUT
});
});
</script>
</head>

<body>
<h2>OpsMage Prebid Test Page</h2>
<div id="div-gpt-ad-1771335974713-0" style="width:300px; height:250px;"></div>
<article>The tech world is currently buzzing over the highly anticipated market debut of fakeDSP, a trailblazing
startup poised to redefine the landscape of digital signal processing. Leveraging proprietary neuromorphic
algorithms and quantum-ready architecture, fakeDSP promises to accelerate real-time data synthesis by speeds
previously thought impossible. With early analysts calling it a definitive disruptor in both the
telecommunications and audio-engineering sectors, the company’s entrance signifies a major leap forward in how
complex signals are analyzed and reconstructed in the AI era.</article>
</body>

</html>
79 changes: 79 additions & 0 deletions modules/datamageRrdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# DataMage RTD Submodule

DataMage provides contextual classification (IAB Categories, Sentiment, Brands, Locations, Public Figures, Restricted Categories, and related IDs) that can be used to enrich demand signals and Google Ad Manager targeting.

## What it does

DataMage supports two outcomes in a Prebid + GAM setup:

1) **Passes data to bidders (ORTB2 enrichment)**
- DataMage fetches classification for the current page/content.
- The results are inserted into the bid request using OpenRTB (ORTB2), so bidders can receive the contextual signal.

2) **Passes data to Google Ad Manager (direct GPT targeting)**
- DataMage publishes a targeting map on the page (`window.__DATAMAGE_GPT_TARGETING__`) and emits an event (`datamage:gptTargeting`).
- Your page then sets those key-values into GPT/GAM using `googletag.pubads().setTargeting(...)`.
- This works **even if there are no bids**, as long as GPT is refreshed after targeting is set.

## Keys provided

DataMage can provide the following keys (when available):

- `om_iab_cat_ids`, `om_iab_cats`
- `om_brand_ids`, `om_brands`
- `om_sentiment_ids`, `om_sentiment`
- `om_location_ids`, `om_locations`
- `om_public_figure_ids`, `om_public_figures`
- `om_restricted_cat_ids`, `om_restricted_cats`
- `om_ops_mage_data_id`
- `om_res_score_bucket`
- `om_res_score` (only when present)

> Publisher domain keys are not used.

## Integration

### 1) Build Prebid.js with DataMage
Include the module in your Prebid build:
```bash
gulp build --modules=datamageRtdProvider,...
```

### 2) Enable the RTD provider in Prebid config
Example:
```js
pbjs.setConfig({
realTimeData: {
auctionDelay: 500,
dataProviders: [{
name: "datamage",
params: {
api_key: "YOUR_API_KEY",
selector: "article",
auction_timeout_ms: 0,
fetch_timeout_ms: 2500
}
}]
}
});
```

### 3) GAM (GPT) setup requirements
To ensure DataMage key-values are included in the GAM request:

1. Call `googletag.pubads().disableInitialLoad()` before the ad request.
2. Define the slot and keep a reference to it.
3. Call `googletag.display()` once (no request yet because initial load is disabled).
4. Run `pbjs.requestBids(...)`.
5. After bids return:
- Call `pbjs.setTargetingForGPTAsync()` (for hb_* keys when bids exist).
- Wait for DataMage targeting (`window.__DATAMAGE_GPT_TARGETING__` or the `datamage:gptTargeting` event).
- Apply DataMage targeting via `googletag.pubads().setTargeting(...)`.
6. Call `googletag.pubads().refresh([slot])` to make the GAM request.

This sequence ensures:
- DataMage targeting reaches GAM
- ORTB2 enrichment reaches bidders
- DataMage targeting can still be applied even if there are no bids

Note: Datamage api URLs will cache for 5 minutes, so you may not see content return until the cache has cleared.
Loading