@xenia-martech/sdk-js 1.0.0-dev.25 → 1.0.0-dev.27
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 +31 -45
- package/dist/{actions-DfuHrjUL.d.cts → actions-CkiSdvSa.d.cts} +73 -11
- package/dist/{actions-DfuHrjUL.d.ts → actions-CkiSdvSa.d.ts} +73 -11
- package/dist/{chunk-PTYUK4P3.js → chunk-7D2HJMW6.js} +33 -4
- package/dist/chunk-7D2HJMW6.js.map +1 -0
- package/dist/{chunk-JEFNAD5J.cjs → chunk-UOW3GK4I.cjs} +34 -3
- package/dist/chunk-UOW3GK4I.cjs.map +1 -0
- package/dist/client/index.cjs +75 -9
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +75 -9
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.cjs +75 -9
- package/dist/client/react/index.cjs.map +1 -1
- package/dist/client/react/index.d.cts +3 -3
- package/dist/client/react/index.d.ts +3 -3
- package/dist/client/react/index.js +75 -9
- package/dist/client/react/index.js.map +1 -1
- package/dist/{cookie-names-BQ-L04A8.d.cts → cookie-names-C3LKDhsg.d.cts} +42 -6
- package/dist/{cookie-names-BQ-L04A8.d.ts → cookie-names-C3LKDhsg.d.ts} +42 -6
- package/dist/index.cjs +23 -19
- package/dist/index.d.cts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.js +1 -1
- package/dist/server/index.cjs +53 -22
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +37 -14
- package/dist/server/index.d.ts +37 -14
- package/dist/server/index.js +43 -12
- 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/package.json +1 -1
- package/dist/chunk-JEFNAD5J.cjs.map +0 -1
- package/dist/chunk-PTYUK4P3.js.map +0 -1
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ import { XeniaProvider } from "@xenia-martech/sdk-js/client/react";
|
|
|
130
130
|
|
|
131
131
|
export default function App() {
|
|
132
132
|
return (
|
|
133
|
-
<XeniaProvider
|
|
133
|
+
<XeniaProvider tenantId="t1" environmentKey="prod">
|
|
134
134
|
{/* Declarative auto-tracking: data-xenia="<eventId>&<click|view-port|page-load>" */}
|
|
135
135
|
<button data-xenia="checkout&click">Checkout</button>
|
|
136
136
|
<section data-xenia="hero&view-port">Hero</section>
|
|
@@ -187,7 +187,7 @@ Every hook and component in this section is listed in full in the [API reference
|
|
|
187
187
|
```ts
|
|
188
188
|
import { createXeniaClient } from "@xenia-martech/sdk-js/client";
|
|
189
189
|
|
|
190
|
-
const xenia = createXeniaClient({
|
|
190
|
+
const xenia = createXeniaClient({ tenantId: "t1", environmentKey: "prod" });
|
|
191
191
|
await xenia.init(); // resolves device id, starts data-xenia auto-tracking
|
|
192
192
|
|
|
193
193
|
xenia.track("add_to_cart", { productId: "p1" }, { trigger: true });
|
|
@@ -207,7 +207,7 @@ import { createXeniaClient } from "@xenia-martech/sdk-js/client";
|
|
|
207
207
|
|
|
208
208
|
export const XeniaPlugin = {
|
|
209
209
|
install(app: App) {
|
|
210
|
-
const xenia = createXeniaClient({
|
|
210
|
+
const xenia = createXeniaClient({ tenantId: "t1", environmentKey: "prod" });
|
|
211
211
|
void xenia.init();
|
|
212
212
|
app.provide("xenia", xenia);
|
|
213
213
|
app.config.globalProperties.$xenia = xenia;
|
|
@@ -223,7 +223,6 @@ import { createXeniaClient, type XeniaClient } from "@xenia-martech/sdk-js/clien
|
|
|
223
223
|
@Injectable({ providedIn: "root" })
|
|
224
224
|
export class XeniaService implements OnDestroy {
|
|
225
225
|
readonly client: XeniaClient = createXeniaClient({
|
|
226
|
-
baseUrl: "/xenia",
|
|
227
226
|
tenantId: "t1",
|
|
228
227
|
environmentKey: "prod",
|
|
229
228
|
});
|
|
@@ -245,7 +244,7 @@ One script tag, then annotate elements. Exposes a global `window.Xenia`. Grab th
|
|
|
245
244
|
```html
|
|
246
245
|
<script src="/js/xenia-sdk.iife.js"></script>
|
|
247
246
|
<script>
|
|
248
|
-
Xenia.init({
|
|
247
|
+
Xenia.init({ tenantId: "t1", environmentKey: "prod" });
|
|
249
248
|
Xenia.consent({ analytics: true, marketing: false, personalization: false }); // nothing analytics-only sends until this is called
|
|
250
249
|
// Xenia.identify("visitor-123");
|
|
251
250
|
</script>
|
|
@@ -287,8 +286,8 @@ Serve tokens to the browser, and fire server-side events/webhooks/personalizatio
|
|
|
287
286
|
import { createTokenRoute } from "@xenia-martech/sdk-js/server";
|
|
288
287
|
|
|
289
288
|
export const GET = createTokenRoute({
|
|
290
|
-
baseUrl: process.env.XENIA_URL!, // e.g. https://app.example.com/xenia
|
|
291
289
|
sdkToken: process.env.XENIA_SDK_TOKEN!,
|
|
290
|
+
// baseUrl: process.env.XENIA_URL, // only to override the default hosted engine
|
|
292
291
|
});
|
|
293
292
|
```
|
|
294
293
|
|
|
@@ -300,10 +299,10 @@ Not on a framework with route handlers? Call the same exchange yourself with `re
|
|
|
300
299
|
import { createXeniaServer } from "@xenia-martech/sdk-js/server";
|
|
301
300
|
|
|
302
301
|
const xenia = createXeniaServer({
|
|
303
|
-
baseUrl: process.env.XENIA_URL!,
|
|
304
302
|
tenantId: "t1",
|
|
305
303
|
environmentKey: "prod",
|
|
306
304
|
token: process.env.XENIA_TOKEN,
|
|
305
|
+
// baseUrl: process.env.XENIA_URL, // only to override the default hosted engine
|
|
307
306
|
});
|
|
308
307
|
|
|
309
308
|
await xenia.track("purchase", {
|
|
@@ -354,7 +353,7 @@ Passed to `XeniaProvider`, `createXeniaClient`, `createXeniaServer`, or `Xenia.i
|
|
|
354
353
|
|
|
355
354
|
| Option | Type | Default | Notes |
|
|
356
355
|
|---|---|---|---|
|
|
357
|
-
| `baseUrl` | `string` |
|
|
356
|
+
| `baseUrl` | `string` | `https://engine.xeniamartech.com/xenia` | Xenia base URL. Override only for a self-hosted or non-default environment. |
|
|
358
357
|
| `tenantId` | `string` | — | Sent as `X-Tenant-Id`. |
|
|
359
358
|
| `environmentKey` | `string` | — | Sent as `X-Environment`. |
|
|
360
359
|
| `token` | `string` | — | SDK Bearer token, sent as `Authorization: Bearer <token>` on every request. Required for `resolve()`/`resolveBatch()`; needed for events/webhooks only when a trigger node has its own auth token. |
|
|
@@ -494,7 +493,7 @@ function OfferCard({ action, close, fireCta }: XeniaMessageComponentProps) {
|
|
|
494
493
|
}
|
|
495
494
|
|
|
496
495
|
<XeniaProvider
|
|
497
|
-
|
|
496
|
+
tenantId="t1" environmentKey="prod"
|
|
498
497
|
messageComponent={reactMessageComponent(OfferCard)}
|
|
499
498
|
>
|
|
500
499
|
<App />
|
|
@@ -536,7 +535,27 @@ Each entry in `swaps` is `{ identifier, replacementEntryId, mode }` — `identif
|
|
|
536
535
|
|
|
537
536
|
**Auth:** resolve **requires** the SDK Bearer token — the *same* `token` / `getToken` config as events (there is no separate personalization token). Events *optionally* send it (public otherwise); resolve *requires* it (401 without). Set it server-side; a browser without a token can still `track()` publicly but cannot `resolve()`.
|
|
538
537
|
|
|
539
|
-
**
|
|
538
|
+
**SDK-provided fields are attached automatically.** Every `resolve()`/`resolveBatch()` call carries a fixed set of fields into `data` — `path`, `query`, `deviceId`, `visitorId`, `customerId`, `email`, `url`, `referrer`, `utmSource`, `utmMedium`, `utmCampaign`, `utmContent`, `utmId` — plus the reserved `_xenia_vid` key the backend reads for holdout/exposure bucketing. These are the same "SDK-provided fields" a Personalization Resolve journey trigger exposes natively to downstream nodes. None of them can be overridden per call — the SDK's own values always win, even if your `data` happens to use one of these names.
|
|
539
|
+
|
|
540
|
+
- **Browser:** collected ambiently — page URL/path/query/referrer/UTMs come from `window`/`document`; `visitorId`/`deviceId` from `identify()`/the auto-resolved device id; `customerId`/`email` from `identify(visitorId, { customerId, email })`. Nothing to configure.
|
|
541
|
+
- **Server:** there's no ambient `window`/`document`, so bind the request **once** when you construct the per-request client and every call carries the fields automatically — no per-call wiring:
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
import { createXeniaServer } from "@xenia-martech/sdk-js/server";
|
|
545
|
+
import { contextFromUrl } from "@xenia-martech/sdk-js";
|
|
546
|
+
|
|
547
|
+
const xenia = createXeniaServer({
|
|
548
|
+
tenantId, environmentKey, token,
|
|
549
|
+
visitorId: getVisitorId(cookieHeader), // the browser's xenia_vid cookie
|
|
550
|
+
context: contextFromUrl(currentUrl, referrer), // path / query / url / referrer / utm*
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
await xenia.resolve("hero"); // ← fields already attached
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
`contextFromUrl(url, referrer?)` derives the same fields the browser reads off `window.location`, so a rule can gate on `query.<name>` or `path` on a server-rendered page. In a framework where the render context can't see its own URL (e.g. a Next.js RSC), stamp the URL onto the request in middleware and read it back from `headers()` — see the `nextjs` example.
|
|
557
|
+
|
|
558
|
+
`opts.context`/`opts.identity` remain available per call, as an override on top of the ambient context or for values known only at that call site (e.g. `identity: { customerId: "c1", email: "a@b.com" }`). `visitorId`/`_xenia_vid` still come from `config.visitorId` and win over a per-call `identity.visitorId`. A client with neither bound behaves exactly as before.
|
|
540
559
|
|
|
541
560
|
```tsx
|
|
542
561
|
import { usePersonalizationResolve } from "@xenia-martech/sdk-js/client/react";
|
|
@@ -662,39 +681,6 @@ Server-side, `XeniaServer.track(eventName, opts)` and `XeniaServer.identify(body
|
|
|
662
681
|
|
|
663
682
|
Real wiring for the situations that come up once you're past "hello world" — pulled from working reference implementations, not hypotheticals.
|
|
664
683
|
|
|
665
|
-
### Same-origin dev proxy (avoids CORS on `resolve()`)
|
|
666
|
-
|
|
667
|
-
`resolve()`/`resolveBatch()` are auth-required and don't send wildcard-CORS headers, so they only work same-origin (or through a reverse proxy that presents them as same-origin). `track()`/`request()` are public and CORS-open, so this matters specifically for personalization.
|
|
668
|
-
|
|
669
|
-
```ts
|
|
670
|
-
// vite.config.ts
|
|
671
|
-
export default defineConfig({
|
|
672
|
-
server: {
|
|
673
|
-
proxy: {
|
|
674
|
-
"/xenia": {
|
|
675
|
-
target: "http://localhost:8081",
|
|
676
|
-
changeOrigin: true,
|
|
677
|
-
// Strip Origin so the backend treats this as same-origin/server-to-server,
|
|
678
|
-
// not a cross-origin browser call it would otherwise 403 on the auth-required
|
|
679
|
-
// personalization endpoints.
|
|
680
|
-
configure: (proxy) => proxy.on("proxyReq", (req) => req.removeHeader("origin")),
|
|
681
|
-
},
|
|
682
|
-
},
|
|
683
|
-
},
|
|
684
|
-
});
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
```ts
|
|
688
|
-
// next.config.ts
|
|
689
|
-
const nextConfig = {
|
|
690
|
-
async rewrites() {
|
|
691
|
-
return [{ source: "/xenia/:path*", destination: `${process.env.XENIA_URL}/:path*` }];
|
|
692
|
-
},
|
|
693
|
-
};
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
Point `baseUrl: "/xenia"` (the default) at either proxy and `track`, `request`, **and** `resolve` all work with zero CORS configuration on the backend.
|
|
697
|
-
|
|
698
684
|
### Server actions — cart abandonment, checkout conversion
|
|
699
685
|
|
|
700
686
|
A Next.js server-actions storefront, wiring `add_to_cart` to advance an abandoned-cart journey and `purchase` to record a conversion. The same shape applies to any backend framework.
|
|
@@ -709,11 +695,11 @@ const CONSENT = { analytics: true, marketing: true, personalization: true };
|
|
|
709
695
|
|
|
710
696
|
export async function xeniaServer(visitorId?: string) {
|
|
711
697
|
return createXeniaServer({
|
|
712
|
-
baseUrl: process.env.XENIA_URL!,
|
|
713
698
|
tenantId: process.env.XENIA_TENANT_ID!,
|
|
714
699
|
environmentKey: process.env.XENIA_ENV_KEY!,
|
|
715
700
|
token: process.env.XENIA_SDK_TOKEN,
|
|
716
701
|
visitorId,
|
|
702
|
+
// baseUrl: process.env.XENIA_URL, // only to override the default hosted engine
|
|
717
703
|
});
|
|
718
704
|
}
|
|
719
705
|
|
|
@@ -808,7 +794,7 @@ function OfferBanner({ triggerId }: { triggerId: string }) {
|
|
|
808
794
|
|
|
809
795
|
export function WelcomeOffer({ triggerId }: { triggerId: string }) {
|
|
810
796
|
return (
|
|
811
|
-
<XeniaProvider
|
|
797
|
+
<XeniaProvider tenantId="t1" environmentKey="prod" getToken={getToken}>
|
|
812
798
|
<OfferBanner triggerId={triggerId} />
|
|
813
799
|
</XeniaProvider>
|
|
814
800
|
);
|
|
@@ -40,15 +40,16 @@ interface RetryConfig {
|
|
|
40
40
|
/**
|
|
41
41
|
* Root configuration shared by client and server runtimes.
|
|
42
42
|
*
|
|
43
|
-
* Developers supply the minimum:
|
|
44
|
-
*
|
|
43
|
+
* Developers supply the minimum: tenant scope. Everything else (identity,
|
|
44
|
+
* UTM, journey attribution, device id) is handled by the SDK.
|
|
45
45
|
*/
|
|
46
46
|
interface XeniaConfig {
|
|
47
47
|
/**
|
|
48
|
-
* Base URL of the Xenia backend
|
|
49
|
-
*
|
|
48
|
+
* Base URL of the Xenia backend. Defaults to the hosted engine,
|
|
49
|
+
* `https://engine.xeniamartech.com/xenia` — omit this unless you're
|
|
50
|
+
* pointing at a self-hosted or non-default environment.
|
|
50
51
|
*/
|
|
51
|
-
baseUrl
|
|
52
|
+
baseUrl?: string;
|
|
52
53
|
/** Tenant id — sent as `X-Tenant-Id` on header-scoped endpoints. */
|
|
53
54
|
tenantId: string;
|
|
54
55
|
/** Environment key — sent as `X-Environment` on header-scoped endpoints. */
|
|
@@ -79,6 +80,23 @@ interface XeniaConfig {
|
|
|
79
80
|
* cookie read via `getVisitorId()`.
|
|
80
81
|
*/
|
|
81
82
|
visitorId?: string;
|
|
83
|
+
/**
|
|
84
|
+
* The ambient request context for a **server** client — the SDK-provided page/campaign fields
|
|
85
|
+
* (`path`, `query`, `url`, `referrer`, `utmSource`/`utmMedium`/`utmCampaign`/`utmContent`/`utmId`)
|
|
86
|
+
* that get attached to EVERY `resolve()`/`resolveBatch()`/`webhook()` made with this client, so
|
|
87
|
+
* journey rules can gate on `query.<name>` or `path` without any per-call wiring.
|
|
88
|
+
*
|
|
89
|
+
* The browser client needs nothing here — it reads these off `window.location` itself. A server
|
|
90
|
+
* runtime has no ambient request, so bind it once when constructing the per-request client:
|
|
91
|
+
*
|
|
92
|
+
* ```ts
|
|
93
|
+
* createXeniaServer({ …, context: contextFromUrl(currentUrl, referrer) })
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* Lowest precedence: a per-call `context`/`identity` overrides it, and the SDK-owned `visitorId`
|
|
97
|
+
* still wins over everything.
|
|
98
|
+
*/
|
|
99
|
+
context?: EventContext;
|
|
82
100
|
/**
|
|
83
101
|
* Persist the SDK visitor id to a first-party `xenia_vid` cookie so your
|
|
84
102
|
* **same-origin** server can read it (`getVisitorId(cookieHeader)`) and bind
|
|
@@ -114,6 +132,13 @@ interface VisitorCookieOptions {
|
|
|
114
132
|
/** Cookie lifetime in days. Default 365. */
|
|
115
133
|
maxAgeDays?: number;
|
|
116
134
|
}
|
|
135
|
+
/** Well-known event fields understood by the backend event-trigger node. */
|
|
136
|
+
interface EventIdentity {
|
|
137
|
+
deviceId?: string;
|
|
138
|
+
visitorId?: string;
|
|
139
|
+
customerId?: string;
|
|
140
|
+
email?: string;
|
|
141
|
+
}
|
|
117
142
|
/** UTM + page context captured automatically by the client. */
|
|
118
143
|
interface EventContext {
|
|
119
144
|
utmSource?: string;
|
|
@@ -290,6 +315,23 @@ interface ResolveOptions {
|
|
|
290
315
|
locale?: string;
|
|
291
316
|
/** When true, ask the server to splice and return `content`. */
|
|
292
317
|
merge?: boolean;
|
|
318
|
+
/**
|
|
319
|
+
* Caller-supplied identity fields (`deviceId`/`visitorId`/`customerId`/
|
|
320
|
+
* `email`), merged into `data` the same way `ServerWebhookOptions.identity`
|
|
321
|
+
* is merged for `webhook()`. Only relevant server-side — the browser client
|
|
322
|
+
* collects these ambiently instead and ignores this field. The SDK-owned
|
|
323
|
+
* `visitorId` (from `config.visitorId`) still wins over a value supplied
|
|
324
|
+
* here, same precedence as `webhook()`.
|
|
325
|
+
*/
|
|
326
|
+
identity?: EventIdentity;
|
|
327
|
+
/**
|
|
328
|
+
* Caller-supplied page context (`path`/`query`/`url`/`referrer`/`utm*`),
|
|
329
|
+
* merged into `data` the same way `ServerWebhookOptions.context` is merged
|
|
330
|
+
* for `webhook()`. There's no ambient `window`/`document` server-side, so a
|
|
331
|
+
* server caller who wants these SDK-provided fields on a resolve() call
|
|
332
|
+
* must pass them explicitly (e.g. from the current request's headers).
|
|
333
|
+
*/
|
|
334
|
+
context?: EventContext;
|
|
293
335
|
}
|
|
294
336
|
/** Body posted to `POST /api/events/v1/identify` — binds visitor to a known user. */
|
|
295
337
|
interface IdentifyRequest {
|
|
@@ -426,7 +468,8 @@ type MessageComponentRenderer = (container: HTMLElement, ctx: MessageMountContex
|
|
|
426
468
|
/**
|
|
427
469
|
* Low-level, isomorphic Xenia API client. Wraps the raw endpoints; identity,
|
|
428
470
|
* UTM capture, and action rendering are layered on top by the client runtime.
|
|
429
|
-
* All paths are relative to `baseUrl
|
|
471
|
+
* All paths are relative to `baseUrl`, which defaults to the hosted engine
|
|
472
|
+
* (`https://engine.xeniamartech.com/xenia`) when omitted from config.
|
|
430
473
|
*/
|
|
431
474
|
declare class XeniaApi {
|
|
432
475
|
private readonly http;
|
|
@@ -541,6 +584,10 @@ declare class Identity {
|
|
|
541
584
|
/** Persist a visitor id so it rides on every future event. */
|
|
542
585
|
identify(visitorId: string): void;
|
|
543
586
|
getVisitorId(): string | null;
|
|
587
|
+
setCustomerId(customerId: string | null): void;
|
|
588
|
+
getCustomerId(): string | null;
|
|
589
|
+
setEmail(email: string | null): void;
|
|
590
|
+
getEmail(): string | null;
|
|
544
591
|
/**
|
|
545
592
|
* Resolve the current session id: reused while activity continues within
|
|
546
593
|
* {@link SESSION_TIMEOUT_MS}, otherwise a new one is minted. Persisted in
|
|
@@ -626,8 +673,15 @@ declare class XeniaClient {
|
|
|
626
673
|
* Persist a visitor id, attached to every subsequent event, and bind it to
|
|
627
674
|
* a known user server-side (`POST /api/events/v1/identify`, fire-and-
|
|
628
675
|
* forget — resolves to an `"identify"` client event carrying `personId`).
|
|
676
|
+
*
|
|
677
|
+
* `extra.customerId`/`extra.email`, when supplied, are also persisted
|
|
678
|
+
* ambiently (like the visitor id) so they ride on every later `resolve()`
|
|
679
|
+
* call automatically — part of the SDK-provided-fields parity list.
|
|
629
680
|
*/
|
|
630
|
-
identify(visitorId: string
|
|
681
|
+
identify(visitorId: string, extra?: {
|
|
682
|
+
customerId?: string;
|
|
683
|
+
email?: string;
|
|
684
|
+
}): void;
|
|
631
685
|
/** Override the customer-journey UUID (else derived from utm_id). */
|
|
632
686
|
setJourneyId(id: string | null): void;
|
|
633
687
|
/**
|
|
@@ -673,10 +727,18 @@ declare class XeniaClient {
|
|
|
673
727
|
/** The SDK-owned visitor anchor: the `identify()`/`config.visitorId` id, else the device id. */
|
|
674
728
|
private resolveVisitorId;
|
|
675
729
|
/**
|
|
676
|
-
* Stamp the SDK-
|
|
677
|
-
* `
|
|
678
|
-
*
|
|
679
|
-
*
|
|
730
|
+
* Stamp the full set of SDK-provided fields into the resolve `data` —
|
|
731
|
+
* `path`, `query`, `deviceId`, `visitorId`, `customerId`, `email`, `url`,
|
|
732
|
+
* `referrer`, and the five `utm*` fields — the same field-name/type parity
|
|
733
|
+
* the webhook trigger's "SDK-provided fields" already promises, now native
|
|
734
|
+
* (always attached, no marketer toggle) for the Personalization Resolve
|
|
735
|
+
* trigger too. Plus the reserved `_xenia_vid` key the backend reads for
|
|
736
|
+
* holdout/exposure bucketing. All of these are spread LAST (after the
|
|
737
|
+
* caller's own `data`) so none can be overridden per call — the same
|
|
738
|
+
* override-protection guarantee `_xenia_vid` alone used to have, now
|
|
739
|
+
* widened to the full set. A no-op (returns `opts` unchanged) when nothing
|
|
740
|
+
* is resolvable yet (e.g. called before `init()` on a fresh visit with no
|
|
741
|
+
* page context available).
|
|
680
742
|
*/
|
|
681
743
|
private withVisitor;
|
|
682
744
|
private dntBlocked;
|
|
@@ -40,15 +40,16 @@ interface RetryConfig {
|
|
|
40
40
|
/**
|
|
41
41
|
* Root configuration shared by client and server runtimes.
|
|
42
42
|
*
|
|
43
|
-
* Developers supply the minimum:
|
|
44
|
-
*
|
|
43
|
+
* Developers supply the minimum: tenant scope. Everything else (identity,
|
|
44
|
+
* UTM, journey attribution, device id) is handled by the SDK.
|
|
45
45
|
*/
|
|
46
46
|
interface XeniaConfig {
|
|
47
47
|
/**
|
|
48
|
-
* Base URL of the Xenia backend
|
|
49
|
-
*
|
|
48
|
+
* Base URL of the Xenia backend. Defaults to the hosted engine,
|
|
49
|
+
* `https://engine.xeniamartech.com/xenia` — omit this unless you're
|
|
50
|
+
* pointing at a self-hosted or non-default environment.
|
|
50
51
|
*/
|
|
51
|
-
baseUrl
|
|
52
|
+
baseUrl?: string;
|
|
52
53
|
/** Tenant id — sent as `X-Tenant-Id` on header-scoped endpoints. */
|
|
53
54
|
tenantId: string;
|
|
54
55
|
/** Environment key — sent as `X-Environment` on header-scoped endpoints. */
|
|
@@ -79,6 +80,23 @@ interface XeniaConfig {
|
|
|
79
80
|
* cookie read via `getVisitorId()`.
|
|
80
81
|
*/
|
|
81
82
|
visitorId?: string;
|
|
83
|
+
/**
|
|
84
|
+
* The ambient request context for a **server** client — the SDK-provided page/campaign fields
|
|
85
|
+
* (`path`, `query`, `url`, `referrer`, `utmSource`/`utmMedium`/`utmCampaign`/`utmContent`/`utmId`)
|
|
86
|
+
* that get attached to EVERY `resolve()`/`resolveBatch()`/`webhook()` made with this client, so
|
|
87
|
+
* journey rules can gate on `query.<name>` or `path` without any per-call wiring.
|
|
88
|
+
*
|
|
89
|
+
* The browser client needs nothing here — it reads these off `window.location` itself. A server
|
|
90
|
+
* runtime has no ambient request, so bind it once when constructing the per-request client:
|
|
91
|
+
*
|
|
92
|
+
* ```ts
|
|
93
|
+
* createXeniaServer({ …, context: contextFromUrl(currentUrl, referrer) })
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* Lowest precedence: a per-call `context`/`identity` overrides it, and the SDK-owned `visitorId`
|
|
97
|
+
* still wins over everything.
|
|
98
|
+
*/
|
|
99
|
+
context?: EventContext;
|
|
82
100
|
/**
|
|
83
101
|
* Persist the SDK visitor id to a first-party `xenia_vid` cookie so your
|
|
84
102
|
* **same-origin** server can read it (`getVisitorId(cookieHeader)`) and bind
|
|
@@ -114,6 +132,13 @@ interface VisitorCookieOptions {
|
|
|
114
132
|
/** Cookie lifetime in days. Default 365. */
|
|
115
133
|
maxAgeDays?: number;
|
|
116
134
|
}
|
|
135
|
+
/** Well-known event fields understood by the backend event-trigger node. */
|
|
136
|
+
interface EventIdentity {
|
|
137
|
+
deviceId?: string;
|
|
138
|
+
visitorId?: string;
|
|
139
|
+
customerId?: string;
|
|
140
|
+
email?: string;
|
|
141
|
+
}
|
|
117
142
|
/** UTM + page context captured automatically by the client. */
|
|
118
143
|
interface EventContext {
|
|
119
144
|
utmSource?: string;
|
|
@@ -290,6 +315,23 @@ interface ResolveOptions {
|
|
|
290
315
|
locale?: string;
|
|
291
316
|
/** When true, ask the server to splice and return `content`. */
|
|
292
317
|
merge?: boolean;
|
|
318
|
+
/**
|
|
319
|
+
* Caller-supplied identity fields (`deviceId`/`visitorId`/`customerId`/
|
|
320
|
+
* `email`), merged into `data` the same way `ServerWebhookOptions.identity`
|
|
321
|
+
* is merged for `webhook()`. Only relevant server-side — the browser client
|
|
322
|
+
* collects these ambiently instead and ignores this field. The SDK-owned
|
|
323
|
+
* `visitorId` (from `config.visitorId`) still wins over a value supplied
|
|
324
|
+
* here, same precedence as `webhook()`.
|
|
325
|
+
*/
|
|
326
|
+
identity?: EventIdentity;
|
|
327
|
+
/**
|
|
328
|
+
* Caller-supplied page context (`path`/`query`/`url`/`referrer`/`utm*`),
|
|
329
|
+
* merged into `data` the same way `ServerWebhookOptions.context` is merged
|
|
330
|
+
* for `webhook()`. There's no ambient `window`/`document` server-side, so a
|
|
331
|
+
* server caller who wants these SDK-provided fields on a resolve() call
|
|
332
|
+
* must pass them explicitly (e.g. from the current request's headers).
|
|
333
|
+
*/
|
|
334
|
+
context?: EventContext;
|
|
293
335
|
}
|
|
294
336
|
/** Body posted to `POST /api/events/v1/identify` — binds visitor to a known user. */
|
|
295
337
|
interface IdentifyRequest {
|
|
@@ -426,7 +468,8 @@ type MessageComponentRenderer = (container: HTMLElement, ctx: MessageMountContex
|
|
|
426
468
|
/**
|
|
427
469
|
* Low-level, isomorphic Xenia API client. Wraps the raw endpoints; identity,
|
|
428
470
|
* UTM capture, and action rendering are layered on top by the client runtime.
|
|
429
|
-
* All paths are relative to `baseUrl
|
|
471
|
+
* All paths are relative to `baseUrl`, which defaults to the hosted engine
|
|
472
|
+
* (`https://engine.xeniamartech.com/xenia`) when omitted from config.
|
|
430
473
|
*/
|
|
431
474
|
declare class XeniaApi {
|
|
432
475
|
private readonly http;
|
|
@@ -541,6 +584,10 @@ declare class Identity {
|
|
|
541
584
|
/** Persist a visitor id so it rides on every future event. */
|
|
542
585
|
identify(visitorId: string): void;
|
|
543
586
|
getVisitorId(): string | null;
|
|
587
|
+
setCustomerId(customerId: string | null): void;
|
|
588
|
+
getCustomerId(): string | null;
|
|
589
|
+
setEmail(email: string | null): void;
|
|
590
|
+
getEmail(): string | null;
|
|
544
591
|
/**
|
|
545
592
|
* Resolve the current session id: reused while activity continues within
|
|
546
593
|
* {@link SESSION_TIMEOUT_MS}, otherwise a new one is minted. Persisted in
|
|
@@ -626,8 +673,15 @@ declare class XeniaClient {
|
|
|
626
673
|
* Persist a visitor id, attached to every subsequent event, and bind it to
|
|
627
674
|
* a known user server-side (`POST /api/events/v1/identify`, fire-and-
|
|
628
675
|
* forget — resolves to an `"identify"` client event carrying `personId`).
|
|
676
|
+
*
|
|
677
|
+
* `extra.customerId`/`extra.email`, when supplied, are also persisted
|
|
678
|
+
* ambiently (like the visitor id) so they ride on every later `resolve()`
|
|
679
|
+
* call automatically — part of the SDK-provided-fields parity list.
|
|
629
680
|
*/
|
|
630
|
-
identify(visitorId: string
|
|
681
|
+
identify(visitorId: string, extra?: {
|
|
682
|
+
customerId?: string;
|
|
683
|
+
email?: string;
|
|
684
|
+
}): void;
|
|
631
685
|
/** Override the customer-journey UUID (else derived from utm_id). */
|
|
632
686
|
setJourneyId(id: string | null): void;
|
|
633
687
|
/**
|
|
@@ -673,10 +727,18 @@ declare class XeniaClient {
|
|
|
673
727
|
/** The SDK-owned visitor anchor: the `identify()`/`config.visitorId` id, else the device id. */
|
|
674
728
|
private resolveVisitorId;
|
|
675
729
|
/**
|
|
676
|
-
* Stamp the SDK-
|
|
677
|
-
* `
|
|
678
|
-
*
|
|
679
|
-
*
|
|
730
|
+
* Stamp the full set of SDK-provided fields into the resolve `data` —
|
|
731
|
+
* `path`, `query`, `deviceId`, `visitorId`, `customerId`, `email`, `url`,
|
|
732
|
+
* `referrer`, and the five `utm*` fields — the same field-name/type parity
|
|
733
|
+
* the webhook trigger's "SDK-provided fields" already promises, now native
|
|
734
|
+
* (always attached, no marketer toggle) for the Personalization Resolve
|
|
735
|
+
* trigger too. Plus the reserved `_xenia_vid` key the backend reads for
|
|
736
|
+
* holdout/exposure bucketing. All of these are spread LAST (after the
|
|
737
|
+
* caller's own `data`) so none can be overridden per call — the same
|
|
738
|
+
* override-protection guarantee `_xenia_vid` alone used to have, now
|
|
739
|
+
* widened to the full set. A no-op (returns `opts` unchanged) when nothing
|
|
740
|
+
* is resolvable yet (e.g. called before `init()` on a fresh visit with no
|
|
741
|
+
* page context available).
|
|
680
742
|
*/
|
|
681
743
|
private withVisitor;
|
|
682
744
|
private dntBlocked;
|
|
@@ -85,6 +85,7 @@ function parseActions(body) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// src/core/http-client.ts
|
|
88
|
+
var DEFAULT_BASE_URL = "https://engine.xeniamartech.com/xenia";
|
|
88
89
|
function isBrowser() {
|
|
89
90
|
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
90
91
|
}
|
|
@@ -114,7 +115,7 @@ var HttpClient = class {
|
|
|
114
115
|
retry;
|
|
115
116
|
debug;
|
|
116
117
|
constructor(opts) {
|
|
117
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
118
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
118
119
|
this.defaultHeaders = opts.defaultHeaders ?? {};
|
|
119
120
|
this.timeout = opts.timeout ?? 8e3;
|
|
120
121
|
this.retry = {
|
|
@@ -515,6 +516,34 @@ function extractContentSwaps(body) {
|
|
|
515
516
|
return Array.isArray(swaps) ? swaps : void 0;
|
|
516
517
|
}
|
|
517
518
|
|
|
519
|
+
// src/core/request-context.ts
|
|
520
|
+
function contextFromUrl(url, referrer) {
|
|
521
|
+
const ctx = {};
|
|
522
|
+
if (referrer) ctx.referrer = referrer;
|
|
523
|
+
if (!url) return ctx;
|
|
524
|
+
let parsed;
|
|
525
|
+
try {
|
|
526
|
+
parsed = new URL(url);
|
|
527
|
+
} catch {
|
|
528
|
+
return ctx;
|
|
529
|
+
}
|
|
530
|
+
ctx.url = parsed.href;
|
|
531
|
+
ctx.path = parsed.pathname;
|
|
532
|
+
ctx.query = Object.fromEntries(parsed.searchParams);
|
|
533
|
+
const utm = (name) => parsed.searchParams.get(name) || void 0;
|
|
534
|
+
const utmSource = utm("utm_source");
|
|
535
|
+
const utmMedium = utm("utm_medium");
|
|
536
|
+
const utmCampaign = utm("utm_campaign");
|
|
537
|
+
const utmContent = utm("utm_content");
|
|
538
|
+
const utmId = utm("utm_id");
|
|
539
|
+
if (utmSource) ctx.utmSource = utmSource;
|
|
540
|
+
if (utmMedium) ctx.utmMedium = utmMedium;
|
|
541
|
+
if (utmCampaign) ctx.utmCampaign = utmCampaign;
|
|
542
|
+
if (utmContent) ctx.utmContent = utmContent;
|
|
543
|
+
if (utmId) ctx.utmId = utmId;
|
|
544
|
+
return ctx;
|
|
545
|
+
}
|
|
546
|
+
|
|
518
547
|
// src/core/response-store.ts
|
|
519
548
|
var MemoryResponseStore = class {
|
|
520
549
|
map = /* @__PURE__ */ new Map();
|
|
@@ -569,6 +598,6 @@ var LocalStorageResponseStore = class {
|
|
|
569
598
|
}
|
|
570
599
|
};
|
|
571
600
|
|
|
572
|
-
export { CONSENT_COOKIE, DEFAULT_PREFIX, HttpClient, HttpError, LocalStorageResponseStore, MAX_EVENT_ID_LENGTH, MemoryResponseStore, VISITOR_COOKIE, XeniaApi, buildUrl, consentCookieName, extractContentSwaps, generateUlid, isBrowser, parseActions, spliceSlots, toEventId, visitorCookieName };
|
|
573
|
-
//# sourceMappingURL=chunk-
|
|
574
|
-
//# sourceMappingURL=chunk-
|
|
601
|
+
export { CONSENT_COOKIE, DEFAULT_PREFIX, HttpClient, HttpError, LocalStorageResponseStore, MAX_EVENT_ID_LENGTH, MemoryResponseStore, VISITOR_COOKIE, XeniaApi, buildUrl, compact, consentCookieName, contextFromUrl, extractContentSwaps, generateUlid, isBrowser, parseActions, spliceSlots, toEventId, visitorCookieName };
|
|
602
|
+
//# sourceMappingURL=chunk-7D2HJMW6.js.map
|
|
603
|
+
//# sourceMappingURL=chunk-7D2HJMW6.js.map
|