Skip to content

Commit 07de08e

Browse files
bmilekicBosko Milekic
andauthored
feat: add ctxSegments() and ctxTargetingKeyValues() for contextual targeting (#292)
## Summary Adds a contextual-classification path to the SDK: fetch the DCN's taxonomy/category classifications for a page URL, cache them on the SDK instance, and turn the cached response into GAM-ready targeting key-values. ## What's new ### SDK methods (`lib/sdk.ts`, `lib/edge/contextual_segments.ts`) - **`OptableSDK.ctxSegments(url?)`** — POSTs to `/v1beta1/contextual` and returns `ContextualSegmentsResponse` with shape `{ classifications: { categories: [{ taxonomy, id, score, ... }] } }`. `url` defaults to `window.location.href`. The response is cached on the instance; calling again refreshes the cache. Pass-through behavior (no validation / normalization), matching `Targeting()` at the edge layer. - **`OptableSDK.ctxTargetingKeyValues(taxonomyKeys?)`** — reads the cached response and returns `Record<string, string[]>` of category ids grouped by taxonomy, ready for `googletag.pubads().setTargeting()`. Without a map, keys are the raw taxonomy values; with a map, keys are renamed and only the listed taxonomies are emitted (filter + rename). Ids are deduped, order preserved. - **`initContextual`** is widened from `boolean` to `boolean | ((response: ContextualSegmentsResponse) => void)`. When truthy, the SDK fires a pageview witness and `ctxSegments()` at init time (fire-and-forget), so the cache is populated automatically before any consumer reads it. When set to a callback function, the SDK additionally invokes the callback with the response once `ctxSegments()` resolves — useful for chaining an ad-server load (e.g. GAM) on the contextual response without making a second `ctxSegments()` call. The callback is not invoked if the request fails. ### Tests (`lib/sdk.test.ts`, `lib/test/handlers.ts`) - MSW handler for `/v1beta1/contextual`. - Breaking-change tests for both new methods. - Behavioral coverage: happy path, `window.location.href` default, empty / missing / malformed responses, categories spanning multiple taxonomies, cache population, default keying, rename, filter of unmapped taxonomies, that `initContextual: true` triggers a contextual request, and that `initContextual` as a function triggers the request and invokes the callback with the response. ### Docs & demos - **README** — documents the Contextual Segments API: `ctxSegments()` caching semantics, the `initContextual` precondition for `ctxTargetingKeyValues()`, the `boolean | callback` union type, the callback-driven `loadGAM` pattern, the explicit `ctxSegments().then(loadGAM)` alternative, key rename/allow-list, and the requirement that the URL already be classified by the DCN. - **Two new vanilla demo pages** — `demos/vanilla/targeting/ctx_segments.html.tpl` and `demos/vanilla/nocookies/targeting/ctx_segments.html.tpl` — call `ctxSegments()`, render categories grouped by taxonomy with score bars, show the derived GAM key-values, and display the raw JSON response. Both pages document the `initContextual` callback pattern in the intro section and in the GAM activation section. - Both templates registered in the Makefile `demo-html` target and linked from the Audience Targeting section of `demos/index.html` and `demos/index-nocookies.html`. Co-authored-by: Bosko Milekic <bmilekic@MBP-0041.local>
1 parent 771fe9a commit 07de08e

11 files changed

Lines changed: 1173 additions & 6 deletions

File tree

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ demo-html:
5252
envsubst $(DEMO_VARS) < demos/vanilla/targeting/gam360-cached.html.tpl > demos/vanilla/targeting/gam360-cached.html
5353
envsubst $(DEMO_VARS) < demos/vanilla/targeting/gam360-adcp.html.tpl > demos/vanilla/targeting/gam360-adcp.html
5454
envsubst $(DEMO_VARS) < demos/vanilla/targeting/prebid.html.tpl > demos/vanilla/targeting/prebid.html
55+
envsubst $(DEMO_VARS) < demos/vanilla/targeting/ctx_segments.html.tpl > demos/vanilla/targeting/ctx_segments.html
5556
envsubst $(DEMO_VARS) < demos/vanilla/nocookies/identify.html.tpl > demos/vanilla/nocookies/identify.html
5657
envsubst $(DEMO_VARS) < demos/vanilla/nocookies/witness.html.tpl > demos/vanilla/nocookies/witness.html
5758
envsubst $(DEMO_VARS) < demos/vanilla/nocookies/profile.html.tpl > demos/vanilla/nocookies/profile.html
5859
envsubst $(DEMO_VARS) < demos/vanilla/nocookies/targeting/gam360.html.tpl > demos/vanilla/nocookies/targeting/gam360.html
5960
envsubst $(DEMO_VARS) < demos/vanilla/nocookies/targeting/gam360-cached.html.tpl > demos/vanilla/nocookies/targeting/gam360-cached.html
6061
envsubst $(DEMO_VARS) < demos/vanilla/nocookies/targeting/gam360-adcp.html.tpl > demos/vanilla/nocookies/targeting/gam360-adcp.html
6162
envsubst $(DEMO_VARS) < demos/vanilla/nocookies/targeting/prebid.html.tpl > demos/vanilla/nocookies/targeting/prebid.html
63+
envsubst $(DEMO_VARS) < demos/vanilla/nocookies/targeting/ctx_segments.html.tpl > demos/vanilla/nocookies/targeting/ctx_segments.html
6264
envsubst $(DEMO_VARS) < demos/vanilla/uid2_token/login.html.tpl > demos/vanilla/uid2_token/login.html
6365
envsubst $(DEMO_VARS) < demos/vanilla/uid2_token/index.html.tpl > demos/vanilla/uid2_token/index.html
6466
envsubst $(DEMO_VARS) < demos/vanilla/pair/index.html.tpl > demos/vanilla/pair/index.html

README.md

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ JavaScript SDK for integrating with an [Optable Data Connectivity Node (DCN)](ht
2525
- [Caching Targeting Data](#caching-targeting-data)
2626
- [Witness API](#witness-api)
2727
- [Contextual Pageview Tracking](#contextual-pageview-tracking)
28+
- [Contextual Segments API](#contextual-segments-api)
29+
- [Contextual targeting key-values](#contextual-targeting-key-values)
2830
- [Using a script tag](#using-a-script-tag)
2931
- [Option 1: Automatic Initialization](#option-1-automatic-initialization)
3032
- [Option 2: Manual Initialization with Commands Queue](#option-2-manual-initialization-with-commands-queue)
@@ -147,8 +149,9 @@ When creating an instance of `OptableSDK`, you can pass an `InitConfig` object t
147149
- **`pageContext` (`PageContextConfig | boolean`, default: `undefined`)**
148150
When set, enables page context extraction for contextual intelligence. Set to `true` to use defaults, or pass a `PageContextConfig` object to customize what is extracted (HTML content, content selector, max lengths). Extracted context is automatically attached to the first `witness()` call that uses `{ includeContext: true }`.
149151

150-
- **`initContextual` (boolean, default: `false`)**
151-
If `true`, the SDK will automatically fire a `pageview` witness event with full page context during initialization. This is the recommended way to enable contextual pageview tracking without writing custom code. Implies `pageContext: true` when no `pageContext` is explicitly configured.
152+
- **`initContextual` (`boolean | (response: ContextualSegmentsResponse) => void`, default: `false`)**
153+
If `true`, the SDK will automatically fire a `pageview` witness event with full page context during initialization, and also call `ctxSegments()` to fetch contextual segments for the current page, caching the result on the instance for later use via `ctxTargetingKeyValues()`. This is the recommended way to enable contextual pageview tracking and contextual targeting without writing custom code. Implies `pageContext: true` when no `pageContext` is explicitly configured.
154+
When set to a callback function, the SDK does everything `true` does **and** invokes the callback with the `ContextualSegmentsResponse` once it resolves — useful for chaining an ad-server load (e.g. GAM) on the contextual response without making a second `ctxSegments()` call. The callback is not invoked if the request fails.
152155

153156
- **`consent` (`InitConsent`)**
154157
Defines the consent settings for data collection and processing.
@@ -404,6 +407,103 @@ sdk.witness("pageview", { url }, { includeContext: true });
404407

405408
To reset the context (e.g. on SPA navigation), call `sdk.resetContext()` before the next `witness()` call.
406409

410+
### Contextual Segments API
411+
412+
In addition to pageview tracking, the SDK can classify a page URL against one or more contextual taxonomies (such as the [IAB Content Taxonomy](https://iabtechlab.com/standards/content-taxonomy/)) and use the result for ad targeting. Call `ctxSegments()` to fetch the contextual classifications for a URL:
413+
414+
```javascript
415+
// Classify the current page (defaults to window.location.href):
416+
const response = await sdk.ctxSegments();
417+
418+
// Or classify an explicit URL:
419+
const response = await sdk.ctxSegments("https://example.com/article");
420+
```
421+
422+
The response has the shape:
423+
424+
```typescript
425+
type ContextualSegmentsResponse = {
426+
classifications: {
427+
categories: { id: string; name: string; score: number; taxonomy: string }[];
428+
};
429+
};
430+
```
431+
432+
Each call to `ctxSegments()` caches its response on the SDK instance (calling it again refreshes the cache). When `initContextual: true`, the SDK calls `ctxSegments()` for you during initialization, so the cache is populated automatically.
433+
434+
> **Note:** The requested URL must already have been classified by the DCN. If the DCN has no classification for the URL, the response will contain an empty `categories` array.
435+
436+
#### Contextual targeting key-values
437+
438+
`ctxTargetingKeyValues(taxonomyKeys?)` reads the cached `ctxSegments()` response and builds a `Record<string, string[]>` of category ids grouped by taxonomy, ready to pass to an ad server such as Google Ad Manager via `googletag.pubads().setTargeting()`.
439+
440+
Without arguments, each taxonomy value is used as the key:
441+
442+
```javascript
443+
sdk.ctxTargetingKeyValues();
444+
// => { "iab_ct_3_1": ["53", "91", "58", "115", "90", "52"] }
445+
```
446+
447+
Pass a `taxonomyKeys` map to rename keys. Only taxonomies present in the map are emitted (filter + rename), which is useful when you only want to set keys you have configured in your ad server:
448+
449+
```javascript
450+
sdk.ctxTargetingKeyValues({ iab_ct_3_1: "foo" });
451+
// => { "foo": ["53", "91", "58", "115", "90", "52"] }
452+
```
453+
454+
A typical Google Ad Manager activation uses a `loadGAM()` helper:
455+
456+
```javascript
457+
// Helper to load GAM ads with optional targeting data:
458+
var loadGAM = function (tdata = {}) {
459+
window.googletag = window.googletag || { cmd: [] };
460+
googletag.cmd.push(function () {
461+
for (const [key, values] of Object.entries(tdata)) {
462+
googletag.pubads().setTargeting(key, values);
463+
}
464+
googletag.pubads().refresh();
465+
});
466+
};
467+
```
468+
469+
Because `ctxTargetingKeyValues()` reads the cached response, the instance should be initialized with `initContextual: true` so the segments are fetched during initialization and the cache is likely populated by the time `loadGAM()` runs:
470+
471+
```javascript
472+
loadGAM(optable.instance.ctxTargetingKeyValues());
473+
```
474+
475+
If you want `loadGAM()` to run as soon as the contextual segments arrive — without making a second `ctxSegments()` call — pass a callback to `initContextual`. The SDK fires the contextual request automatically during initialization and invokes the callback with the response, populating the cache before `ctxTargetingKeyValues()` reads from it:
476+
477+
```javascript
478+
const sdk = new OptableSDK({
479+
host: "dcn.customer.com",
480+
site: "my-site",
481+
initContextual: function (response) {
482+
loadGAM(sdk.ctxTargetingKeyValues());
483+
},
484+
});
485+
```
486+
487+
If you are not using `initContextual` at all, fetch the segments explicitly and call `loadGAM()` once `ctxSegments()` resolves (falling back to an untargeted load on error):
488+
489+
```javascript
490+
optable.cmd.push(function () {
491+
optable.instance
492+
.ctxSegments()
493+
.then(loadGAM)
494+
.catch((err) => {
495+
loadGAM();
496+
});
497+
});
498+
```
499+
500+
You can also rename and allow-list the GAM keys by passing a `taxonomyKeys` map to `ctxTargetingKeyValues()` (only the taxonomies present in the map are emitted):
501+
502+
```javascript
503+
// Emit only the "iab_ct_3_1" taxonomy, under the GAM key "ctx_iab":
504+
loadGAM(optable.instance.ctxTargetingKeyValues({ iab_ct_3_1: "ctx_iab" }));
505+
```
506+
407507
## Using a script tag
408508

409509
For each [SDK release](https://github.com/Optable/optable-web-sdk/releases), a webpack-generated browser bundle targeting the browsers list described by `pnpm dlx browserslist "> 0.25%, not dead"` can be loaded on a website via a `script` tag.

demos/index-nocookies.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@ <h5>Audience Targeting</h5>
131131
>.
132132
</td>
133133
</tr>
134+
<tr>
135+
<td><a href="/vanilla/nocookies/targeting/ctx_segments.html">contextual segments</a></td>
136+
<td>
137+
Shows how to call <code>ctxSegments()</code> to classify a page URL against contextual taxonomies
138+
(e.g. the <a href="https://iabtechlab.com/standards/content-taxonomy/">IAB Content Taxonomy</a>) and
139+
inspect the categories and confidence scores the DCN returns.
140+
</td>
141+
</tr>
134142
</tbody>
135143
</table>
136144
</div>

demos/index.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ <h5>Audience Targeting</h5>
138138
>.
139139
</td>
140140
</tr>
141+
<tr>
142+
<td><a href="/vanilla/targeting/ctx_segments.html">contextual segments</a></td>
143+
<td>
144+
Shows how to call <code>ctxSegments()</code> to classify a page URL against contextual taxonomies
145+
(e.g. the <a href="https://iabtechlab.com/standards/content-taxonomy/">IAB Content Taxonomy</a>) and
146+
inspect the categories and confidence scores the DCN returns.
147+
</td>
148+
</tr>
141149
</tbody>
142150
</table>
143151
</div>

0 commit comments

Comments
 (0)