@xenia-martech/sdk-js 1.0.1-dev.1 → 1.1.0-dev.1
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 +53 -4
- package/dist/{actions-CcBYV--3.d.cts → actions-CKeiofxJ.d.cts} +19 -1
- package/dist/{actions-CcBYV--3.d.ts → actions-CKeiofxJ.d.ts} +19 -1
- package/dist/{cookie-names-B3b0XNc2.d.cts → api-URhxxi4x.d.cts} +19 -42
- package/dist/{cookie-names-B3b0XNc2.d.ts → api-URhxxi4x.d.ts} +19 -42
- package/dist/chunk-5D7SJIO4.cjs +109 -0
- package/dist/chunk-5D7SJIO4.cjs.map +1 -0
- package/dist/chunk-BHNOKSJH.js +214 -0
- package/dist/chunk-BHNOKSJH.js.map +1 -0
- package/dist/{chunk-XXNDHYAS.js → chunk-FYSEKFIY.js} +45 -103
- package/dist/chunk-FYSEKFIY.js.map +1 -0
- package/dist/{chunk-WYM4CQVG.cjs → chunk-JEKVN7HK.cjs} +46 -106
- package/dist/chunk-JEKVN7HK.cjs.map +1 -0
- package/dist/chunk-LBRZMWE2.cjs +220 -0
- package/dist/chunk-LBRZMWE2.cjs.map +1 -0
- package/dist/chunk-TLTG3BTC.js +104 -0
- package/dist/chunk-TLTG3BTC.js.map +1 -0
- package/dist/client/index.cjs +60 -6
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +25 -3
- package/dist/client/index.d.ts +25 -3
- package/dist/client/index.js +59 -7
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.cjs +58 -6
- package/dist/client/react/index.cjs.map +1 -1
- package/dist/client/react/index.d.cts +2 -2
- package/dist/client/react/index.d.ts +2 -2
- package/dist/client/react/index.js +58 -6
- package/dist/client/react/index.js.map +1 -1
- package/dist/cookie-names-8FxbGKGS.d.ts +127 -0
- package/dist/cookie-names-QYMiEMAq.d.cts +127 -0
- package/dist/index.cjs +41 -32
- package/dist/index.d.cts +4 -71
- package/dist/index.d.ts +4 -71
- package/dist/index.js +2 -1
- package/dist/next/index.cjs +59 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +45 -0
- package/dist/next/index.d.ts +45 -0
- package/dist/next/index.js +54 -0
- package/dist/next/index.js.map +1 -0
- package/dist/server/index.cjs +38 -214
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +5 -104
- package/dist/server/index.d.ts +5 -104
- package/dist/server/index.js +5 -204
- package/dist/server/index.js.map +1 -1
- package/dist/xenia-sdk.iife.js +3 -3
- package/dist/xenia-sdk.iife.js.map +1 -1
- package/dist/xenia-server-BG4Vkaua.d.cts +110 -0
- package/dist/xenia-server-CpFUOxbB.d.ts +110 -0
- package/package.json +26 -7
- package/dist/chunk-WYM4CQVG.cjs.map +0 -1
- package/dist/chunk-XXNDHYAS.js.map +0 -1
package/README.md
CHANGED
|
@@ -101,6 +101,7 @@ Building a plain HTML site (no npm, no bundler)? Skip the install and pull the p
|
|
|
101
101
|
| `@xenia-martech/sdk-js/client` | browser, **framework-agnostic** | `createXeniaClient()` — identity, auto-tracking, action rendering. No React. Works from Vue, Angular, Svelte, or plain JS. |
|
|
102
102
|
| `@xenia-martech/sdk-js/client/react` | browser + React | `<XeniaProvider>` + hooks (`useTrack`, `usePersonalization`, `useWebhook`, …), built on top of `/client`. |
|
|
103
103
|
| `@xenia-martech/sdk-js/server` | Node / SSR / RSC | Server-side events, webhooks, personalization, and token minting. Stateless — identity passed explicitly. |
|
|
104
|
+
| `@xenia-martech/sdk-js/next` | Next.js 15+ (App Router) | `xeniaProxy()` for `proxy.ts`/`middleware.ts` and `xeniaServerFromNext()` — the visitor cookie and URL context wired for you. Needs `next` (optional peer). |
|
|
104
105
|
| `@xenia-martech/sdk-js/iife` | `<script>` tag | Prebuilt `window.Xenia` for sites without a bundler. |
|
|
105
106
|
|
|
106
107
|
---
|
|
@@ -343,6 +344,44 @@ await xenia.track("purchase", { identity: { email }, properties: { /* … */ } }
|
|
|
343
344
|
|
|
344
345
|
See [Recipes](#recipes--end-to-end-patterns) below for the full pattern in an RSC app that has no browser SDK writing that cookie for you.
|
|
345
346
|
|
|
347
|
+
### Next.js: two calls instead of the wiring
|
|
348
|
+
|
|
349
|
+
`@xenia-martech/sdk-js/next` does the cookie and URL plumbing above for the App Router:
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
// proxy.ts (middleware.ts before Next 16)
|
|
353
|
+
import type { NextRequest } from "next/server";
|
|
354
|
+
import { xeniaProxy } from "@xenia-martech/sdk-js/next";
|
|
355
|
+
|
|
356
|
+
// Mints the xenia_vid cookie for new visitors and stamps the URL onto the request.
|
|
357
|
+
// Pass { headers } to forward headers of your own as well.
|
|
358
|
+
export const proxy = (request: NextRequest) => xeniaProxy(request);
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
// Any Server Component, Server Action or Route Handler
|
|
363
|
+
import { consentFromNext, xeniaServerFromNext } from "@xenia-martech/sdk-js/next";
|
|
364
|
+
|
|
365
|
+
const xenia = await xeniaServerFromNext(cfg); // bound to this visitor + path/query/UTMs/referrer
|
|
366
|
+
const res = await xenia.webhook("cog_prod_…");
|
|
367
|
+
|
|
368
|
+
// In a Server Action, after a form submit:
|
|
369
|
+
const consent = await consentFromNext();
|
|
370
|
+
if (consent.analytics) {
|
|
371
|
+
await xenia.identify({ email }); // visitor id defaults to the bound one
|
|
372
|
+
await xenia.track("formSubmitted", { eventId: `contact-${ref}`, consent });
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Your own consent banner (no browser SDK)? Write the cookie the server reads with `consentCookie()`:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
import { consentCookie } from "@xenia-martech/sdk-js";
|
|
380
|
+
document.cookie = consentCookie({ analytics: true, consentSource: "banner" });
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
The browser client's `consent()` writes the same cookie for you.
|
|
384
|
+
|
|
346
385
|
---
|
|
347
386
|
|
|
348
387
|
## Configuration
|
|
@@ -361,6 +400,7 @@ Passed to `XeniaProvider`, `createXeniaClient`, `createXeniaServer`, or `Xenia.i
|
|
|
361
400
|
| `prefix` | `string` | `"xenia"` | Storage keys, the `data-<prefix>` attribute name, **and the visitor/consent cookie names** (`<prefix>_vid`, `<prefix>_consent`). If you change it, pass the same prefix to the server helpers — `getVisitorId(header, prefix)` — or the server looks for a cookie the browser never wrote. |
|
|
362
401
|
| `timeout` | `number` | `8000` | Per-request timeout (ms). |
|
|
363
402
|
| `retry` | `{ attempts?, baseDelayMs? }` | `{2, 300}` | Retry policy; honors `Retry-After`. |
|
|
403
|
+
| `circuitBreaker` | `boolean \| { failureThreshold?, cooldownMs? }` | off | After `failureThreshold` (3) consecutive failures on one URL, calls to it throw `CircuitOpenError` instantly for `cooldownMs` (30s), then one call probes. State is shared across clients, so it works with one server client per request. Each webhook trigger has its own circuit. |
|
|
364
404
|
| `headers` | `Record<string,string>` | — | Extra headers on every request. |
|
|
365
405
|
| `debug` | `boolean` | `false` | Verbose console diagnostics. |
|
|
366
406
|
| `consent` | `ConsentState` | — | Initial consent flags. **Browser only** — the server client ignores this; pass `consent` per `track()` call instead. |
|
|
@@ -931,10 +971,19 @@ Never block the page on this — render `baseHero` immediately and swap in the p
|
|
|
931
971
|
- `track()` → `AnalyticsIngestResponse` — single-call unified event (no batching queue); `opts.trigger`, `opts.eventId`, `opts.source` (origin tag, default `"xenia-server-sdk"`), `identity`/`context`/`properties`/**`consent`** (see [Analytics](#analytics--conversions) — **consent is per-call here**, `createXeniaServer({ consent })` is ignored).
|
|
932
972
|
- `webhook()` — `opts.identity`/`opts.context` merge in the same reserved fields (`path`, `query`, `url`, `referrer`, UTM, `deviceId`, `visitorId`, etc.) the browser client auto-attaches; there's no ambient `window` on the server, so pass them explicitly.
|
|
933
973
|
- `resolve(contentKey, opts?)` → `ResolveResult` · `resolveBatch(items, opts?)` → `ResolveBatchResult[]` — REST personalization (requires the SDK token).
|
|
934
|
-
- `identify(body
|
|
974
|
+
- `identify(body)` → `IdentifyResponse` — bind a visitor to a known user. `xeniaVisitorId` defaults to the bound `config.visitorId`.
|
|
935
975
|
- `refreshSdkToken(opts)` → `{ token, expiresAt? }` — exchanges your long-lived SDK token for a short-lived one; call this directly if you're not on a framework with route handlers.
|
|
936
976
|
- `createTokenRoute(config)` → `(request) => Promise<Response>` — a ready-made handler wrapping `refreshSdkToken`; mount at your token endpoint.
|
|
937
|
-
- `parseCookies(header)`, `getVisitorId(header, prefix?)`, `getConsent(header, prefix?)`, `visitorCookieName(prefix?)`, `consentCookieName(prefix?)`. `VISITOR_COOKIE` (`"xenia_vid"`) / `CONSENT_COOKIE` (`"xenia_consent"`) are the DEFAULT-prefix names — with a custom `prefix`, use the functions and pass the same prefix the browser client uses.
|
|
977
|
+
- `parseCookies(header)`, `getVisitorId(header, prefix?)`, `getConsent(header, prefix?)`, `consentCookie(state, prefix?)`, `visitorCookieName(prefix?)`, `consentCookieName(prefix?)`. `VISITOR_COOKIE` (`"xenia_vid"`) / `CONSENT_COOKIE` (`"xenia_consent"`) are the DEFAULT-prefix names — with a custom `prefix`, use the functions and pass the same prefix the browser client uses.
|
|
978
|
+
|
|
979
|
+
</details>
|
|
980
|
+
|
|
981
|
+
<details>
|
|
982
|
+
<summary><strong>Next.js (<code>/next</code>)</strong> — App Router 15+</summary>
|
|
983
|
+
|
|
984
|
+
- `xeniaProxy(request, { headers?, prefix?, cookie? })` → `NextResponse` — ensures the visitor cookie and sets the `x-xenia-url` request header (`URL_HEADER`).
|
|
985
|
+
- `xeniaServerFromNext(config)` → `Promise<XeniaServer>` — reads the visitor cookie and URL header back; `config.visitorId`/`config.context` still win.
|
|
986
|
+
- `consentFromNext(prefix?)` → `Promise<ConsentState>` — `getConsent()` on this request's cookies.
|
|
938
987
|
|
|
939
988
|
</details>
|
|
940
989
|
|
|
@@ -944,7 +993,7 @@ Never block the page on this — render `baseHero` immediately and swap in the p
|
|
|
944
993
|
|
|
945
994
|
- `XeniaApi` — low-level endpoint client (`sendEvents`, `webhook`, `resolve`, `resolveBatch`, `identify`, `consent`). App code normally goes through `/client` or `/server` instead.
|
|
946
995
|
- `parseActions(body)`, `generateUlid()`, `toEventId(raw)`, `MAX_EVENT_ID_LENGTH`, `spliceSlots(slots, swaps)`, `extractContentSwaps(body)`, `VISITOR_COOKIE`, `CONSENT_COOKIE`.
|
|
947
|
-
- `HttpClient`, `HttpError`, `buildUrl(base, path)`, `isBrowser()` — the underlying HTTP layer; `isBrowser()` is handy for guarding SSR-unsafe code in your own app.
|
|
996
|
+
- `HttpClient`, `HttpError`, `CircuitOpenError`, `buildUrl(base, path)`, `isBrowser()` — the underlying HTTP layer; `isBrowser()` is handy for guarding SSR-unsafe code in your own app.
|
|
948
997
|
- `MemoryResponseStore`, `LocalStorageResponseStore` (implementations of the `ResponseStore` interface).
|
|
949
998
|
- All types (`XeniaConfig`, `VisitorCookieOptions`, `XeniaEvent`, `TrackOptions`, `WebhookResponse`, `XeniaAction`, `MessagePosition`, `AnalyticsEventName`, `AnalyticsIngestResponse`, `AnalyticsContext`, `ResolveResult`, `ResolveBatchResult`, `PersonalizationSwap`, `ContentSwap`, `WidgetType`, `WidgetSegment`, `WidgetFormSubmit`, `WidgetStyle`, `WidgetPrize`, `WidgetSubmitResponse`, …).
|
|
950
999
|
|
|
@@ -954,7 +1003,7 @@ Never block the page on this — render `baseHero` immediately and swap in the p
|
|
|
954
1003
|
|
|
955
1004
|
## Resilience
|
|
956
1005
|
|
|
957
|
-
Events, webhooks, and personalization **never block your render**. A `usePersonalization()` call that fails just surfaces via `error` and leaves `data` as `null`, same as a no-match — your own base/fallback content keeps rendering. Requests time out (default 8s) and retry transient failures with backoff, honoring `Retry-After`. Treat a slow or unreachable Xenia as "render the base and move on."
|
|
1006
|
+
Events, webhooks, and personalization **never block your render**. A `usePersonalization()` call that fails just surfaces via `error` and leaves `data` as `null`, same as a no-match — your own base/fallback content keeps rendering. Requests time out (default 8s) and retry transient failures with backoff, honoring `Retry-After`. Treat a slow or unreachable Xenia as "render the base and move on." During an outage, set `circuitBreaker` so calls fail instantly instead of each visitor waiting out the timeout.
|
|
958
1007
|
|
|
959
1008
|
---
|
|
960
1009
|
|
|
@@ -37,6 +37,19 @@ interface RetryConfig {
|
|
|
37
37
|
/** Base backoff in ms (exponential). Default 300. */
|
|
38
38
|
baseDelayMs?: number;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Opt-in memory of "the backend is down" across requests (and across clients, so a server that
|
|
42
|
+
* builds one client per request still benefits). After `failureThreshold` consecutive failures on
|
|
43
|
+
* one URL (query included, so each webhook trigger has its own circuit), calls to it fail instantly
|
|
44
|
+
* with `CircuitOpenError` for `cooldownMs`; then one call probes recovery. Failures are network
|
|
45
|
+
* errors, timeouts, `429` and `5xx`, after retries; any other response means the backend is up.
|
|
46
|
+
*/
|
|
47
|
+
interface CircuitBreakerConfig {
|
|
48
|
+
/** Consecutive failures that open the circuit. Default 3. */
|
|
49
|
+
failureThreshold?: number;
|
|
50
|
+
/** How long an open circuit fails fast before the next probe, in ms. Default 30000. */
|
|
51
|
+
cooldownMs?: number;
|
|
52
|
+
}
|
|
40
53
|
/**
|
|
41
54
|
* Root configuration shared by client and server runtimes.
|
|
42
55
|
*
|
|
@@ -114,6 +127,11 @@ interface XeniaConfig {
|
|
|
114
127
|
timeout?: number;
|
|
115
128
|
/** Retry policy for transient failures. */
|
|
116
129
|
retry?: RetryConfig;
|
|
130
|
+
/**
|
|
131
|
+
* Fail fast while an endpoint is down instead of waiting out the timeout on every call. `true` for
|
|
132
|
+
* the defaults, or tune it. Off by default. See {@link CircuitBreakerConfig}.
|
|
133
|
+
*/
|
|
134
|
+
circuitBreaker?: boolean | CircuitBreakerConfig;
|
|
117
135
|
/** Emit verbose diagnostics to the console. Default false. */
|
|
118
136
|
debug?: boolean;
|
|
119
137
|
/** Extra headers merged onto every request. */
|
|
@@ -984,4 +1002,4 @@ declare function extractContentSwaps(body: JsonValue): ContentSwap[] | undefined
|
|
|
984
1002
|
*/
|
|
985
1003
|
declare function parseActions(body: JsonValue): XeniaAction[];
|
|
986
1004
|
|
|
987
|
-
export { type AnalyticsContext as A, extractContentSwaps as B, type
|
|
1005
|
+
export { type AnalyticsContext as A, extractContentSwaps as B, type ConsentState as C, parseActions as D, spliceSlots as E, type InteractionType as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type ResolveBatchItem as R, type TrackOptions as T, type WidgetFormSubmit as W, type XeniaConfig as X, type XeniaEvent as a, type MessageTemplateFn as b, type MessageComponentRenderer as c, type WidgetSubmitResponse as d, type XeniaAction as e, type MessagePlacement as f, type AnalyticsEventName as g, type AnalyticsIdentity as h, type AnalyticsIngestResponse as i, type ClientEvent as j, type ContentSwap as k, type IdentifyRequest as l, type IdentifyResponse as m, Identity as n, type MessageMountContext as o, type MessageRenderContext as p, type ResolveBatchResult as q, type ResolveOptions as r, type ResolveResult as s, type WebhookResponse as t, type WidgetPrize as u, type WidgetSegment as v, type WidgetType as w, XeniaClient as x, type XeniaClientOptions as y, createXeniaClient as z };
|
|
@@ -37,6 +37,19 @@ interface RetryConfig {
|
|
|
37
37
|
/** Base backoff in ms (exponential). Default 300. */
|
|
38
38
|
baseDelayMs?: number;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Opt-in memory of "the backend is down" across requests (and across clients, so a server that
|
|
42
|
+
* builds one client per request still benefits). After `failureThreshold` consecutive failures on
|
|
43
|
+
* one URL (query included, so each webhook trigger has its own circuit), calls to it fail instantly
|
|
44
|
+
* with `CircuitOpenError` for `cooldownMs`; then one call probes recovery. Failures are network
|
|
45
|
+
* errors, timeouts, `429` and `5xx`, after retries; any other response means the backend is up.
|
|
46
|
+
*/
|
|
47
|
+
interface CircuitBreakerConfig {
|
|
48
|
+
/** Consecutive failures that open the circuit. Default 3. */
|
|
49
|
+
failureThreshold?: number;
|
|
50
|
+
/** How long an open circuit fails fast before the next probe, in ms. Default 30000. */
|
|
51
|
+
cooldownMs?: number;
|
|
52
|
+
}
|
|
40
53
|
/**
|
|
41
54
|
* Root configuration shared by client and server runtimes.
|
|
42
55
|
*
|
|
@@ -114,6 +127,11 @@ interface XeniaConfig {
|
|
|
114
127
|
timeout?: number;
|
|
115
128
|
/** Retry policy for transient failures. */
|
|
116
129
|
retry?: RetryConfig;
|
|
130
|
+
/**
|
|
131
|
+
* Fail fast while an endpoint is down instead of waiting out the timeout on every call. `true` for
|
|
132
|
+
* the defaults, or tune it. Off by default. See {@link CircuitBreakerConfig}.
|
|
133
|
+
*/
|
|
134
|
+
circuitBreaker?: boolean | CircuitBreakerConfig;
|
|
117
135
|
/** Emit verbose diagnostics to the console. Default false. */
|
|
118
136
|
debug?: boolean;
|
|
119
137
|
/** Extra headers merged onto every request. */
|
|
@@ -984,4 +1002,4 @@ declare function extractContentSwaps(body: JsonValue): ContentSwap[] | undefined
|
|
|
984
1002
|
*/
|
|
985
1003
|
declare function parseActions(body: JsonValue): XeniaAction[];
|
|
986
1004
|
|
|
987
|
-
export { type AnalyticsContext as A, extractContentSwaps as B, type
|
|
1005
|
+
export { type AnalyticsContext as A, extractContentSwaps as B, type ConsentState as C, parseActions as D, spliceSlots as E, type InteractionType as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type ResolveBatchItem as R, type TrackOptions as T, type WidgetFormSubmit as W, type XeniaConfig as X, type XeniaEvent as a, type MessageTemplateFn as b, type MessageComponentRenderer as c, type WidgetSubmitResponse as d, type XeniaAction as e, type MessagePlacement as f, type AnalyticsEventName as g, type AnalyticsIdentity as h, type AnalyticsIngestResponse as i, type ClientEvent as j, type ContentSwap as k, type IdentifyRequest as l, type IdentifyResponse as m, Identity as n, type MessageMountContext as o, type MessageRenderContext as p, type ResolveBatchResult as q, type ResolveOptions as r, type ResolveResult as s, type WebhookResponse as t, type WidgetPrize as u, type WidgetSegment as v, type WidgetType as w, XeniaClient as x, type XeniaClientOptions as y, createXeniaClient as z };
|
|
@@ -43,6 +43,19 @@ interface RetryConfig {
|
|
|
43
43
|
/** Base backoff in ms (exponential). Default 300. */
|
|
44
44
|
baseDelayMs?: number;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Opt-in memory of "the backend is down" across requests (and across clients, so a server that
|
|
48
|
+
* builds one client per request still benefits). After `failureThreshold` consecutive failures on
|
|
49
|
+
* one URL (query included, so each webhook trigger has its own circuit), calls to it fail instantly
|
|
50
|
+
* with `CircuitOpenError` for `cooldownMs`; then one call probes recovery. Failures are network
|
|
51
|
+
* errors, timeouts, `429` and `5xx`, after retries; any other response means the backend is up.
|
|
52
|
+
*/
|
|
53
|
+
interface CircuitBreakerConfig {
|
|
54
|
+
/** Consecutive failures that open the circuit. Default 3. */
|
|
55
|
+
failureThreshold?: number;
|
|
56
|
+
/** How long an open circuit fails fast before the next probe, in ms. Default 30000. */
|
|
57
|
+
cooldownMs?: number;
|
|
58
|
+
}
|
|
46
59
|
/**
|
|
47
60
|
* Root configuration shared by client and server runtimes.
|
|
48
61
|
*
|
|
@@ -120,6 +133,11 @@ interface XeniaConfig {
|
|
|
120
133
|
timeout?: number;
|
|
121
134
|
/** Retry policy for transient failures. */
|
|
122
135
|
retry?: RetryConfig;
|
|
136
|
+
/**
|
|
137
|
+
* Fail fast while an endpoint is down instead of waiting out the timeout on every call. `true` for
|
|
138
|
+
* the defaults, or tune it. Off by default. See {@link CircuitBreakerConfig}.
|
|
139
|
+
*/
|
|
140
|
+
circuitBreaker?: boolean | CircuitBreakerConfig;
|
|
123
141
|
/** Emit verbose diagnostics to the console. Default false. */
|
|
124
142
|
debug?: boolean;
|
|
125
143
|
/** Extra headers merged onto every request. */
|
|
@@ -639,45 +657,4 @@ declare class XeniaApi {
|
|
|
639
657
|
resolveBatch(items: ResolveBatchItem[], opts?: ResolveOptions): Promise<ResolveBatchResult[]>;
|
|
640
658
|
}
|
|
641
659
|
|
|
642
|
-
|
|
643
|
-
* Parse render actions out of a webhook-response body.
|
|
644
|
-
*
|
|
645
|
-
* The convention: marketers return `{ "xeniaActions": [ ... ] }` (or a bare
|
|
646
|
-
* array) from the `webhook-response` node. Any body without a recognizable
|
|
647
|
-
* `xeniaActions` array yields an empty list — a safe no-op.
|
|
648
|
-
*/
|
|
649
|
-
declare function parseActions(body: JsonValue): XeniaAction[];
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* Cookie names shared by the browser and server halves of the SDK.
|
|
653
|
-
*
|
|
654
|
-
* The visitor cookie is the one channel where two runtimes must agree on a literal string: the
|
|
655
|
-
* browser writes it, a same-origin server reads it back, and that match is what lets a server-side
|
|
656
|
-
* conversion attribute to a browser-entered journey. `config.prefix` namespaces it — so two Xenia
|
|
657
|
-
* apps on one domain keep separate visitors — which means **both halves must derive the name the
|
|
658
|
-
* same way**. Deriving it on one side and hardcoding it on the other silently breaks the link for
|
|
659
|
-
* anyone using a custom prefix: the server just finds no cookie and falls back to no visitor.
|
|
660
|
-
*
|
|
661
|
-
* Always build the name with these helpers rather than interpolating it by hand.
|
|
662
|
-
*/
|
|
663
|
-
/** Default storage/cookie prefix, matching `XeniaConfig.prefix`. */
|
|
664
|
-
declare const DEFAULT_PREFIX = "xenia";
|
|
665
|
-
/** Name of the first-party cookie carrying the SDK-owned visitor id, for a given prefix. */
|
|
666
|
-
declare function visitorCookieName(prefix?: string): string;
|
|
667
|
-
/** Name of the first-party cookie carrying consent scopes, for a given prefix. */
|
|
668
|
-
declare function consentCookieName(prefix?: string): string;
|
|
669
|
-
/**
|
|
670
|
-
* The visitor cookie name for the DEFAULT prefix (`"xenia_vid"`).
|
|
671
|
-
*
|
|
672
|
-
* Correct for the common case, and kept so existing code (e.g. reading the cookie straight out of a
|
|
673
|
-
* framework's cookie jar) keeps working. If you set `config.prefix`, use {@link visitorCookieName}
|
|
674
|
-
* instead — this constant will not match what the browser wrote.
|
|
675
|
-
*/
|
|
676
|
-
declare const VISITOR_COOKIE: string;
|
|
677
|
-
/**
|
|
678
|
-
* The consent cookie name for the DEFAULT prefix (`"xenia_consent"`). Same custom-prefix caveat as
|
|
679
|
-
* {@link VISITOR_COOKIE}.
|
|
680
|
-
*/
|
|
681
|
-
declare const CONSENT_COOKIE: string;
|
|
682
|
-
|
|
683
|
-
export { type AnalyticsContext as A, CONSENT_COOKIE as C, DEFAULT_PREFIX as D, type EventContext as E, type HttpMethod as H, type IdentifyRequest as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type RetryConfig as R, type TrackOptions as T, VISITOR_COOKIE as V, type WebhookResponse as W, type XeniaAction as X, type AnalyticsEventName as a, type AnalyticsIdentity as b, type AnalyticsIngestResponse as c, type ConsentState as d, type EventIdentity as e, type IdentifyResponse as f, type InteractionType as g, type JsonObject as h, type MessagePlacement as i, type ResolveBatchItem as j, type ResolveBatchResult as k, type ResolveOptions as l, type ResolveResult as m, type WidgetFormSubmit as n, type WidgetPrize as o, type WidgetSegment as p, type WidgetSubmitResponse as q, type WidgetType as r, XeniaApi as s, type XeniaConfig as t, type XeniaEvent as u, consentCookieName as v, parseActions as w, visitorCookieName as x, type CallerIdentity as y };
|
|
660
|
+
export { type AnalyticsContext as A, type CircuitBreakerConfig as C, type EventContext as E, type HttpMethod as H, type IdentifyRequest as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type ResolveBatchItem as R, type TrackOptions as T, type VisitorCookieOptions as V, type WebhookResponse as W, type XeniaAction as X, type AnalyticsEventName as a, type AnalyticsIdentity as b, type AnalyticsIngestResponse as c, type ConsentState as d, type EventIdentity as e, type IdentifyResponse as f, type InteractionType as g, type JsonObject as h, type MessagePlacement as i, type ResolveBatchResult as j, type ResolveOptions as k, type ResolveResult as l, type RetryConfig as m, type WidgetFormSubmit as n, type WidgetPrize as o, type WidgetSegment as p, type WidgetSubmitResponse as q, type WidgetType as r, XeniaApi as s, type XeniaConfig as t, type XeniaEvent as u, type CallerIdentity as v };
|
|
@@ -43,6 +43,19 @@ interface RetryConfig {
|
|
|
43
43
|
/** Base backoff in ms (exponential). Default 300. */
|
|
44
44
|
baseDelayMs?: number;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Opt-in memory of "the backend is down" across requests (and across clients, so a server that
|
|
48
|
+
* builds one client per request still benefits). After `failureThreshold` consecutive failures on
|
|
49
|
+
* one URL (query included, so each webhook trigger has its own circuit), calls to it fail instantly
|
|
50
|
+
* with `CircuitOpenError` for `cooldownMs`; then one call probes recovery. Failures are network
|
|
51
|
+
* errors, timeouts, `429` and `5xx`, after retries; any other response means the backend is up.
|
|
52
|
+
*/
|
|
53
|
+
interface CircuitBreakerConfig {
|
|
54
|
+
/** Consecutive failures that open the circuit. Default 3. */
|
|
55
|
+
failureThreshold?: number;
|
|
56
|
+
/** How long an open circuit fails fast before the next probe, in ms. Default 30000. */
|
|
57
|
+
cooldownMs?: number;
|
|
58
|
+
}
|
|
46
59
|
/**
|
|
47
60
|
* Root configuration shared by client and server runtimes.
|
|
48
61
|
*
|
|
@@ -120,6 +133,11 @@ interface XeniaConfig {
|
|
|
120
133
|
timeout?: number;
|
|
121
134
|
/** Retry policy for transient failures. */
|
|
122
135
|
retry?: RetryConfig;
|
|
136
|
+
/**
|
|
137
|
+
* Fail fast while an endpoint is down instead of waiting out the timeout on every call. `true` for
|
|
138
|
+
* the defaults, or tune it. Off by default. See {@link CircuitBreakerConfig}.
|
|
139
|
+
*/
|
|
140
|
+
circuitBreaker?: boolean | CircuitBreakerConfig;
|
|
123
141
|
/** Emit verbose diagnostics to the console. Default false. */
|
|
124
142
|
debug?: boolean;
|
|
125
143
|
/** Extra headers merged onto every request. */
|
|
@@ -639,45 +657,4 @@ declare class XeniaApi {
|
|
|
639
657
|
resolveBatch(items: ResolveBatchItem[], opts?: ResolveOptions): Promise<ResolveBatchResult[]>;
|
|
640
658
|
}
|
|
641
659
|
|
|
642
|
-
|
|
643
|
-
* Parse render actions out of a webhook-response body.
|
|
644
|
-
*
|
|
645
|
-
* The convention: marketers return `{ "xeniaActions": [ ... ] }` (or a bare
|
|
646
|
-
* array) from the `webhook-response` node. Any body without a recognizable
|
|
647
|
-
* `xeniaActions` array yields an empty list — a safe no-op.
|
|
648
|
-
*/
|
|
649
|
-
declare function parseActions(body: JsonValue): XeniaAction[];
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* Cookie names shared by the browser and server halves of the SDK.
|
|
653
|
-
*
|
|
654
|
-
* The visitor cookie is the one channel where two runtimes must agree on a literal string: the
|
|
655
|
-
* browser writes it, a same-origin server reads it back, and that match is what lets a server-side
|
|
656
|
-
* conversion attribute to a browser-entered journey. `config.prefix` namespaces it — so two Xenia
|
|
657
|
-
* apps on one domain keep separate visitors — which means **both halves must derive the name the
|
|
658
|
-
* same way**. Deriving it on one side and hardcoding it on the other silently breaks the link for
|
|
659
|
-
* anyone using a custom prefix: the server just finds no cookie and falls back to no visitor.
|
|
660
|
-
*
|
|
661
|
-
* Always build the name with these helpers rather than interpolating it by hand.
|
|
662
|
-
*/
|
|
663
|
-
/** Default storage/cookie prefix, matching `XeniaConfig.prefix`. */
|
|
664
|
-
declare const DEFAULT_PREFIX = "xenia";
|
|
665
|
-
/** Name of the first-party cookie carrying the SDK-owned visitor id, for a given prefix. */
|
|
666
|
-
declare function visitorCookieName(prefix?: string): string;
|
|
667
|
-
/** Name of the first-party cookie carrying consent scopes, for a given prefix. */
|
|
668
|
-
declare function consentCookieName(prefix?: string): string;
|
|
669
|
-
/**
|
|
670
|
-
* The visitor cookie name for the DEFAULT prefix (`"xenia_vid"`).
|
|
671
|
-
*
|
|
672
|
-
* Correct for the common case, and kept so existing code (e.g. reading the cookie straight out of a
|
|
673
|
-
* framework's cookie jar) keeps working. If you set `config.prefix`, use {@link visitorCookieName}
|
|
674
|
-
* instead — this constant will not match what the browser wrote.
|
|
675
|
-
*/
|
|
676
|
-
declare const VISITOR_COOKIE: string;
|
|
677
|
-
/**
|
|
678
|
-
* The consent cookie name for the DEFAULT prefix (`"xenia_consent"`). Same custom-prefix caveat as
|
|
679
|
-
* {@link VISITOR_COOKIE}.
|
|
680
|
-
*/
|
|
681
|
-
declare const CONSENT_COOKIE: string;
|
|
682
|
-
|
|
683
|
-
export { type AnalyticsContext as A, CONSENT_COOKIE as C, DEFAULT_PREFIX as D, type EventContext as E, type HttpMethod as H, type IdentifyRequest as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type RetryConfig as R, type TrackOptions as T, VISITOR_COOKIE as V, type WebhookResponse as W, type XeniaAction as X, type AnalyticsEventName as a, type AnalyticsIdentity as b, type AnalyticsIngestResponse as c, type ConsentState as d, type EventIdentity as e, type IdentifyResponse as f, type InteractionType as g, type JsonObject as h, type MessagePlacement as i, type ResolveBatchItem as j, type ResolveBatchResult as k, type ResolveOptions as l, type ResolveResult as m, type WidgetFormSubmit as n, type WidgetPrize as o, type WidgetSegment as p, type WidgetSubmitResponse as q, type WidgetType as r, XeniaApi as s, type XeniaConfig as t, type XeniaEvent as u, consentCookieName as v, parseActions as w, visitorCookieName as x, type CallerIdentity as y };
|
|
660
|
+
export { type AnalyticsContext as A, type CircuitBreakerConfig as C, type EventContext as E, type HttpMethod as H, type IdentifyRequest as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type ResolveBatchItem as R, type TrackOptions as T, type VisitorCookieOptions as V, type WebhookResponse as W, type XeniaAction as X, type AnalyticsEventName as a, type AnalyticsIdentity as b, type AnalyticsIngestResponse as c, type ConsentState as d, type EventIdentity as e, type IdentifyResponse as f, type InteractionType as g, type JsonObject as h, type MessagePlacement as i, type ResolveBatchResult as j, type ResolveOptions as k, type ResolveResult as l, type RetryConfig as m, type WidgetFormSubmit as n, type WidgetPrize as o, type WidgetSegment as p, type WidgetSubmitResponse as q, type WidgetType as r, XeniaApi as s, type XeniaConfig as t, type XeniaEvent as u, type CallerIdentity as v };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/content-splice.ts
|
|
4
|
+
function isPlainObject(v) {
|
|
5
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
6
|
+
}
|
|
7
|
+
function cloneJson(value) {
|
|
8
|
+
return value === null || typeof value !== "object" ? value : JSON.parse(JSON.stringify(value));
|
|
9
|
+
}
|
|
10
|
+
function setAtPath(node, path, value, mode) {
|
|
11
|
+
if (path.length === 0) {
|
|
12
|
+
if (mode === "merge" && isPlainObject(node) && isPlainObject(value)) {
|
|
13
|
+
return { ...node, ...value };
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
const [key, ...rest] = path;
|
|
18
|
+
if (Array.isArray(node)) {
|
|
19
|
+
const index = Number(key);
|
|
20
|
+
if (!Number.isInteger(index) || index < 0 || index >= node.length) return node;
|
|
21
|
+
const next = node.slice();
|
|
22
|
+
next[index] = setAtPath(next[index], rest, value, mode);
|
|
23
|
+
return next;
|
|
24
|
+
}
|
|
25
|
+
if (isPlainObject(node)) {
|
|
26
|
+
if (!(key in node)) return node;
|
|
27
|
+
return { ...node, [key]: setAtPath(node[key], rest, value, mode) };
|
|
28
|
+
}
|
|
29
|
+
return node;
|
|
30
|
+
}
|
|
31
|
+
function spliceSlots(slots, swaps) {
|
|
32
|
+
const result = {};
|
|
33
|
+
for (const [key, value] of Object.entries(slots)) {
|
|
34
|
+
result[key] = cloneJson(value);
|
|
35
|
+
}
|
|
36
|
+
if (!swaps) return result;
|
|
37
|
+
for (const swap of swaps) {
|
|
38
|
+
const [slotKey, ...pathParts] = swap.identifier.split(".");
|
|
39
|
+
if (!(slotKey in result)) continue;
|
|
40
|
+
result[slotKey] = setAtPath(result[slotKey], pathParts, swap.replacement, swap.mode ?? "replace");
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
function extractContentSwaps(body) {
|
|
45
|
+
if (!isPlainObject(body)) return void 0;
|
|
46
|
+
const swaps = body.xeniaContentSwaps;
|
|
47
|
+
return Array.isArray(swaps) ? swaps : void 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/core/response-store.ts
|
|
51
|
+
var MemoryResponseStore = class {
|
|
52
|
+
map = /* @__PURE__ */ new Map();
|
|
53
|
+
set(key, value) {
|
|
54
|
+
this.map.set(key, value);
|
|
55
|
+
}
|
|
56
|
+
get(key) {
|
|
57
|
+
return this.map.get(key);
|
|
58
|
+
}
|
|
59
|
+
keys() {
|
|
60
|
+
return [...this.map.keys()];
|
|
61
|
+
}
|
|
62
|
+
clear() {
|
|
63
|
+
this.map.clear();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var LocalStorageResponseStore = class {
|
|
67
|
+
nsKey;
|
|
68
|
+
constructor(prefix = "xenia") {
|
|
69
|
+
this.nsKey = `${prefix}_responses`;
|
|
70
|
+
}
|
|
71
|
+
read() {
|
|
72
|
+
try {
|
|
73
|
+
const raw = localStorage.getItem(this.nsKey);
|
|
74
|
+
return raw ? JSON.parse(raw) : {};
|
|
75
|
+
} catch {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
write(data) {
|
|
80
|
+
try {
|
|
81
|
+
localStorage.setItem(this.nsKey, JSON.stringify(data));
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
set(key, value) {
|
|
86
|
+
const data = this.read();
|
|
87
|
+
data[key] = value;
|
|
88
|
+
this.write(data);
|
|
89
|
+
}
|
|
90
|
+
get(key) {
|
|
91
|
+
return this.read()[key];
|
|
92
|
+
}
|
|
93
|
+
keys() {
|
|
94
|
+
return Object.keys(this.read());
|
|
95
|
+
}
|
|
96
|
+
clear() {
|
|
97
|
+
try {
|
|
98
|
+
localStorage.removeItem(this.nsKey);
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
exports.LocalStorageResponseStore = LocalStorageResponseStore;
|
|
105
|
+
exports.MemoryResponseStore = MemoryResponseStore;
|
|
106
|
+
exports.extractContentSwaps = extractContentSwaps;
|
|
107
|
+
exports.spliceSlots = spliceSlots;
|
|
108
|
+
//# sourceMappingURL=chunk-5D7SJIO4.cjs.map
|
|
109
|
+
//# sourceMappingURL=chunk-5D7SJIO4.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/content-splice.ts","../src/core/response-store.ts"],"names":[],"mappings":"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","file":"chunk-5D7SJIO4.cjs","sourcesContent":["import type { JsonValue } from \"./types\";\n\n/**\n * A single content swap, as emitted in a webhook-response body's\n * `xeniaContentSwaps` array (the multi-slot personalization analogue of\n * `xeniaActions`). `identifier` is `\"<slotKey>\"` (whole-slot replace) or\n * `\"<slotKey>.<dotted.path>\"` addressing a node inside that slot's JSON\n * (object keys or array indices).\n *\n * NOTE: this field name/shape is a proposed contract, not yet emitted by any\n * journey node — confirm with the Journey Orchestrator (FE/BE) team before\n * relying on it in production.\n */\nexport interface ContentSwap {\n identifier: string;\n replacement: JsonValue;\n /** `\"replace\"` (default) swaps the whole matched node; `\"merge\"` shallow-merges when both sides are objects. */\n mode?: \"replace\" | \"merge\";\n}\n\nfunction isPlainObject(v: JsonValue): v is { [key: string]: JsonValue } {\n return v !== null && typeof v === \"object\" && !Array.isArray(v);\n}\n\nfunction cloneJson<T extends JsonValue>(value: T): T {\n return value === null || typeof value !== \"object\" ? value : (JSON.parse(JSON.stringify(value)) as T);\n}\n\nfunction setAtPath(node: JsonValue, path: string[], value: JsonValue, mode: \"replace\" | \"merge\"): JsonValue {\n if (path.length === 0) {\n if (mode === \"merge\" && isPlainObject(node) && isPlainObject(value)) {\n return { ...node, ...value };\n }\n return value;\n }\n\n const [key, ...rest] = path;\n if (Array.isArray(node)) {\n const index = Number(key);\n if (!Number.isInteger(index) || index < 0 || index >= node.length) return node;\n const next = node.slice();\n next[index] = setAtPath(next[index], rest, value, mode);\n return next;\n }\n if (isPlainObject(node)) {\n if (!(key in node)) return node;\n return { ...node, [key]: setAtPath(node[key], rest, value, mode) };\n }\n return node;\n}\n\n/**\n * Splice `swaps` into a base map of named content slots, recursively at any\n * nesting depth. Defensive by design (mirrors the PHP SDK's personalization\n * client): a swap whose `identifier` doesn't resolve to an existing slot/path\n * is silently ignored rather than thrown — this never fails, it degrades to\n * the base slot value. Slots are deep-cloned; inputs are never mutated.\n */\nexport function spliceSlots(\n slots: Record<string, JsonValue>,\n swaps: ContentSwap[] | undefined,\n): Record<string, JsonValue> {\n const result: Record<string, JsonValue> = {};\n for (const [key, value] of Object.entries(slots)) {\n result[key] = cloneJson(value);\n }\n if (!swaps) return result;\n\n for (const swap of swaps) {\n const [slotKey, ...pathParts] = swap.identifier.split(\".\");\n if (!(slotKey in result)) continue;\n result[slotKey] = setAtPath(result[slotKey], pathParts, swap.replacement, swap.mode ?? \"replace\");\n }\n return result;\n}\n\n/** Extract `xeniaContentSwaps` from a webhook-response body, if present and well-formed. */\nexport function extractContentSwaps(body: JsonValue): ContentSwap[] | undefined {\n if (!isPlainObject(body)) return undefined;\n const swaps = body.xeniaContentSwaps;\n return Array.isArray(swaps) ? (swaps as unknown as ContentSwap[]) : undefined;\n}\n","import type { WebhookResponse } from \"./types\";\n\n/**\n * Persists webhook responses the SDK receives, keyed by an arbitrary string\n * (typically the eventId, triggerId, or journeyId). Lets the app read back the\n * last server response for a given interaction without re-firing it.\n */\nexport interface ResponseStore {\n set(key: string, value: WebhookResponse): void;\n get(key: string): WebhookResponse | undefined;\n keys(): string[];\n clear(): void;\n}\n\n/** In-memory store. Default; lost on reload. */\nexport class MemoryResponseStore implements ResponseStore {\n private readonly map = new Map<string, WebhookResponse>();\n\n set(key: string, value: WebhookResponse): void {\n this.map.set(key, value);\n }\n get(key: string): WebhookResponse | undefined {\n return this.map.get(key);\n }\n keys(): string[] {\n return [...this.map.keys()];\n }\n clear(): void {\n this.map.clear();\n }\n}\n\n/**\n * localStorage-backed store — survives reloads. Falls back to memory semantics\n * (silent no-op reads/writes) when localStorage is unavailable or throws.\n */\nexport class LocalStorageResponseStore implements ResponseStore {\n private readonly nsKey: string;\n\n constructor(prefix = \"xenia\") {\n this.nsKey = `${prefix}_responses`;\n }\n\n private read(): Record<string, WebhookResponse> {\n try {\n const raw = localStorage.getItem(this.nsKey);\n return raw ? (JSON.parse(raw) as Record<string, WebhookResponse>) : {};\n } catch {\n return {};\n }\n }\n\n private write(data: Record<string, WebhookResponse>): void {\n try {\n localStorage.setItem(this.nsKey, JSON.stringify(data));\n } catch {\n /* storage full / unavailable — ignore */\n }\n }\n\n set(key: string, value: WebhookResponse): void {\n const data = this.read();\n data[key] = value;\n this.write(data);\n }\n get(key: string): WebhookResponse | undefined {\n return this.read()[key];\n }\n keys(): string[] {\n return Object.keys(this.read());\n }\n clear(): void {\n try {\n localStorage.removeItem(this.nsKey);\n } catch {\n /* ignore */\n }\n }\n}\n"]}
|