@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.
Files changed (63) hide show
  1. package/README.md +50 -0
  2. package/assets/coverage.json +1 -0
  3. package/dist/{actions-CKeiofxJ.d.cts → actions-D9_aAvCv.d.cts} +188 -2
  4. package/dist/{actions-CKeiofxJ.d.ts → actions-D9_aAvCv.d.ts} +188 -2
  5. package/dist/api-BEPW0Uan.d.cts +88 -0
  6. package/dist/api-CktVQ6Gl.d.ts +88 -0
  7. package/dist/{chunk-U2WBBOYT.cjs → chunk-3PYPPIUM.cjs} +35 -10
  8. package/dist/chunk-3PYPPIUM.cjs.map +1 -0
  9. package/dist/{chunk-FYSEKFIY.js → chunk-7NGJC7CF.js} +249 -3
  10. package/dist/chunk-7NGJC7CF.js.map +1 -0
  11. package/dist/{chunk-WYAMPEVV.js → chunk-DWFVDNU5.js} +28 -3
  12. package/dist/chunk-DWFVDNU5.js.map +1 -0
  13. package/dist/{chunk-JEKVN7HK.cjs → chunk-IBNIRT7J.cjs} +251 -2
  14. package/dist/chunk-IBNIRT7J.cjs.map +1 -0
  15. package/dist/{chunk-TLTG3BTC.js → chunk-OUZU4Z5B.js} +2 -2
  16. package/dist/{chunk-TLTG3BTC.js.map → chunk-OUZU4Z5B.js.map} +1 -1
  17. package/dist/{chunk-5D7SJIO4.cjs → chunk-PKWANLT7.cjs} +2 -2
  18. package/dist/{chunk-5D7SJIO4.cjs.map → chunk-PKWANLT7.cjs.map} +1 -1
  19. package/dist/client/index.cjs +287 -9
  20. package/dist/client/index.cjs.map +1 -1
  21. package/dist/client/index.d.cts +2 -2
  22. package/dist/client/index.d.ts +2 -2
  23. package/dist/client/index.js +285 -10
  24. package/dist/client/index.js.map +1 -1
  25. package/dist/client/react/index.cjs +375 -13
  26. package/dist/client/react/index.cjs.map +1 -1
  27. package/dist/client/react/index.d.cts +53 -3
  28. package/dist/client/react/index.d.ts +53 -3
  29. package/dist/client/react/index.js +369 -14
  30. package/dist/client/react/index.js.map +1 -1
  31. package/dist/cms/contentful.cjs +138 -0
  32. package/dist/cms/contentful.cjs.map +1 -0
  33. package/dist/cms/contentful.d.cts +57 -0
  34. package/dist/cms/contentful.d.ts +57 -0
  35. package/dist/cms/contentful.js +134 -0
  36. package/dist/cms/contentful.js.map +1 -0
  37. package/dist/{cookie-names-QYMiEMAq.d.cts → cookie-names-BACctagz.d.cts} +1 -1
  38. package/dist/{cookie-names-8FxbGKGS.d.ts → cookie-names-CLqtmGBM.d.ts} +1 -1
  39. package/dist/index.cjs +35 -23
  40. package/dist/index.d.cts +4 -3
  41. package/dist/index.d.ts +4 -3
  42. package/dist/index.js +2 -2
  43. package/dist/next/index.cjs +41 -9
  44. package/dist/next/index.cjs.map +1 -1
  45. package/dist/next/index.d.cts +32 -3
  46. package/dist/next/index.d.ts +32 -3
  47. package/dist/next/index.js +14 -3
  48. package/dist/next/index.js.map +1 -1
  49. package/dist/{api-URhxxi4x.d.cts → personalize-content-Clc1oMYY.d.cts} +152 -69
  50. package/dist/{api-URhxxi4x.d.ts → personalize-content-Clc1oMYY.d.ts} +152 -69
  51. package/dist/server/index.cjs +29 -17
  52. package/dist/server/index.d.cts +5 -4
  53. package/dist/server/index.d.ts +5 -4
  54. package/dist/server/index.js +4 -4
  55. package/dist/xenia-sdk.iife.js +3 -3
  56. package/dist/xenia-sdk.iife.js.map +1 -1
  57. package/dist/{xenia-server-Cti94W49.d.cts → xenia-server-BWhWD0La.d.ts} +15 -1
  58. package/dist/{xenia-server-DDtQp6Wi.d.ts → xenia-server-o_wDOcei.d.cts} +15 -1
  59. package/package.json +15 -1
  60. package/dist/chunk-FYSEKFIY.js.map +0 -1
  61. package/dist/chunk-JEKVN7HK.cjs.map +0 -1
  62. package/dist/chunk-U2WBBOYT.cjs.map +0 -1
  63. package/dist/chunk-WYAMPEVV.js.map +0 -1
@@ -549,6 +549,25 @@ var XeniaApi = class {
549
549
  });
550
550
  return data;
551
551
  }
552
+ /**
553
+ * Resolve personalized CMS entries by id for one visitor (≤200) —
554
+ * `POST /api/personalization/resolve-entries`. The page resolver
555
+ * ({@link personalizeContent}) calls this once per round with the runtime
556
+ * targets it found; callers rarely need it directly.
557
+ */
558
+ async resolveEntries(entryIds, opts = {}) {
559
+ const { data } = await this.http.request({
560
+ method: "POST",
561
+ path: "/api/personalization/resolve-entries",
562
+ headers: await this.authHeaders(),
563
+ body: compact({
564
+ entryIds,
565
+ data: opts.data,
566
+ locale: opts.locale
567
+ })
568
+ });
569
+ return data;
570
+ }
552
571
  };
553
572
 
554
573
  // src/core/cookie-names.ts
@@ -663,6 +682,233 @@ function toEventId(raw) {
663
682
  return digest;
664
683
  }
665
684
 
685
+ // src/core/personalize-content.ts
686
+ var DEFAULT_TIMEOUT_MS = 300;
687
+ var DEFAULT_MAX_ROUNDS = 3;
688
+ var copyOf = (v) => v && typeof v === "object" && typeof structuredClone === "function" ? structuredClone(v) : v;
689
+ async function personalizeContent(payload, options) {
690
+ const { cms } = options;
691
+ const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
692
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
693
+ const applied = [];
694
+ const report = (swap) => {
695
+ applied.push(swap);
696
+ try {
697
+ options.onApplied?.(swap);
698
+ } catch {
699
+ }
700
+ };
701
+ let content = payload;
702
+ try {
703
+ if (!options.inPlace && typeof structuredClone === "function") content = structuredClone(content);
704
+ content = cms.prepare ? cms.prepare(content) : content;
705
+ } catch (error) {
706
+ return { content: payload, applied, complete: false, error };
707
+ }
708
+ const box = { root: content };
709
+ const settled = /* @__PURE__ */ new WeakSet();
710
+ const decided = /* @__PURE__ */ new Map();
711
+ let frontier = [{ parent: box, key: "root", value: box.root }];
712
+ const settle = (target, d, round) => {
713
+ settled.add(target.entry);
714
+ cms.clearMarker?.(target.entry);
715
+ const items = d.items.map(copyOf);
716
+ if (!d.decision || items.length === 0) {
717
+ return deferredSlots(target, cms);
718
+ }
719
+ for (const item of items) if (item && typeof item === "object") settled.add(item);
720
+ const op = d.decision.op ?? target.marker.op ?? "replace";
721
+ const next = apply(target, op, d.decision.field ?? target.marker.field, items, cms);
722
+ report({
723
+ target: target.id,
724
+ kind: "runtime",
725
+ op,
726
+ entryIds: items.map((e) => cms.entryId(e)).filter((id) => !!id),
727
+ contentKey: d.decision.contentKey,
728
+ bucket: d.decision.bucket,
729
+ round
730
+ });
731
+ return next;
732
+ };
733
+ for (let round = 0; ; round++) {
734
+ const pending = walk(frontier, cms, settled, round, report, (t) => {
735
+ const d = decided.get(t.id);
736
+ return d ? settle(t, d, round) : void 0;
737
+ });
738
+ if (pending.size === 0) return { content: box.root, applied, complete: true };
739
+ if (!options.resolve || round >= maxRounds) {
740
+ return { content: box.root, applied, complete: !options.resolve };
741
+ }
742
+ progress(options, { content: box.root, round, pending: [...pending.keys()] });
743
+ let decisions;
744
+ let entriesById;
745
+ let queried;
746
+ try {
747
+ decisions = await withDeadline(options.resolve([...pending.keys()]), deadline);
748
+ ({ entriesById, queried } = await withDeadline(loadReplacements(decisions, cms, options.locale), deadline));
749
+ } catch (error) {
750
+ return { content: box.root, applied, complete: false, error: error === TIMEOUT ? void 0 : error };
751
+ }
752
+ const byTarget = new Map(decisions.map((d) => [d.target, d]));
753
+ frontier = [];
754
+ for (const target of pending.values()) {
755
+ const decision = byTarget.get(target.id);
756
+ const items = decision ? decision.ids ? decision.ids.map((id) => entriesById.get(id)).filter((e) => e !== void 0) : queried.get(decision) ?? [] : [];
757
+ const d = { decision, items };
758
+ decided.set(target.id, d);
759
+ frontier.push(...settle(target, d, round));
760
+ }
761
+ progress(options, { content: box.root, round, pending: [] });
762
+ }
763
+ }
764
+ function progress(options, p) {
765
+ try {
766
+ options.onProgress?.(p);
767
+ } catch {
768
+ }
769
+ }
770
+ function walk(frontier, cms, settled, round, report, reuse) {
771
+ const pending = /* @__PURE__ */ new Map();
772
+ const seen = /* @__PURE__ */ new WeakSet();
773
+ const deferred = /* @__PURE__ */ new WeakSet();
774
+ const queue = [...frontier];
775
+ while (queue.length > 0) {
776
+ const slot = queue.shift();
777
+ const value = slot.value;
778
+ if (!value || typeof value !== "object" || deferred.has(value)) continue;
779
+ const id = settled.has(value) ? void 0 : cms.entryId(value);
780
+ const marker = id ? cms.marker(value) : null;
781
+ if (id && marker?.kind === "runtime") {
782
+ const again = reuse({ id, marker, entry: value, slots: [slot] });
783
+ if (again) {
784
+ queue.push(...again);
785
+ continue;
786
+ }
787
+ const existing = pending.get(id);
788
+ if (existing) {
789
+ existing.slots.push(slot);
790
+ } else {
791
+ pending.set(id, { id, marker, entry: value, slots: [slot] });
792
+ if (marker.op === "items" && marker.field) {
793
+ const held = cms.getField(value, marker.field);
794
+ if (held && typeof held === "object") deferred.add(held);
795
+ queue.push(...children(value));
796
+ }
797
+ }
798
+ continue;
799
+ }
800
+ if (id && marker?.kind === "static") {
801
+ const variants = cms.staticVariants(value).filter((v) => v && typeof v === "object");
802
+ settled.add(value);
803
+ cms.clearMarker?.(value);
804
+ if (variants.length > 0) {
805
+ for (const v of variants) settled.add(v);
806
+ const op = marker.op ?? "replace";
807
+ const target = { entry: value, slots: [slot] };
808
+ queue.push(...apply(target, op, marker.field, variants, cms));
809
+ report({
810
+ target: id,
811
+ kind: "static",
812
+ op,
813
+ entryIds: variants.map((v) => cms.entryId(v)).filter((x) => !!x),
814
+ round
815
+ });
816
+ continue;
817
+ }
818
+ }
819
+ if (seen.has(value)) continue;
820
+ seen.add(value);
821
+ queue.push(...children(value));
822
+ }
823
+ return pending;
824
+ }
825
+ function apply(target, op, field, items, cms) {
826
+ if (op === "items") {
827
+ if (field) {
828
+ cms.setField(target.entry, field, items);
829
+ return [{ parent: target.entry, key: field, value: cms.getField(target.entry, field) }];
830
+ }
831
+ if (cms.mergeItems) {
832
+ cms.mergeItems(target.entry, items[0]);
833
+ return target.slots.map((s) => ({ ...s, value: target.entry }));
834
+ }
835
+ op = "replace";
836
+ }
837
+ const next = [];
838
+ for (const slot of target.slots) {
839
+ const placed = op === "expand" && Array.isArray(slot.parent) ? items : [items[0]];
840
+ if (Array.isArray(slot.parent)) {
841
+ const index = slot.parent.indexOf(target.entry);
842
+ if (index < 0) continue;
843
+ slot.parent.splice(index, 1, ...placed);
844
+ } else {
845
+ slot.parent[slot.key] = placed[0];
846
+ }
847
+ for (const value of placed) next.push({ parent: slot.parent, key: slot.key, value });
848
+ }
849
+ return next;
850
+ }
851
+ function deferredSlots(target, cms) {
852
+ if (target.marker.op === "items" && target.marker.field) {
853
+ return [{ parent: target.entry, key: target.marker.field, value: cms.getField(target.entry, target.marker.field) }];
854
+ }
855
+ return target.slots.map((s) => ({ ...s, value: target.entry }));
856
+ }
857
+ function children(value) {
858
+ if (Array.isArray(value)) return value.map((v, i) => ({ parent: value, key: i, value: v }));
859
+ return Object.keys(value).map((k) => ({ parent: value, key: k, value: value[k] }));
860
+ }
861
+ async function loadReplacements(decisions, cms, locale) {
862
+ const ids = [...new Set(decisions.flatMap((d) => d.ids ?? []))];
863
+ const queries = decisions.filter((d) => !d.ids && d.query);
864
+ const [fetched, ...results] = await Promise.all([
865
+ ids.length > 0 ? cms.fetchEntries(ids, locale) : Promise.resolve([]),
866
+ ...queries.map((d) => cms.queryEntries(d.query, locale).catch(() => []))
867
+ ]);
868
+ const entriesById = /* @__PURE__ */ new Map();
869
+ for (const entry of fetched) {
870
+ const id = cms.entryId(entry);
871
+ if (id) entriesById.set(id, entry);
872
+ }
873
+ const queried = /* @__PURE__ */ new Map();
874
+ queries.forEach((d, i) => queried.set(d, results[i]));
875
+ return { entriesById, queried };
876
+ }
877
+ var TIMEOUT = /* @__PURE__ */ Symbol("timeout");
878
+ function withDeadline(promise, deadline) {
879
+ const remaining = deadline - Date.now();
880
+ if (remaining <= 0) return Promise.reject(TIMEOUT);
881
+ let timer;
882
+ return Promise.race([
883
+ promise,
884
+ new Promise((_, reject) => {
885
+ timer = setTimeout(() => reject(TIMEOUT), remaining);
886
+ })
887
+ ]).finally(() => clearTimeout(timer));
888
+ }
889
+ function pendingTargets(payload, cms) {
890
+ try {
891
+ let content = typeof structuredClone === "function" ? structuredClone(payload) : payload;
892
+ content = cms.prepare ? cms.prepare(content) : content;
893
+ const root = { parent: { root: content }, key: "root", value: content };
894
+ return [...walk([root], cms, /* @__PURE__ */ new WeakSet(), 0, () => {
895
+ }, () => void 0).keys()];
896
+ } catch {
897
+ return [];
898
+ }
899
+ }
900
+ async function personalizePage(ref, options) {
901
+ let page;
902
+ try {
903
+ if (!options.cms.fetchPage) throw new Error("This CMS adapter cannot fetch pages; use personalizeContent(page)");
904
+ page = await options.cms.fetchPage(ref, options.locale);
905
+ } catch (error) {
906
+ return { content: null, applied: [], complete: false, error };
907
+ }
908
+ if (page == null) return { content: null, applied: [], complete: true };
909
+ return personalizeContent(page, options);
910
+ }
911
+
666
912
  // src/core/response-store.ts
667
913
  var MemoryResponseStore = class {
668
914
  map = /* @__PURE__ */ new Map();
@@ -1914,7 +2160,10 @@ var XeniaClient = class {
1914
2160
  renderer;
1915
2161
  analyticsQueue;
1916
2162
  tracker = null;
1917
- ready = false;
2163
+ /** The in-flight (or finished) boot; shared so concurrent `init()` calls start one tracker. */
2164
+ booting = null;
2165
+ /** Bumped by `destroy()`, so a boot still waiting on the device id knows to stop. */
2166
+ generation = 0;
1918
2167
  consentState;
1919
2168
  listeners = /* @__PURE__ */ new Map();
1920
2169
  constructor(options) {
@@ -1944,10 +2193,14 @@ var XeniaClient = class {
1944
2193
  });
1945
2194
  }
1946
2195
  /** Boot the client: resolve device id and start auto-tracking. Idempotent. */
1947
- async init() {
1948
- if (this.ready || !isBrowser()) return;
2196
+ init() {
2197
+ if (!isBrowser()) return Promise.resolve();
2198
+ return this.booting ??= this.boot();
2199
+ }
2200
+ async boot() {
2201
+ const generation = this.generation;
1949
2202
  await this.identity.resolveDeviceId();
1950
- this.ready = true;
2203
+ if (generation !== this.generation) return;
1951
2204
  if (this.analyticsAllowed()) this.analyticsQueue.start();
1952
2205
  if (this.options.autoTrack !== false) {
1953
2206
  this.tracker = new AutoTracker({
@@ -1983,7 +2236,8 @@ var XeniaClient = class {
1983
2236
  this.tracker = null;
1984
2237
  this.renderer.destroy();
1985
2238
  this.analyticsQueue.stop();
1986
- this.ready = false;
2239
+ this.booting = null;
2240
+ this.generation++;
1987
2241
  }
1988
2242
  // ── Identity ───────────────────────────────────────────────────────────────
1989
2243
  /**
@@ -2112,6 +2366,27 @@ var XeniaClient = class {
2112
2366
  async resolveBatch(items, opts) {
2113
2367
  return this.api.resolveBatch(items, this.withVisitor(opts));
2114
2368
  }
2369
+ /**
2370
+ * Personalize a fetched CMS page: static swaps are applied from the payload, runtime targets are
2371
+ * decided by Xenia in one `resolve-entries` call per nesting level, and replacements are loaded
2372
+ * through `opts.cms` (e.g. `contentfulAdapter(...)`). Never throws — anything undecided within
2373
+ * `opts.timeoutMs` keeps its base content.
2374
+ */
2375
+ async personalizeContent(page, opts) {
2376
+ return personalizeContent(page, { ...opts, resolve: this.entryResolver(opts) });
2377
+ }
2378
+ /**
2379
+ * Fetch a page through `opts.cms` and personalize it — the app passes only the page's identity
2380
+ * (entry id, or `{ contentType, fields: { slug } }`). Never throws: a failed fetch is `content:
2381
+ * null` with `error`, a missing page `content: null`.
2382
+ */
2383
+ async personalizePage(ref, opts) {
2384
+ return personalizePage(ref, { ...opts, resolve: this.entryResolver(opts) });
2385
+ }
2386
+ entryResolver(opts) {
2387
+ const resolveOpts = this.withVisitor({ data: opts.data, locale: opts.locale });
2388
+ return async (ids) => (await this.api.resolveEntries(ids, resolveOpts)).decisions ?? [];
2389
+ }
2115
2390
  /** The SDK-owned visitor anchor: the `identify()`/`config.visitorId` id, else the device id. */
2116
2391
  resolveVisitorId() {
2117
2392
  return this.identity.getVisitorId() ?? this.identity.getDeviceId() ?? void 0;
@@ -2190,8 +2465,8 @@ var XeniaClient = class {
2190
2465
  const response = await this.api.webhook(triggerId, body, opts?.method ?? "POST");
2191
2466
  this.responses.set(triggerId, response);
2192
2467
  this.emit("response", { triggerId, response });
2193
- const apply = opts?.apply ?? this.options.autoApplyActions !== false;
2194
- if (apply && response.actions.length) {
2468
+ const apply2 = opts?.apply ?? this.options.autoApplyActions !== false;
2469
+ if (apply2 && response.actions.length) {
2195
2470
  this.applyActions(response.actions);
2196
2471
  }
2197
2472
  return response;
@@ -2245,8 +2520,8 @@ var XeniaClient = class {
2245
2520
  compact({ ...ambient, fields })
2246
2521
  );
2247
2522
  this.emit("response", { triggerId: ref, response });
2248
- const apply = opts?.apply ?? this.options.autoApplyActions !== false;
2249
- if (apply && response.actions.length) this.applyActions(response.actions);
2523
+ const apply2 = opts?.apply ?? this.options.autoApplyActions !== false;
2524
+ if (apply2 && response.actions.length) this.applyActions(response.actions);
2250
2525
  return response;
2251
2526
  }
2252
2527
  // ── Imperative render helpers ─────────────────────────────────────────────────
@@ -2324,7 +2599,7 @@ function useXenia() {
2324
2599
  }
2325
2600
  return client;
2326
2601
  }
2327
- function XeniaProvider({ children, client, ...options }) {
2602
+ function XeniaProvider({ children: children2, client, ...options }) {
2328
2603
  const ref = react.useRef(null);
2329
2604
  const instance = react.useMemo(() => {
2330
2605
  if (client) return client;
@@ -2335,7 +2610,7 @@ function XeniaProvider({ children, client, ...options }) {
2335
2610
  void instance.init();
2336
2611
  return () => instance.destroy();
2337
2612
  }, [instance]);
2338
- return /* @__PURE__ */ jsxRuntime.jsx(XeniaContext.Provider, { value: instance, children });
2613
+ return /* @__PURE__ */ jsxRuntime.jsx(XeniaContext.Provider, { value: instance, children: children2 });
2339
2614
  }
2340
2615
 
2341
2616
  // src/core/content-splice.ts
@@ -2522,12 +2797,92 @@ function useXeniaResponse(key) {
2522
2797
  const xenia = useXenia();
2523
2798
  return xenia.getResponse(key);
2524
2799
  }
2800
+ function usePersonalizedContent(page, opts) {
2801
+ const xenia = useXenia();
2802
+ const hide = opts.render === "hide-until-resolved";
2803
+ const initialPending = react.useMemo(
2804
+ () => new Set(page == null ? [] : pendingTargets(page, opts.cms)),
2805
+ [page]
2806
+ // markers live in `page`; a new adapter object for the same page changes nothing
2807
+ );
2808
+ const [state, setState] = react.useState(
2809
+ () => ({ page, content: hide ? null : page ?? null, done: false, complete: false, applied: [], pending: initialPending })
2810
+ );
2811
+ const optsRef = react.useRef(opts);
2812
+ optsRef.current = opts;
2813
+ const optsKey = JSON.stringify({ data: opts.data ?? {}, locale: opts.locale });
2814
+ react.useEffect(() => {
2815
+ if (page == null) return;
2816
+ let active = true;
2817
+ const o = optsRef.current;
2818
+ setState({ page, content: hide ? null : page, done: false, complete: false, applied: [], pending: initialPending });
2819
+ const reveal = hide ? setTimeout(() => active && setState((s) => s.content ? s : { ...s, content: page }), o.hideTimeoutMs ?? o.timeoutMs ?? 300) : void 0;
2820
+ const applied = [];
2821
+ void xenia.personalizeContent(page, {
2822
+ ...o,
2823
+ onApplied(swap) {
2824
+ applied.push(swap);
2825
+ o.onApplied?.(swap);
2826
+ },
2827
+ onProgress(p) {
2828
+ o.onProgress?.(p);
2829
+ if (!active || hide) return;
2830
+ setState({ page, content: p.content, done: false, complete: false, applied: [...applied], pending: new Set(p.pending) });
2831
+ }
2832
+ }).then((r) => {
2833
+ if (active) setState({ page, content: r.content, done: true, complete: r.complete, applied: r.applied, pending: /* @__PURE__ */ new Set() });
2834
+ }).finally(() => clearTimeout(reveal));
2835
+ return () => {
2836
+ active = false;
2837
+ clearTimeout(reveal);
2838
+ };
2839
+ }, [xenia, page, optsKey, hide, initialPending]);
2840
+ const personalized = new Set(state.applied.map((a) => a.target));
2841
+ const pending = state.page === page ? state.pending : initialPending;
2842
+ return {
2843
+ content: state.content,
2844
+ isLoading: page != null && !state.done,
2845
+ complete: state.complete,
2846
+ applied: state.applied,
2847
+ status: (id) => pending.has(id) ? "pending" : personalized.has(id) ? "personalized" : "base"
2848
+ };
2849
+ }
2850
+ function usePersonalizedPage(ref, opts) {
2851
+ const [page, setPage] = react.useState({ value: null, loaded: false });
2852
+ const cmsRef = react.useRef(opts.cms);
2853
+ cmsRef.current = opts.cms;
2854
+ const refKey = JSON.stringify([ref, opts.locale]);
2855
+ react.useEffect(() => {
2856
+ let active = true;
2857
+ setPage({ value: null, loaded: false });
2858
+ const fetchPage = cmsRef.current.fetchPage;
2859
+ Promise.resolve(fetchPage ? fetchPage.call(cmsRef.current, ref, opts.locale) : null).then(
2860
+ (value) => active && setPage({ value: value ?? null, loaded: true }),
2861
+ () => active && setPage({ value: null, loaded: true, failed: true })
2862
+ );
2863
+ return () => {
2864
+ active = false;
2865
+ };
2866
+ }, [refKey]);
2867
+ const result = usePersonalizedContent(page.value, opts);
2868
+ const complete = page.value == null ? page.loaded && !page.failed : result.complete;
2869
+ return { ...result, isLoading: !page.loaded || result.isLoading, complete };
2870
+ }
2871
+ var PersonalizedContentContext = react.createContext(null);
2872
+ function XeniaContentProvider(props) {
2873
+ return react.createElement(PersonalizedContentContext.Provider, { value: props.value }, props.children);
2874
+ }
2875
+ function useXeniaEntry(entryId) {
2876
+ const ctx = react.useContext(PersonalizedContentContext);
2877
+ const status = ctx ? ctx.status(entryId) : "base";
2878
+ return { status, isLoading: status === "pending" };
2879
+ }
2525
2880
  function XeniaTrack({
2526
2881
  event,
2527
2882
  on = "click",
2528
2883
  props,
2529
2884
  as = "span",
2530
- children,
2885
+ children: children2,
2531
2886
  className
2532
2887
  }) {
2533
2888
  const xenia = useXenia();
@@ -2559,7 +2914,7 @@ function XeniaTrack({
2559
2914
  const handlers = on === "click" ? {
2560
2915
  onClick: () => xenia.track(event, { ...props, interactionType: "click" }, { trigger: true })
2561
2916
  } : {};
2562
- return react.createElement(as, { ref, className, ...handlers }, children);
2917
+ return react.createElement(as, { ref, className, ...handlers }, children2);
2563
2918
  }
2564
2919
  function XeniaMessage({ actions }) {
2565
2920
  const xenia = useXenia();
@@ -2577,6 +2932,7 @@ function reactMessageComponent(Component) {
2577
2932
  }
2578
2933
 
2579
2934
  exports.XeniaClient = XeniaClient;
2935
+ exports.XeniaContentProvider = XeniaContentProvider;
2580
2936
  exports.XeniaContext = XeniaContext;
2581
2937
  exports.XeniaMessage = XeniaMessage;
2582
2938
  exports.XeniaProvider = XeniaProvider;
@@ -2584,15 +2940,21 @@ exports.XeniaTrack = XeniaTrack;
2584
2940
  exports.createXeniaClient = createXeniaClient;
2585
2941
  exports.extractContentSwaps = extractContentSwaps;
2586
2942
  exports.parseActions = parseActions;
2943
+ exports.pendingTargets = pendingTargets;
2944
+ exports.personalizeContent = personalizeContent;
2945
+ exports.personalizePage = personalizePage;
2587
2946
  exports.reactMessageComponent = reactMessageComponent;
2588
2947
  exports.spliceSlots = spliceSlots;
2589
2948
  exports.usePersonalization = usePersonalization;
2590
2949
  exports.usePersonalizationResolve = usePersonalizationResolve;
2591
2950
  exports.usePersonalizationResolveSlots = usePersonalizationResolveSlots;
2592
2951
  exports.usePersonalizationSlots = usePersonalizationSlots;
2952
+ exports.usePersonalizedContent = usePersonalizedContent;
2953
+ exports.usePersonalizedPage = usePersonalizedPage;
2593
2954
  exports.useTrack = useTrack;
2594
2955
  exports.useWebhook = useWebhook;
2595
2956
  exports.useXenia = useXenia;
2957
+ exports.useXeniaEntry = useXeniaEntry;
2596
2958
  exports.useXeniaResponse = useXeniaResponse;
2597
2959
  //# sourceMappingURL=index.cjs.map
2598
2960
  //# sourceMappingURL=index.cjs.map