@appfunnel-dev/sdk 2.0.0-canary.4 → 2.0.0-canary.7

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 (60) hide show
  1. package/README.md +1 -1
  2. package/dist/{checkout-Dz8cGkB_.d.cts → checkout-CMEdxpgo.d.cts} +13 -13
  3. package/dist/{checkout-7Dy6IedP.d.ts → checkout-D1NUPv4p.d.ts} +13 -13
  4. package/dist/{chunk-AKO6XKXP.js → chunk-7YQLPPPG.js} +66 -91
  5. package/dist/chunk-7YQLPPPG.js.map +1 -0
  6. package/dist/{chunk-JSRKA375.cjs → chunk-BPOAWI4G.cjs} +68 -94
  7. package/dist/chunk-BPOAWI4G.cjs.map +1 -0
  8. package/dist/{chunk-M6U3FNRW.js → chunk-CFFMZYPE.js} +3 -3
  9. package/dist/{chunk-M6U3FNRW.js.map → chunk-CFFMZYPE.js.map} +1 -1
  10. package/dist/chunk-EMMSS5I5.cjs +37 -0
  11. package/dist/chunk-EMMSS5I5.cjs.map +1 -0
  12. package/dist/chunk-G3PMV62Z.js +33 -0
  13. package/dist/chunk-G3PMV62Z.js.map +1 -0
  14. package/dist/{chunk-LJYLGLFS.cjs → chunk-QMAZGLGV.cjs} +2 -35
  15. package/dist/chunk-QMAZGLGV.cjs.map +1 -0
  16. package/dist/{chunk-YY375F2B.js → chunk-TNLRQPVY.js} +3 -33
  17. package/dist/chunk-TNLRQPVY.js.map +1 -0
  18. package/dist/{chunk-CY4VBSMX.cjs → chunk-VAOR77NW.cjs} +7 -7
  19. package/dist/{chunk-CY4VBSMX.cjs.map → chunk-VAOR77NW.cjs.map} +1 -1
  20. package/dist/chunk-WYUDL4FI.cjs +8 -0
  21. package/dist/chunk-WYUDL4FI.cjs.map +1 -0
  22. package/dist/chunk-ZZJG4EYL.js +6 -0
  23. package/dist/chunk-ZZJG4EYL.js.map +1 -0
  24. package/dist/driver-paddle.cjs +23 -22
  25. package/dist/driver-paddle.cjs.map +1 -1
  26. package/dist/driver-paddle.d.cts +1 -1
  27. package/dist/driver-paddle.d.ts +1 -1
  28. package/dist/driver-paddle.js +3 -2
  29. package/dist/driver-paddle.js.map +1 -1
  30. package/dist/driver-stripe.cjs +277 -224
  31. package/dist/driver-stripe.cjs.map +1 -1
  32. package/dist/driver-stripe.d.cts +18 -2
  33. package/dist/driver-stripe.d.ts +18 -2
  34. package/dist/driver-stripe.js +244 -192
  35. package/dist/driver-stripe.js.map +1 -1
  36. package/dist/index.cjs +750 -150
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +151 -90
  39. package/dist/index.d.ts +151 -90
  40. package/dist/index.js +702 -98
  41. package/dist/index.js.map +1 -1
  42. package/dist/{manifest-Cr2y1op6.d.cts → manifest-CMe8yVkr.d.cts} +131 -88
  43. package/dist/{manifest-Cr2y1op6.d.ts → manifest-CMe8yVkr.d.ts} +131 -88
  44. package/dist/manifest-entry.cjs +62 -47
  45. package/dist/manifest-entry.cjs.map +1 -1
  46. package/dist/manifest-entry.d.cts +15 -15
  47. package/dist/manifest-entry.d.ts +15 -15
  48. package/dist/manifest-entry.js +25 -14
  49. package/dist/manifest-entry.js.map +1 -1
  50. package/dist/protocol.cjs +13 -0
  51. package/dist/protocol.cjs.map +1 -0
  52. package/dist/protocol.d.cts +182 -0
  53. package/dist/protocol.d.ts +182 -0
  54. package/dist/protocol.js +4 -0
  55. package/dist/protocol.js.map +1 -0
  56. package/package.json +14 -2
  57. package/dist/chunk-AKO6XKXP.js.map +0 -1
  58. package/dist/chunk-JSRKA375.cjs.map +0 -1
  59. package/dist/chunk-LJYLGLFS.cjs.map +0 -1
  60. package/dist/chunk-YY375F2B.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunkJSRKA375_cjs = require('./chunk-JSRKA375.cjs');
4
- var chunkLJYLGLFS_cjs = require('./chunk-LJYLGLFS.cjs');
3
+ var chunkBPOAWI4G_cjs = require('./chunk-BPOAWI4G.cjs');
4
+ var chunkWYUDL4FI_cjs = require('./chunk-WYUDL4FI.cjs');
5
+ var chunkQMAZGLGV_cjs = require('./chunk-QMAZGLGV.cjs');
6
+ require('./chunk-EMMSS5I5.cjs');
5
7
  var React = require('react');
6
8
  var ReactDOM = require('react-dom');
7
9
  var jsxRuntime = require('react/jsx-runtime');
@@ -385,7 +387,40 @@ function createBus(accessors, now2 = () => Date.now()) {
385
387
  }
386
388
  }
387
389
  };
388
- return { on, off, events, emit, ...accessors };
390
+ let nav = null;
391
+ let warnedNoNav = false;
392
+ const currentNav = () => {
393
+ if (!nav && !warnedNoNav) {
394
+ warnedNoNav = true;
395
+ console.warn(
396
+ "[appfunnel] window.appfunnel navigation called before the funnel flow mounted \u2014 ignored"
397
+ );
398
+ }
399
+ return nav;
400
+ };
401
+ const registerNavigation = (n) => {
402
+ nav = n;
403
+ warnedNoNav = false;
404
+ return () => {
405
+ if (nav === n) nav = null;
406
+ };
407
+ };
408
+ const goTo = (pageKey) => currentNav()?.goTo(pageKey) ?? false;
409
+ const goNext = () => currentNav()?.goNext();
410
+ const goPrevious = () => currentNav()?.goPrevious();
411
+ const navigation = () => nav?.snapshot() ?? null;
412
+ return {
413
+ on,
414
+ off,
415
+ events,
416
+ emit,
417
+ registerNavigation,
418
+ goTo,
419
+ goNext,
420
+ goPrevious,
421
+ navigation,
422
+ ...accessors
423
+ };
389
424
  }
390
425
  function withBus(tracker, bus) {
391
426
  return {
@@ -407,7 +442,19 @@ function withBus(tracker, bus) {
407
442
  function attachBus(bus) {
408
443
  if (typeof window === "undefined") return () => {
409
444
  };
410
- const { on, off, getVariable, getVariables, getCurrentPageId, getCustomerId, getVisitorId } = bus;
445
+ const {
446
+ on,
447
+ off,
448
+ getVariable,
449
+ getVariables,
450
+ getCurrentPageId,
451
+ getCustomerId,
452
+ getVisitorId,
453
+ goTo,
454
+ goNext,
455
+ goPrevious,
456
+ navigation
457
+ } = bus;
411
458
  const api = {
412
459
  on,
413
460
  off,
@@ -416,6 +463,11 @@ function attachBus(bus) {
416
463
  getCurrentPageId,
417
464
  getCustomerId,
418
465
  getVisitorId,
466
+ // Flow control (bound closures over the bus's live nav registration).
467
+ goTo,
468
+ goNext,
469
+ goPrevious,
470
+ navigation,
419
471
  get events() {
420
472
  return bus.events;
421
473
  }
@@ -452,9 +504,9 @@ function LocaleProvider({
452
504
  override,
453
505
  children
454
506
  }) {
455
- const [locale, setLocale] = React.useState(() => chunkJSRKA375_cjs.resolveLocale(config, detected, override));
507
+ const [locale, setLocale] = React.useState(() => chunkBPOAWI4G_cjs.resolveLocale(config, detected, override));
456
508
  React.useEffect(() => {
457
- setLocale(chunkJSRKA375_cjs.resolveLocale(config, detected, override));
509
+ setLocale(chunkBPOAWI4G_cjs.resolveLocale(config, detected, override));
458
510
  }, [config, detected, override]);
459
511
  const value = React.useMemo(
460
512
  () => ({ locale, setLocale, config, catalog }),
@@ -526,7 +578,7 @@ function useTranslation() {
526
578
  fmt,
527
579
  locale,
528
580
  setLocale,
529
- dir: chunkJSRKA375_cjs.isRtl(locale) ? "rtl" : "ltr",
581
+ dir: chunkBPOAWI4G_cjs.isRtl(locale) ? "rtl" : "ltr",
530
582
  locales: config?.supported ?? [locale]
531
583
  };
532
584
  }
@@ -543,20 +595,119 @@ function useCatalog() {
543
595
  return React.useContext(CatalogContext) ?? EMPTY;
544
596
  }
545
597
  var EMPTY = /* @__PURE__ */ new Map();
546
- function useProduct(id) {
598
+ function useOffering(id) {
547
599
  const catalog = useCatalog();
548
600
  const locale = useActiveLocale();
549
601
  const resolved = id ? catalog.get(id) : void 0;
550
- return React.useMemo(() => resolved ? chunkJSRKA375_cjs.formatProduct(resolved, locale) : void 0, [resolved, locale]);
602
+ return React.useMemo(
603
+ () => resolved ? chunkBPOAWI4G_cjs.formatOffering(resolved, locale) : void 0,
604
+ [resolved, locale]
605
+ );
551
606
  }
552
- function useProducts() {
607
+ function useOfferings() {
553
608
  const catalog = useCatalog();
554
609
  const locale = useActiveLocale();
555
610
  return React.useMemo(
556
- () => Array.from(catalog.values()).map((r) => chunkJSRKA375_cjs.formatProduct(r, locale)),
611
+ () => Array.from(catalog.values()).map((r) => chunkBPOAWI4G_cjs.formatOffering(r, locale)),
557
612
  [catalog, locale]
558
613
  );
559
614
  }
615
+
616
+ // src/flow/screenshot.ts
617
+ var MIME = { png: "image/png", jpeg: "image/jpeg", webp: "image/webp" };
618
+ var ScreenshotError = class extends Error {
619
+ constructor(code, message) {
620
+ super(message);
621
+ this.code = code;
622
+ }
623
+ };
624
+ function resolveTarget(t) {
625
+ if (!t || t.kind === "page") return document.getElementById("root") ?? document.body;
626
+ if (t.kind === "viewport") return document.body;
627
+ return document.querySelector(t.selector);
628
+ }
629
+ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
630
+ function hideAll(selectors) {
631
+ const touched = [];
632
+ for (const sel of selectors) {
633
+ document.querySelectorAll(sel).forEach((el) => {
634
+ touched.push([el, el.style.visibility]);
635
+ el.style.visibility = "hidden";
636
+ });
637
+ }
638
+ return () => touched.forEach(([el, v]) => el.style.visibility = v);
639
+ }
640
+ async function scrollThrough(el) {
641
+ const prev = el.scrollTop;
642
+ const step = el.clientHeight || window.innerHeight;
643
+ for (let y = 0; y < el.scrollHeight; y += step) {
644
+ el.scrollTop = y;
645
+ await delay(16);
646
+ }
647
+ el.scrollTop = prev;
648
+ await delay(16);
649
+ }
650
+ function blobToDataURL(blob) {
651
+ return new Promise((resolve, reject) => {
652
+ const fr = new FileReader();
653
+ fr.onload = () => resolve(String(fr.result));
654
+ fr.onerror = () => reject(fr.error);
655
+ fr.readAsDataURL(blob);
656
+ });
657
+ }
658
+ async function captureScreenshot(params = {}) {
659
+ const el = resolveTarget(params.target);
660
+ if (!el) throw new ScreenshotError("bad_params", "screenshot target not found");
661
+ const restore = params.hideSelectors?.length ? hideAll(params.hideSelectors) : null;
662
+ const scale = Math.min(params.scale ?? 2, params.maxScale ?? 3);
663
+ const format = params.format ?? "png";
664
+ const timeout = params.timeout ?? 1e4;
665
+ try {
666
+ if (params.waitForFonts !== false && document.fonts?.ready) {
667
+ await Promise.race([document.fonts.ready, delay(timeout)]);
668
+ }
669
+ if (params.triggerLazyLoad) await scrollThrough(el);
670
+ if (params.waitForImages !== false) {
671
+ await Promise.all(
672
+ Array.from(el.querySelectorAll("img")).map(
673
+ (img) => typeof img.decode === "function" ? img.decode().catch(() => {
674
+ }) : Promise.resolve()
675
+ )
676
+ );
677
+ }
678
+ const { domToBlob } = await import('modern-screenshot');
679
+ let blob;
680
+ try {
681
+ blob = await domToBlob(el, {
682
+ type: MIME[format],
683
+ quality: params.quality ?? 0.92,
684
+ scale,
685
+ width: params.width,
686
+ height: params.height,
687
+ // `null` = transparent (png/webp); only fall back to white when the caller omitted it. `??`
688
+ // would wrongly collapse an explicit `null` to white.
689
+ backgroundColor: params.backgroundColor === void 0 ? "#ffffff" : params.backgroundColor,
690
+ timeout
691
+ });
692
+ } catch (e) {
693
+ throw new ScreenshotError("failed", `capture failed (an image/font may be served without CORS '*'): ${String(e?.message || e)}`);
694
+ }
695
+ const result = {
696
+ mime: MIME[format],
697
+ width: Math.round((params.width ?? el.clientWidth) * scale),
698
+ height: Math.round((params.height ?? el.scrollHeight) * scale),
699
+ scale,
700
+ bytesApprox: blob.size
701
+ };
702
+ if ((params.encoding ?? "blob") === "dataURL") result.dataURL = await blobToDataURL(blob);
703
+ else result.blob = blob;
704
+ return result;
705
+ } finally {
706
+ restore?.();
707
+ }
708
+ }
709
+
710
+ // src/flow/flow.tsx
560
711
  var NavContext = React.createContext(null);
561
712
  var ExperimentContext = React.createContext({});
562
713
  function useExperiment(id) {
@@ -609,6 +760,7 @@ function usePageExitTimer(tracker, currentKey) {
609
760
  function FunnelView({
610
761
  pages,
611
762
  initialKey,
763
+ trustInitialKey = false,
612
764
  layout,
613
765
  fallback,
614
766
  prefetch: prefetchMode = "auto",
@@ -634,10 +786,10 @@ function FunnelView({
634
786
  [pages]
635
787
  );
636
788
  const seedRef = React.useRef(null);
637
- if (seedRef.current === null) seedRef.current = chunkJSRKA375_cjs.bucketingSeed(funnel.context);
789
+ if (seedRef.current === null) seedRef.current = chunkBPOAWI4G_cjs.bucketingSeed(funnel.context);
638
790
  const seed = seedRef.current;
639
791
  const experiments = React.useMemo(
640
- () => chunkJSRKA375_cjs.resolveExperiments(pages, funnel.experiments, seed),
792
+ () => chunkBPOAWI4G_cjs.resolveExperiments(pages, funnel.experiments, seed),
641
793
  [pages, funnel.experiments, seed]
642
794
  );
643
795
  const toSlot = React.useCallback((k) => experiments.slotOf[k] ?? k, [experiments]);
@@ -648,16 +800,21 @@ function FunnelView({
648
800
  [experiments, pageByKey]
649
801
  );
650
802
  const flow = React.useMemo(
651
- () => flowPages.map((p) => ({ key: p.key, meta: p.meta })),
652
- [flowPages]
803
+ () => flowPages.map((p) => {
804
+ const servedVariant = experiments.render[p.key];
805
+ const variantNext = servedVariant ? pageByKey.get(servedVariant)?.meta?.next : void 0;
806
+ return variantNext ? { key: p.key, meta: { ...p.meta ?? {}, next: variantNext } } : { key: p.key, meta: p.meta };
807
+ }),
808
+ [flowPages, experiments, pageByKey]
653
809
  );
654
810
  const [history, setHistory] = React.useState(() => {
655
811
  const start = flowPages[0]?.key;
656
812
  if (!initialKey || initialKey === start) return start ? [start] : [];
657
813
  const target = toSlot(initialKey);
658
814
  const page2 = flowPages.find((p) => p.key === target);
659
- const guard = chunkJSRKA375_cjs.entryGuard(page2?.meta);
660
- const ok = page2 && (!guard || chunkJSRKA375_cjs.evaluateGate(guard, funnel.snapshot()));
815
+ if (trustInitialKey) return page2 ? [target] : start ? [start] : [];
816
+ const guard = chunkBPOAWI4G_cjs.entryGuard(page2?.meta);
817
+ const ok = page2 && (!guard || chunkBPOAWI4G_cjs.evaluateGate(guard, funnel.snapshot()));
661
818
  const first = ok ? target : start;
662
819
  return first ? [first] : [];
663
820
  });
@@ -665,7 +822,7 @@ function FunnelView({
665
822
  const index = history.length - 1;
666
823
  const startKey = history[0];
667
824
  const total = React.useMemo(
668
- () => chunkJSRKA375_cjs.expectedPathLength(flow, startKey, funnel.snapshot()),
825
+ () => chunkBPOAWI4G_cjs.expectedPathLength(flow, startKey, funnel.snapshot()),
669
826
  // currentKey in deps: re-trace as each step's answers settle the branch.
670
827
  [flow, startKey, currentKey, funnel]
671
828
  );
@@ -700,7 +857,7 @@ function FunnelView({
700
857
  usePageExitTimer(tracker, currentKey);
701
858
  const [isNavigating, startNav] = React.useTransition();
702
859
  const next = React.useCallback(() => {
703
- const target = chunkJSRKA375_cjs.nextPage(flow, currentKey, funnel.snapshot());
860
+ const target = chunkBPOAWI4G_cjs.nextPage(flow, currentKey, funnel.snapshot());
704
861
  const resolved = target ? toSlot(target) : target;
705
862
  if (resolved) startNav(() => setHistory((h) => [...h, resolved]));
706
863
  }, [flow, currentKey, funnel, toSlot]);
@@ -724,7 +881,7 @@ function FunnelView({
724
881
  [loaders, toRenderKey]
725
882
  );
726
883
  const nextCandidates = React.useMemo(
727
- () => [...new Set(chunkJSRKA375_cjs.outgoingKeys(flow, currentKey).map(toRenderKey))],
884
+ () => [...new Set(chunkBPOAWI4G_cjs.outgoingKeys(flow, currentKey).map(toRenderKey))],
728
885
  [flow, currentKey, toRenderKey]
729
886
  );
730
887
  React.useEffect(() => {
@@ -741,6 +898,44 @@ function FunnelView({
741
898
  canGoBack: history.length > 1,
742
899
  isNavigating
743
900
  };
901
+ const bus = useBusInternal();
902
+ const navRef = React.useRef(value);
903
+ navRef.current = value;
904
+ React.useEffect(() => {
905
+ if (!bus) return;
906
+ const control = {
907
+ goTo: (key) => {
908
+ if (!components.has(toRenderKey(key))) return false;
909
+ navRef.current.go(key);
910
+ return true;
911
+ },
912
+ goNext: () => navRef.current.next(),
913
+ goPrevious: () => navRef.current.back(),
914
+ snapshot: () => {
915
+ const v = navRef.current;
916
+ return {
917
+ key: v.page.key,
918
+ index: v.page.index,
919
+ total: v.page.total,
920
+ progressPercentage: v.page.progressPercentage,
921
+ canGoBack: v.canGoBack,
922
+ nextCandidates: v.nextCandidates
923
+ };
924
+ }
925
+ };
926
+ return bus.registerNavigation(control);
927
+ }, [bus]);
928
+ React.useEffect(() => {
929
+ const v = navRef.current;
930
+ bus?.emit("navigation", {
931
+ key: v.page.key,
932
+ index: v.page.index,
933
+ total: v.page.total,
934
+ progressPercentage: v.page.progressPercentage,
935
+ canGoBack: v.canGoBack,
936
+ nextCandidates: v.nextCandidates
937
+ });
938
+ }, [bus, currentKey]);
744
939
  const renderKey = toRenderKey(currentKey);
745
940
  const Current = components.get(renderKey);
746
941
  const pageEl = React.createElement(React.Suspense, { fallback: fallback ?? null }, Current ? React.createElement(Current) : null);
@@ -756,10 +951,85 @@ function useNavigation() {
756
951
  if (!ctx) throw new Error("useNavigation must be used inside <FunnelView>");
757
952
  return ctx;
758
953
  }
954
+ var EMBED_CAPABILITIES = {
955
+ methods: ["nav.snapshot", "nav.goTo", "nav.goNext", "nav.goPrevious", "screenshot"],
956
+ events: ["navigation"]
957
+ };
958
+ function EmbedBridge() {
959
+ const bus = useBusInternal();
960
+ React.useEffect(() => {
961
+ if (!bus || typeof window === "undefined" || window.parent === window) return;
962
+ const parent = window.parent;
963
+ let disposed = false;
964
+ const send = (msg) => {
965
+ try {
966
+ parent.postMessage({ af: chunkWYUDL4FI_cjs.AF_PROTOCOL, source: "af-preview", ...msg }, "*");
967
+ } catch {
968
+ }
969
+ };
970
+ const hello = () => send({ kind: "hello", role: "funnel", protocol: chunkWYUDL4FI_cjs.AF_PROTOCOL, capabilities: EMBED_CAPABILITIES });
971
+ const sendSnapshot = () => {
972
+ const s = bus.navigation();
973
+ if (s) send({ kind: "event", event: "navigation", data: s });
974
+ };
975
+ async function invoke(method, params) {
976
+ switch (method) {
977
+ case "nav.snapshot":
978
+ return bus.navigation();
979
+ case "nav.goTo": {
980
+ const key = params?.key;
981
+ if (typeof key !== "string" || !bus.goTo(key)) throw { code: "bad_params", message: `unknown page key: ${String(key)}` };
982
+ return void 0;
983
+ }
984
+ case "nav.goNext":
985
+ bus.goNext();
986
+ return void 0;
987
+ case "nav.goPrevious":
988
+ bus.goPrevious();
989
+ return void 0;
990
+ case "screenshot":
991
+ return await captureScreenshot(params ?? {});
992
+ default:
993
+ throw { code: "unknown_method", message: `unknown method: ${method}` };
994
+ }
995
+ }
996
+ async function onMsg(e) {
997
+ if (e.source !== parent) return;
998
+ const d = e.data;
999
+ if (!d || typeof d !== "object" || d.af !== chunkWYUDL4FI_cjs.AF_PROTOCOL || d.source !== "af-host") return;
1000
+ if (d.kind === "hello") {
1001
+ hello();
1002
+ sendSnapshot();
1003
+ return;
1004
+ }
1005
+ if (d.kind !== "request" || typeof d.id !== "string") return;
1006
+ try {
1007
+ const result = await invoke(d.method, d.params);
1008
+ if (!disposed) send({ kind: "response", id: d.id, ok: true, result });
1009
+ } catch (err) {
1010
+ const e2 = err;
1011
+ const error = e2 && typeof e2.code === "string" ? { code: e2.code, message: String(e2.message ?? e2.code) } : { code: "failed", message: String(e2?.message ?? err) };
1012
+ if (!disposed) send({ kind: "response", id: d.id, ok: false, error });
1013
+ }
1014
+ }
1015
+ window.addEventListener("message", onMsg);
1016
+ const offNav = bus.on("navigation", (data) => {
1017
+ if (!disposed) send({ kind: "event", event: "navigation", data });
1018
+ });
1019
+ hello();
1020
+ sendSnapshot();
1021
+ return () => {
1022
+ disposed = true;
1023
+ offNav();
1024
+ window.removeEventListener("message", onMsg);
1025
+ };
1026
+ }, [bus]);
1027
+ return null;
1028
+ }
759
1029
  function usePage() {
760
1030
  return useNavigation().page;
761
1031
  }
762
- var symModalId = /* @__PURE__ */ Symbol("AppFunnelModalId");
1032
+ var symModalId = /* @__PURE__ */ Symbol("AppfunnelModalId");
763
1033
  var initialState = {};
764
1034
  var ModalStateContext = React.createContext(initialState);
765
1035
  var ModalIdContext = React.createContext(null);
@@ -774,7 +1044,9 @@ var runtimeStack = [];
774
1044
  var dispatch = (action) => {
775
1045
  const top = runtimeStack[runtimeStack.length - 1];
776
1046
  if (!top) {
777
- throw new Error("No modal dispatch \u2014 render inside <FunnelProvider> (which mounts the modal runtime).");
1047
+ throw new Error(
1048
+ "No modal dispatch \u2014 render inside <FunnelProvider> (which mounts the modal runtime)."
1049
+ );
778
1050
  }
779
1051
  top(action);
780
1052
  };
@@ -873,26 +1145,42 @@ function useModal(modal, args) {
873
1145
  const d = React.useContext(ModalDispatchContext) ?? dispatch;
874
1146
  const isComponent = !!modal && typeof modal !== "string";
875
1147
  const id = modal ? idOf(modal) : contextId;
876
- if (!id) throw new Error("useModal: no modal id (call inside a defineModal component, or pass an id/component).");
1148
+ if (!id)
1149
+ throw new Error(
1150
+ "useModal: no modal id (call inside a defineModal component, or pass an id/component)."
1151
+ );
877
1152
  React.useEffect(() => {
878
- if (isComponent && !REGISTRY[id]) registerModal(id, modal, args);
1153
+ if (isComponent && !REGISTRY[id])
1154
+ registerModal(id, modal, args);
879
1155
  }, [isComponent, id, modal, args]);
880
1156
  const info = modals[id];
881
- const show = React.useCallback((a) => showModalVia(d, id, a), [d, id]);
1157
+ const show = React.useCallback(
1158
+ (a) => showModalVia(d, id, a),
1159
+ [d, id]
1160
+ );
882
1161
  const hide = React.useCallback(() => hideModalVia(d, id), [d, id]);
883
1162
  const remove = React.useCallback(() => removeModalVia(d, id), [d, id]);
884
- const resolve = React.useCallback((v) => {
885
- modalCallbacks[id]?.resolve(v);
886
- delete modalCallbacks[id];
887
- }, [id]);
888
- const reject = React.useCallback((v) => {
889
- modalCallbacks[id]?.reject(v);
890
- delete modalCallbacks[id];
891
- }, [id]);
892
- const resolveHide = React.useCallback((v) => {
893
- hideCallbacks[id]?.resolve(v);
894
- delete hideCallbacks[id];
895
- }, [id]);
1163
+ const resolve = React.useCallback(
1164
+ (v) => {
1165
+ modalCallbacks[id]?.resolve(v);
1166
+ delete modalCallbacks[id];
1167
+ },
1168
+ [id]
1169
+ );
1170
+ const reject = React.useCallback(
1171
+ (v) => {
1172
+ modalCallbacks[id]?.reject(v);
1173
+ delete modalCallbacks[id];
1174
+ },
1175
+ [id]
1176
+ );
1177
+ const resolveHide = React.useCallback(
1178
+ (v) => {
1179
+ hideCallbacks[id]?.resolve(v);
1180
+ delete hideCallbacks[id];
1181
+ },
1182
+ [id]
1183
+ );
896
1184
  return React.useMemo(
897
1185
  () => ({
898
1186
  id,
@@ -906,11 +1194,27 @@ function useModal(modal, args) {
906
1194
  reject,
907
1195
  resolveHide
908
1196
  }),
909
- [id, info?.args, info?.visible, info?.keepMounted, show, hide, remove, resolve, reject, resolveHide]
1197
+ [
1198
+ id,
1199
+ info?.args,
1200
+ info?.visible,
1201
+ info?.keepMounted,
1202
+ show,
1203
+ hide,
1204
+ remove,
1205
+ resolve,
1206
+ reject,
1207
+ resolveHide
1208
+ ]
910
1209
  );
911
1210
  }
912
1211
  function defineModal(Comp) {
913
- return function Wrapped({ id, defaultVisible, keepMounted, ...props }) {
1212
+ return function Wrapped({
1213
+ id,
1214
+ defaultVisible,
1215
+ keepMounted,
1216
+ ...props
1217
+ }) {
914
1218
  const { args, show } = useModal(id);
915
1219
  const modals = React.useContext(ModalStateContext);
916
1220
  const d = React.useContext(ModalDispatchContext) ?? dispatch;
@@ -943,7 +1247,9 @@ function ModalRoot() {
943
1247
  return React.createElement(
944
1248
  "div",
945
1249
  { "data-appfunnel-modal-root": "" },
946
- ...toRender.map((t) => React.createElement(t.comp, { key: t.id, id: t.id, ...t.props }))
1250
+ ...toRender.map(
1251
+ (t) => React.createElement(t.comp, { key: t.id, id: t.id, ...t.props })
1252
+ )
947
1253
  );
948
1254
  }
949
1255
  function ModalRuntime({ children }) {
@@ -1071,12 +1377,20 @@ function Modal(props) {
1071
1377
 
1072
1378
  // src/commerce/checkout.tsx
1073
1379
  function createMockDriver(provider = "stripe", options = {}) {
1074
- const success = options.result ?? { ok: true, amountMinor: 0, currency: "USD" };
1075
- const offError = typeof options.failOffSession === "object" ? options.failOffSession : chunkLJYLGLFS_cjs.checkoutError("authentication_required", "off-session charge declined (mock)");
1380
+ const success = options.result ?? {
1381
+ ok: true,
1382
+ amountMinor: 0,
1383
+ currency: "USD"
1384
+ };
1385
+ const offError = typeof options.failOffSession === "object" ? options.failOffSession : chunkQMAZGLGV_cjs.checkoutError(
1386
+ "authentication_required",
1387
+ "off-session charge declined (mock)"
1388
+ );
1076
1389
  return {
1077
1390
  provider,
1078
1391
  start: (req) => {
1079
- if (options.failOffSession && !req.surface) return Promise.resolve({ ok: false, error: offError });
1392
+ if (options.failOffSession && !req.surface)
1393
+ return Promise.resolve({ ok: false, error: offError });
1080
1394
  return Promise.resolve(success);
1081
1395
  },
1082
1396
  renderInline: (req, cb) => React.createElement(
@@ -1087,7 +1401,13 @@ function createMockDriver(provider = "stripe", options = {}) {
1087
1401
  onClick: () => {
1088
1402
  cb.onStart?.();
1089
1403
  if (success.ok) cb.onSuccess(success);
1090
- else cb.onError(success.error ?? chunkLJYLGLFS_cjs.checkoutError("processing_error", "failed (mock)"));
1404
+ else
1405
+ cb.onError(
1406
+ success.error ?? chunkQMAZGLGV_cjs.checkoutError(
1407
+ "processing_error",
1408
+ "failed (mock)"
1409
+ )
1410
+ );
1091
1411
  }
1092
1412
  },
1093
1413
  `Pay (mock ${req.surface})`
@@ -1145,11 +1465,11 @@ function useCheckout(opts = {}) {
1145
1465
  [catalog, kind]
1146
1466
  );
1147
1467
  const recovering = React.useRef(false);
1148
- const failRef = React.useRef(() => {
1149
- });
1150
- const attemptRef = React.useRef(
1151
- async () => ({ ok: false })
1468
+ const failRef = React.useRef(
1469
+ () => {
1470
+ }
1152
1471
  );
1472
+ const attemptRef = React.useRef(async () => ({ ok: false }));
1153
1473
  const succeed = React.useCallback(
1154
1474
  (result, product) => {
1155
1475
  setStatus("success");
@@ -1163,7 +1483,12 @@ function useCheckout(opts = {}) {
1163
1483
  // server-minted (browser↔CAPI dedup); undefined if none
1164
1484
  });
1165
1485
  if (isSubscription) {
1166
- tracker.track("subscription.created", { amount, currency: result.currency, productId, eventId: result.eventId });
1486
+ tracker.track("subscription.created", {
1487
+ amount,
1488
+ currency: result.currency,
1489
+ productId,
1490
+ eventId: result.eventId
1491
+ });
1167
1492
  }
1168
1493
  opts.onSuccess?.(result);
1169
1494
  if (advance) nav.next();
@@ -1177,18 +1502,29 @@ function useCheckout(opts = {}) {
1177
1502
  setStatus("loading");
1178
1503
  setError(null);
1179
1504
  const { productId, amount, currency } = productFacts(product);
1180
- tracker.track("checkout.start", { productId, amount, currency, surface, eventId: newEventId() });
1505
+ tracker.track("checkout.start", {
1506
+ productId,
1507
+ amount,
1508
+ currency,
1509
+ surface,
1510
+ eventId: newEventId()
1511
+ });
1181
1512
  },
1182
1513
  [tracker, productFacts]
1183
1514
  );
1184
1515
  const paymentAdded = React.useCallback(
1185
1516
  (product) => {
1186
1517
  const { productId, amount, currency } = productFacts(product);
1187
- tracker.track("checkout.payment_added", { productId, amount, currency, eventId: newEventId() });
1518
+ tracker.track("checkout.payment_added", {
1519
+ productId,
1520
+ amount,
1521
+ currency,
1522
+ eventId: newEventId()
1523
+ });
1188
1524
  },
1189
1525
  [tracker, productFacts]
1190
1526
  );
1191
- const hostInSheet = (surface) => surface === "sheet" || chunkLJYLGLFS_cjs.isInlineSurface(surface) && !!chunkLJYLGLFS_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet;
1527
+ const hostInSheet = (surface) => surface === "sheet" || chunkQMAZGLGV_cjs.isInlineSurface(surface) && !!chunkQMAZGLGV_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet;
1192
1528
  const startRecovery = (product, surface) => {
1193
1529
  recovering.current = true;
1194
1530
  if (hostInSheet(surface)) {
@@ -1207,7 +1543,9 @@ function useCheckout(opts = {}) {
1207
1543
  }
1208
1544
  };
1209
1545
  failRef.current = (err, product) => {
1210
- setStatus(err.category === "authentication_required" ? "requires_action" : "error");
1546
+ setStatus(
1547
+ err.category === "authentication_required" ? "requires_action" : "error"
1548
+ );
1211
1549
  setError(err);
1212
1550
  tracker.track("checkout.failed", {
1213
1551
  category: err.category,
@@ -1238,35 +1576,59 @@ function useCheckout(opts = {}) {
1238
1576
  if (!product) return;
1239
1577
  recovering.current = true;
1240
1578
  if (!surface) {
1241
- if (intent === "upsell") void attemptRef.current(product);
1242
- else startRecovery(product, chunkLJYLGLFS_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet ? "sheet" : "popup");
1579
+ if (intent === "upsell")
1580
+ void attemptRef.current(product);
1581
+ else
1582
+ startRecovery(
1583
+ product,
1584
+ chunkQMAZGLGV_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet ? "sheet" : "popup"
1585
+ );
1243
1586
  return;
1244
1587
  }
1245
1588
  startRecovery(product, surface);
1246
1589
  }
1247
1590
  };
1248
- void showModal(route.modal, props);
1591
+ void showModal(
1592
+ route.modal,
1593
+ props
1594
+ );
1249
1595
  } else if ("fallback" in route) {
1250
- if (product && !recovering.current) startRecovery(product, route.fallback);
1596
+ if (product && !recovering.current)
1597
+ startRecovery(product, route.fallback);
1251
1598
  }
1252
1599
  };
1253
1600
  attemptRef.current = async (product, surface) => {
1254
1601
  begin(product, surface);
1255
1602
  try {
1256
- const result = await driver.start({ product, intent, surface, kind });
1603
+ const result = await driver.start({
1604
+ product,
1605
+ intent,
1606
+ surface,
1607
+ kind
1608
+ });
1257
1609
  if (result.ok) succeedRef.current(result, product);
1258
- else failRef.current(result.error ?? chunkLJYLGLFS_cjs.checkoutError("unknown", "Checkout failed"), product);
1610
+ else
1611
+ failRef.current(
1612
+ result.error ?? chunkQMAZGLGV_cjs.checkoutError("unknown", "Checkout failed"),
1613
+ product
1614
+ );
1259
1615
  return result;
1260
1616
  } catch (e) {
1261
- const err = chunkLJYLGLFS_cjs.checkoutError("processing_error", e instanceof Error ? e.message : "Checkout failed");
1617
+ const err = chunkQMAZGLGV_cjs.checkoutError(
1618
+ "processing_error",
1619
+ e instanceof Error ? e.message : "Checkout failed"
1620
+ );
1262
1621
  failRef.current(err, product);
1263
1622
  return { ok: false, error: err };
1264
1623
  }
1265
1624
  };
1266
- const open = React.useCallback(async (product, surface) => {
1267
- recovering.current = false;
1268
- return attemptRef.current(product, surface);
1269
- }, []);
1625
+ const open = React.useCallback(
1626
+ async (product, surface) => {
1627
+ recovering.current = false;
1628
+ return attemptRef.current(product, surface);
1629
+ },
1630
+ []
1631
+ );
1270
1632
  const callbacksFor = React.useCallback(
1271
1633
  (product, surface) => ({
1272
1634
  onStart: () => {
@@ -1284,7 +1646,14 @@ function useCheckout(opts = {}) {
1284
1646
  setStatus("idle");
1285
1647
  setError(null);
1286
1648
  }, []);
1287
- return { open, status, error, isLoading: status === "loading", reset, callbacksFor };
1649
+ return {
1650
+ open,
1651
+ status,
1652
+ error,
1653
+ isLoading: status === "loading",
1654
+ reset,
1655
+ callbacksFor
1656
+ };
1288
1657
  }
1289
1658
  var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, onError, onPaymentAdded }) => {
1290
1659
  const driver = useDriver();
@@ -1297,7 +1666,10 @@ var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, on
1297
1666
  if (!hostable) {
1298
1667
  resolved.current = true;
1299
1668
  onErrorRef.current(
1300
- chunkLJYLGLFS_cjs.checkoutError("processing_error", "This checkout driver has no inline surface to host in the sheet")
1669
+ chunkQMAZGLGV_cjs.checkoutError(
1670
+ "processing_error",
1671
+ "This checkout driver has no inline surface to host in the sheet"
1672
+ )
1301
1673
  );
1302
1674
  void modal.hide();
1303
1675
  modal.remove();
@@ -1305,7 +1677,12 @@ var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, on
1305
1677
  }
1306
1678
  return () => {
1307
1679
  if (!resolved.current) {
1308
- onErrorRef.current(chunkLJYLGLFS_cjs.checkoutError("canceled", "Checkout was dismissed before completing"));
1680
+ onErrorRef.current(
1681
+ chunkQMAZGLGV_cjs.checkoutError(
1682
+ "canceled",
1683
+ "Checkout was dismissed before completing"
1684
+ )
1685
+ );
1309
1686
  }
1310
1687
  };
1311
1688
  }, []);
@@ -1332,29 +1709,42 @@ var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, on
1332
1709
  )
1333
1710
  });
1334
1711
  });
1335
- function trigger(onClick, { asChild, children, className, disabled }) {
1712
+ function trigger(onClick, {
1713
+ asChild,
1714
+ children,
1715
+ className,
1716
+ disabled
1717
+ }) {
1336
1718
  if (asChild && React.isValidElement(children)) {
1337
1719
  const child = children;
1338
1720
  return React.cloneElement(child, { onClick });
1339
1721
  }
1340
- return React.createElement("button", { type: "button", className, disabled, onClick }, children ?? "Continue");
1722
+ return React.createElement(
1723
+ "button",
1724
+ { type: "button", className, disabled, onClick },
1725
+ children ?? "Continue"
1726
+ );
1341
1727
  }
1342
1728
  function Checkout({
1343
- product,
1729
+ offering,
1344
1730
  surface,
1345
1731
  asChild,
1346
1732
  children,
1347
1733
  className,
1348
1734
  ...options
1349
1735
  }) {
1736
+ const product = offering;
1350
1737
  const driver = useDriver();
1351
1738
  const checkout = useCheckout(options);
1352
1739
  const intent = options.intent ?? "purchase";
1353
- if (chunkLJYLGLFS_cjs.isInlineSurface(surface)) {
1740
+ if (chunkQMAZGLGV_cjs.isInlineSurface(surface)) {
1354
1741
  return driver.renderInline ? React.createElement(
1355
1742
  "div",
1356
1743
  { key: product, className },
1357
- driver.renderInline({ product, intent, surface }, checkout.callbacksFor(product, surface))
1744
+ driver.renderInline(
1745
+ { product, intent, surface },
1746
+ checkout.callbacksFor(product, surface)
1747
+ )
1358
1748
  ) : null;
1359
1749
  }
1360
1750
  if (surface === "sheet") {
@@ -1371,7 +1761,12 @@ function Checkout({
1371
1761
  onPaymentAdded: cb.onPaymentAdded
1372
1762
  });
1373
1763
  };
1374
- return trigger(open, { asChild, children, className, disabled: checkout.isLoading });
1764
+ return trigger(open, {
1765
+ asChild,
1766
+ children,
1767
+ className,
1768
+ disabled: checkout.isLoading
1769
+ });
1375
1770
  }
1376
1771
  return trigger(() => checkout.open(product, surface), {
1377
1772
  asChild,
@@ -1380,18 +1775,29 @@ function Checkout({
1380
1775
  disabled: checkout.isLoading
1381
1776
  });
1382
1777
  }
1383
- function Upsell({ product, fallback, asChild, children, className, ...options }) {
1778
+ function Upsell({
1779
+ offering,
1780
+ fallback,
1781
+ asChild,
1782
+ children,
1783
+ className,
1784
+ ...options
1785
+ }) {
1786
+ const product = offering;
1384
1787
  const checkout = useCheckout({
1385
1788
  ...options,
1386
1789
  intent: "upsell",
1387
1790
  recoverySurface: fallback ?? options.recoverySurface
1388
1791
  });
1389
- return trigger(() => void checkout.open(product), {
1390
- asChild,
1391
- children: children ?? "Add to my plan",
1392
- className,
1393
- disabled: checkout.isLoading
1394
- });
1792
+ return trigger(
1793
+ () => void checkout.open(product),
1794
+ {
1795
+ asChild,
1796
+ children: children ?? "Add to my plan",
1797
+ className,
1798
+ disabled: checkout.isLoading
1799
+ }
1800
+ );
1395
1801
  }
1396
1802
  var consumedResumes = /* @__PURE__ */ new Set();
1397
1803
  function CheckoutResume(options = {}) {
@@ -1409,10 +1815,19 @@ function CheckoutResume(options = {}) {
1409
1815
  const cb = callbacksRef.current();
1410
1816
  if (result.ok) {
1411
1817
  url.searchParams.delete("af_checkout");
1412
- window.history.replaceState(window.history.state, "", url.toString());
1818
+ window.history.replaceState(
1819
+ window.history.state,
1820
+ "",
1821
+ url.toString()
1822
+ );
1413
1823
  cb.onSuccess(result);
1414
1824
  } else {
1415
- cb.onError(result.error ?? chunkLJYLGLFS_cjs.checkoutError("unknown", "The checkout could not be verified"));
1825
+ cb.onError(
1826
+ result.error ?? chunkQMAZGLGV_cjs.checkoutError(
1827
+ "unknown",
1828
+ "The checkout could not be verified"
1829
+ )
1830
+ );
1416
1831
  }
1417
1832
  });
1418
1833
  }, []);
@@ -2527,7 +2942,8 @@ var Namespace = class {
2527
2942
  const out = {};
2528
2943
  const all = this.store.getState();
2529
2944
  for (const key in all) {
2530
- if (key.startsWith(this.dot)) out[key.slice(this.dot.length)] = all[key];
2945
+ if (key.startsWith(this.dot))
2946
+ out[key.slice(this.dot.length)] = all[key];
2531
2947
  }
2532
2948
  return out;
2533
2949
  }
@@ -2573,9 +2989,10 @@ var FunnelCtx = React.createContext(null);
2573
2989
  function FunnelProvider({
2574
2990
  config,
2575
2991
  sessionValues,
2992
+ deferSessionValues = false,
2576
2993
  tracker,
2577
2994
  context: contextOpts,
2578
- products,
2995
+ offerings,
2579
2996
  experiments,
2580
2997
  checkout,
2581
2998
  messages,
@@ -2588,7 +3005,12 @@ function FunnelProvider({
2588
3005
  const [value] = React.useState(() => {
2589
3006
  const s = store ?? createFunnelStore(
2590
3007
  { responses: config?.responses, data: config?.data },
2591
- sessionValues
3008
+ // FLASH-FREE HYDRATION: with deferSessionValues the initial render is seeded with
3009
+ // DEFAULTS ONLY, so the client's first render is byte-identical to the build-time
3010
+ // DEFAULT-state prerender it hydrates against (clean match). The visitor's saved
3011
+ // answers arrive as a post-hydration setMany below. Without the flag, sessionValues
3012
+ // seed the store at construction (SSR + non-prerendered paths).
3013
+ deferSessionValues ? void 0 : sessionValues
2592
3014
  );
2593
3015
  const ctx = buildContext({
2594
3016
  funnelId: config?.id,
@@ -2606,7 +3028,7 @@ function FunnelProvider({
2606
3028
  const value2 = s.get("user.email");
2607
3029
  return typeof value2 === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value2) ? value2 : void 0;
2608
3030
  };
2609
- const catalog = chunkJSRKA375_cjs.buildCatalog(products ?? []);
3031
+ const catalog = chunkBPOAWI4G_cjs.buildCatalog(offerings ?? []);
2610
3032
  return {
2611
3033
  funnel: createFunnel(s, ctx, busTracker, experiments ?? []),
2612
3034
  store: s,
@@ -2614,12 +3036,25 @@ function FunnelProvider({
2614
3036
  catalog,
2615
3037
  // Slot → catalog-key resolution wraps the email-enriching driver, so the wire charges the real
2616
3038
  // catalog key while pages + analytics keep the durable SLOT identity (req.product).
2617
- driver: driverWithCatalog(driverWithEmail(checkout ?? createMockDriver(), emailForCheckout), catalog),
3039
+ driver: driverWithCatalog(
3040
+ driverWithEmail(
3041
+ checkout ?? createMockDriver(),
3042
+ emailForCheckout
3043
+ ),
3044
+ catalog
3045
+ ),
2618
3046
  tracker: busTracker,
2619
3047
  bus
2620
3048
  };
2621
3049
  });
2622
3050
  React.useEffect(() => attachBus(value.bus), [value.bus]);
3051
+ React.useEffect(() => {
3052
+ if (!deferSessionValues || !sessionValues) return;
3053
+ const updates = {};
3054
+ for (const [key, v] of Object.entries(sessionValues))
3055
+ if (v !== void 0) updates[key] = v;
3056
+ if (Object.keys(updates).length > 0) value.store.setMany(updates);
3057
+ }, []);
2623
3058
  React.useEffect(() => {
2624
3059
  value.tracker.setAcquisition?.(buildAcquisition(value.context));
2625
3060
  }, [value]);
@@ -2648,13 +3083,18 @@ var FALLBACK_TRACKER = createConsoleTracker({ silent: true });
2648
3083
  function useFunnelCtx() {
2649
3084
  const ctx = React.useContext(FunnelCtx);
2650
3085
  if (!ctx) {
2651
- throw new Error("useFunnel/useResponse/\u2026 must be used inside <FunnelProvider>");
3086
+ throw new Error(
3087
+ "useFunnel/useResponse/\u2026 must be used inside <FunnelProvider>"
3088
+ );
2652
3089
  }
2653
3090
  return ctx;
2654
3091
  }
2655
3092
  function useFunnel() {
2656
3093
  return useFunnelCtx().funnel;
2657
3094
  }
3095
+ function useBusInternal() {
3096
+ return React.useContext(FunnelCtx)?.bus ?? null;
3097
+ }
2658
3098
  function useStoreKey(store, key) {
2659
3099
  const subscribe = React.useCallback(
2660
3100
  (cb) => store.subscribe(cb, { keys: [key] }),
@@ -2683,7 +3123,9 @@ function useField(path) {
2683
3123
  const key = path.slice(dot + 1);
2684
3124
  const { funnel, store } = useFunnelCtx();
2685
3125
  if (!(ns in NAMESPACE_PREFIX)) {
2686
- throw new Error(`useField: "${path}" is not a writable namespace (user/responses/data)`);
3126
+ throw new Error(
3127
+ `useField: "${path}" is not a writable namespace (user/responses/data)`
3128
+ );
2687
3129
  }
2688
3130
  const value = useStoreKey(store, `${NAMESPACE_PREFIX[ns]}.${key}`);
2689
3131
  const set = React.useCallback(
@@ -2713,11 +3155,160 @@ function useSystem() {
2713
3155
  function useContextValue(path) {
2714
3156
  const ctx = useContextObject();
2715
3157
  return React.useMemo(
2716
- () => path.split(".").reduce((acc, k) => acc?.[k], ctx),
3158
+ () => path.split(".").reduce(
3159
+ (acc, k) => acc?.[k],
3160
+ ctx
3161
+ ),
2717
3162
  [ctx, path]
2718
3163
  );
2719
3164
  }
2720
3165
 
3166
+ // src/tracking/snapshotPersistence.ts
3167
+ function createSnapshotPersistence(opts) {
3168
+ const baseDelay = opts.baseDelayMs ?? 1e3;
3169
+ const maxDelay = opts.maxDelayMs ?? 3e4;
3170
+ const setT = opts.timers?.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
3171
+ const clearT = opts.timers?.clearTimeout ?? ((h) => clearTimeout(h));
3172
+ const rand = opts.random ?? Math.random;
3173
+ let pending = null;
3174
+ let seq = 0;
3175
+ let pendingSeq = 0;
3176
+ let inFlight = false;
3177
+ let attempt = 0;
3178
+ let retryTimer = null;
3179
+ let disposed = false;
3180
+ let fatalSeq = -1;
3181
+ const clearRetry = () => {
3182
+ if (retryTimer !== null) {
3183
+ clearT(retryTimer);
3184
+ retryTimer = null;
3185
+ }
3186
+ };
3187
+ const buildBody = (snapshot, s) => JSON.stringify({ sessionId: opts.getSessionId(), userData: snapshot, seq: s });
3188
+ const trySend = () => {
3189
+ if (disposed || inFlight || pending === null) return;
3190
+ if (retryTimer !== null) return;
3191
+ const sessionId = opts.getSessionId();
3192
+ if (!sessionId) return;
3193
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return;
3194
+ if (pendingSeq === fatalSeq) return;
3195
+ const snapshot = pending;
3196
+ const sentSeq = pendingSeq;
3197
+ const body = buildBody(snapshot, sentSeq);
3198
+ inFlight = true;
3199
+ void fetch(opts.url, {
3200
+ method: "POST",
3201
+ headers: { "content-type": "application/json" },
3202
+ body,
3203
+ keepalive: true
3204
+ }).then((res) => {
3205
+ inFlight = false;
3206
+ if (disposed) return;
3207
+ if (res.ok) {
3208
+ attempt = 0;
3209
+ if (pendingSeq === sentSeq) pending = null;
3210
+ else trySend();
3211
+ return;
3212
+ }
3213
+ if (res.status >= 400 && res.status < 500) {
3214
+ if (process.env.NODE_ENV !== "production") {
3215
+ console.warn(
3216
+ `[appfunnel] snapshot persist rejected ${res.status} (dropping snapshot; check session/payload)`
3217
+ );
3218
+ }
3219
+ fatalSeq = sentSeq;
3220
+ if (pendingSeq === sentSeq) pending = null;
3221
+ else trySend();
3222
+ return;
3223
+ }
3224
+ scheduleRetry();
3225
+ }).catch(() => {
3226
+ inFlight = false;
3227
+ if (disposed) return;
3228
+ scheduleRetry();
3229
+ });
3230
+ };
3231
+ const scheduleRetry = () => {
3232
+ if (disposed || pending === null) return;
3233
+ const exp = Math.min(maxDelay, baseDelay * 2 ** attempt);
3234
+ const delay2 = Math.round(exp * (0.5 + rand() * 0.5));
3235
+ attempt++;
3236
+ clearRetry();
3237
+ retryTimer = setT(() => {
3238
+ retryTimer = null;
3239
+ trySend();
3240
+ }, delay2);
3241
+ };
3242
+ const push = (snapshot) => {
3243
+ if (disposed) return;
3244
+ pending = snapshot;
3245
+ pendingSeq = ++seq;
3246
+ if (fatalSeq !== -1 && pendingSeq !== fatalSeq) fatalSeq = -1;
3247
+ if (retryTimer === null) trySend();
3248
+ };
3249
+ const flushBeacon = () => {
3250
+ if (disposed || pending === null) return;
3251
+ const sessionId = opts.getSessionId();
3252
+ if (!sessionId) return;
3253
+ const body = buildBody(pending, pendingSeq);
3254
+ let delivered = false;
3255
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
3256
+ try {
3257
+ delivered = navigator.sendBeacon(
3258
+ opts.url,
3259
+ new Blob([body], { type: "application/json" })
3260
+ );
3261
+ } catch {
3262
+ delivered = false;
3263
+ }
3264
+ }
3265
+ if (!delivered) {
3266
+ try {
3267
+ void fetch(opts.url, {
3268
+ method: "POST",
3269
+ headers: { "content-type": "application/json" },
3270
+ body,
3271
+ keepalive: true
3272
+ }).catch(() => {
3273
+ });
3274
+ delivered = true;
3275
+ } catch {
3276
+ delivered = false;
3277
+ }
3278
+ }
3279
+ if (delivered) pending = null;
3280
+ };
3281
+ const onHidden = () => {
3282
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
3283
+ flushBeacon();
3284
+ }
3285
+ };
3286
+ const onPageHide = () => flushBeacon();
3287
+ const onOnline = () => trySend();
3288
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
3289
+ try {
3290
+ document.addEventListener("visibilitychange", onHidden);
3291
+ window.addEventListener("pagehide", onPageHide);
3292
+ window.addEventListener("online", onOnline);
3293
+ } catch {
3294
+ }
3295
+ }
3296
+ const dispose = () => {
3297
+ disposed = true;
3298
+ clearRetry();
3299
+ pending = null;
3300
+ if (typeof window !== "undefined" && typeof window.removeEventListener === "function") {
3301
+ try {
3302
+ document.removeEventListener("visibilitychange", onHidden);
3303
+ window.removeEventListener("pagehide", onPageHide);
3304
+ window.removeEventListener("online", onOnline);
3305
+ } catch {
3306
+ }
3307
+ }
3308
+ };
3309
+ return { push, flushBeacon, dispose };
3310
+ }
3311
+
2721
3312
  // src/tracking/platformTracker.ts
2722
3313
  function validClickIds(acq) {
2723
3314
  return Object.entries(acq.clickIds ?? {}).filter(
@@ -2823,32 +3414,35 @@ function createPlatformTracker(cfg) {
2823
3414
  }
2824
3415
  };
2825
3416
  const VARS_DEBOUNCE_MS = 800;
3417
+ const dataUrl = `${base}/campaign/${encodeURIComponent(cfg.campaignId)}/event/session/data`;
2826
3418
  let pendingVars = null;
2827
3419
  let varsTimer = null;
3420
+ const persistence = createSnapshotPersistence({
3421
+ url: dataUrl,
3422
+ getSessionId: () => sessionId
3423
+ });
2828
3424
  const flushVars = () => {
2829
3425
  if (varsTimer) {
2830
3426
  clearTimeout(varsTimer);
2831
3427
  varsTimer = null;
2832
3428
  }
2833
3429
  if (!pendingVars) return;
2834
- const doPost = () => {
2835
- if (!pendingVars || !sessionId) return;
2836
- const userData = pendingVars;
2837
- pendingVars = null;
2838
- void fetch(`${base}/campaign/${encodeURIComponent(cfg.campaignId)}/event/session/data`, {
2839
- method: "POST",
2840
- headers: { "content-type": "application/json" },
2841
- body: JSON.stringify({ sessionId, userData }),
2842
- keepalive: true
2843
- }).catch((e) => {
2844
- if (process.env.NODE_ENV !== "production") console.warn("[appfunnel] variable persist failed:", e);
2845
- });
2846
- };
2847
- if (sessionId) doPost();
2848
- else chain = chain.then(doPost);
3430
+ const snapshot = pendingVars;
3431
+ pendingVars = null;
3432
+ persistence.push(snapshot);
3433
+ if (!sessionId) chain = chain.then(() => {
3434
+ persistence.push(snapshot);
3435
+ });
3436
+ };
3437
+ const drainOnExit = () => {
3438
+ if (pendingVars) {
3439
+ flushVars();
3440
+ persistence.flushBeacon();
3441
+ }
2849
3442
  };
2850
3443
  try {
2851
- window.addEventListener("pagehide", flushVars);
3444
+ window.addEventListener("visibilitychange", drainOnExit);
3445
+ window.addEventListener("pagehide", drainOnExit);
2852
3446
  } catch {
2853
3447
  }
2854
3448
  if (cfg.experiment?.id && cfg.experiment.variant) {
@@ -2891,6 +3485,7 @@ function createFunnelTree({
2891
3485
  }) {
2892
3486
  return function tree(opts) {
2893
3487
  const mode = opts.mode ?? "live";
3488
+ const embedEnabled = opts.embed ?? false;
2894
3489
  const tracker = opts.tracking ? createPlatformTracker({
2895
3490
  ...opts.tracking,
2896
3491
  visitorId: opts.visitorId,
@@ -2912,6 +3507,7 @@ function createFunnelTree({
2912
3507
  React.Fragment,
2913
3508
  null,
2914
3509
  React.createElement(CheckoutResume, null),
3510
+ embedEnabled ? React.createElement(EmbedBridge, null) : null,
2915
3511
  layout ? React.createElement(layout, null, children) : children
2916
3512
  );
2917
3513
  };
@@ -2919,12 +3515,13 @@ function createFunnelTree({
2919
3515
  assets: opts.assets,
2920
3516
  children: React.createElement(FunnelProvider, {
2921
3517
  config,
2922
- products: opts.products ?? [],
3518
+ offerings: opts.offerings ?? [],
2923
3519
  messages: opts.messages ?? messages,
2924
3520
  locale: opts.locale,
2925
3521
  tracker,
2926
3522
  checkout,
2927
3523
  sessionValues: opts.sessionValues,
3524
+ deferSessionValues: opts.deferSessionValues,
2928
3525
  experiments: opts.experiments,
2929
3526
  context: {
2930
3527
  mode,
@@ -2936,6 +3533,13 @@ function createFunnelTree({
2936
3533
  pages,
2937
3534
  layout: layoutWithResume,
2938
3535
  initialKey: opts.initialKey,
3536
+ // FLASH-FREE HYDRATION (Option B, FIX 1): trust the SERVER-resolved initialKey instead of
3537
+ // re-running the entry guard. The CLIENT sets this via deferSessionValues (its deferred
3538
+ // snapshot is empty, so re-running the guard would bounce a qualified deep-link and
3539
+ // mismatch the markup). The build-time PRERENDER sets `trustInitialKey` explicitly so a
3540
+ // guarded page prerenders its OWN default-state markup (the renderer picks target-vs-bounce
3541
+ // at serve time). Live SSR fallback leaves both off → the guard runs against real values.
3542
+ trustInitialKey: opts.trustInitialKey ?? opts.deferSessionValues,
2939
3543
  prefetch: "off"
2940
3544
  // renderer background-modulepreloads all chunks
2941
3545
  })
@@ -3084,147 +3688,143 @@ function Script({
3084
3688
 
3085
3689
  Object.defineProperty(exports, "assignVariant", {
3086
3690
  enumerable: true,
3087
- get: function () { return chunkJSRKA375_cjs.assignVariant; }
3691
+ get: function () { return chunkBPOAWI4G_cjs.assignVariant; }
3088
3692
  });
3089
3693
  Object.defineProperty(exports, "bucketingSeed", {
3090
3694
  enumerable: true,
3091
- get: function () { return chunkJSRKA375_cjs.bucketingSeed; }
3695
+ get: function () { return chunkBPOAWI4G_cjs.bucketingSeed; }
3092
3696
  });
3093
3697
  Object.defineProperty(exports, "buildCatalog", {
3094
3698
  enumerable: true,
3095
- get: function () { return chunkJSRKA375_cjs.buildCatalog; }
3699
+ get: function () { return chunkBPOAWI4G_cjs.buildCatalog; }
3096
3700
  });
3097
3701
  Object.defineProperty(exports, "compileManifest", {
3098
3702
  enumerable: true,
3099
- get: function () { return chunkJSRKA375_cjs.compileManifest; }
3703
+ get: function () { return chunkBPOAWI4G_cjs.compileManifest; }
3100
3704
  });
3101
3705
  Object.defineProperty(exports, "currencyExponent", {
3102
3706
  enumerable: true,
3103
- get: function () { return chunkJSRKA375_cjs.currencyExponent; }
3707
+ get: function () { return chunkBPOAWI4G_cjs.currencyExponent; }
3104
3708
  });
3105
3709
  Object.defineProperty(exports, "defineFunnel", {
3106
3710
  enumerable: true,
3107
- get: function () { return chunkJSRKA375_cjs.defineFunnel; }
3711
+ get: function () { return chunkBPOAWI4G_cjs.defineFunnel; }
3108
3712
  });
3109
3713
  Object.defineProperty(exports, "definePage", {
3110
3714
  enumerable: true,
3111
- get: function () { return chunkJSRKA375_cjs.definePage; }
3715
+ get: function () { return chunkBPOAWI4G_cjs.definePage; }
3112
3716
  });
3113
3717
  Object.defineProperty(exports, "entryGuard", {
3114
3718
  enumerable: true,
3115
- get: function () { return chunkJSRKA375_cjs.entryGuard; }
3719
+ get: function () { return chunkBPOAWI4G_cjs.entryGuard; }
3116
3720
  });
3117
3721
  Object.defineProperty(exports, "evaluateCondition", {
3118
3722
  enumerable: true,
3119
- get: function () { return chunkJSRKA375_cjs.evaluateCondition; }
3723
+ get: function () { return chunkBPOAWI4G_cjs.evaluateCondition; }
3120
3724
  });
3121
3725
  Object.defineProperty(exports, "evaluateGate", {
3122
3726
  enumerable: true,
3123
- get: function () { return chunkJSRKA375_cjs.evaluateGate; }
3727
+ get: function () { return chunkBPOAWI4G_cjs.evaluateGate; }
3124
3728
  });
3125
3729
  Object.defineProperty(exports, "expectedPathLength", {
3126
3730
  enumerable: true,
3127
- get: function () { return chunkJSRKA375_cjs.expectedPathLength; }
3731
+ get: function () { return chunkBPOAWI4G_cjs.expectedPathLength; }
3128
3732
  });
3129
3733
  Object.defineProperty(exports, "fnv1a", {
3130
3734
  enumerable: true,
3131
- get: function () { return chunkJSRKA375_cjs.fnv1a; }
3735
+ get: function () { return chunkBPOAWI4G_cjs.fnv1a; }
3132
3736
  });
3133
3737
  Object.defineProperty(exports, "formatMoney", {
3134
3738
  enumerable: true,
3135
- get: function () { return chunkJSRKA375_cjs.formatMoney; }
3739
+ get: function () { return chunkBPOAWI4G_cjs.formatMoney; }
3136
3740
  });
3137
- Object.defineProperty(exports, "formatProduct", {
3741
+ Object.defineProperty(exports, "formatOffering", {
3138
3742
  enumerable: true,
3139
- get: function () { return chunkJSRKA375_cjs.formatProduct; }
3743
+ get: function () { return chunkBPOAWI4G_cjs.formatOffering; }
3140
3744
  });
3141
3745
  Object.defineProperty(exports, "hashToUnit", {
3142
3746
  enumerable: true,
3143
- get: function () { return chunkJSRKA375_cjs.hashToUnit; }
3747
+ get: function () { return chunkBPOAWI4G_cjs.hashToUnit; }
3144
3748
  });
3145
3749
  Object.defineProperty(exports, "isRtl", {
3146
3750
  enumerable: true,
3147
- get: function () { return chunkJSRKA375_cjs.isRtl; }
3751
+ get: function () { return chunkBPOAWI4G_cjs.isRtl; }
3148
3752
  });
3149
3753
  Object.defineProperty(exports, "isVariantKey", {
3150
3754
  enumerable: true,
3151
- get: function () { return chunkJSRKA375_cjs.isVariantKey; }
3755
+ get: function () { return chunkBPOAWI4G_cjs.isVariantKey; }
3152
3756
  });
3153
3757
  Object.defineProperty(exports, "nextPage", {
3154
3758
  enumerable: true,
3155
- get: function () { return chunkJSRKA375_cjs.nextPage; }
3759
+ get: function () { return chunkBPOAWI4G_cjs.nextPage; }
3156
3760
  });
3157
3761
  Object.defineProperty(exports, "outgoingKeys", {
3158
3762
  enumerable: true,
3159
- get: function () { return chunkJSRKA375_cjs.outgoingKeys; }
3763
+ get: function () { return chunkBPOAWI4G_cjs.outgoingKeys; }
3160
3764
  });
3161
3765
  Object.defineProperty(exports, "pageMeta", {
3162
3766
  enumerable: true,
3163
- get: function () { return chunkJSRKA375_cjs.pageMeta; }
3767
+ get: function () { return chunkBPOAWI4G_cjs.pageMeta; }
3164
3768
  });
3165
3769
  Object.defineProperty(exports, "parseSlotKey", {
3166
3770
  enumerable: true,
3167
- get: function () { return chunkJSRKA375_cjs.parseSlotKey; }
3771
+ get: function () { return chunkBPOAWI4G_cjs.parseSlotKey; }
3168
3772
  });
3169
3773
  Object.defineProperty(exports, "pickByWeight", {
3170
3774
  enumerable: true,
3171
- get: function () { return chunkJSRKA375_cjs.pickByWeight; }
3775
+ get: function () { return chunkBPOAWI4G_cjs.pickByWeight; }
3172
3776
  });
3173
3777
  Object.defineProperty(exports, "resolveExperiments", {
3174
3778
  enumerable: true,
3175
- get: function () { return chunkJSRKA375_cjs.resolveExperiments; }
3779
+ get: function () { return chunkBPOAWI4G_cjs.resolveExperiments; }
3176
3780
  });
3177
3781
  Object.defineProperty(exports, "resolveLocale", {
3178
3782
  enumerable: true,
3179
- get: function () { return chunkJSRKA375_cjs.resolveLocale; }
3783
+ get: function () { return chunkBPOAWI4G_cjs.resolveLocale; }
3180
3784
  });
3181
- Object.defineProperty(exports, "resolveProduct", {
3785
+ Object.defineProperty(exports, "resolveOffering", {
3182
3786
  enumerable: true,
3183
- get: function () { return chunkJSRKA375_cjs.resolveProduct; }
3787
+ get: function () { return chunkBPOAWI4G_cjs.resolveOffering; }
3184
3788
  });
3185
3789
  Object.defineProperty(exports, "resolveRoute", {
3186
3790
  enumerable: true,
3187
- get: function () { return chunkJSRKA375_cjs.resolveRoute; }
3188
- });
3189
- Object.defineProperty(exports, "validateExperiments", {
3190
- enumerable: true,
3191
- get: function () { return chunkJSRKA375_cjs.validateExperiments; }
3791
+ get: function () { return chunkBPOAWI4G_cjs.resolveRoute; }
3192
3792
  });
3193
3793
  Object.defineProperty(exports, "INLINE_SURFACES", {
3194
3794
  enumerable: true,
3195
- get: function () { return chunkLJYLGLFS_cjs.INLINE_SURFACES; }
3795
+ get: function () { return chunkQMAZGLGV_cjs.INLINE_SURFACES; }
3196
3796
  });
3197
3797
  Object.defineProperty(exports, "PROVIDER_PROFILES", {
3198
3798
  enumerable: true,
3199
- get: function () { return chunkLJYLGLFS_cjs.PROVIDER_PROFILES; }
3799
+ get: function () { return chunkQMAZGLGV_cjs.PROVIDER_PROFILES; }
3200
3800
  });
3201
3801
  Object.defineProperty(exports, "checkoutError", {
3202
3802
  enumerable: true,
3203
- get: function () { return chunkLJYLGLFS_cjs.checkoutError; }
3803
+ get: function () { return chunkQMAZGLGV_cjs.checkoutError; }
3204
3804
  });
3205
3805
  Object.defineProperty(exports, "isInlineSurface", {
3206
3806
  enumerable: true,
3207
- get: function () { return chunkLJYLGLFS_cjs.isInlineSurface; }
3807
+ get: function () { return chunkQMAZGLGV_cjs.isInlineSurface; }
3208
3808
  });
3209
3809
  Object.defineProperty(exports, "isMerchantOfRecord", {
3210
3810
  enumerable: true,
3211
- get: function () { return chunkLJYLGLFS_cjs.isMerchantOfRecord; }
3811
+ get: function () { return chunkQMAZGLGV_cjs.isMerchantOfRecord; }
3212
3812
  });
3213
3813
  Object.defineProperty(exports, "isOrchestrator", {
3214
3814
  enumerable: true,
3215
- get: function () { return chunkLJYLGLFS_cjs.isOrchestrator; }
3815
+ get: function () { return chunkQMAZGLGV_cjs.isOrchestrator; }
3216
3816
  });
3217
3817
  Object.defineProperty(exports, "surfacesFor", {
3218
3818
  enumerable: true,
3219
- get: function () { return chunkLJYLGLFS_cjs.surfacesFor; }
3819
+ get: function () { return chunkQMAZGLGV_cjs.surfacesFor; }
3220
3820
  });
3221
3821
  Object.defineProperty(exports, "validateCheckout", {
3222
3822
  enumerable: true,
3223
- get: function () { return chunkLJYLGLFS_cjs.validateCheckout; }
3823
+ get: function () { return chunkQMAZGLGV_cjs.validateCheckout; }
3224
3824
  });
3225
3825
  Object.defineProperty(exports, "validateUpsell", {
3226
3826
  enumerable: true,
3227
- get: function () { return chunkLJYLGLFS_cjs.validateUpsell; }
3827
+ get: function () { return chunkQMAZGLGV_cjs.validateUpsell; }
3228
3828
  });
3229
3829
  exports.AssetsContext = AssetsContext;
3230
3830
  exports.AssetsProvider = AssetsProvider;
@@ -3277,9 +3877,9 @@ exports.useFunnel = useFunnel;
3277
3877
  exports.useLocale = useLocale;
3278
3878
  exports.useModal = useModal;
3279
3879
  exports.useNavigation = useNavigation;
3880
+ exports.useOffering = useOffering;
3881
+ exports.useOfferings = useOfferings;
3280
3882
  exports.usePage = usePage;
3281
- exports.useProduct = useProduct;
3282
- exports.useProducts = useProducts;
3283
3883
  exports.useResponse = useResponse;
3284
3884
  exports.useSystem = useSystem;
3285
3885
  exports.useTracker = useTracker;