@xenia-martech/sdk-js 1.1.0 → 1.2.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 +50 -0
- package/assets/coverage.json +1 -0
- package/dist/{actions-CKeiofxJ.d.cts → actions-D9_aAvCv.d.cts} +188 -2
- package/dist/{actions-CKeiofxJ.d.ts → actions-D9_aAvCv.d.ts} +188 -2
- package/dist/api-BEPW0Uan.d.cts +88 -0
- package/dist/api-CktVQ6Gl.d.ts +88 -0
- package/dist/{chunk-U2WBBOYT.cjs → chunk-3PYPPIUM.cjs} +35 -10
- package/dist/chunk-3PYPPIUM.cjs.map +1 -0
- package/dist/{chunk-FYSEKFIY.js → chunk-7NGJC7CF.js} +249 -3
- package/dist/chunk-7NGJC7CF.js.map +1 -0
- package/dist/{chunk-WYAMPEVV.js → chunk-DWFVDNU5.js} +28 -3
- package/dist/chunk-DWFVDNU5.js.map +1 -0
- package/dist/{chunk-JEKVN7HK.cjs → chunk-IBNIRT7J.cjs} +251 -2
- package/dist/chunk-IBNIRT7J.cjs.map +1 -0
- package/dist/{chunk-TLTG3BTC.js → chunk-OUZU4Z5B.js} +2 -2
- package/dist/{chunk-TLTG3BTC.js.map → chunk-OUZU4Z5B.js.map} +1 -1
- package/dist/{chunk-5D7SJIO4.cjs → chunk-PKWANLT7.cjs} +2 -2
- package/dist/{chunk-5D7SJIO4.cjs.map → chunk-PKWANLT7.cjs.map} +1 -1
- package/dist/client/index.cjs +287 -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 +285 -10
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.cjs +375 -13
- package/dist/client/react/index.cjs.map +1 -1
- package/dist/client/react/index.d.cts +53 -3
- package/dist/client/react/index.d.ts +53 -3
- package/dist/client/react/index.js +369 -14
- package/dist/client/react/index.js.map +1 -1
- package/dist/cms/contentful.cjs +138 -0
- package/dist/cms/contentful.cjs.map +1 -0
- package/dist/cms/contentful.d.cts +57 -0
- package/dist/cms/contentful.d.ts +57 -0
- package/dist/cms/contentful.js +134 -0
- package/dist/cms/contentful.js.map +1 -0
- package/dist/{cookie-names-QYMiEMAq.d.cts → cookie-names-BACctagz.d.cts} +1 -1
- package/dist/{cookie-names-8FxbGKGS.d.ts → cookie-names-CLqtmGBM.d.ts} +1 -1
- package/dist/index.cjs +35 -23
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/next/index.cjs +41 -9
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +32 -3
- package/dist/next/index.d.ts +32 -3
- package/dist/next/index.js +14 -3
- package/dist/next/index.js.map +1 -1
- package/dist/{api-URhxxi4x.d.cts → personalize-content-Clc1oMYY.d.cts} +152 -69
- package/dist/{api-URhxxi4x.d.ts → personalize-content-Clc1oMYY.d.ts} +152 -69
- package/dist/server/index.cjs +29 -17
- package/dist/server/index.d.cts +5 -4
- package/dist/server/index.d.ts +5 -4
- package/dist/server/index.js +4 -4
- package/dist/xenia-sdk.iife.js +3 -3
- package/dist/xenia-sdk.iife.js.map +1 -1
- package/dist/{xenia-server-Cti94W49.d.cts → xenia-server-BWhWD0La.d.ts} +15 -1
- package/dist/{xenia-server-DDtQp6Wi.d.ts → xenia-server-o_wDOcei.d.cts} +15 -1
- package/package.json +15 -1
- package/dist/chunk-FYSEKFIY.js.map +0 -1
- package/dist/chunk-JEKVN7HK.cjs.map +0 -1
- package/dist/chunk-U2WBBOYT.cjs.map +0 -1
- package/dist/chunk-WYAMPEVV.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @xenia-martech/sdk-js
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@xenia-martech/sdk-js)
|
|
4
|
+
[](https://www.npmjs.com/package/@xenia-martech/sdk-js)
|
|
5
|
+
[](https://www.npmjs.com/package/@xenia-martech/sdk-js)
|
|
6
|
+
[](https://www.npmjs.com/package/@xenia-martech/sdk-js)
|
|
7
|
+
[](https://bundlejs.com/?q=%40xenia-martech/sdk-js)
|
|
8
|
+
[](https://www.npmjs.com/package/@xenia-martech/sdk-js)
|
|
9
|
+
[](https://www.npmjs.com/package/@xenia-martech/sdk-js)
|
|
10
|
+
|
|
3
11
|
The official Xenia Marketer SDK for **web (pull) channels**. Drop in one wrapper, annotate a few elements, and Xenia handles identity, attribution, event delivery, in-page personalization, and analytics for you — from any framework, or none.
|
|
4
12
|
|
|
5
13
|
**Jump to a quick start:** [React](#-react) · [Vue / Angular / plain JS](#-vue-angular-or-plain-js) · [No-build (`<script>` tag)](#-no-build-iife) · [Server / Next.js / RSC](#-server-ssr--rsc)
|
|
@@ -131,6 +139,7 @@ Building a plain HTML site (no npm, no bundler)? Skip the install and pull the p
|
|
|
131
139
|
| `@xenia-martech/sdk-js/client/react` | browser + React | `<XeniaProvider>` + hooks (`useTrack`, `usePersonalization`, `useWebhook`, …), built on top of `/client`. |
|
|
132
140
|
| `@xenia-martech/sdk-js/server` | Node / SSR / RSC | Server-side events, webhooks, personalization, and token minting. Stateless — identity passed explicitly. |
|
|
133
141
|
| `@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). |
|
|
142
|
+
| `@xenia-martech/sdk-js/cms/contentful` | any | `contentfulAdapter()` for [CMS page personalization](#cms-page-personalization). |
|
|
134
143
|
| `@xenia-martech/sdk-js/iife` | `<script>` tag | Prebuilt `window.Xenia` for sites without a bundler. |
|
|
135
144
|
|
|
136
145
|
---
|
|
@@ -694,6 +703,47 @@ function Hero({ base }: { base: HeroContent }) {
|
|
|
694
703
|
|
|
695
704
|
The tracker reads `data-xenia-content-key` (required), `data-xenia-content-entry-id`, `data-xenia-base-entry-id`, and optional `data-xenia-personalized`. Server-side holdout *exposure* is recorded automatically by the `resolve()` call — the SDK does nothing for that.
|
|
696
705
|
|
|
706
|
+
### CMS page personalization
|
|
707
|
+
|
|
708
|
+
Tell the SDK which page to render; it fetches it from your CMS and swaps the entries a journey personalizes, at any depth:
|
|
709
|
+
|
|
710
|
+
```ts
|
|
711
|
+
import { contentfulAdapter } from "@xenia-martech/sdk-js/cms/contentful";
|
|
712
|
+
|
|
713
|
+
const cms = contentfulAdapter({ space, accessToken, include: 6 }); // or { client: contentful }
|
|
714
|
+
const { content } = await xenia.personalizePage({ contentType: "page", fields: { slug } }, { cms, timeoutMs: 250 });
|
|
715
|
+
// or by entry id: xenia.personalizePage("1Tl24n6FueOegEOALask0F", { cms })
|
|
716
|
+
// content is the page entry, or null when it doesn't exist / the CMS fetch failed (`error` says which)
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
Already fetching the page yourself (your own caching, a CMS client you keep)? Pass it in instead: `xenia.personalizeContent(page, { cms })`.
|
|
720
|
+
|
|
721
|
+
How it works:
|
|
722
|
+
|
|
723
|
+
- **Marking.** Xenia writes two fields onto personalized entries: `xenia` (the marker: `kind`, `op`, `field`) and `xeniaVariants` (static replacements).
|
|
724
|
+
- **Static swaps cost nothing.** A static swap's replacement comes back in your own CMS fetch, so it is applied with no Xenia call.
|
|
725
|
+
- **Runtime swaps cost one call per nesting level.** All runtime targets the walk finds go to Xenia in one `POST /api/personalization/resolve-entries` call. Their replacements are then loaded in one CMS call.
|
|
726
|
+
- **Nested targets.** A runtime target's own subtree is not walked until it has been decided, because it may be replaced. So targets inside it, or inside its replacement, are decided in the next round.
|
|
727
|
+
- **One-to-many.** `op: "expand"` splices N entries where one stood. `op: "items"` replaces an array field, e.g. a carousel's slides, with explicit entries or a content-type query (for example, "4 products in the visitor's category").
|
|
728
|
+
- **Bounded and safe.** All rounds share the `timeoutMs` budget (default 300 ms), capped by `maxRounds` (default 3). It never throws: anything undecided keeps its base content.
|
|
729
|
+
- **Skeletons, not flicker.** `pendingTargets(page, cms)` returns the entry ids Xenia still has to decide, read from the markers with no network call. Render those as skeletons and the visitor never sees base content that is about to change. The hooks' `status(id)` / `useXeniaEntry(id)` report `"pending"` from the first render, server render included.
|
|
730
|
+
|
|
731
|
+
Where to run it:
|
|
732
|
+
|
|
733
|
+
| Where | How | Trade-off |
|
|
734
|
+
|---|---|---|
|
|
735
|
+
| Server (SSR) | `await xeniaServer.personalizePage(ref, { cms })` | No flicker. Adds at most one Xenia round trip per nesting level to the response. |
|
|
736
|
+
| Next.js RSC, streamed | `personalizePage(ref, { cms, maxRounds: 0 })` for the page with no Xenia call; render the `pendingTargets()` entries as skeletons in a `<Suspense>` fallback, and the full `personalizePage` inside it (share one run per request with `personalizeOnce(key, fn)` from `/next`) | Fast first byte. Skeletons fill in when ready; no content changes. |
|
|
737
|
+
| Server + browser | Run on the server with `maxRounds: 1`, then pass the result to `usePersonalizedContent` | Above-the-fold content is personalized on the server. Deeper levels finish in the browser. Entries already decided are not asked about again. |
|
|
738
|
+
| Browser | `usePersonalizedPage(ref, { cms, render })` (or `usePersonalizedContent(page, …)` for a page you fetched) | `"base-first"` shows the page at once; render `status(id) === "pending"` entries as skeletons and they fill in as they are decided. `"hide-until-resolved"` holds the content back (up to `hideTimeoutMs`) so you can show a skeleton. `status(id)` and `useXeniaEntry(id)` give a loading state per entry. |
|
|
739
|
+
|
|
740
|
+
**Other CMSs.** Implement `CmsAdapter`:
|
|
741
|
+
|
|
742
|
+
- `entryId`, `marker`, `staticVariants`, `getField` / `setField` and `fetchEntries` / `queryEntries` are what you write;
|
|
743
|
+
- `fetchPage` (needed for `personalizePage`), `prepare`, `mergeItems` and `clearMarker` are optional.
|
|
744
|
+
|
|
745
|
+
The walk, rounds, budget and splicing are shared.
|
|
746
|
+
|
|
697
747
|
### Webhook-backed personalization
|
|
698
748
|
|
|
699
749
|
`usePersonalization(triggerId, data?)` is an explicit, standalone-looking call — but under the hood it's entirely webhook-based: it fires the given webhook trigger (same journey/webhook mechanism as `useWebhook()`) and hands you back the response body as `data`, re-firing whenever `triggerId` or `data` changes:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion":1,"label":"coverage","message":"98%","color":"brightgreen"}
|
|
@@ -351,6 +351,41 @@ interface ResolveOptions {
|
|
|
351
351
|
*/
|
|
352
352
|
context?: EventContext;
|
|
353
353
|
}
|
|
354
|
+
/** How a personalized entry's replacement is spliced into the page. */
|
|
355
|
+
type EntrySwapOp = "replace" | "expand" | "items";
|
|
356
|
+
/** CMS-neutral filter of a content-type query; the adapter maps it to its CMS syntax. */
|
|
357
|
+
interface EntryFilter {
|
|
358
|
+
field: string;
|
|
359
|
+
op: "eq" | "ne" | "in" | "nin" | "exists" | "lt" | "lte" | "gt" | "gte" | "match";
|
|
360
|
+
/** Already rendered from the visitor data by the backend; comma-separated for `in`/`nin`. */
|
|
361
|
+
value: string;
|
|
362
|
+
}
|
|
363
|
+
/** "Entries of this content type", run by the adapter against the tenant's own delivery API. */
|
|
364
|
+
interface EntryQuery {
|
|
365
|
+
contentType: string;
|
|
366
|
+
filters?: EntryFilter[];
|
|
367
|
+
/** CMS order expression, e.g. `-sys.updatedAt`. */
|
|
368
|
+
order?: string;
|
|
369
|
+
limit: number;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Xenia's decision for one personalized entry: splice `ids` (explicit entries) or the result of
|
|
373
|
+
* `query` at `target`. A requested target with no decision keeps its base content.
|
|
374
|
+
*/
|
|
375
|
+
interface EntryDecision {
|
|
376
|
+
target: string;
|
|
377
|
+
op: EntrySwapOp;
|
|
378
|
+
/** Array field replaced by `op: "items"`; absent = merge the replacement's array fields. */
|
|
379
|
+
field?: string;
|
|
380
|
+
ids?: string[];
|
|
381
|
+
query?: EntryQuery;
|
|
382
|
+
contentKey: string;
|
|
383
|
+
bucket?: string;
|
|
384
|
+
}
|
|
385
|
+
/** Response of `POST /api/personalization/resolve-entries`. */
|
|
386
|
+
interface ResolveEntriesResult {
|
|
387
|
+
decisions: EntryDecision[];
|
|
388
|
+
}
|
|
354
389
|
/** Body posted to `POST /api/events/v1/identify` — binds visitor to a known user. */
|
|
355
390
|
interface IdentifyRequest {
|
|
356
391
|
xeniaVisitorId: string;
|
|
@@ -699,8 +734,141 @@ declare class XeniaApi {
|
|
|
699
734
|
* semantics as {@link resolve}.
|
|
700
735
|
*/
|
|
701
736
|
resolveBatch(items: ResolveBatchItem[], opts?: ResolveOptions): Promise<ResolveBatchResult[]>;
|
|
737
|
+
/**
|
|
738
|
+
* Resolve personalized CMS entries by id for one visitor (≤200) —
|
|
739
|
+
* `POST /api/personalization/resolve-entries`. The page resolver
|
|
740
|
+
* ({@link personalizeContent}) calls this once per round with the runtime
|
|
741
|
+
* targets it found; callers rarely need it directly.
|
|
742
|
+
*/
|
|
743
|
+
resolveEntries(entryIds: string[], opts?: ResolveOptions): Promise<ResolveEntriesResult>;
|
|
702
744
|
}
|
|
703
745
|
|
|
746
|
+
/**
|
|
747
|
+
* CMS page personalization — walk a fetched CMS page breadth-first, swap the
|
|
748
|
+
* entries Xenia marked as personalized, and do it in as few round trips as
|
|
749
|
+
* the page's nesting allows.
|
|
750
|
+
*
|
|
751
|
+
* Rules (see the "Rounds" section of the feature plan):
|
|
752
|
+
* - **Static** targets carry their replacement in the payload (`xeniaVariants`)
|
|
753
|
+
* and are swapped inline — no network at all.
|
|
754
|
+
* - **Runtime** targets are a barrier: their id is collected, their subtree is
|
|
755
|
+
* NOT walked (it may be replaced). One `resolve-entries` call decides every
|
|
756
|
+
* target found in a round, one parallel CMS fetch loads every replacement,
|
|
757
|
+
* and the next round walks whatever now sits at each position.
|
|
758
|
+
* - Never throws: on error or when `timeoutMs` runs out, whatever is not yet
|
|
759
|
+
* decided keeps its base content.
|
|
760
|
+
*
|
|
761
|
+
* CMS specifics live behind {@link CmsAdapter}; built-in adapters ship as
|
|
762
|
+
* `@xenia-martech/sdk-js/cms/contentful` (and later `/cms/contentstack`). Any
|
|
763
|
+
* other CMS works by implementing the interface.
|
|
764
|
+
*/
|
|
765
|
+
|
|
766
|
+
/** Any value inside a CMS payload — the walker is shape-agnostic; adapters give it meaning. */
|
|
767
|
+
type Node = any;
|
|
768
|
+
/** The parsed `xenia` marker field of a personalized entry. */
|
|
769
|
+
interface XeniaMarker {
|
|
770
|
+
kind: "static" | "runtime";
|
|
771
|
+
op?: EntrySwapOp;
|
|
772
|
+
/** Array field replaced by `op: "items"`. */
|
|
773
|
+
field?: string;
|
|
774
|
+
limit?: number;
|
|
775
|
+
}
|
|
776
|
+
/** What the page resolver needs to know about a CMS's entries. `N` is the entry shape. */
|
|
777
|
+
interface CmsAdapter<N extends object = Node> {
|
|
778
|
+
/** The entry id when `value` is a CMS entry (not a link stub, asset or plain object). */
|
|
779
|
+
entryId(value: unknown): string | undefined;
|
|
780
|
+
/** The entry's Xenia marker, or null when it is not personalized. */
|
|
781
|
+
marker(entry: N): XeniaMarker | null;
|
|
782
|
+
/** Static replacements pinned on the entry (its resolved `xeniaVariants`). */
|
|
783
|
+
staticVariants(entry: N): N[];
|
|
784
|
+
getField(entry: N, field: string): unknown;
|
|
785
|
+
setField(entry: N, field: string, value: unknown): void;
|
|
786
|
+
/** Legacy `items` without a field: copy the replacement's array fields onto the entry. */
|
|
787
|
+
mergeItems?(entry: N, replacement: N): void;
|
|
788
|
+
/**
|
|
789
|
+
* Remove the marker from an entry that has been dealt with (kept base, or had its items replaced),
|
|
790
|
+
* so personalizing the output again — e.g. the browser continuing a server render — never decides
|
|
791
|
+
* it twice. Optional; without it a re-run re-asks Xenia about those entries.
|
|
792
|
+
*/
|
|
793
|
+
clearMarker?(entry: N): void;
|
|
794
|
+
/** Normalize a fetched payload before walking (e.g. resolve links). Defaults to identity. */
|
|
795
|
+
prepare?(payload: unknown): unknown;
|
|
796
|
+
/** Load entries by id in ONE call; order does not matter. */
|
|
797
|
+
fetchEntries(ids: string[], locale?: string): Promise<N[]>;
|
|
798
|
+
/** Run a content-type query in ONE call, in the query's order. */
|
|
799
|
+
queryEntries(query: EntryQuery, locale?: string): Promise<N[]>;
|
|
800
|
+
/**
|
|
801
|
+
* Load one page with its whole tree (links resolved), or null when there is none. Optional; it
|
|
802
|
+
* is what lets `personalizePage(ref)` do the fetch, so apps never fetch the page themselves.
|
|
803
|
+
*/
|
|
804
|
+
fetchPage?(ref: PageRef, locale?: string): Promise<N | null>;
|
|
805
|
+
}
|
|
806
|
+
/** Identifies a page in the CMS: its entry id, or a content type plus exact field values (a slug). */
|
|
807
|
+
type PageRef = string | {
|
|
808
|
+
contentType: string;
|
|
809
|
+
fields: Record<string, string | number | boolean>;
|
|
810
|
+
};
|
|
811
|
+
/** Resolves a round's runtime targets — `XeniaApi.resolveEntries` bound to the visitor. */
|
|
812
|
+
type EntryResolver = (entryIds: string[]) => Promise<EntryDecision[]>;
|
|
813
|
+
/** Reported for every swap actually applied (for impressions / analytics). */
|
|
814
|
+
interface AppliedSwap {
|
|
815
|
+
target: string;
|
|
816
|
+
kind: "static" | "runtime";
|
|
817
|
+
op: EntrySwapOp;
|
|
818
|
+
/** Ids of the entries now at the target's position (or in its field). */
|
|
819
|
+
entryIds: string[];
|
|
820
|
+
contentKey?: string;
|
|
821
|
+
bucket?: string;
|
|
822
|
+
round: number;
|
|
823
|
+
}
|
|
824
|
+
interface PersonalizeContentOptions {
|
|
825
|
+
cms: CmsAdapter;
|
|
826
|
+
/** Decides runtime targets. Omit to apply static swaps only (zero network). */
|
|
827
|
+
resolve?: EntryResolver;
|
|
828
|
+
locale?: string;
|
|
829
|
+
/** Max network rounds (nesting depth of runtime targets). Default 3. */
|
|
830
|
+
maxRounds?: number;
|
|
831
|
+
/** Total budget for all rounds, in ms. Undecided targets keep their base. Default 300. */
|
|
832
|
+
timeoutMs?: number;
|
|
833
|
+
/** Mutate the payload in place instead of personalizing a copy. Default false. */
|
|
834
|
+
inPlace?: boolean;
|
|
835
|
+
onApplied?(swap: AppliedSwap): void;
|
|
836
|
+
/**
|
|
837
|
+
* Called when a round starts waiting on the network (`pending` = targets being decided) and when
|
|
838
|
+
* it has been applied (`pending` empty). `content` is the live, partially personalized tree —
|
|
839
|
+
* lets a UI render level by level instead of waiting for every round.
|
|
840
|
+
*/
|
|
841
|
+
onProgress?(progress: {
|
|
842
|
+
content: unknown;
|
|
843
|
+
round: number;
|
|
844
|
+
pending: string[];
|
|
845
|
+
}): void;
|
|
846
|
+
}
|
|
847
|
+
/** `personalizeContent` options as taken by `XeniaClient` / `XeniaServer`, which supply `resolve`. */
|
|
848
|
+
type PagePersonalizeOptions = Omit<PersonalizeContentOptions, "resolve"> & Pick<ResolveOptions, "data" | "identity" | "context">;
|
|
849
|
+
interface PersonalizeContentResult<T> {
|
|
850
|
+
content: T;
|
|
851
|
+
applied: AppliedSwap[];
|
|
852
|
+
/** False when the budget ran out, a call failed, or `maxRounds` was hit with targets left. */
|
|
853
|
+
complete: boolean;
|
|
854
|
+
error?: unknown;
|
|
855
|
+
}
|
|
856
|
+
declare function personalizeContent<T>(payload: T, options: PersonalizeContentOptions): Promise<PersonalizeContentResult<T>>;
|
|
857
|
+
/**
|
|
858
|
+
* The entries Xenia still has to decide on this page — runtime targets reachable without crossing
|
|
859
|
+
* another undecided one. Synchronous and network-free (it reads the `xenia` markers), so a page can
|
|
860
|
+
* render a skeleton in exactly those places from the first frame instead of flashing base content.
|
|
861
|
+
* Nested targets inside a pending one are found once it is decided.
|
|
862
|
+
*/
|
|
863
|
+
declare function pendingTargets(payload: unknown, cms: CmsAdapter): string[];
|
|
864
|
+
/**
|
|
865
|
+
* Fetch a page through `options.cms` and personalize it — {@link personalizeContent} without the
|
|
866
|
+
* fetch in app code. Never throws: a failed fetch comes back as `content: null` with `error`; a page
|
|
867
|
+
* that does not exist as `content: null`, `complete: true`. The page fetch is not counted against
|
|
868
|
+
* `timeoutMs` — that budget is for personalization only.
|
|
869
|
+
*/
|
|
870
|
+
declare function personalizePage<T>(ref: PageRef, options: PersonalizeContentOptions): Promise<PersonalizeContentResult<T | null>>;
|
|
871
|
+
|
|
704
872
|
/**
|
|
705
873
|
* Persists webhook responses the SDK receives, keyed by an arbitrary string
|
|
706
874
|
* (typically the eventId, triggerId, or journeyId). Lets the app read back the
|
|
@@ -831,12 +999,16 @@ declare class XeniaClient {
|
|
|
831
999
|
private readonly renderer;
|
|
832
1000
|
private readonly analyticsQueue;
|
|
833
1001
|
private tracker;
|
|
834
|
-
|
|
1002
|
+
/** The in-flight (or finished) boot; shared so concurrent `init()` calls start one tracker. */
|
|
1003
|
+
private booting;
|
|
1004
|
+
/** Bumped by `destroy()`, so a boot still waiting on the device id knows to stop. */
|
|
1005
|
+
private generation;
|
|
835
1006
|
private consentState;
|
|
836
1007
|
private readonly listeners;
|
|
837
1008
|
constructor(options: XeniaClientOptions);
|
|
838
1009
|
/** Boot the client: resolve device id and start auto-tracking. Idempotent. */
|
|
839
1010
|
init(): Promise<void>;
|
|
1011
|
+
private boot;
|
|
840
1012
|
/** Tear down listeners, observers, and injected UI. */
|
|
841
1013
|
destroy(): void;
|
|
842
1014
|
/**
|
|
@@ -894,6 +1066,20 @@ declare class XeniaClient {
|
|
|
894
1066
|
resolve(contentKey: string, opts?: ResolveOptions): Promise<ResolveResult>;
|
|
895
1067
|
/** Resolve several content keys in one round-trip (`POST /api/personalization/resolve-batch`). */
|
|
896
1068
|
resolveBatch(items: ResolveBatchItem[], opts?: ResolveOptions): Promise<ResolveBatchResult[]>;
|
|
1069
|
+
/**
|
|
1070
|
+
* Personalize a fetched CMS page: static swaps are applied from the payload, runtime targets are
|
|
1071
|
+
* decided by Xenia in one `resolve-entries` call per nesting level, and replacements are loaded
|
|
1072
|
+
* through `opts.cms` (e.g. `contentfulAdapter(...)`). Never throws — anything undecided within
|
|
1073
|
+
* `opts.timeoutMs` keeps its base content.
|
|
1074
|
+
*/
|
|
1075
|
+
personalizeContent<T>(page: T, opts: PagePersonalizeOptions): Promise<PersonalizeContentResult<T>>;
|
|
1076
|
+
/**
|
|
1077
|
+
* Fetch a page through `opts.cms` and personalize it — the app passes only the page's identity
|
|
1078
|
+
* (entry id, or `{ contentType, fields: { slug } }`). Never throws: a failed fetch is `content:
|
|
1079
|
+
* null` with `error`, a missing page `content: null`.
|
|
1080
|
+
*/
|
|
1081
|
+
personalizePage<T = unknown>(ref: PageRef, opts: PagePersonalizeOptions): Promise<PersonalizeContentResult<T | null>>;
|
|
1082
|
+
private entryResolver;
|
|
897
1083
|
/** The SDK-owned visitor anchor: the `identify()`/`config.visitorId` id, else the device id. */
|
|
898
1084
|
private resolveVisitorId;
|
|
899
1085
|
/**
|
|
@@ -1002,4 +1188,4 @@ declare function extractContentSwaps(body: JsonValue): ContentSwap[] | undefined
|
|
|
1002
1188
|
*/
|
|
1003
1189
|
declare function parseActions(body: JsonValue): XeniaAction[];
|
|
1004
1190
|
|
|
1005
|
-
export { type AnalyticsContext as A,
|
|
1191
|
+
export { type AnalyticsContext as A, type WidgetType as B, type ConsentState as C, XeniaClient as D, type XeniaClientOptions as E, type XeniaMarker as F, createXeniaClient as G, extractContentSwaps as H, type InteractionType as I, type JsonValue as J, parseActions as K, pendingTargets as L, type MessageCta as M, personalizeContent as N, personalizePage as O, type PagePersonalizeOptions as P, spliceSlots as Q, 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 AppliedSwap as j, type ClientEvent as k, type CmsAdapter as l, type ContentSwap as m, type IdentifyRequest as n, type IdentifyResponse as o, Identity as p, type MessageMountContext as q, type MessageRenderContext as r, type PageRef as s, type PersonalizationSwap as t, type ResolveBatchResult as u, type ResolveOptions as v, type ResolveResult as w, type WebhookResponse as x, type WidgetPrize as y, type WidgetSegment as z };
|
|
@@ -351,6 +351,41 @@ interface ResolveOptions {
|
|
|
351
351
|
*/
|
|
352
352
|
context?: EventContext;
|
|
353
353
|
}
|
|
354
|
+
/** How a personalized entry's replacement is spliced into the page. */
|
|
355
|
+
type EntrySwapOp = "replace" | "expand" | "items";
|
|
356
|
+
/** CMS-neutral filter of a content-type query; the adapter maps it to its CMS syntax. */
|
|
357
|
+
interface EntryFilter {
|
|
358
|
+
field: string;
|
|
359
|
+
op: "eq" | "ne" | "in" | "nin" | "exists" | "lt" | "lte" | "gt" | "gte" | "match";
|
|
360
|
+
/** Already rendered from the visitor data by the backend; comma-separated for `in`/`nin`. */
|
|
361
|
+
value: string;
|
|
362
|
+
}
|
|
363
|
+
/** "Entries of this content type", run by the adapter against the tenant's own delivery API. */
|
|
364
|
+
interface EntryQuery {
|
|
365
|
+
contentType: string;
|
|
366
|
+
filters?: EntryFilter[];
|
|
367
|
+
/** CMS order expression, e.g. `-sys.updatedAt`. */
|
|
368
|
+
order?: string;
|
|
369
|
+
limit: number;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Xenia's decision for one personalized entry: splice `ids` (explicit entries) or the result of
|
|
373
|
+
* `query` at `target`. A requested target with no decision keeps its base content.
|
|
374
|
+
*/
|
|
375
|
+
interface EntryDecision {
|
|
376
|
+
target: string;
|
|
377
|
+
op: EntrySwapOp;
|
|
378
|
+
/** Array field replaced by `op: "items"`; absent = merge the replacement's array fields. */
|
|
379
|
+
field?: string;
|
|
380
|
+
ids?: string[];
|
|
381
|
+
query?: EntryQuery;
|
|
382
|
+
contentKey: string;
|
|
383
|
+
bucket?: string;
|
|
384
|
+
}
|
|
385
|
+
/** Response of `POST /api/personalization/resolve-entries`. */
|
|
386
|
+
interface ResolveEntriesResult {
|
|
387
|
+
decisions: EntryDecision[];
|
|
388
|
+
}
|
|
354
389
|
/** Body posted to `POST /api/events/v1/identify` — binds visitor to a known user. */
|
|
355
390
|
interface IdentifyRequest {
|
|
356
391
|
xeniaVisitorId: string;
|
|
@@ -699,8 +734,141 @@ declare class XeniaApi {
|
|
|
699
734
|
* semantics as {@link resolve}.
|
|
700
735
|
*/
|
|
701
736
|
resolveBatch(items: ResolveBatchItem[], opts?: ResolveOptions): Promise<ResolveBatchResult[]>;
|
|
737
|
+
/**
|
|
738
|
+
* Resolve personalized CMS entries by id for one visitor (≤200) —
|
|
739
|
+
* `POST /api/personalization/resolve-entries`. The page resolver
|
|
740
|
+
* ({@link personalizeContent}) calls this once per round with the runtime
|
|
741
|
+
* targets it found; callers rarely need it directly.
|
|
742
|
+
*/
|
|
743
|
+
resolveEntries(entryIds: string[], opts?: ResolveOptions): Promise<ResolveEntriesResult>;
|
|
702
744
|
}
|
|
703
745
|
|
|
746
|
+
/**
|
|
747
|
+
* CMS page personalization — walk a fetched CMS page breadth-first, swap the
|
|
748
|
+
* entries Xenia marked as personalized, and do it in as few round trips as
|
|
749
|
+
* the page's nesting allows.
|
|
750
|
+
*
|
|
751
|
+
* Rules (see the "Rounds" section of the feature plan):
|
|
752
|
+
* - **Static** targets carry their replacement in the payload (`xeniaVariants`)
|
|
753
|
+
* and are swapped inline — no network at all.
|
|
754
|
+
* - **Runtime** targets are a barrier: their id is collected, their subtree is
|
|
755
|
+
* NOT walked (it may be replaced). One `resolve-entries` call decides every
|
|
756
|
+
* target found in a round, one parallel CMS fetch loads every replacement,
|
|
757
|
+
* and the next round walks whatever now sits at each position.
|
|
758
|
+
* - Never throws: on error or when `timeoutMs` runs out, whatever is not yet
|
|
759
|
+
* decided keeps its base content.
|
|
760
|
+
*
|
|
761
|
+
* CMS specifics live behind {@link CmsAdapter}; built-in adapters ship as
|
|
762
|
+
* `@xenia-martech/sdk-js/cms/contentful` (and later `/cms/contentstack`). Any
|
|
763
|
+
* other CMS works by implementing the interface.
|
|
764
|
+
*/
|
|
765
|
+
|
|
766
|
+
/** Any value inside a CMS payload — the walker is shape-agnostic; adapters give it meaning. */
|
|
767
|
+
type Node = any;
|
|
768
|
+
/** The parsed `xenia` marker field of a personalized entry. */
|
|
769
|
+
interface XeniaMarker {
|
|
770
|
+
kind: "static" | "runtime";
|
|
771
|
+
op?: EntrySwapOp;
|
|
772
|
+
/** Array field replaced by `op: "items"`. */
|
|
773
|
+
field?: string;
|
|
774
|
+
limit?: number;
|
|
775
|
+
}
|
|
776
|
+
/** What the page resolver needs to know about a CMS's entries. `N` is the entry shape. */
|
|
777
|
+
interface CmsAdapter<N extends object = Node> {
|
|
778
|
+
/** The entry id when `value` is a CMS entry (not a link stub, asset or plain object). */
|
|
779
|
+
entryId(value: unknown): string | undefined;
|
|
780
|
+
/** The entry's Xenia marker, or null when it is not personalized. */
|
|
781
|
+
marker(entry: N): XeniaMarker | null;
|
|
782
|
+
/** Static replacements pinned on the entry (its resolved `xeniaVariants`). */
|
|
783
|
+
staticVariants(entry: N): N[];
|
|
784
|
+
getField(entry: N, field: string): unknown;
|
|
785
|
+
setField(entry: N, field: string, value: unknown): void;
|
|
786
|
+
/** Legacy `items` without a field: copy the replacement's array fields onto the entry. */
|
|
787
|
+
mergeItems?(entry: N, replacement: N): void;
|
|
788
|
+
/**
|
|
789
|
+
* Remove the marker from an entry that has been dealt with (kept base, or had its items replaced),
|
|
790
|
+
* so personalizing the output again — e.g. the browser continuing a server render — never decides
|
|
791
|
+
* it twice. Optional; without it a re-run re-asks Xenia about those entries.
|
|
792
|
+
*/
|
|
793
|
+
clearMarker?(entry: N): void;
|
|
794
|
+
/** Normalize a fetched payload before walking (e.g. resolve links). Defaults to identity. */
|
|
795
|
+
prepare?(payload: unknown): unknown;
|
|
796
|
+
/** Load entries by id in ONE call; order does not matter. */
|
|
797
|
+
fetchEntries(ids: string[], locale?: string): Promise<N[]>;
|
|
798
|
+
/** Run a content-type query in ONE call, in the query's order. */
|
|
799
|
+
queryEntries(query: EntryQuery, locale?: string): Promise<N[]>;
|
|
800
|
+
/**
|
|
801
|
+
* Load one page with its whole tree (links resolved), or null when there is none. Optional; it
|
|
802
|
+
* is what lets `personalizePage(ref)` do the fetch, so apps never fetch the page themselves.
|
|
803
|
+
*/
|
|
804
|
+
fetchPage?(ref: PageRef, locale?: string): Promise<N | null>;
|
|
805
|
+
}
|
|
806
|
+
/** Identifies a page in the CMS: its entry id, or a content type plus exact field values (a slug). */
|
|
807
|
+
type PageRef = string | {
|
|
808
|
+
contentType: string;
|
|
809
|
+
fields: Record<string, string | number | boolean>;
|
|
810
|
+
};
|
|
811
|
+
/** Resolves a round's runtime targets — `XeniaApi.resolveEntries` bound to the visitor. */
|
|
812
|
+
type EntryResolver = (entryIds: string[]) => Promise<EntryDecision[]>;
|
|
813
|
+
/** Reported for every swap actually applied (for impressions / analytics). */
|
|
814
|
+
interface AppliedSwap {
|
|
815
|
+
target: string;
|
|
816
|
+
kind: "static" | "runtime";
|
|
817
|
+
op: EntrySwapOp;
|
|
818
|
+
/** Ids of the entries now at the target's position (or in its field). */
|
|
819
|
+
entryIds: string[];
|
|
820
|
+
contentKey?: string;
|
|
821
|
+
bucket?: string;
|
|
822
|
+
round: number;
|
|
823
|
+
}
|
|
824
|
+
interface PersonalizeContentOptions {
|
|
825
|
+
cms: CmsAdapter;
|
|
826
|
+
/** Decides runtime targets. Omit to apply static swaps only (zero network). */
|
|
827
|
+
resolve?: EntryResolver;
|
|
828
|
+
locale?: string;
|
|
829
|
+
/** Max network rounds (nesting depth of runtime targets). Default 3. */
|
|
830
|
+
maxRounds?: number;
|
|
831
|
+
/** Total budget for all rounds, in ms. Undecided targets keep their base. Default 300. */
|
|
832
|
+
timeoutMs?: number;
|
|
833
|
+
/** Mutate the payload in place instead of personalizing a copy. Default false. */
|
|
834
|
+
inPlace?: boolean;
|
|
835
|
+
onApplied?(swap: AppliedSwap): void;
|
|
836
|
+
/**
|
|
837
|
+
* Called when a round starts waiting on the network (`pending` = targets being decided) and when
|
|
838
|
+
* it has been applied (`pending` empty). `content` is the live, partially personalized tree —
|
|
839
|
+
* lets a UI render level by level instead of waiting for every round.
|
|
840
|
+
*/
|
|
841
|
+
onProgress?(progress: {
|
|
842
|
+
content: unknown;
|
|
843
|
+
round: number;
|
|
844
|
+
pending: string[];
|
|
845
|
+
}): void;
|
|
846
|
+
}
|
|
847
|
+
/** `personalizeContent` options as taken by `XeniaClient` / `XeniaServer`, which supply `resolve`. */
|
|
848
|
+
type PagePersonalizeOptions = Omit<PersonalizeContentOptions, "resolve"> & Pick<ResolveOptions, "data" | "identity" | "context">;
|
|
849
|
+
interface PersonalizeContentResult<T> {
|
|
850
|
+
content: T;
|
|
851
|
+
applied: AppliedSwap[];
|
|
852
|
+
/** False when the budget ran out, a call failed, or `maxRounds` was hit with targets left. */
|
|
853
|
+
complete: boolean;
|
|
854
|
+
error?: unknown;
|
|
855
|
+
}
|
|
856
|
+
declare function personalizeContent<T>(payload: T, options: PersonalizeContentOptions): Promise<PersonalizeContentResult<T>>;
|
|
857
|
+
/**
|
|
858
|
+
* The entries Xenia still has to decide on this page — runtime targets reachable without crossing
|
|
859
|
+
* another undecided one. Synchronous and network-free (it reads the `xenia` markers), so a page can
|
|
860
|
+
* render a skeleton in exactly those places from the first frame instead of flashing base content.
|
|
861
|
+
* Nested targets inside a pending one are found once it is decided.
|
|
862
|
+
*/
|
|
863
|
+
declare function pendingTargets(payload: unknown, cms: CmsAdapter): string[];
|
|
864
|
+
/**
|
|
865
|
+
* Fetch a page through `options.cms` and personalize it — {@link personalizeContent} without the
|
|
866
|
+
* fetch in app code. Never throws: a failed fetch comes back as `content: null` with `error`; a page
|
|
867
|
+
* that does not exist as `content: null`, `complete: true`. The page fetch is not counted against
|
|
868
|
+
* `timeoutMs` — that budget is for personalization only.
|
|
869
|
+
*/
|
|
870
|
+
declare function personalizePage<T>(ref: PageRef, options: PersonalizeContentOptions): Promise<PersonalizeContentResult<T | null>>;
|
|
871
|
+
|
|
704
872
|
/**
|
|
705
873
|
* Persists webhook responses the SDK receives, keyed by an arbitrary string
|
|
706
874
|
* (typically the eventId, triggerId, or journeyId). Lets the app read back the
|
|
@@ -831,12 +999,16 @@ declare class XeniaClient {
|
|
|
831
999
|
private readonly renderer;
|
|
832
1000
|
private readonly analyticsQueue;
|
|
833
1001
|
private tracker;
|
|
834
|
-
|
|
1002
|
+
/** The in-flight (or finished) boot; shared so concurrent `init()` calls start one tracker. */
|
|
1003
|
+
private booting;
|
|
1004
|
+
/** Bumped by `destroy()`, so a boot still waiting on the device id knows to stop. */
|
|
1005
|
+
private generation;
|
|
835
1006
|
private consentState;
|
|
836
1007
|
private readonly listeners;
|
|
837
1008
|
constructor(options: XeniaClientOptions);
|
|
838
1009
|
/** Boot the client: resolve device id and start auto-tracking. Idempotent. */
|
|
839
1010
|
init(): Promise<void>;
|
|
1011
|
+
private boot;
|
|
840
1012
|
/** Tear down listeners, observers, and injected UI. */
|
|
841
1013
|
destroy(): void;
|
|
842
1014
|
/**
|
|
@@ -894,6 +1066,20 @@ declare class XeniaClient {
|
|
|
894
1066
|
resolve(contentKey: string, opts?: ResolveOptions): Promise<ResolveResult>;
|
|
895
1067
|
/** Resolve several content keys in one round-trip (`POST /api/personalization/resolve-batch`). */
|
|
896
1068
|
resolveBatch(items: ResolveBatchItem[], opts?: ResolveOptions): Promise<ResolveBatchResult[]>;
|
|
1069
|
+
/**
|
|
1070
|
+
* Personalize a fetched CMS page: static swaps are applied from the payload, runtime targets are
|
|
1071
|
+
* decided by Xenia in one `resolve-entries` call per nesting level, and replacements are loaded
|
|
1072
|
+
* through `opts.cms` (e.g. `contentfulAdapter(...)`). Never throws — anything undecided within
|
|
1073
|
+
* `opts.timeoutMs` keeps its base content.
|
|
1074
|
+
*/
|
|
1075
|
+
personalizeContent<T>(page: T, opts: PagePersonalizeOptions): Promise<PersonalizeContentResult<T>>;
|
|
1076
|
+
/**
|
|
1077
|
+
* Fetch a page through `opts.cms` and personalize it — the app passes only the page's identity
|
|
1078
|
+
* (entry id, or `{ contentType, fields: { slug } }`). Never throws: a failed fetch is `content:
|
|
1079
|
+
* null` with `error`, a missing page `content: null`.
|
|
1080
|
+
*/
|
|
1081
|
+
personalizePage<T = unknown>(ref: PageRef, opts: PagePersonalizeOptions): Promise<PersonalizeContentResult<T | null>>;
|
|
1082
|
+
private entryResolver;
|
|
897
1083
|
/** The SDK-owned visitor anchor: the `identify()`/`config.visitorId` id, else the device id. */
|
|
898
1084
|
private resolveVisitorId;
|
|
899
1085
|
/**
|
|
@@ -1002,4 +1188,4 @@ declare function extractContentSwaps(body: JsonValue): ContentSwap[] | undefined
|
|
|
1002
1188
|
*/
|
|
1003
1189
|
declare function parseActions(body: JsonValue): XeniaAction[];
|
|
1004
1190
|
|
|
1005
|
-
export { type AnalyticsContext as A,
|
|
1191
|
+
export { type AnalyticsContext as A, type WidgetType as B, type ConsentState as C, XeniaClient as D, type XeniaClientOptions as E, type XeniaMarker as F, createXeniaClient as G, extractContentSwaps as H, type InteractionType as I, type JsonValue as J, parseActions as K, pendingTargets as L, type MessageCta as M, personalizeContent as N, personalizePage as O, type PagePersonalizeOptions as P, spliceSlots as Q, 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 AppliedSwap as j, type ClientEvent as k, type CmsAdapter as l, type ContentSwap as m, type IdentifyRequest as n, type IdentifyResponse as o, Identity as p, type MessageMountContext as q, type MessageRenderContext as r, type PageRef as s, type PersonalizationSwap as t, type ResolveBatchResult as u, type ResolveOptions as v, type ResolveResult as w, type WebhookResponse as x, type WidgetPrize as y, type WidgetSegment as z };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { L as XeniaConfig, J as JsonValue, W as WebhookResponse, F as WidgetSubmitResponse, N as XeniaEvent, c as AnalyticsIngestResponse, I as IdentifyRequest, m as IdentifyResponse, f as ConsentState, w as ResolveOptions, x as ResolveResult, R as ResolveBatchItem, u as ResolveBatchResult, v as ResolveEntriesResult } from './personalize-content-Clc1oMYY.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Low-level, isomorphic Xenia API client. Wraps the raw endpoints; identity,
|
|
5
|
+
* UTM capture, and action rendering are layered on top by the client runtime.
|
|
6
|
+
* All paths are relative to `baseUrl`, which defaults to the hosted engine
|
|
7
|
+
* (`https://engine.xeniamartech.com/xenia`) when omitted from config.
|
|
8
|
+
*/
|
|
9
|
+
declare class XeniaApi {
|
|
10
|
+
private readonly http;
|
|
11
|
+
private readonly tenantId;
|
|
12
|
+
private readonly environmentKey;
|
|
13
|
+
private readonly tokenProvider;
|
|
14
|
+
constructor(config: XeniaConfig);
|
|
15
|
+
/** Tenant-scope headers required by header-scoped endpoints. */
|
|
16
|
+
private tenantHeaders;
|
|
17
|
+
/**
|
|
18
|
+
* Tenant headers plus a resolved `Authorization: Bearer` header when
|
|
19
|
+
* `config.token`/`config.getToken` is set — e.g. required by a webhook
|
|
20
|
+
* trigger node configured with its own auth token. No-op (tenant headers
|
|
21
|
+
* only) when no token source is configured, so this is safe to call
|
|
22
|
+
* unconditionally on every request.
|
|
23
|
+
*/
|
|
24
|
+
private authHeaders;
|
|
25
|
+
/**
|
|
26
|
+
* Webhook request/response — `GET|POST /flow?id={triggerId}`. Held open by the
|
|
27
|
+
* backend until a `webhook-response` node resolves. Returns the normalized
|
|
28
|
+
* response, including any `xeniaActions` parsed from the body.
|
|
29
|
+
*
|
|
30
|
+
* Not retried by default: the backend long-polls this call, so a retry would
|
|
31
|
+
* duplicate the journey run.
|
|
32
|
+
*/
|
|
33
|
+
webhook(triggerId: string, data?: JsonValue, method?: "GET" | "POST"): Promise<WebhookResponse>;
|
|
34
|
+
/**
|
|
35
|
+
* Widget form submission — `POST /widget-submissions?ref={bindingRef}`. Used whenever the
|
|
36
|
+
* action's `formSubmit.requiresResolve` is true (spin-wheel, which must have a
|
|
37
|
+
* server-resolved outcome before anything renders) or `mode === "endpoint"` — the common
|
|
38
|
+
* plain-popup case (`mode === "journey" && !requiresResolve`) bypasses this entirely and calls
|
|
39
|
+
* {@link webhook} against `flowRef` instead (see `XeniaClient.submitWidgetForm`'s endpoint
|
|
40
|
+
* selection).
|
|
41
|
+
*
|
|
42
|
+
* Not retried (`noRetry: true`), styled like {@link webhook} — a retried POST must never
|
|
43
|
+
* double-fire a journey or double-resolve a prize server-side.
|
|
44
|
+
*/
|
|
45
|
+
submitWidget(ref: string, body?: JsonValue): Promise<WidgetSubmitResponse>;
|
|
46
|
+
/**
|
|
47
|
+
* Unified event batch (max 100/request per the backend) — `POST /api/events/v1`.
|
|
48
|
+
* Records analytics for every event and matches/advances a journey for any
|
|
49
|
+
* event with `trigger: true`. Replaces the old `sendEvent()` (journey trigger)
|
|
50
|
+
* and `sendAnalytics()` (CDP) calls.
|
|
51
|
+
*/
|
|
52
|
+
sendEvents(events: XeniaEvent[], opts?: {
|
|
53
|
+
keepalive?: boolean;
|
|
54
|
+
}): Promise<AnalyticsIngestResponse>;
|
|
55
|
+
/** Bind visitor→user — `POST /api/events/v1/identify`. */
|
|
56
|
+
identify(body: IdentifyRequest): Promise<IdentifyResponse>;
|
|
57
|
+
/**
|
|
58
|
+
* Update consent — `POST /api/events/v1/consent`.
|
|
59
|
+
*
|
|
60
|
+
* The three scope flags are required (non-null) by the backend, so an omitted
|
|
61
|
+
* one is sent as an explicit `false` rather than left out: a partial body like
|
|
62
|
+
* `consent({ analytics: true })` — the shape most callers actually write — is
|
|
63
|
+
* otherwise rejected with a 400 and the consent record is never stored.
|
|
64
|
+
*/
|
|
65
|
+
consent(xeniaVisitorId: string, consent: ConsentState): Promise<JsonValue>;
|
|
66
|
+
/**
|
|
67
|
+
* Resolve in-entry content personalization for one content key —
|
|
68
|
+
* `POST /api/personalization/resolve?merge=<bool>`. Requires the SDK Bearer
|
|
69
|
+
* token (same credential as events; resolve is auth-required). `merge=true`
|
|
70
|
+
* returns server-spliced `content`; otherwise splice `swaps` client-side.
|
|
71
|
+
*/
|
|
72
|
+
resolve(contentKey: string, opts?: ResolveOptions): Promise<ResolveResult>;
|
|
73
|
+
/**
|
|
74
|
+
* Resolve several content keys in one round-trip (≤50) —
|
|
75
|
+
* `POST /api/personalization/resolve-batch?merge=<bool>`. Same auth/merge
|
|
76
|
+
* semantics as {@link resolve}.
|
|
77
|
+
*/
|
|
78
|
+
resolveBatch(items: ResolveBatchItem[], opts?: ResolveOptions): Promise<ResolveBatchResult[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Resolve personalized CMS entries by id for one visitor (≤200) —
|
|
81
|
+
* `POST /api/personalization/resolve-entries`. The page resolver
|
|
82
|
+
* ({@link personalizeContent}) calls this once per round with the runtime
|
|
83
|
+
* targets it found; callers rarely need it directly.
|
|
84
|
+
*/
|
|
85
|
+
resolveEntries(entryIds: string[], opts?: ResolveOptions): Promise<ResolveEntriesResult>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { XeniaApi as X };
|