@xenia-martech/sdk-js 1.1.0 → 1.2.0-dev.2
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-BcdlokGO.d.cts} +192 -4
- package/dist/{actions-CKeiofxJ.d.ts → actions-BcdlokGO.d.ts} +192 -4
- 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 +314 -12
- 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 +312 -13
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.cjs +402 -16
- 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 +396 -17
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ElementType, ComponentType } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export { A as AnalyticsContext, h as AnalyticsIdentity, i as AnalyticsIngestResponse,
|
|
3
|
+
import { E as XeniaClientOptions, D as XeniaClient, w as ResolveResult, u as ResolveBatchResult, J as JsonValue, x as WebhookResponse, R as ResolveBatchItem, g as AnalyticsEventName, T as TrackOptions, P as PagePersonalizeOptions, j as AppliedSwap, s as PageRef, e as XeniaAction, I as InteractionType, q as MessageMountContext, c as MessageComponentRenderer } from '../../actions-BcdlokGO.cjs';
|
|
4
|
+
export { A as AnalyticsContext, h as AnalyticsIdentity, i as AnalyticsIngestResponse, k as ClientEvent, l as CmsAdapter, C as ConsentState, m as ContentSwap, n as IdentifyRequest, o as IdentifyResponse, M as MessageCta, f as MessagePlacement, r as MessageRenderContext, b as MessageTemplateFn, t as PersonalizationSwap, v as ResolveOptions, X as XeniaConfig, a as XeniaEvent, F as XeniaMarker, G as createXeniaClient, H as extractContentSwaps, K as parseActions, L as pendingTargets, N as personalizeContent, O as personalizePage, Q as spliceSlots } from '../../actions-BcdlokGO.cjs';
|
|
5
5
|
|
|
6
6
|
interface XeniaProviderProps extends XeniaClientOptions {
|
|
7
7
|
children: ReactNode;
|
|
@@ -118,6 +118,56 @@ declare function useWebhook(triggerId: string): UseWebhookResult;
|
|
|
118
118
|
/** Read the last saved webhook response for a key (reactive to sends via re-render). */
|
|
119
119
|
declare function useXeniaResponse(key: string): WebhookResponse | undefined;
|
|
120
120
|
|
|
121
|
+
/** Where one personalized entry stands while the page resolves. */
|
|
122
|
+
type EntryStatus = "pending" | "personalized" | "base";
|
|
123
|
+
interface UsePersonalizedContentOptions extends PagePersonalizeOptions {
|
|
124
|
+
/**
|
|
125
|
+
* What to render before Xenia answers:
|
|
126
|
+
* - `"base-first"` (default): the CMS content immediately, swapped in place round by round
|
|
127
|
+
* (best LCP; personalized sections can visibly change).
|
|
128
|
+
* - `"hide-until-resolved"`: `content` is null until done or `hideTimeoutMs` — render a skeleton
|
|
129
|
+
* while `isLoading` (no flicker, costs up to `hideTimeoutMs` of blank).
|
|
130
|
+
*/
|
|
131
|
+
render?: "base-first" | "hide-until-resolved";
|
|
132
|
+
/** Cap on the hidden period for `"hide-until-resolved"`. Default: `timeoutMs` (300). */
|
|
133
|
+
hideTimeoutMs?: number;
|
|
134
|
+
}
|
|
135
|
+
interface UsePersonalizedContentResult<T> {
|
|
136
|
+
content: T | null;
|
|
137
|
+
isLoading: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* True once every personalized entry was decided. False while loading, and after a timeout or a
|
|
140
|
+
* failed Xenia/CMS call — the undecided entries then show their base content.
|
|
141
|
+
*/
|
|
142
|
+
complete: boolean;
|
|
143
|
+
applied: AppliedSwap[];
|
|
144
|
+
/** Per-entry state, for skeletons / fades on just the sections being decided. */
|
|
145
|
+
status(entryId: string): EntryStatus;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Personalize a CMS page in the browser — `XeniaClient.personalizeContent` as a hook. Re-runs when
|
|
149
|
+
* `page` (by identity) or the serialized `data`/`locale` change. Never throws: on failure the base
|
|
150
|
+
* content renders. For pages that can render on the server, prefer `XeniaServer.personalizeContent`
|
|
151
|
+
* (no flicker, no client round trip).
|
|
152
|
+
*/
|
|
153
|
+
declare function usePersonalizedContent<T>(page: T | null | undefined, opts: UsePersonalizedContentOptions): UsePersonalizedContentResult<T>;
|
|
154
|
+
/**
|
|
155
|
+
* Fetch a page through `opts.cms` and personalize it in the browser — `usePersonalizedContent`
|
|
156
|
+
* without the fetch in app code. `content` is null (and `isLoading` true) while the page loads; a
|
|
157
|
+
* failed or missing page ends with `content: null`, `isLoading: false`.
|
|
158
|
+
*/
|
|
159
|
+
declare function usePersonalizedPage<T = unknown>(ref: PageRef, opts: UsePersonalizedContentOptions): UsePersonalizedContentResult<T>;
|
|
160
|
+
/** Shares a `usePersonalizedContent` result with nested sections (`useXeniaEntry`). */
|
|
161
|
+
declare function XeniaContentProvider(props: {
|
|
162
|
+
value: UsePersonalizedContentResult<unknown>;
|
|
163
|
+
children?: ReactNode;
|
|
164
|
+
}): react.FunctionComponentElement<react.ProviderProps<UsePersonalizedContentResult<unknown> | null>>;
|
|
165
|
+
/** Loading state of one entry inside a `<XeniaContentProvider>` — e.g. a skeleton for just that section. */
|
|
166
|
+
declare function useXeniaEntry(entryId: string): {
|
|
167
|
+
status: EntryStatus;
|
|
168
|
+
isLoading: boolean;
|
|
169
|
+
};
|
|
170
|
+
|
|
121
171
|
interface XeniaTrackProps {
|
|
122
172
|
/** Event id to fire. */
|
|
123
173
|
event: string;
|
|
@@ -173,4 +223,4 @@ type XeniaMessageComponentProps = MessageMountContext;
|
|
|
173
223
|
*/
|
|
174
224
|
declare function reactMessageComponent(Component: ComponentType<XeniaMessageComponentProps>): MessageComponentRenderer;
|
|
175
225
|
|
|
176
|
-
export { AnalyticsEventName, InteractionType, JsonValue, MessageComponentRenderer, MessageMountContext, ResolveBatchItem, ResolveBatchResult, ResolveResult, TrackOptions, type UsePersonalizationResolveResult, type UsePersonalizationResolveSlotsResult, type UsePersonalizationResult, type UsePersonalizationSlotsResult, type UseWebhookResult, WebhookResponse, XeniaAction, XeniaClient, XeniaClientOptions, XeniaContext, XeniaMessage, type XeniaMessageComponentProps, type XeniaMessageProps, XeniaProvider, type XeniaProviderProps, XeniaTrack, type XeniaTrackProps, reactMessageComponent, usePersonalization, usePersonalizationResolve, usePersonalizationResolveSlots, usePersonalizationSlots, useTrack, useWebhook, useXenia, useXeniaResponse };
|
|
226
|
+
export { AnalyticsEventName, AppliedSwap, type EntryStatus, InteractionType, JsonValue, MessageComponentRenderer, MessageMountContext, PagePersonalizeOptions, PageRef, ResolveBatchItem, ResolveBatchResult, ResolveResult, TrackOptions, type UsePersonalizationResolveResult, type UsePersonalizationResolveSlotsResult, type UsePersonalizationResult, type UsePersonalizationSlotsResult, type UsePersonalizedContentOptions, type UsePersonalizedContentResult, type UseWebhookResult, WebhookResponse, XeniaAction, XeniaClient, XeniaClientOptions, XeniaContentProvider, XeniaContext, XeniaMessage, type XeniaMessageComponentProps, type XeniaMessageProps, XeniaProvider, type XeniaProviderProps, XeniaTrack, type XeniaTrackProps, reactMessageComponent, usePersonalization, usePersonalizationResolve, usePersonalizationResolveSlots, usePersonalizationSlots, usePersonalizedContent, usePersonalizedPage, useTrack, useWebhook, useXenia, useXeniaEntry, useXeniaResponse };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ElementType, ComponentType } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export { A as AnalyticsContext, h as AnalyticsIdentity, i as AnalyticsIngestResponse,
|
|
3
|
+
import { E as XeniaClientOptions, D as XeniaClient, w as ResolveResult, u as ResolveBatchResult, J as JsonValue, x as WebhookResponse, R as ResolveBatchItem, g as AnalyticsEventName, T as TrackOptions, P as PagePersonalizeOptions, j as AppliedSwap, s as PageRef, e as XeniaAction, I as InteractionType, q as MessageMountContext, c as MessageComponentRenderer } from '../../actions-BcdlokGO.js';
|
|
4
|
+
export { A as AnalyticsContext, h as AnalyticsIdentity, i as AnalyticsIngestResponse, k as ClientEvent, l as CmsAdapter, C as ConsentState, m as ContentSwap, n as IdentifyRequest, o as IdentifyResponse, M as MessageCta, f as MessagePlacement, r as MessageRenderContext, b as MessageTemplateFn, t as PersonalizationSwap, v as ResolveOptions, X as XeniaConfig, a as XeniaEvent, F as XeniaMarker, G as createXeniaClient, H as extractContentSwaps, K as parseActions, L as pendingTargets, N as personalizeContent, O as personalizePage, Q as spliceSlots } from '../../actions-BcdlokGO.js';
|
|
5
5
|
|
|
6
6
|
interface XeniaProviderProps extends XeniaClientOptions {
|
|
7
7
|
children: ReactNode;
|
|
@@ -118,6 +118,56 @@ declare function useWebhook(triggerId: string): UseWebhookResult;
|
|
|
118
118
|
/** Read the last saved webhook response for a key (reactive to sends via re-render). */
|
|
119
119
|
declare function useXeniaResponse(key: string): WebhookResponse | undefined;
|
|
120
120
|
|
|
121
|
+
/** Where one personalized entry stands while the page resolves. */
|
|
122
|
+
type EntryStatus = "pending" | "personalized" | "base";
|
|
123
|
+
interface UsePersonalizedContentOptions extends PagePersonalizeOptions {
|
|
124
|
+
/**
|
|
125
|
+
* What to render before Xenia answers:
|
|
126
|
+
* - `"base-first"` (default): the CMS content immediately, swapped in place round by round
|
|
127
|
+
* (best LCP; personalized sections can visibly change).
|
|
128
|
+
* - `"hide-until-resolved"`: `content` is null until done or `hideTimeoutMs` — render a skeleton
|
|
129
|
+
* while `isLoading` (no flicker, costs up to `hideTimeoutMs` of blank).
|
|
130
|
+
*/
|
|
131
|
+
render?: "base-first" | "hide-until-resolved";
|
|
132
|
+
/** Cap on the hidden period for `"hide-until-resolved"`. Default: `timeoutMs` (300). */
|
|
133
|
+
hideTimeoutMs?: number;
|
|
134
|
+
}
|
|
135
|
+
interface UsePersonalizedContentResult<T> {
|
|
136
|
+
content: T | null;
|
|
137
|
+
isLoading: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* True once every personalized entry was decided. False while loading, and after a timeout or a
|
|
140
|
+
* failed Xenia/CMS call — the undecided entries then show their base content.
|
|
141
|
+
*/
|
|
142
|
+
complete: boolean;
|
|
143
|
+
applied: AppliedSwap[];
|
|
144
|
+
/** Per-entry state, for skeletons / fades on just the sections being decided. */
|
|
145
|
+
status(entryId: string): EntryStatus;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Personalize a CMS page in the browser — `XeniaClient.personalizeContent` as a hook. Re-runs when
|
|
149
|
+
* `page` (by identity) or the serialized `data`/`locale` change. Never throws: on failure the base
|
|
150
|
+
* content renders. For pages that can render on the server, prefer `XeniaServer.personalizeContent`
|
|
151
|
+
* (no flicker, no client round trip).
|
|
152
|
+
*/
|
|
153
|
+
declare function usePersonalizedContent<T>(page: T | null | undefined, opts: UsePersonalizedContentOptions): UsePersonalizedContentResult<T>;
|
|
154
|
+
/**
|
|
155
|
+
* Fetch a page through `opts.cms` and personalize it in the browser — `usePersonalizedContent`
|
|
156
|
+
* without the fetch in app code. `content` is null (and `isLoading` true) while the page loads; a
|
|
157
|
+
* failed or missing page ends with `content: null`, `isLoading: false`.
|
|
158
|
+
*/
|
|
159
|
+
declare function usePersonalizedPage<T = unknown>(ref: PageRef, opts: UsePersonalizedContentOptions): UsePersonalizedContentResult<T>;
|
|
160
|
+
/** Shares a `usePersonalizedContent` result with nested sections (`useXeniaEntry`). */
|
|
161
|
+
declare function XeniaContentProvider(props: {
|
|
162
|
+
value: UsePersonalizedContentResult<unknown>;
|
|
163
|
+
children?: ReactNode;
|
|
164
|
+
}): react.FunctionComponentElement<react.ProviderProps<UsePersonalizedContentResult<unknown> | null>>;
|
|
165
|
+
/** Loading state of one entry inside a `<XeniaContentProvider>` — e.g. a skeleton for just that section. */
|
|
166
|
+
declare function useXeniaEntry(entryId: string): {
|
|
167
|
+
status: EntryStatus;
|
|
168
|
+
isLoading: boolean;
|
|
169
|
+
};
|
|
170
|
+
|
|
121
171
|
interface XeniaTrackProps {
|
|
122
172
|
/** Event id to fire. */
|
|
123
173
|
event: string;
|
|
@@ -173,4 +223,4 @@ type XeniaMessageComponentProps = MessageMountContext;
|
|
|
173
223
|
*/
|
|
174
224
|
declare function reactMessageComponent(Component: ComponentType<XeniaMessageComponentProps>): MessageComponentRenderer;
|
|
175
225
|
|
|
176
|
-
export { AnalyticsEventName, InteractionType, JsonValue, MessageComponentRenderer, MessageMountContext, ResolveBatchItem, ResolveBatchResult, ResolveResult, TrackOptions, type UsePersonalizationResolveResult, type UsePersonalizationResolveSlotsResult, type UsePersonalizationResult, type UsePersonalizationSlotsResult, type UseWebhookResult, WebhookResponse, XeniaAction, XeniaClient, XeniaClientOptions, XeniaContext, XeniaMessage, type XeniaMessageComponentProps, type XeniaMessageProps, XeniaProvider, type XeniaProviderProps, XeniaTrack, type XeniaTrackProps, reactMessageComponent, usePersonalization, usePersonalizationResolve, usePersonalizationResolveSlots, usePersonalizationSlots, useTrack, useWebhook, useXenia, useXeniaResponse };
|
|
226
|
+
export { AnalyticsEventName, AppliedSwap, type EntryStatus, InteractionType, JsonValue, MessageComponentRenderer, MessageMountContext, PagePersonalizeOptions, PageRef, ResolveBatchItem, ResolveBatchResult, ResolveResult, TrackOptions, type UsePersonalizationResolveResult, type UsePersonalizationResolveSlotsResult, type UsePersonalizationResult, type UsePersonalizationSlotsResult, type UsePersonalizedContentOptions, type UsePersonalizedContentResult, type UseWebhookResult, WebhookResponse, XeniaAction, XeniaClient, XeniaClientOptions, XeniaContentProvider, XeniaContext, XeniaMessage, type XeniaMessageComponentProps, type XeniaMessageProps, XeniaProvider, type XeniaProviderProps, XeniaTrack, type XeniaTrackProps, reactMessageComponent, usePersonalization, usePersonalizationResolve, usePersonalizationResolveSlots, usePersonalizationSlots, usePersonalizedContent, usePersonalizedPage, useTrack, useWebhook, useXenia, useXeniaEntry, useXeniaResponse };
|
|
@@ -547,6 +547,25 @@ var XeniaApi = class {
|
|
|
547
547
|
});
|
|
548
548
|
return data;
|
|
549
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Resolve personalized CMS entries by id for one visitor (≤200) —
|
|
552
|
+
* `POST /api/personalization/resolve-entries`. The page resolver
|
|
553
|
+
* ({@link personalizeContent}) calls this once per round with the runtime
|
|
554
|
+
* targets it found; callers rarely need it directly.
|
|
555
|
+
*/
|
|
556
|
+
async resolveEntries(entryIds, opts = {}) {
|
|
557
|
+
const { data } = await this.http.request({
|
|
558
|
+
method: "POST",
|
|
559
|
+
path: "/api/personalization/resolve-entries",
|
|
560
|
+
headers: await this.authHeaders(),
|
|
561
|
+
body: compact({
|
|
562
|
+
entryIds,
|
|
563
|
+
data: opts.data,
|
|
564
|
+
locale: opts.locale
|
|
565
|
+
})
|
|
566
|
+
});
|
|
567
|
+
return data;
|
|
568
|
+
}
|
|
550
569
|
};
|
|
551
570
|
|
|
552
571
|
// src/core/cookie-names.ts
|
|
@@ -661,6 +680,233 @@ function toEventId(raw) {
|
|
|
661
680
|
return digest;
|
|
662
681
|
}
|
|
663
682
|
|
|
683
|
+
// src/core/personalize-content.ts
|
|
684
|
+
var DEFAULT_TIMEOUT_MS = 300;
|
|
685
|
+
var DEFAULT_MAX_ROUNDS = 3;
|
|
686
|
+
var copyOf = (v) => v && typeof v === "object" && typeof structuredClone === "function" ? structuredClone(v) : v;
|
|
687
|
+
async function personalizeContent(payload, options) {
|
|
688
|
+
const { cms } = options;
|
|
689
|
+
const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
|
|
690
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
691
|
+
const applied = [];
|
|
692
|
+
const report = (swap) => {
|
|
693
|
+
applied.push(swap);
|
|
694
|
+
try {
|
|
695
|
+
options.onApplied?.(swap);
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
let content = payload;
|
|
700
|
+
try {
|
|
701
|
+
if (!options.inPlace && typeof structuredClone === "function") content = structuredClone(content);
|
|
702
|
+
content = cms.prepare ? cms.prepare(content) : content;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
return { content: payload, applied, complete: false, error };
|
|
705
|
+
}
|
|
706
|
+
const box = { root: content };
|
|
707
|
+
const settled = /* @__PURE__ */ new WeakSet();
|
|
708
|
+
const decided = /* @__PURE__ */ new Map();
|
|
709
|
+
let frontier = [{ parent: box, key: "root", value: box.root }];
|
|
710
|
+
const settle = (target, d, round) => {
|
|
711
|
+
settled.add(target.entry);
|
|
712
|
+
cms.clearMarker?.(target.entry);
|
|
713
|
+
const items = d.items.map(copyOf);
|
|
714
|
+
if (!d.decision || items.length === 0) {
|
|
715
|
+
return deferredSlots(target, cms);
|
|
716
|
+
}
|
|
717
|
+
for (const item of items) if (item && typeof item === "object") settled.add(item);
|
|
718
|
+
const op = d.decision.op ?? target.marker.op ?? "replace";
|
|
719
|
+
const next = apply(target, op, d.decision.field ?? target.marker.field, items, cms);
|
|
720
|
+
report({
|
|
721
|
+
target: target.id,
|
|
722
|
+
kind: "runtime",
|
|
723
|
+
op,
|
|
724
|
+
entryIds: items.map((e) => cms.entryId(e)).filter((id) => !!id),
|
|
725
|
+
contentKey: d.decision.contentKey,
|
|
726
|
+
bucket: d.decision.bucket,
|
|
727
|
+
round
|
|
728
|
+
});
|
|
729
|
+
return next;
|
|
730
|
+
};
|
|
731
|
+
for (let round = 0; ; round++) {
|
|
732
|
+
const pending = walk(frontier, cms, settled, round, report, (t) => {
|
|
733
|
+
const d = decided.get(t.id);
|
|
734
|
+
return d ? settle(t, d, round) : void 0;
|
|
735
|
+
});
|
|
736
|
+
if (pending.size === 0) return { content: box.root, applied, complete: true };
|
|
737
|
+
if (!options.resolve || round >= maxRounds) {
|
|
738
|
+
return { content: box.root, applied, complete: !options.resolve };
|
|
739
|
+
}
|
|
740
|
+
progress(options, { content: box.root, round, pending: [...pending.keys()] });
|
|
741
|
+
let decisions;
|
|
742
|
+
let entriesById;
|
|
743
|
+
let queried;
|
|
744
|
+
try {
|
|
745
|
+
decisions = await withDeadline(options.resolve([...pending.keys()]), deadline);
|
|
746
|
+
({ entriesById, queried } = await withDeadline(loadReplacements(decisions, cms, options.locale), deadline));
|
|
747
|
+
} catch (error) {
|
|
748
|
+
return { content: box.root, applied, complete: false, error: error === TIMEOUT ? void 0 : error };
|
|
749
|
+
}
|
|
750
|
+
const byTarget = new Map(decisions.map((d) => [d.target, d]));
|
|
751
|
+
frontier = [];
|
|
752
|
+
for (const target of pending.values()) {
|
|
753
|
+
const decision = byTarget.get(target.id);
|
|
754
|
+
const items = decision ? decision.ids ? decision.ids.map((id) => entriesById.get(id)).filter((e) => e !== void 0) : queried.get(decision) ?? [] : [];
|
|
755
|
+
const d = { decision, items };
|
|
756
|
+
decided.set(target.id, d);
|
|
757
|
+
frontier.push(...settle(target, d, round));
|
|
758
|
+
}
|
|
759
|
+
progress(options, { content: box.root, round, pending: [] });
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function progress(options, p) {
|
|
763
|
+
try {
|
|
764
|
+
options.onProgress?.(p);
|
|
765
|
+
} catch {
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function walk(frontier, cms, settled, round, report, reuse) {
|
|
769
|
+
const pending = /* @__PURE__ */ new Map();
|
|
770
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
771
|
+
const deferred = /* @__PURE__ */ new WeakSet();
|
|
772
|
+
const queue = [...frontier];
|
|
773
|
+
while (queue.length > 0) {
|
|
774
|
+
const slot = queue.shift();
|
|
775
|
+
const value = slot.value;
|
|
776
|
+
if (!value || typeof value !== "object" || deferred.has(value)) continue;
|
|
777
|
+
const id = settled.has(value) ? void 0 : cms.entryId(value);
|
|
778
|
+
const marker = id ? cms.marker(value) : null;
|
|
779
|
+
if (id && marker?.kind === "runtime") {
|
|
780
|
+
const again = reuse({ id, marker, entry: value, slots: [slot] });
|
|
781
|
+
if (again) {
|
|
782
|
+
queue.push(...again);
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const existing = pending.get(id);
|
|
786
|
+
if (existing) {
|
|
787
|
+
existing.slots.push(slot);
|
|
788
|
+
} else {
|
|
789
|
+
pending.set(id, { id, marker, entry: value, slots: [slot] });
|
|
790
|
+
if (marker.op === "items" && marker.field) {
|
|
791
|
+
const held = cms.getField(value, marker.field);
|
|
792
|
+
if (held && typeof held === "object") deferred.add(held);
|
|
793
|
+
queue.push(...children(value));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
if (id && marker?.kind === "static") {
|
|
799
|
+
const variants = cms.staticVariants(value).filter((v) => v && typeof v === "object");
|
|
800
|
+
settled.add(value);
|
|
801
|
+
cms.clearMarker?.(value);
|
|
802
|
+
if (variants.length > 0) {
|
|
803
|
+
for (const v of variants) settled.add(v);
|
|
804
|
+
const op = marker.op ?? "replace";
|
|
805
|
+
const target = { entry: value, slots: [slot] };
|
|
806
|
+
queue.push(...apply(target, op, marker.field, variants, cms));
|
|
807
|
+
report({
|
|
808
|
+
target: id,
|
|
809
|
+
kind: "static",
|
|
810
|
+
op,
|
|
811
|
+
entryIds: variants.map((v) => cms.entryId(v)).filter((x) => !!x),
|
|
812
|
+
round
|
|
813
|
+
});
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (seen.has(value)) continue;
|
|
818
|
+
seen.add(value);
|
|
819
|
+
queue.push(...children(value));
|
|
820
|
+
}
|
|
821
|
+
return pending;
|
|
822
|
+
}
|
|
823
|
+
function apply(target, op, field, items, cms) {
|
|
824
|
+
if (op === "items") {
|
|
825
|
+
if (field) {
|
|
826
|
+
cms.setField(target.entry, field, items);
|
|
827
|
+
return [{ parent: target.entry, key: field, value: cms.getField(target.entry, field) }];
|
|
828
|
+
}
|
|
829
|
+
if (cms.mergeItems) {
|
|
830
|
+
cms.mergeItems(target.entry, items[0]);
|
|
831
|
+
return target.slots.map((s) => ({ ...s, value: target.entry }));
|
|
832
|
+
}
|
|
833
|
+
op = "replace";
|
|
834
|
+
}
|
|
835
|
+
const next = [];
|
|
836
|
+
for (const slot of target.slots) {
|
|
837
|
+
const placed = op === "expand" && Array.isArray(slot.parent) ? items : [items[0]];
|
|
838
|
+
if (Array.isArray(slot.parent)) {
|
|
839
|
+
const index = slot.parent.indexOf(target.entry);
|
|
840
|
+
if (index < 0) continue;
|
|
841
|
+
slot.parent.splice(index, 1, ...placed);
|
|
842
|
+
} else {
|
|
843
|
+
slot.parent[slot.key] = placed[0];
|
|
844
|
+
}
|
|
845
|
+
for (const value of placed) next.push({ parent: slot.parent, key: slot.key, value });
|
|
846
|
+
}
|
|
847
|
+
return next;
|
|
848
|
+
}
|
|
849
|
+
function deferredSlots(target, cms) {
|
|
850
|
+
if (target.marker.op === "items" && target.marker.field) {
|
|
851
|
+
return [{ parent: target.entry, key: target.marker.field, value: cms.getField(target.entry, target.marker.field) }];
|
|
852
|
+
}
|
|
853
|
+
return target.slots.map((s) => ({ ...s, value: target.entry }));
|
|
854
|
+
}
|
|
855
|
+
function children(value) {
|
|
856
|
+
if (Array.isArray(value)) return value.map((v, i) => ({ parent: value, key: i, value: v }));
|
|
857
|
+
return Object.keys(value).map((k) => ({ parent: value, key: k, value: value[k] }));
|
|
858
|
+
}
|
|
859
|
+
async function loadReplacements(decisions, cms, locale) {
|
|
860
|
+
const ids = [...new Set(decisions.flatMap((d) => d.ids ?? []))];
|
|
861
|
+
const queries = decisions.filter((d) => !d.ids && d.query);
|
|
862
|
+
const [fetched, ...results] = await Promise.all([
|
|
863
|
+
ids.length > 0 ? cms.fetchEntries(ids, locale) : Promise.resolve([]),
|
|
864
|
+
...queries.map((d) => cms.queryEntries(d.query, locale).catch(() => []))
|
|
865
|
+
]);
|
|
866
|
+
const entriesById = /* @__PURE__ */ new Map();
|
|
867
|
+
for (const entry of fetched) {
|
|
868
|
+
const id = cms.entryId(entry);
|
|
869
|
+
if (id) entriesById.set(id, entry);
|
|
870
|
+
}
|
|
871
|
+
const queried = /* @__PURE__ */ new Map();
|
|
872
|
+
queries.forEach((d, i) => queried.set(d, results[i]));
|
|
873
|
+
return { entriesById, queried };
|
|
874
|
+
}
|
|
875
|
+
var TIMEOUT = /* @__PURE__ */ Symbol("timeout");
|
|
876
|
+
function withDeadline(promise, deadline) {
|
|
877
|
+
const remaining = deadline - Date.now();
|
|
878
|
+
if (remaining <= 0) return Promise.reject(TIMEOUT);
|
|
879
|
+
let timer;
|
|
880
|
+
return Promise.race([
|
|
881
|
+
promise,
|
|
882
|
+
new Promise((_, reject) => {
|
|
883
|
+
timer = setTimeout(() => reject(TIMEOUT), remaining);
|
|
884
|
+
})
|
|
885
|
+
]).finally(() => clearTimeout(timer));
|
|
886
|
+
}
|
|
887
|
+
function pendingTargets(payload, cms) {
|
|
888
|
+
try {
|
|
889
|
+
let content = typeof structuredClone === "function" ? structuredClone(payload) : payload;
|
|
890
|
+
content = cms.prepare ? cms.prepare(content) : content;
|
|
891
|
+
const root = { parent: { root: content }, key: "root", value: content };
|
|
892
|
+
return [...walk([root], cms, /* @__PURE__ */ new WeakSet(), 0, () => {
|
|
893
|
+
}, () => void 0).keys()];
|
|
894
|
+
} catch {
|
|
895
|
+
return [];
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
async function personalizePage(ref, options) {
|
|
899
|
+
let page;
|
|
900
|
+
try {
|
|
901
|
+
if (!options.cms.fetchPage) throw new Error("This CMS adapter cannot fetch pages; use personalizeContent(page)");
|
|
902
|
+
page = await options.cms.fetchPage(ref, options.locale);
|
|
903
|
+
} catch (error) {
|
|
904
|
+
return { content: null, applied: [], complete: false, error };
|
|
905
|
+
}
|
|
906
|
+
if (page == null) return { content: null, applied: [], complete: true };
|
|
907
|
+
return personalizeContent(page, options);
|
|
908
|
+
}
|
|
909
|
+
|
|
664
910
|
// src/core/response-store.ts
|
|
665
911
|
var MemoryResponseStore = class {
|
|
666
912
|
map = /* @__PURE__ */ new Map();
|
|
@@ -990,6 +1236,20 @@ var AutoTracker = class {
|
|
|
990
1236
|
}
|
|
991
1237
|
};
|
|
992
1238
|
|
|
1239
|
+
// src/server/cookies.ts
|
|
1240
|
+
function parseCookies(header) {
|
|
1241
|
+
const out = {};
|
|
1242
|
+
if (!header) return out;
|
|
1243
|
+
for (const part of header.split(";")) {
|
|
1244
|
+
const idx = part.indexOf("=");
|
|
1245
|
+
if (idx === -1) continue;
|
|
1246
|
+
const key = part.slice(0, idx).trim();
|
|
1247
|
+
const value = part.slice(idx + 1).trim();
|
|
1248
|
+
if (key) out[key] = decodeURIComponent(value);
|
|
1249
|
+
}
|
|
1250
|
+
return out;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
993
1253
|
// src/client/identity.ts
|
|
994
1254
|
var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
995
1255
|
var Identity = class {
|
|
@@ -1037,10 +1297,19 @@ var Identity = class {
|
|
|
1037
1297
|
} catch {
|
|
1038
1298
|
}
|
|
1039
1299
|
}
|
|
1300
|
+
/** The visitor id already in the cookie — e.g. minted by `xeniaProxy()` on the first request. */
|
|
1301
|
+
readVisitorCookie() {
|
|
1302
|
+
if (!this.cookie || typeof document === "undefined") return null;
|
|
1303
|
+
try {
|
|
1304
|
+
return parseCookies(document.cookie)[this.cookieName()] || null;
|
|
1305
|
+
} catch {
|
|
1306
|
+
return null;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1040
1309
|
// ── Device id ────────────────────────────────────────────────────────────
|
|
1041
1310
|
/**
|
|
1042
|
-
* Resolve a stable device id: localStorage cache →
|
|
1043
|
-
* browser fingerprint. Cached for the session and across reloads.
|
|
1311
|
+
* Resolve a stable device id: localStorage cache → the visitor cookie a server already set →
|
|
1312
|
+
* MediaDevices → SHA-256 browser fingerprint. Cached for the session and across reloads.
|
|
1044
1313
|
*/
|
|
1045
1314
|
async resolveDeviceId() {
|
|
1046
1315
|
if (this.deviceId) {
|
|
@@ -1057,7 +1326,8 @@ var Identity = class {
|
|
|
1057
1326
|
}
|
|
1058
1327
|
} catch {
|
|
1059
1328
|
}
|
|
1060
|
-
let id =
|
|
1329
|
+
let id = this.readVisitorCookie();
|
|
1330
|
+
if (!id) id = await this.mediaDeviceId();
|
|
1061
1331
|
if (!id) id = await this.fingerprint();
|
|
1062
1332
|
this.deviceId = id;
|
|
1063
1333
|
try {
|
|
@@ -1912,7 +2182,10 @@ var XeniaClient = class {
|
|
|
1912
2182
|
renderer;
|
|
1913
2183
|
analyticsQueue;
|
|
1914
2184
|
tracker = null;
|
|
1915
|
-
|
|
2185
|
+
/** The in-flight (or finished) boot; shared so concurrent `init()` calls start one tracker. */
|
|
2186
|
+
booting = null;
|
|
2187
|
+
/** Bumped by `destroy()`, so a boot still waiting on the device id knows to stop. */
|
|
2188
|
+
generation = 0;
|
|
1916
2189
|
consentState;
|
|
1917
2190
|
listeners = /* @__PURE__ */ new Map();
|
|
1918
2191
|
constructor(options) {
|
|
@@ -1942,10 +2215,14 @@ var XeniaClient = class {
|
|
|
1942
2215
|
});
|
|
1943
2216
|
}
|
|
1944
2217
|
/** Boot the client: resolve device id and start auto-tracking. Idempotent. */
|
|
1945
|
-
|
|
1946
|
-
if (
|
|
2218
|
+
init() {
|
|
2219
|
+
if (!isBrowser()) return Promise.resolve();
|
|
2220
|
+
return this.booting ??= this.boot();
|
|
2221
|
+
}
|
|
2222
|
+
async boot() {
|
|
2223
|
+
const generation = this.generation;
|
|
1947
2224
|
await this.identity.resolveDeviceId();
|
|
1948
|
-
this.
|
|
2225
|
+
if (generation !== this.generation) return;
|
|
1949
2226
|
if (this.analyticsAllowed()) this.analyticsQueue.start();
|
|
1950
2227
|
if (this.options.autoTrack !== false) {
|
|
1951
2228
|
this.tracker = new AutoTracker({
|
|
@@ -1981,7 +2258,8 @@ var XeniaClient = class {
|
|
|
1981
2258
|
this.tracker = null;
|
|
1982
2259
|
this.renderer.destroy();
|
|
1983
2260
|
this.analyticsQueue.stop();
|
|
1984
|
-
this.
|
|
2261
|
+
this.booting = null;
|
|
2262
|
+
this.generation++;
|
|
1985
2263
|
}
|
|
1986
2264
|
// ── Identity ───────────────────────────────────────────────────────────────
|
|
1987
2265
|
/**
|
|
@@ -2110,6 +2388,27 @@ var XeniaClient = class {
|
|
|
2110
2388
|
async resolveBatch(items, opts) {
|
|
2111
2389
|
return this.api.resolveBatch(items, this.withVisitor(opts));
|
|
2112
2390
|
}
|
|
2391
|
+
/**
|
|
2392
|
+
* Personalize a fetched CMS page: static swaps are applied from the payload, runtime targets are
|
|
2393
|
+
* decided by Xenia in one `resolve-entries` call per nesting level, and replacements are loaded
|
|
2394
|
+
* through `opts.cms` (e.g. `contentfulAdapter(...)`). Never throws — anything undecided within
|
|
2395
|
+
* `opts.timeoutMs` keeps its base content.
|
|
2396
|
+
*/
|
|
2397
|
+
async personalizeContent(page, opts) {
|
|
2398
|
+
return personalizeContent(page, { ...opts, resolve: this.entryResolver(opts) });
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Fetch a page through `opts.cms` and personalize it — the app passes only the page's identity
|
|
2402
|
+
* (entry id, or `{ contentType, fields: { slug } }`). Never throws: a failed fetch is `content:
|
|
2403
|
+
* null` with `error`, a missing page `content: null`.
|
|
2404
|
+
*/
|
|
2405
|
+
async personalizePage(ref, opts) {
|
|
2406
|
+
return personalizePage(ref, { ...opts, resolve: this.entryResolver(opts) });
|
|
2407
|
+
}
|
|
2408
|
+
entryResolver(opts) {
|
|
2409
|
+
const resolveOpts = this.withVisitor({ data: opts.data, locale: opts.locale });
|
|
2410
|
+
return async (ids) => (await this.api.resolveEntries(ids, resolveOpts)).decisions ?? [];
|
|
2411
|
+
}
|
|
2113
2412
|
/** The SDK-owned visitor anchor: the `identify()`/`config.visitorId` id, else the device id. */
|
|
2114
2413
|
resolveVisitorId() {
|
|
2115
2414
|
return this.identity.getVisitorId() ?? this.identity.getDeviceId() ?? void 0;
|
|
@@ -2188,8 +2487,8 @@ var XeniaClient = class {
|
|
|
2188
2487
|
const response = await this.api.webhook(triggerId, body, opts?.method ?? "POST");
|
|
2189
2488
|
this.responses.set(triggerId, response);
|
|
2190
2489
|
this.emit("response", { triggerId, response });
|
|
2191
|
-
const
|
|
2192
|
-
if (
|
|
2490
|
+
const apply2 = opts?.apply ?? this.options.autoApplyActions !== false;
|
|
2491
|
+
if (apply2 && response.actions.length) {
|
|
2193
2492
|
this.applyActions(response.actions);
|
|
2194
2493
|
}
|
|
2195
2494
|
return response;
|
|
@@ -2243,8 +2542,8 @@ var XeniaClient = class {
|
|
|
2243
2542
|
compact({ ...ambient, fields })
|
|
2244
2543
|
);
|
|
2245
2544
|
this.emit("response", { triggerId: ref, response });
|
|
2246
|
-
const
|
|
2247
|
-
if (
|
|
2545
|
+
const apply2 = opts?.apply ?? this.options.autoApplyActions !== false;
|
|
2546
|
+
if (apply2 && response.actions.length) this.applyActions(response.actions);
|
|
2248
2547
|
return response;
|
|
2249
2548
|
}
|
|
2250
2549
|
// ── Imperative render helpers ─────────────────────────────────────────────────
|
|
@@ -2322,7 +2621,7 @@ function useXenia() {
|
|
|
2322
2621
|
}
|
|
2323
2622
|
return client;
|
|
2324
2623
|
}
|
|
2325
|
-
function XeniaProvider({ children, client, ...options }) {
|
|
2624
|
+
function XeniaProvider({ children: children2, client, ...options }) {
|
|
2326
2625
|
const ref = useRef(null);
|
|
2327
2626
|
const instance = useMemo(() => {
|
|
2328
2627
|
if (client) return client;
|
|
@@ -2333,7 +2632,7 @@ function XeniaProvider({ children, client, ...options }) {
|
|
|
2333
2632
|
void instance.init();
|
|
2334
2633
|
return () => instance.destroy();
|
|
2335
2634
|
}, [instance]);
|
|
2336
|
-
return /* @__PURE__ */ jsx(XeniaContext.Provider, { value: instance, children });
|
|
2635
|
+
return /* @__PURE__ */ jsx(XeniaContext.Provider, { value: instance, children: children2 });
|
|
2337
2636
|
}
|
|
2338
2637
|
|
|
2339
2638
|
// src/core/content-splice.ts
|
|
@@ -2520,12 +2819,92 @@ function useXeniaResponse(key) {
|
|
|
2520
2819
|
const xenia = useXenia();
|
|
2521
2820
|
return xenia.getResponse(key);
|
|
2522
2821
|
}
|
|
2822
|
+
function usePersonalizedContent(page, opts) {
|
|
2823
|
+
const xenia = useXenia();
|
|
2824
|
+
const hide = opts.render === "hide-until-resolved";
|
|
2825
|
+
const initialPending = useMemo(
|
|
2826
|
+
() => new Set(page == null ? [] : pendingTargets(page, opts.cms)),
|
|
2827
|
+
[page]
|
|
2828
|
+
// markers live in `page`; a new adapter object for the same page changes nothing
|
|
2829
|
+
);
|
|
2830
|
+
const [state, setState] = useState(
|
|
2831
|
+
() => ({ page, content: hide ? null : page ?? null, done: false, complete: false, applied: [], pending: initialPending })
|
|
2832
|
+
);
|
|
2833
|
+
const optsRef = useRef(opts);
|
|
2834
|
+
optsRef.current = opts;
|
|
2835
|
+
const optsKey = JSON.stringify({ data: opts.data ?? {}, locale: opts.locale });
|
|
2836
|
+
useEffect(() => {
|
|
2837
|
+
if (page == null) return;
|
|
2838
|
+
let active = true;
|
|
2839
|
+
const o = optsRef.current;
|
|
2840
|
+
setState({ page, content: hide ? null : page, done: false, complete: false, applied: [], pending: initialPending });
|
|
2841
|
+
const reveal = hide ? setTimeout(() => active && setState((s) => s.content ? s : { ...s, content: page }), o.hideTimeoutMs ?? o.timeoutMs ?? 300) : void 0;
|
|
2842
|
+
const applied = [];
|
|
2843
|
+
void xenia.personalizeContent(page, {
|
|
2844
|
+
...o,
|
|
2845
|
+
onApplied(swap) {
|
|
2846
|
+
applied.push(swap);
|
|
2847
|
+
o.onApplied?.(swap);
|
|
2848
|
+
},
|
|
2849
|
+
onProgress(p) {
|
|
2850
|
+
o.onProgress?.(p);
|
|
2851
|
+
if (!active || hide) return;
|
|
2852
|
+
setState({ page, content: p.content, done: false, complete: false, applied: [...applied], pending: new Set(p.pending) });
|
|
2853
|
+
}
|
|
2854
|
+
}).then((r) => {
|
|
2855
|
+
if (active) setState({ page, content: r.content, done: true, complete: r.complete, applied: r.applied, pending: /* @__PURE__ */ new Set() });
|
|
2856
|
+
}).finally(() => clearTimeout(reveal));
|
|
2857
|
+
return () => {
|
|
2858
|
+
active = false;
|
|
2859
|
+
clearTimeout(reveal);
|
|
2860
|
+
};
|
|
2861
|
+
}, [xenia, page, optsKey, hide, initialPending]);
|
|
2862
|
+
const personalized = new Set(state.applied.map((a) => a.target));
|
|
2863
|
+
const pending = state.page === page ? state.pending : initialPending;
|
|
2864
|
+
return {
|
|
2865
|
+
content: state.content,
|
|
2866
|
+
isLoading: page != null && !state.done,
|
|
2867
|
+
complete: state.complete,
|
|
2868
|
+
applied: state.applied,
|
|
2869
|
+
status: (id) => pending.has(id) ? "pending" : personalized.has(id) ? "personalized" : "base"
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
function usePersonalizedPage(ref, opts) {
|
|
2873
|
+
const [page, setPage] = useState({ value: null, loaded: false });
|
|
2874
|
+
const cmsRef = useRef(opts.cms);
|
|
2875
|
+
cmsRef.current = opts.cms;
|
|
2876
|
+
const refKey = JSON.stringify([ref, opts.locale]);
|
|
2877
|
+
useEffect(() => {
|
|
2878
|
+
let active = true;
|
|
2879
|
+
setPage({ value: null, loaded: false });
|
|
2880
|
+
const fetchPage = cmsRef.current.fetchPage;
|
|
2881
|
+
Promise.resolve(fetchPage ? fetchPage.call(cmsRef.current, ref, opts.locale) : null).then(
|
|
2882
|
+
(value) => active && setPage({ value: value ?? null, loaded: true }),
|
|
2883
|
+
() => active && setPage({ value: null, loaded: true, failed: true })
|
|
2884
|
+
);
|
|
2885
|
+
return () => {
|
|
2886
|
+
active = false;
|
|
2887
|
+
};
|
|
2888
|
+
}, [refKey]);
|
|
2889
|
+
const result = usePersonalizedContent(page.value, opts);
|
|
2890
|
+
const complete = page.value == null ? page.loaded && !page.failed : result.complete;
|
|
2891
|
+
return { ...result, isLoading: !page.loaded || result.isLoading, complete };
|
|
2892
|
+
}
|
|
2893
|
+
var PersonalizedContentContext = createContext(null);
|
|
2894
|
+
function XeniaContentProvider(props) {
|
|
2895
|
+
return createElement(PersonalizedContentContext.Provider, { value: props.value }, props.children);
|
|
2896
|
+
}
|
|
2897
|
+
function useXeniaEntry(entryId) {
|
|
2898
|
+
const ctx = useContext(PersonalizedContentContext);
|
|
2899
|
+
const status = ctx ? ctx.status(entryId) : "base";
|
|
2900
|
+
return { status, isLoading: status === "pending" };
|
|
2901
|
+
}
|
|
2523
2902
|
function XeniaTrack({
|
|
2524
2903
|
event,
|
|
2525
2904
|
on = "click",
|
|
2526
2905
|
props,
|
|
2527
2906
|
as = "span",
|
|
2528
|
-
children,
|
|
2907
|
+
children: children2,
|
|
2529
2908
|
className
|
|
2530
2909
|
}) {
|
|
2531
2910
|
const xenia = useXenia();
|
|
@@ -2557,7 +2936,7 @@ function XeniaTrack({
|
|
|
2557
2936
|
const handlers = on === "click" ? {
|
|
2558
2937
|
onClick: () => xenia.track(event, { ...props, interactionType: "click" }, { trigger: true })
|
|
2559
2938
|
} : {};
|
|
2560
|
-
return createElement(as, { ref, className, ...handlers },
|
|
2939
|
+
return createElement(as, { ref, className, ...handlers }, children2);
|
|
2561
2940
|
}
|
|
2562
2941
|
function XeniaMessage({ actions }) {
|
|
2563
2942
|
const xenia = useXenia();
|
|
@@ -2574,6 +2953,6 @@ function reactMessageComponent(Component) {
|
|
|
2574
2953
|
};
|
|
2575
2954
|
}
|
|
2576
2955
|
|
|
2577
|
-
export { XeniaClient, XeniaContext, XeniaMessage, XeniaProvider, XeniaTrack, createXeniaClient, extractContentSwaps, parseActions, reactMessageComponent, spliceSlots, usePersonalization, usePersonalizationResolve, usePersonalizationResolveSlots, usePersonalizationSlots, useTrack, useWebhook, useXenia, useXeniaResponse };
|
|
2956
|
+
export { XeniaClient, XeniaContentProvider, XeniaContext, XeniaMessage, XeniaProvider, XeniaTrack, createXeniaClient, extractContentSwaps, parseActions, pendingTargets, personalizeContent, personalizePage, reactMessageComponent, spliceSlots, usePersonalization, usePersonalizationResolve, usePersonalizationResolveSlots, usePersonalizationSlots, usePersonalizedContent, usePersonalizedPage, useTrack, useWebhook, useXenia, useXeniaEntry, useXeniaResponse };
|
|
2578
2957
|
//# sourceMappingURL=index.js.map
|
|
2579
2958
|
//# sourceMappingURL=index.js.map
|