@optable/web-sdk 0.59.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,12 +46,19 @@ JavaScript SDK for integrating with an [Optable Data Connectivity Node (DCN)](ht
46
46
  - [Insert oeid into your Email newsletter template](#insert-oeid-into-your-email-newsletter-template)
47
47
  - [Call tryIdentifyFromParams SDK API](#call-tryidentifyfromparams-sdk-api)
48
48
  - [Passport and Visitor ID](#passport-and-visitor-id)
49
+ - [Optable Identity System (OIS)](#optable-identity-system-ois)
50
+ - [The cookie identity needs no SDK code](#the-cookie-identity-needs-no-sdk-code)
51
+ - [The derived identity is what the SDK holds](#the-derived-identity-is-what-the-sdk-holds)
52
+ - [Reading the stored ID](#reading-the-stored-id)
53
+ - [How it travels](#how-it-travels)
54
+ - [QA and debug flags](#qa-and-debug-flags)
49
55
  - [Multi-Node Targeting Resolver](#multi-node-targeting-resolver)
50
56
  - [Usage](#usage)
51
57
  - [Rules](#rules)
52
58
  - [Return Value](#return-value)
53
59
  - [Input Type](#input-type)
54
60
  - [Geo-routing](#geo-routing)
61
+ - [Bot detection](#bot-detection)
55
62
  - [Demo Pages](#demo-pages)
56
63
 
57
64
  ## Installing
@@ -166,6 +173,9 @@ When creating an instance of `OptableSDK`, you can pass an `InitConfig` object t
166
173
  - **`forwardSignals` (boolean, default: `false`)**
167
174
  When set to `true`, forwards soft device/browser signals (language, timezone, screen size, device memory, CPU cores) to the DCN in a `sig` request parameter. Also requires device access consent, so it is a no-op when consent is not granted. A signal the browser does not expose is omitted rather than sent empty.
168
175
 
176
+ - **`ois` (boolean, default: `false`)**
177
+ When set to `true`, participates in the [Optable Identity System](#optable-identity-system-ois): the SDK stores the derived OIS ID the DCN returns on the `X-Optable-OID` response header and replays it on subsequent requests, so the DCN recognizes the browser instead of deriving a new identity each visit. Pair it with `forwardSignals: true`, which sends the signals the identity is derived from. Requires a DCN node with OIS ID derivation enabled and device access consent, so it is a no-op otherwise. The `OPTABLE_OID` cookie identity is separate and needs no configuration.
178
+
169
179
  These configurations allow fine-tuned control over how the `OptableSDK` interacts with the Optable DCN, ensuring compatibility with different environments and privacy settings.
170
180
 
171
181
  ## Usage Example
@@ -1116,6 +1126,106 @@ If the returned value is `null`, the SDK logs a one-time warning per instance to
1116
1126
  1. The method was called before the passport was cached (e.g. before `sdk.site()` resolved).
1117
1127
  2. The DCN is configured to not echo the passport in response bodies, in which case the client-side cache is never populated.
1118
1128
 
1129
+ ## Optable Identity System (OIS)
1130
+
1131
+ The Optable Identity System is a cross-tenant identity system. On a DCN node configured to use it, the OIS ID replaces the [visitor ID](#passport-and-visitor-id) as the canonical profile identifier for collected events — the DCN makes that substitution itself, based on the node's identity selector.
1132
+
1133
+ An OIS-enabled node recognizes a browser two ways, and only one of them involves the SDK.
1134
+
1135
+ ### The cookie identity needs no SDK code
1136
+
1137
+ The DCN sets an `OPTABLE_OID` cookie and the browser attaches it to every call on its own, so `identify()` and `profile()` are already attributed to it with nothing enabled client-side.
1138
+
1139
+ That cookie is `HttpOnly` and scoped to `optable.co`, which has two consequences worth knowing. Its value is never readable from JavaScript — not via `document.cookie`, and not from the response, because `Set-Cookie` is a forbidden response header name. And because it is a third-party cookie for a publisher page, it is dropped wherever cross-site cookies are blocked (Safari/ITP, Firefox ETP, Chrome's third-party cookie restrictions) — a different problem from the first-party eTLD+1 case described under [Domains and Cookies](#domains-and-cookies), and one a publisher cannot configure away. When that happens the DCN cannot recognize the browser from the cookie, and the derived identity below is what carries it instead. The SDK cannot bridge that gap: if the browser is willing to send the cookie it is already doing so, and if it is not, there is nothing to forward.
1140
+
1141
+ ### The derived identity is what the SDK holds
1142
+
1143
+ The DCN derives this identity from the device signals sent in the `sig` parameter and returns it on the `X-Optable-OID` response header. With `ois: true` the SDK stores it and replays it on the same header, so the DCN recognizes the browser rather than deriving a fresh identity on every visit.
1144
+
1145
+ ```javascript
1146
+ const sdk = new OptableSDK({
1147
+ host: "dcn.customer.com",
1148
+ site: "my-site",
1149
+ ois: true,
1150
+ // The identity is derived from these signals, so without them there is
1151
+ // nothing to derive it from.
1152
+ forwardSignals: true,
1153
+ });
1154
+ ```
1155
+
1156
+ Or with a script tag:
1157
+
1158
+ ```html
1159
+ <script type="text/javascript">
1160
+ window.optable = window.optable || { cmd: [] };
1161
+
1162
+ optable.cmd.push(function () {
1163
+ optable.instance = new optable.SDK({
1164
+ host: "dcn.customer.com",
1165
+ site: "my-site",
1166
+ ois: true,
1167
+ forwardSignals: true,
1168
+ });
1169
+ });
1170
+ </script>
1171
+ <script async src="https://cdn.optable.co/web-sdk/vX.Y.Z/sdk.js"></script>
1172
+ ```
1173
+
1174
+ > :warning: **Requires DCN support.** The node must have OIS ID derivation enabled and must expose `X-Optable-OID` to the browser. The DCN also only derives the identity for requests from a residential IP, so a VPN, datacenter or office IP returns no header. On a node without it the option is inert.
1175
+
1176
+ ### Reading the stored ID
1177
+
1178
+ ```javascript
1179
+ const id = sdk.oisId(); // string | null — the stored derived OIS ID
1180
+ const state = sdk.oisState(); // { id, storageKey }
1181
+ sdk.oisClear(); // forget it; the DCN returns a fresh derivation on the next call
1182
+ ```
1183
+
1184
+ The SDK dispatches an `optable-ois:change` event on `window` whenever the stored ID changes, so a page can react without polling:
1185
+
1186
+ ```javascript
1187
+ window.addEventListener("optable-ois:change", (e) => console.log(e.detail));
1188
+ ```
1189
+
1190
+ `oisId()` returns `null` until a response has returned an ID. Unlike `passport()`, that does **not** happen during initialization: `/config` derives no identity, so the first ID arrives on the first `identify()` or `profile()` call.
1191
+
1192
+ ### How it travels
1193
+
1194
+ The ID is cached in `localStorage` under `OPTABLE_OIS_<base64(host[/node])>` as an opaque string, and sent back on `X-Optable-OID`.
1195
+
1196
+ Both directions are limited to the endpoints where the DCN derives an identity: `/identify`, `/uid2/token` and `/profile`. It is deliberately absent from `/config` — a custom header makes a request non-simple, and adding a CORS preflight to the SDK's initialization path would cost a round trip on every page load for an endpoint that returns no ID anyway — and from `/witness`, where the DCN records an event without deriving one.
1197
+
1198
+ There is no write policy to reason about. The DCN returns the identity it derived for the _current_ request rather than the one the client replayed, so as those signals drift (a new IP subnet, a browser upgrade, a resized window) the stored value simply rolls forward. The SDK stores whatever the last response returned.
1199
+
1200
+ Nothing is stored and no header is sent without device access consent, so the option is a no-op when consent has not been granted.
1201
+
1202
+ ## QA and debug flags
1203
+
1204
+ Flags are per-session overrides for exercising SDK behaviour that is otherwise decided automatically — forcing a split-test variant, bypassing consent, turning on verbose logging. They are set from the page URL and read back through `getFlags()`.
1205
+
1206
+ ```
1207
+ https://example.com/article?optableDebug&optableForceTargeting
1208
+ ```
1209
+
1210
+ A bare flag name means enabled, `=0` means explicitly off. Flags supplied in the URL are persisted to `sessionStorage`, so a flag set once stays in effect for the rest of the tab session without re-appending the query string.
1211
+
1212
+ Use `flagEnabled()` for on/off flags, and `getFlags()` when a flag has more than two meanings:
1213
+
1214
+ ```typescript
1215
+ import { flagEnabled, getFlags } from "@optable/web-sdk/lib/dist/core/flags";
1216
+
1217
+ if (flagEnabled("optableDebug")) {
1218
+ console.log("[wrapper]", ...args);
1219
+ }
1220
+
1221
+ // optableControlGroup is two-state: "1" forces control, "0" forces treatment.
1222
+ const controlGroup = getFlags().optableControlGroup;
1223
+ ```
1224
+
1225
+ Flag values are strings, and `"0"` is truthy in JavaScript, so do not test a raw value for truthiness — `if (getFlags().optableDebug)` is `true` for `?optableDebug=0`. Use `flagEnabled()` instead.
1226
+
1227
+ These are a QA and debugging facility; none of them should be set on production traffic. For the full flag table and resolution order, see the [flags README](lib/core/flags.md).
1228
+
1119
1229
  ## Multi-Node Targeting Resolver
1120
1230
 
1121
1231
  Resolves multiple **Node Targeting Rules** based on **priority** or **aggregation**.
@@ -1216,6 +1326,46 @@ if (host) {
1216
1326
 
1217
1327
  Keys are region codes, not country codes. Translating a visitor's country code to a region code (for example `GB`/`UK` → `EU`) is the caller's responsibility — the addon deliberately knows only regions. The caller also supplies the SDK `node`/`site`; this addon only resolves the host.
1218
1328
 
1329
+ For the full region table and custom `GeoMap` usage, see the [geo-routing addon README](lib/addons/geo-routing.md).
1330
+
1331
+ ## Bot detection
1332
+
1333
+ The bot detection addon identifies requests coming from known bots and crawlers, so a wrapper can skip work that only makes sense for real visitors — edge calls, identity resolution, analytics samples. It is a pure function over the user agent, with no network calls or storage access.
1334
+
1335
+ ```typescript
1336
+ import { isBot } from "@optable/web-sdk/lib/dist/addons/botDetection";
1337
+
1338
+ if (isBot()) {
1339
+ return; // Skip targeting and analytics for this request.
1340
+ }
1341
+ ```
1342
+
1343
+ With no argument it reads `navigator.userAgent`; pass a string to test one explicitly.
1344
+
1345
+ When the page also runs a Prebid RTD provider, prefer `SkipTargetingForBots()`. It calls `isBot()` and, for a bot, marks targeting as already done so the RTD module short-circuits instead of waiting for a targeting call that will never be made:
1346
+
1347
+ ```typescript
1348
+ import { SkipTargetingForBots } from "@optable/web-sdk/lib/dist/edge/targeting";
1349
+
1350
+ if (!SkipTargetingForBots()) {
1351
+ await sdk.targeting();
1352
+ }
1353
+ ```
1354
+
1355
+ Matching is substring-based and case-insensitive, covering generic crawlers, headless browsers, HTTP clients and Google's non-search agents. It is deliberately broad and user-agent only — a cost-saving filter, not a fraud signal. For the full match list, see the [bot detection addon README](lib/addons/botDetection.md).
1356
+
1357
+ ## Command queue
1358
+
1359
+ The command queue addon lets a page interact with a wrapper loaded via an async script tag before the script has arrived, in the style of `googletag.cmd` and `pbjs.que`. The page queues functions on a plain-array stub; the wrapper replaces the stub with an instance, which drains the queue and executes later pushes immediately.
1360
+
1361
+ ```typescript
1362
+ import { OptableCommands } from "@optable/web-sdk/lib/dist/addons/commands";
1363
+
1364
+ window.optable.cmd = new OptableCommands(window.optable.cmd || []);
1365
+ ```
1366
+
1367
+ For the page-side stub and behaviour details, see the [command queue addon README](lib/addons/commands.md).
1368
+
1219
1369
  ## Demo Pages
1220
1370
 
1221
1371
  The demo pages are working examples of both `identify` and `targeting` APIs, as well as an integration with the [Google Ad Manager 360](https://admanager.google.com/home/) ad server, enabling the targeting of ads served by GAM360 to audiences activated in the [Optable](https://optable.co/) DCN.
@@ -1235,3 +1385,5 @@ docker-compose up
1235
1385
  Then head to [https://localhost:8180/](localhost:8180) to see the demo pages. You can modify the code in each demo, then run `make build` and finally refresh the demo pages to see your changes take effect. If you want to test the demos with your own DCN, make sure to update the configuration (hostname and site slug) given to the OptableSDK (see `webpack.config.js` for the react example).
1236
1386
 
1237
1387
  Note that using HTTP first-party cookies with a local instance of the demos pages pointing to an Optable DCN will not work because [https://localhost:8180/](localhost:8180) does not share the same top-level domain name `.optable.co`. We recommend using [LocalStorage](https://github.com/Optable/optable-web-sdk#localstorage) instead.
1388
+
1389
+ The [Optable Identity System](#optable-identity-system-ois) demo (`/vanilla/ois.html`, or `/vanilla/nocookies/ois.html`) covers both OIS identities: it explains why the `OPTABLE_OID` cookie identity is invisible to JavaScript, and shows the derived OIS ID the DCN returned, the `localStorage` key holding it, the decoded `sig` signals it was derived from, and the `X-Optable-OID` header sent and received on each call. It needs a DCN node with OIS ID derivation enabled, and only produces an ID for requests from a residential IP.