@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.
Files changed (36) hide show
  1. package/README.md +31 -45
  2. package/dist/{actions-DfuHrjUL.d.cts → actions-CkiSdvSa.d.cts} +73 -11
  3. package/dist/{actions-DfuHrjUL.d.ts → actions-CkiSdvSa.d.ts} +73 -11
  4. package/dist/{chunk-PTYUK4P3.js → chunk-7D2HJMW6.js} +33 -4
  5. package/dist/chunk-7D2HJMW6.js.map +1 -0
  6. package/dist/{chunk-JEFNAD5J.cjs → chunk-UOW3GK4I.cjs} +34 -3
  7. package/dist/chunk-UOW3GK4I.cjs.map +1 -0
  8. package/dist/client/index.cjs +75 -9
  9. package/dist/client/index.cjs.map +1 -1
  10. package/dist/client/index.d.cts +2 -2
  11. package/dist/client/index.d.ts +2 -2
  12. package/dist/client/index.js +75 -9
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/client/react/index.cjs +75 -9
  15. package/dist/client/react/index.cjs.map +1 -1
  16. package/dist/client/react/index.d.cts +3 -3
  17. package/dist/client/react/index.d.ts +3 -3
  18. package/dist/client/react/index.js +75 -9
  19. package/dist/client/react/index.js.map +1 -1
  20. package/dist/{cookie-names-BQ-L04A8.d.cts → cookie-names-C3LKDhsg.d.cts} +42 -6
  21. package/dist/{cookie-names-BQ-L04A8.d.ts → cookie-names-C3LKDhsg.d.ts} +42 -6
  22. package/dist/index.cjs +23 -19
  23. package/dist/index.d.cts +25 -4
  24. package/dist/index.d.ts +25 -4
  25. package/dist/index.js +1 -1
  26. package/dist/server/index.cjs +53 -22
  27. package/dist/server/index.cjs.map +1 -1
  28. package/dist/server/index.d.cts +37 -14
  29. package/dist/server/index.d.ts +37 -14
  30. package/dist/server/index.js +43 -12
  31. package/dist/server/index.js.map +1 -1
  32. package/dist/xenia-sdk.iife.js +3 -3
  33. package/dist/xenia-sdk.iife.js.map +1 -1
  34. package/package.json +1 -1
  35. package/dist/chunk-JEFNAD5J.cjs.map +0 -1
  36. 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 baseUrl="/xenia" tenantId="t1" environmentKey="prod">
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({ baseUrl: "/xenia", tenantId: "t1", environmentKey: "prod" });
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({ baseUrl: "/xenia", tenantId: "t1", environmentKey: "prod" });
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({ baseUrl: "/xenia", tenantId: "t1", environmentKey: "prod" });
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` | — | Xenia base URL including `/xenia` (e.g. `/xenia` when same-origin). |
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
- baseUrl="/xenia" tenantId="t1" environmentKey="prod"
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
- **Visitor id is attached automatically.** The SDK-owned visitor id (see [Analytics](#analytics--conversions)) is injected into the resolve `data` as `_xenia_vid`, so the backend can bucket holdout experiments and record per-visitor exposure — you don't add it yourself, and it can't be overridden per call. The webhook path (`request()` / `usePersonalization()`) carries the same id on the `/flow` body. Browser: resolved from `identify()`/device id; server: from `config.visitorId` (bind the `xenia_vid` cookie per request).
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 baseUrl="/xenia" tenantId="t1" environmentKey="prod" getToken={getToken}>
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: `baseUrl` + tenant scope. Everything else
44
- * (identity, UTM, journey attribution, device id) is handled by the SDK.
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, e.g. `https://app.example.com/xenia` or
49
- * `/xenia` when same-origin (the FE proxies `/xenia/*`). No trailing slash.
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: string;
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` (which already includes `/xenia`).
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): void;
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-owned visitor id into the resolve `data` under the reserved
677
- * `_xenia_vid` key so the backend can bucket holdout experiments and record
678
- * exposure per visitor (it reads visitor identity from `data`). Set last so a
679
- * caller can't override it; a no-op when no visitor id is resolvable yet.
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: `baseUrl` + tenant scope. Everything else
44
- * (identity, UTM, journey attribution, device id) is handled by the SDK.
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, e.g. `https://app.example.com/xenia` or
49
- * `/xenia` when same-origin (the FE proxies `/xenia/*`). No trailing slash.
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: string;
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` (which already includes `/xenia`).
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): void;
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-owned visitor id into the resolve `data` under the reserved
677
- * `_xenia_vid` key so the backend can bucket holdout experiments and record
678
- * exposure per visitor (it reads visitor identity from `data`). Set last so a
679
- * caller can't override it; a no-op when no visitor id is resolvable yet.
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-PTYUK4P3.js.map
574
- //# sourceMappingURL=chunk-PTYUK4P3.js.map
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