Skip to content

Commit

Permalink
targetingFromCache and targetingClearCache APIs (#23)
Browse files Browse the repository at this point in the history
* targetingFromCache and targetingClearCache APIs

- targeting API now also caches returned keyvalues to localStorage
- targetingFromCache will fetch previously cached keyvalues from
  localStorage. Contrary to the targeting API, it is synchronous
- targetingClearCache will clear any previously cached keyvalues
- Introduce demos/vanilla/targeting/gam360-cached.html.tpl to show usage
  of targetingFromCache
- Update README with targeting*Cache docs
  • Loading branch information
bmilekic authored Nov 10, 2020
1 parent a550c1b commit 707564c
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 6 deletions.
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ JavaScript SDK for integrating with optable-sandbox from a web site or web appli
- [Using (script tag)](#using-script-tag)
- [Integrating GAM360](#integrating-gam360)
- [Targeting key values](#targeting-key-values)
- [Targeting key values from local cache](#targeting-key-values-from-local-cache)
- [Witnessing ad events](#witnessing-ad-events)
- [Identifying visitors arriving from Email newsletters](#identifying-visitors-arriving-from-email-newsletters)
- [Insert oeid into your Email newsletter template](#insert-oeid-into-your-email-newsletter-template)
Expand Down Expand Up @@ -136,6 +137,25 @@ sdk

On success, the resulting key values are typically sent as part of a subsequent ad call. Therefore we recommend that you either call targeting() before each ad call, or in parallel periodically, caching the resulting key values which you then provide in ad calls.

#### Caching Targeting Data

The `targeting` API will automatically cache resulting key value data in client storage on success. You can subsequently retrieve the cached key value data as follows:

```{javascript
const cachedTargetingData = sdk.targetingFromCache();
for (const [key, values] of Object.entries(cachedTargetingData)) {
console.log(`Targeting KV: ${key} = ${values.join(",")}`);
}
```

You can also clear the locally cached targeting data:

```javascript
sdk.targetingClearCache();
```

Note that both `targetingFromCache()` and `targetingClearCache()` are synchronous.

### Witness API

To send real-time event data from the user's browser to the sandbox for eventual audience assembly, you can call the witness API as follows:
Expand Down Expand Up @@ -280,6 +300,67 @@ It's suggested to load the GAM banner view with an ad even when the call to your
</script>
```

Note the use of `googletag.pubads().disableInitialLoad()` in the above example. This will disable GAM ads from loading until the call to `googletag.pubads().refresh()` from the `loadGAM()` function.

### Targeting key values from local cache

It's also possible to avoid disabling of the initial ad load by using the SDK's `targetingFromCache()` method instead as in the following example:

```html
<!-- Optable SDK async load: -->
<script async src="https://cdn.optable.co/web-sdk/v0/sdk.js"></script>

<!-- Google Publisher Tag (GPT) async load: -->
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>

<!-- Optable SDK, GPT, and targeting data initialization: -->
<script>
window.optable = window.optable || { cmd: [] };
window.googletag = window.googletag || { cmd: [] };
// Init Optable SDK via command:
optable.cmd.push(function () {
optable.instance = new optable.SDK({ host: "sandbox.customer.com", site: "my-site" });
});
// Init GPT and disable initial ad load so that we can load targeting data first:
googletag.cmd.push(() => {
adSlot = googletag
.defineSlot(...)
.addService(googletag.pubads());
// Attempt to load Optable targeting key values from local cache, then load GAM ads:
optable.cmd.push(function () {
const tdata = optable.instance.targetingFromCache();
for (const [key, values] of Object.entries(tdata)) {
googletag.pubads().setTargeting(key, values);
}
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
});
</script>

<!-- Placeholder DIV for adSlot... referenced by googletag.defineSlot() above: -->
<div id="div-gpt-ad-12345-0"></div>

<script>
// Call Optable sandbox for targeting data which will update the local cache on success.
optable.cmd.push(function () {
optable.instance.targeting().catch((err) => {
// Maybe log error
});
});
googletag.cmd.push(() => {
googletag.display(adSlot);
});
</script>
```

Note that the above example fetches locally cached targeting key values and calls `googletag.pubads().setTargeting()` with them. Note also that the usual `targeting()` call is done as well, though its return value is ignored. This ensures that the local targeting cache is kept updated as activations are modified.

### Witnessing ad events

To automatically capture GPT [SlotRenderEndedEvent](https://developers.google.com/doubleclick-gpt/reference#googletag.events.slotrenderendedevent) and [ImpressionViewableEvent](https://developers.google.com/doubleclick-gpt/reference#googletag.events.impressionviewableevent) and send log data to your sandbox using the **witness API**, simply install GPT event listeners on the SDK instance as follows:
Expand Down
1 change: 1 addition & 0 deletions demos/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ all: html react
html:
envsubst < ./vanilla/identify.html.tpl > ./vanilla/identify.html && \
envsubst < ./vanilla/targeting/gam360.html.tpl > ./vanilla/targeting/gam360.html && \
envsubst < ./vanilla/targeting/gam360-cached.html.tpl > ./vanilla/targeting/gam360-cached.html && \
envsubst < ./publishers/www.lapresse.ca/index.html.tpl > ./publishers/www.lapresse.ca/index.html

.PHONY: react
Expand Down
3 changes: 3 additions & 0 deletions demos/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ <h2>Vanilla</h2>
<li>
<a href="/vanilla/targeting/gam360.html">Targeting API - GAM360</a>
</li>
<li>
<a href="/vanilla/targeting/gam360-cached.html">Cached Targeting API - GAM360</a>
</li>
</ul>

<h2>ReactJS</h2>
Expand Down
107 changes: 107 additions & 0 deletions demos/vanilla/targeting/gam360-cached.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />

<!-- Optable web-sdk loader start -->
<script type="text/javascript">
window.optable = window.optable || { cmd: [] };

optable.cmd.push(function () {
optable.instance = new optable.SDK({
host: "localhost",
site: "web-sdk-demo",
insecure: JSON.parse("true"),
});
});
</script>
<script async src="http://localhost:8081/sdk.js"></script>
<!-- Optable web-sdk loader end -->

<!-- Optable web-sdk inject targeting start -->
<script>
// Hook GPT event listeners and send events to sandbox:
optable.cmd.push(function () {
optable.instance.installGPTEventListeners();
});

// Try to fetch and cache targeting data from sandbox:
optable.cmd.push(function () {
optable.instance.targeting().catch((err) => {
console.log("[OptableSDK] targeting() exception: " + err.message);
});
});
</script>
<!-- Optable web-sdk inject targeting end -->

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

googletag.cmd.push(function () {
headerAd = googletag
.defineSlot("/22081946781/web-sdk-demo-gam360/header-ad", [728, 90], "div-gpt-ad-1598295788551-0")
.addService(googletag.pubads());
boxAd = googletag
.defineSlot(
"/22081946781/web-sdk-demo-gam360/box-ad",
[
[250, 250],
[300, 250],
[200, 200],
],
"div-gpt-ad-1598295897480-0"
)
.addService(googletag.pubads());
footerAd = googletag
.defineSlot("/22081946781/web-sdk-demo-gam360/footer-ad", [728, 90], "div-gpt-ad-1598296001655-0")
.addService(googletag.pubads());

// Setup page-level GAM targeting from any cached targeting data and load ads as usual:
optable.cmd.push(function () {
const tdata = optable.instance.targetingFromCache();
for (const [key, values] of Object.entries(tdata)) {
googletag.pubads().setTargeting(key, values);
console.log("[OptableSDK] googletag.pubads().setTargeting(" + key + ", [" + values + "])");
}

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

console.log("[GPT] googletag.enableServices()");
});
});
</script>
<!-- GPT header tag end -->
</head>
<body>
<h1>Google Ad Manager 360: Targeting API Integration</h1>

<h2>web-sdk-demo-gam360/header-ad</h2>
<!-- GAM360 Ad Unit slot: web-sdk-demo-gam360/header-ad -->
<div id="div-gpt-ad-1598295788551-0" style="width: 728px; height: 90px;"></div>

<h2>web-sdk-demo-gam360/box-ad</h2>
<!-- GAM360 Ad Unit slot: web-sdk-demo-gam360/box-ad -->
<div id="div-gpt-ad-1598295897480-0"></div>

<h2>web-sdk-demo-gam360/footer-ad</h2>
<!-- GAM360 Ad Unit slot: web-sdk-demo-gam360/footer-ad -->
<div id="div-gpt-ad-1598296001655-0"></div>

<hr />

<!-- GPT body tag start -->
<script>
googletag.cmd.push(function () {
googletag.display(headerAd);
googletag.display(boxAd);
googletag.display(footerAd);

console.log("[GPT] googletag.display() all slots");
});
</script>
<!-- GPT body tag end -->
</body>
</html>
107 changes: 107 additions & 0 deletions demos/vanilla/targeting/gam360-cached.html.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />

<!-- Optable web-sdk loader start -->
<script type="text/javascript">
window.optable = window.optable || { cmd: [] };
optable.cmd.push(function () {
optable.instance = new optable.SDK({
host: "${SANDBOX_HOST}",
site: "web-sdk-demo",
insecure: JSON.parse("${SANDBOX_INSECURE}"),
});
});
</script>
<script async src="${SDK_URI}"></script>
<!-- Optable web-sdk loader end -->

<!-- Optable web-sdk inject targeting start -->
<script>
// Hook GPT event listeners and send events to sandbox:
optable.cmd.push(function () {
optable.instance.installGPTEventListeners();
});
// Try to fetch and cache targeting data from sandbox:
optable.cmd.push(function () {
optable.instance.targeting().catch((err) => {
console.log("[OptableSDK] targeting() exception: " + err.message);
});
});
</script>
<!-- Optable web-sdk inject targeting end -->

<!-- GPT header tag start -->
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
<script>
window.googletag = window.googletag || { cmd: [] };
googletag.cmd.push(function () {
headerAd = googletag
.defineSlot("/22081946781/web-sdk-demo-gam360/header-ad", [728, 90], "div-gpt-ad-1598295788551-0")
.addService(googletag.pubads());
boxAd = googletag
.defineSlot(
"/22081946781/web-sdk-demo-gam360/box-ad",
[
[250, 250],
[300, 250],
[200, 200],
],
"div-gpt-ad-1598295897480-0"
)
.addService(googletag.pubads());
footerAd = googletag
.defineSlot("/22081946781/web-sdk-demo-gam360/footer-ad", [728, 90], "div-gpt-ad-1598296001655-0")
.addService(googletag.pubads());
// Setup page-level GAM targeting from any cached targeting data and load ads as usual:
optable.cmd.push(function () {
const tdata = optable.instance.targetingFromCache();
for (const [key, values] of Object.entries(tdata)) {
googletag.pubads().setTargeting(key, values);
console.log("[OptableSDK] googletag.pubads().setTargeting(" + key + ", [" + values + "])");
}
googletag.pubads().enableSingleRequest();
googletag.enableServices();
console.log("[GPT] googletag.enableServices()");
});
});
</script>
<!-- GPT header tag end -->
</head>
<body>
<h1>Google Ad Manager 360: Targeting API Integration</h1>

<h2>web-sdk-demo-gam360/header-ad</h2>
<!-- GAM360 Ad Unit slot: web-sdk-demo-gam360/header-ad -->
<div id="div-gpt-ad-1598295788551-0" style="width: 728px; height: 90px;"></div>

<h2>web-sdk-demo-gam360/box-ad</h2>
<!-- GAM360 Ad Unit slot: web-sdk-demo-gam360/box-ad -->
<div id="div-gpt-ad-1598295897480-0"></div>

<h2>web-sdk-demo-gam360/footer-ad</h2>
<!-- GAM360 Ad Unit slot: web-sdk-demo-gam360/footer-ad -->
<div id="div-gpt-ad-1598296001655-0"></div>

<hr />

<!-- GPT body tag start -->
<script>
googletag.cmd.push(function () {
googletag.display(headerAd);
googletag.display(boxAd);
googletag.display(footerAd);
console.log("[GPT] googletag.display() all slots");
});
</script>
<!-- GPT body tag end -->
</body>
</html>
37 changes: 37 additions & 0 deletions lib/core/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { OptableConfig } from "../config";
import type { TargetingKeyValues } from "../edge/targeting";

function toBinary(str: string): string {
const codeUnits = new Uint16Array(str.length);
for (let i = 0; i < codeUnits.length; i++) {
codeUnits[i] = str.charCodeAt(i);
}
return String.fromCharCode(...new Uint8Array(codeUnits.buffer));
}

class LocalStorage {
private targetingKey: string;

constructor(private Config: OptableConfig) {
const sfx = btoa(toBinary(`${this.Config.host}/${this.Config.site}`));
this.targetingKey = "OPTABLE_TGT_" + sfx;
}

getTargeting(): TargetingKeyValues | null {
const kvs = window.localStorage.getItem(this.targetingKey);
return kvs ? (JSON.parse(kvs) as TargetingKeyValues) : null;
}

setTargeting(keyvalues: TargetingKeyValues) {
if (keyvalues) {
window.localStorage.setItem(this.targetingKey, JSON.stringify(keyvalues));
}
}

clearTargeting() {
window.localStorage.removeItem(this.targetingKey);
}
}

export { LocalStorage };
export default LocalStorage;
Loading

0 comments on commit 707564c

Please sign in to comment.