@appfunnel-dev/sdk 2.0.0-canary.3 → 2.0.0-canary.5
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/dist/{chunk-AKO6XKXP.js → chunk-AXXG3ODP.js} +10 -3
- package/dist/chunk-AXXG3ODP.js.map +1 -0
- package/dist/{chunk-M6U3FNRW.js → chunk-CFFMZYPE.js} +3 -3
- package/dist/{chunk-M6U3FNRW.js.map → chunk-CFFMZYPE.js.map} +1 -1
- package/dist/chunk-EMMSS5I5.cjs +37 -0
- package/dist/chunk-EMMSS5I5.cjs.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/{chunk-JSRKA375.cjs → chunk-G44BUG6G.cjs} +10 -3
- package/dist/chunk-G44BUG6G.cjs.map +1 -0
- package/dist/{chunk-LJYLGLFS.cjs → chunk-QMAZGLGV.cjs} +2 -35
- package/dist/chunk-QMAZGLGV.cjs.map +1 -0
- package/dist/{chunk-YY375F2B.js → chunk-TNLRQPVY.js} +3 -33
- package/dist/chunk-TNLRQPVY.js.map +1 -0
- package/dist/{chunk-CY4VBSMX.cjs → chunk-VAOR77NW.cjs} +7 -7
- package/dist/{chunk-CY4VBSMX.cjs.map → chunk-VAOR77NW.cjs.map} +1 -1
- package/dist/chunk-WYUDL4FI.cjs +8 -0
- package/dist/chunk-WYUDL4FI.cjs.map +1 -0
- package/dist/chunk-ZZJG4EYL.js +6 -0
- package/dist/chunk-ZZJG4EYL.js.map +1 -0
- package/dist/driver-paddle.cjs +23 -22
- package/dist/driver-paddle.cjs.map +1 -1
- package/dist/driver-paddle.js +3 -2
- package/dist/driver-paddle.js.map +1 -1
- package/dist/driver-stripe.cjs +37 -36
- package/dist/driver-stripe.cjs.map +1 -1
- package/dist/driver-stripe.js +3 -2
- package/dist/driver-stripe.js.map +1 -1
- package/dist/index.cjs +541 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -75
- package/dist/index.d.ts +151 -75
- package/dist/index.js +484 -34
- package/dist/index.js.map +1 -1
- package/dist/{manifest-Cr2y1op6.d.cts → manifest-Dl7faaKe.d.cts} +34 -0
- package/dist/{manifest-Cr2y1op6.d.ts → manifest-Dl7faaKe.d.ts} +34 -0
- package/dist/manifest-entry.cjs +45 -30
- package/dist/manifest-entry.cjs.map +1 -1
- package/dist/manifest-entry.d.cts +1 -1
- package/dist/manifest-entry.d.ts +1 -1
- package/dist/manifest-entry.js +10 -3
- package/dist/manifest-entry.js.map +1 -1
- package/dist/protocol.cjs +13 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +182 -0
- package/dist/protocol.d.ts +182 -0
- package/dist/protocol.js +4 -0
- package/dist/protocol.js.map +1 -0
- package/package.json +14 -2
- package/dist/chunk-AKO6XKXP.js.map +0 -1
- package/dist/chunk-JSRKA375.cjs.map +0 -1
- package/dist/chunk-LJYLGLFS.cjs.map +0 -1
- 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
|
|
4
|
-
var
|
|
3
|
+
var chunkG44BUG6G_cjs = require('./chunk-G44BUG6G.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,27 @@ function createBus(accessors, now2 = () => Date.now()) {
|
|
|
385
387
|
}
|
|
386
388
|
}
|
|
387
389
|
};
|
|
388
|
-
|
|
390
|
+
let nav = null;
|
|
391
|
+
let warnedNoNav = false;
|
|
392
|
+
const currentNav = () => {
|
|
393
|
+
if (!nav && !warnedNoNav) {
|
|
394
|
+
warnedNoNav = true;
|
|
395
|
+
console.warn("[appfunnel] window.appfunnel navigation called before the funnel flow mounted \u2014 ignored");
|
|
396
|
+
}
|
|
397
|
+
return nav;
|
|
398
|
+
};
|
|
399
|
+
const registerNavigation = (n) => {
|
|
400
|
+
nav = n;
|
|
401
|
+
warnedNoNav = false;
|
|
402
|
+
return () => {
|
|
403
|
+
if (nav === n) nav = null;
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
const goTo = (pageKey) => currentNav()?.goTo(pageKey) ?? false;
|
|
407
|
+
const goNext = () => currentNav()?.goNext();
|
|
408
|
+
const goPrevious = () => currentNav()?.goPrevious();
|
|
409
|
+
const navigation = () => nav?.snapshot() ?? null;
|
|
410
|
+
return { on, off, events, emit, registerNavigation, goTo, goNext, goPrevious, navigation, ...accessors };
|
|
389
411
|
}
|
|
390
412
|
function withBus(tracker, bus) {
|
|
391
413
|
return {
|
|
@@ -407,7 +429,19 @@ function withBus(tracker, bus) {
|
|
|
407
429
|
function attachBus(bus) {
|
|
408
430
|
if (typeof window === "undefined") return () => {
|
|
409
431
|
};
|
|
410
|
-
const {
|
|
432
|
+
const {
|
|
433
|
+
on,
|
|
434
|
+
off,
|
|
435
|
+
getVariable,
|
|
436
|
+
getVariables,
|
|
437
|
+
getCurrentPageId,
|
|
438
|
+
getCustomerId,
|
|
439
|
+
getVisitorId,
|
|
440
|
+
goTo,
|
|
441
|
+
goNext,
|
|
442
|
+
goPrevious,
|
|
443
|
+
navigation
|
|
444
|
+
} = bus;
|
|
411
445
|
const api = {
|
|
412
446
|
on,
|
|
413
447
|
off,
|
|
@@ -416,6 +450,11 @@ function attachBus(bus) {
|
|
|
416
450
|
getCurrentPageId,
|
|
417
451
|
getCustomerId,
|
|
418
452
|
getVisitorId,
|
|
453
|
+
// Flow control (bound closures over the bus's live nav registration).
|
|
454
|
+
goTo,
|
|
455
|
+
goNext,
|
|
456
|
+
goPrevious,
|
|
457
|
+
navigation,
|
|
419
458
|
get events() {
|
|
420
459
|
return bus.events;
|
|
421
460
|
}
|
|
@@ -452,9 +491,9 @@ function LocaleProvider({
|
|
|
452
491
|
override,
|
|
453
492
|
children
|
|
454
493
|
}) {
|
|
455
|
-
const [locale, setLocale] = React.useState(() =>
|
|
494
|
+
const [locale, setLocale] = React.useState(() => chunkG44BUG6G_cjs.resolveLocale(config, detected, override));
|
|
456
495
|
React.useEffect(() => {
|
|
457
|
-
setLocale(
|
|
496
|
+
setLocale(chunkG44BUG6G_cjs.resolveLocale(config, detected, override));
|
|
458
497
|
}, [config, detected, override]);
|
|
459
498
|
const value = React.useMemo(
|
|
460
499
|
() => ({ locale, setLocale, config, catalog }),
|
|
@@ -526,7 +565,7 @@ function useTranslation() {
|
|
|
526
565
|
fmt,
|
|
527
566
|
locale,
|
|
528
567
|
setLocale,
|
|
529
|
-
dir:
|
|
568
|
+
dir: chunkG44BUG6G_cjs.isRtl(locale) ? "rtl" : "ltr",
|
|
530
569
|
locales: config?.supported ?? [locale]
|
|
531
570
|
};
|
|
532
571
|
}
|
|
@@ -547,16 +586,112 @@ function useProduct(id) {
|
|
|
547
586
|
const catalog = useCatalog();
|
|
548
587
|
const locale = useActiveLocale();
|
|
549
588
|
const resolved = id ? catalog.get(id) : void 0;
|
|
550
|
-
return React.useMemo(() => resolved ?
|
|
589
|
+
return React.useMemo(() => resolved ? chunkG44BUG6G_cjs.formatProduct(resolved, locale) : void 0, [resolved, locale]);
|
|
551
590
|
}
|
|
552
591
|
function useProducts() {
|
|
553
592
|
const catalog = useCatalog();
|
|
554
593
|
const locale = useActiveLocale();
|
|
555
594
|
return React.useMemo(
|
|
556
|
-
() => Array.from(catalog.values()).map((r) =>
|
|
595
|
+
() => Array.from(catalog.values()).map((r) => chunkG44BUG6G_cjs.formatProduct(r, locale)),
|
|
557
596
|
[catalog, locale]
|
|
558
597
|
);
|
|
559
598
|
}
|
|
599
|
+
|
|
600
|
+
// src/flow/screenshot.ts
|
|
601
|
+
var MIME = { png: "image/png", jpeg: "image/jpeg", webp: "image/webp" };
|
|
602
|
+
var ScreenshotError = class extends Error {
|
|
603
|
+
constructor(code, message) {
|
|
604
|
+
super(message);
|
|
605
|
+
this.code = code;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
function resolveTarget(t) {
|
|
609
|
+
if (!t || t.kind === "page") return document.getElementById("root") ?? document.body;
|
|
610
|
+
if (t.kind === "viewport") return document.body;
|
|
611
|
+
return document.querySelector(t.selector);
|
|
612
|
+
}
|
|
613
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
614
|
+
function hideAll(selectors) {
|
|
615
|
+
const touched = [];
|
|
616
|
+
for (const sel of selectors) {
|
|
617
|
+
document.querySelectorAll(sel).forEach((el) => {
|
|
618
|
+
touched.push([el, el.style.visibility]);
|
|
619
|
+
el.style.visibility = "hidden";
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return () => touched.forEach(([el, v]) => el.style.visibility = v);
|
|
623
|
+
}
|
|
624
|
+
async function scrollThrough(el) {
|
|
625
|
+
const prev = el.scrollTop;
|
|
626
|
+
const step = el.clientHeight || window.innerHeight;
|
|
627
|
+
for (let y = 0; y < el.scrollHeight; y += step) {
|
|
628
|
+
el.scrollTop = y;
|
|
629
|
+
await delay(16);
|
|
630
|
+
}
|
|
631
|
+
el.scrollTop = prev;
|
|
632
|
+
await delay(16);
|
|
633
|
+
}
|
|
634
|
+
function blobToDataURL(blob) {
|
|
635
|
+
return new Promise((resolve, reject) => {
|
|
636
|
+
const fr = new FileReader();
|
|
637
|
+
fr.onload = () => resolve(String(fr.result));
|
|
638
|
+
fr.onerror = () => reject(fr.error);
|
|
639
|
+
fr.readAsDataURL(blob);
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
async function captureScreenshot(params = {}) {
|
|
643
|
+
const el = resolveTarget(params.target);
|
|
644
|
+
if (!el) throw new ScreenshotError("bad_params", "screenshot target not found");
|
|
645
|
+
const restore = params.hideSelectors?.length ? hideAll(params.hideSelectors) : null;
|
|
646
|
+
const scale = Math.min(params.scale ?? 2, params.maxScale ?? 3);
|
|
647
|
+
const format = params.format ?? "png";
|
|
648
|
+
const timeout = params.timeout ?? 1e4;
|
|
649
|
+
try {
|
|
650
|
+
if (params.waitForFonts !== false && document.fonts?.ready) {
|
|
651
|
+
await Promise.race([document.fonts.ready, delay(timeout)]);
|
|
652
|
+
}
|
|
653
|
+
if (params.triggerLazyLoad) await scrollThrough(el);
|
|
654
|
+
if (params.waitForImages !== false) {
|
|
655
|
+
await Promise.all(
|
|
656
|
+
Array.from(el.querySelectorAll("img")).map(
|
|
657
|
+
(img) => typeof img.decode === "function" ? img.decode().catch(() => {
|
|
658
|
+
}) : Promise.resolve()
|
|
659
|
+
)
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
const { domToBlob } = await import('modern-screenshot');
|
|
663
|
+
let blob;
|
|
664
|
+
try {
|
|
665
|
+
blob = await domToBlob(el, {
|
|
666
|
+
type: MIME[format],
|
|
667
|
+
quality: params.quality ?? 0.92,
|
|
668
|
+
scale,
|
|
669
|
+
width: params.width,
|
|
670
|
+
height: params.height,
|
|
671
|
+
// `null` = transparent (png/webp); only fall back to white when the caller omitted it. `??`
|
|
672
|
+
// would wrongly collapse an explicit `null` to white.
|
|
673
|
+
backgroundColor: params.backgroundColor === void 0 ? "#ffffff" : params.backgroundColor,
|
|
674
|
+
timeout
|
|
675
|
+
});
|
|
676
|
+
} catch (e) {
|
|
677
|
+
throw new ScreenshotError("failed", `capture failed (an image/font may be served without CORS '*'): ${String(e?.message || e)}`);
|
|
678
|
+
}
|
|
679
|
+
const result = {
|
|
680
|
+
mime: MIME[format],
|
|
681
|
+
width: Math.round((params.width ?? el.clientWidth) * scale),
|
|
682
|
+
height: Math.round((params.height ?? el.scrollHeight) * scale),
|
|
683
|
+
scale,
|
|
684
|
+
bytesApprox: blob.size
|
|
685
|
+
};
|
|
686
|
+
if ((params.encoding ?? "blob") === "dataURL") result.dataURL = await blobToDataURL(blob);
|
|
687
|
+
else result.blob = blob;
|
|
688
|
+
return result;
|
|
689
|
+
} finally {
|
|
690
|
+
restore?.();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/flow/flow.tsx
|
|
560
695
|
var NavContext = React.createContext(null);
|
|
561
696
|
var ExperimentContext = React.createContext({});
|
|
562
697
|
function useExperiment(id) {
|
|
@@ -609,6 +744,7 @@ function usePageExitTimer(tracker, currentKey) {
|
|
|
609
744
|
function FunnelView({
|
|
610
745
|
pages,
|
|
611
746
|
initialKey,
|
|
747
|
+
trustInitialKey = false,
|
|
612
748
|
layout,
|
|
613
749
|
fallback,
|
|
614
750
|
prefetch: prefetchMode = "auto",
|
|
@@ -634,10 +770,10 @@ function FunnelView({
|
|
|
634
770
|
[pages]
|
|
635
771
|
);
|
|
636
772
|
const seedRef = React.useRef(null);
|
|
637
|
-
if (seedRef.current === null) seedRef.current =
|
|
773
|
+
if (seedRef.current === null) seedRef.current = chunkG44BUG6G_cjs.bucketingSeed(funnel.context);
|
|
638
774
|
const seed = seedRef.current;
|
|
639
775
|
const experiments = React.useMemo(
|
|
640
|
-
() =>
|
|
776
|
+
() => chunkG44BUG6G_cjs.resolveExperiments(pages, funnel.experiments, seed),
|
|
641
777
|
[pages, funnel.experiments, seed]
|
|
642
778
|
);
|
|
643
779
|
const toSlot = React.useCallback((k) => experiments.slotOf[k] ?? k, [experiments]);
|
|
@@ -656,8 +792,9 @@ function FunnelView({
|
|
|
656
792
|
if (!initialKey || initialKey === start) return start ? [start] : [];
|
|
657
793
|
const target = toSlot(initialKey);
|
|
658
794
|
const page2 = flowPages.find((p) => p.key === target);
|
|
659
|
-
|
|
660
|
-
const
|
|
795
|
+
if (trustInitialKey) return page2 ? [target] : start ? [start] : [];
|
|
796
|
+
const guard = chunkG44BUG6G_cjs.entryGuard(page2?.meta);
|
|
797
|
+
const ok = page2 && (!guard || chunkG44BUG6G_cjs.evaluateGate(guard, funnel.snapshot()));
|
|
661
798
|
const first = ok ? target : start;
|
|
662
799
|
return first ? [first] : [];
|
|
663
800
|
});
|
|
@@ -665,7 +802,7 @@ function FunnelView({
|
|
|
665
802
|
const index = history.length - 1;
|
|
666
803
|
const startKey = history[0];
|
|
667
804
|
const total = React.useMemo(
|
|
668
|
-
() =>
|
|
805
|
+
() => chunkG44BUG6G_cjs.expectedPathLength(flow, startKey, funnel.snapshot()),
|
|
669
806
|
// currentKey in deps: re-trace as each step's answers settle the branch.
|
|
670
807
|
[flow, startKey, currentKey, funnel]
|
|
671
808
|
);
|
|
@@ -700,7 +837,7 @@ function FunnelView({
|
|
|
700
837
|
usePageExitTimer(tracker, currentKey);
|
|
701
838
|
const [isNavigating, startNav] = React.useTransition();
|
|
702
839
|
const next = React.useCallback(() => {
|
|
703
|
-
const target =
|
|
840
|
+
const target = chunkG44BUG6G_cjs.nextPage(flow, currentKey, funnel.snapshot());
|
|
704
841
|
const resolved = target ? toSlot(target) : target;
|
|
705
842
|
if (resolved) startNav(() => setHistory((h) => [...h, resolved]));
|
|
706
843
|
}, [flow, currentKey, funnel, toSlot]);
|
|
@@ -724,7 +861,7 @@ function FunnelView({
|
|
|
724
861
|
[loaders, toRenderKey]
|
|
725
862
|
);
|
|
726
863
|
const nextCandidates = React.useMemo(
|
|
727
|
-
() => [...new Set(
|
|
864
|
+
() => [...new Set(chunkG44BUG6G_cjs.outgoingKeys(flow, currentKey).map(toRenderKey))],
|
|
728
865
|
[flow, currentKey, toRenderKey]
|
|
729
866
|
);
|
|
730
867
|
React.useEffect(() => {
|
|
@@ -741,6 +878,44 @@ function FunnelView({
|
|
|
741
878
|
canGoBack: history.length > 1,
|
|
742
879
|
isNavigating
|
|
743
880
|
};
|
|
881
|
+
const bus = useBusInternal();
|
|
882
|
+
const navRef = React.useRef(value);
|
|
883
|
+
navRef.current = value;
|
|
884
|
+
React.useEffect(() => {
|
|
885
|
+
if (!bus) return;
|
|
886
|
+
const control = {
|
|
887
|
+
goTo: (key) => {
|
|
888
|
+
if (!components.has(toRenderKey(key))) return false;
|
|
889
|
+
navRef.current.go(key);
|
|
890
|
+
return true;
|
|
891
|
+
},
|
|
892
|
+
goNext: () => navRef.current.next(),
|
|
893
|
+
goPrevious: () => navRef.current.back(),
|
|
894
|
+
snapshot: () => {
|
|
895
|
+
const v = navRef.current;
|
|
896
|
+
return {
|
|
897
|
+
key: v.page.key,
|
|
898
|
+
index: v.page.index,
|
|
899
|
+
total: v.page.total,
|
|
900
|
+
progressPercentage: v.page.progressPercentage,
|
|
901
|
+
canGoBack: v.canGoBack,
|
|
902
|
+
nextCandidates: v.nextCandidates
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
return bus.registerNavigation(control);
|
|
907
|
+
}, [bus]);
|
|
908
|
+
React.useEffect(() => {
|
|
909
|
+
const v = navRef.current;
|
|
910
|
+
bus?.emit("navigation", {
|
|
911
|
+
key: v.page.key,
|
|
912
|
+
index: v.page.index,
|
|
913
|
+
total: v.page.total,
|
|
914
|
+
progressPercentage: v.page.progressPercentage,
|
|
915
|
+
canGoBack: v.canGoBack,
|
|
916
|
+
nextCandidates: v.nextCandidates
|
|
917
|
+
});
|
|
918
|
+
}, [bus, currentKey]);
|
|
744
919
|
const renderKey = toRenderKey(currentKey);
|
|
745
920
|
const Current = components.get(renderKey);
|
|
746
921
|
const pageEl = React.createElement(React.Suspense, { fallback: fallback ?? null }, Current ? React.createElement(Current) : null);
|
|
@@ -756,6 +931,81 @@ function useNavigation() {
|
|
|
756
931
|
if (!ctx) throw new Error("useNavigation must be used inside <FunnelView>");
|
|
757
932
|
return ctx;
|
|
758
933
|
}
|
|
934
|
+
var EMBED_CAPABILITIES = {
|
|
935
|
+
methods: ["nav.snapshot", "nav.goTo", "nav.goNext", "nav.goPrevious", "screenshot"],
|
|
936
|
+
events: ["navigation"]
|
|
937
|
+
};
|
|
938
|
+
function EmbedBridge() {
|
|
939
|
+
const bus = useBusInternal();
|
|
940
|
+
React.useEffect(() => {
|
|
941
|
+
if (!bus || typeof window === "undefined" || window.parent === window) return;
|
|
942
|
+
const parent = window.parent;
|
|
943
|
+
let disposed = false;
|
|
944
|
+
const send = (msg) => {
|
|
945
|
+
try {
|
|
946
|
+
parent.postMessage({ af: chunkWYUDL4FI_cjs.AF_PROTOCOL, source: "af-preview", ...msg }, "*");
|
|
947
|
+
} catch {
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
const hello = () => send({ kind: "hello", role: "funnel", protocol: chunkWYUDL4FI_cjs.AF_PROTOCOL, capabilities: EMBED_CAPABILITIES });
|
|
951
|
+
const sendSnapshot = () => {
|
|
952
|
+
const s = bus.navigation();
|
|
953
|
+
if (s) send({ kind: "event", event: "navigation", data: s });
|
|
954
|
+
};
|
|
955
|
+
async function invoke(method, params) {
|
|
956
|
+
switch (method) {
|
|
957
|
+
case "nav.snapshot":
|
|
958
|
+
return bus.navigation();
|
|
959
|
+
case "nav.goTo": {
|
|
960
|
+
const key = params?.key;
|
|
961
|
+
if (typeof key !== "string" || !bus.goTo(key)) throw { code: "bad_params", message: `unknown page key: ${String(key)}` };
|
|
962
|
+
return void 0;
|
|
963
|
+
}
|
|
964
|
+
case "nav.goNext":
|
|
965
|
+
bus.goNext();
|
|
966
|
+
return void 0;
|
|
967
|
+
case "nav.goPrevious":
|
|
968
|
+
bus.goPrevious();
|
|
969
|
+
return void 0;
|
|
970
|
+
case "screenshot":
|
|
971
|
+
return await captureScreenshot(params ?? {});
|
|
972
|
+
default:
|
|
973
|
+
throw { code: "unknown_method", message: `unknown method: ${method}` };
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
async function onMsg(e) {
|
|
977
|
+
if (e.source !== parent) return;
|
|
978
|
+
const d = e.data;
|
|
979
|
+
if (!d || typeof d !== "object" || d.af !== chunkWYUDL4FI_cjs.AF_PROTOCOL || d.source !== "af-host") return;
|
|
980
|
+
if (d.kind === "hello") {
|
|
981
|
+
hello();
|
|
982
|
+
sendSnapshot();
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (d.kind !== "request" || typeof d.id !== "string") return;
|
|
986
|
+
try {
|
|
987
|
+
const result = await invoke(d.method, d.params);
|
|
988
|
+
if (!disposed) send({ kind: "response", id: d.id, ok: true, result });
|
|
989
|
+
} catch (err) {
|
|
990
|
+
const e2 = err;
|
|
991
|
+
const error = e2 && typeof e2.code === "string" ? { code: e2.code, message: String(e2.message ?? e2.code) } : { code: "failed", message: String(e2?.message ?? err) };
|
|
992
|
+
if (!disposed) send({ kind: "response", id: d.id, ok: false, error });
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
window.addEventListener("message", onMsg);
|
|
996
|
+
const offNav = bus.on("navigation", (data) => {
|
|
997
|
+
if (!disposed) send({ kind: "event", event: "navigation", data });
|
|
998
|
+
});
|
|
999
|
+
hello();
|
|
1000
|
+
sendSnapshot();
|
|
1001
|
+
return () => {
|
|
1002
|
+
disposed = true;
|
|
1003
|
+
offNav();
|
|
1004
|
+
window.removeEventListener("message", onMsg);
|
|
1005
|
+
};
|
|
1006
|
+
}, [bus]);
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
759
1009
|
function usePage() {
|
|
760
1010
|
return useNavigation().page;
|
|
761
1011
|
}
|
|
@@ -1072,7 +1322,7 @@ function Modal(props) {
|
|
|
1072
1322
|
// src/commerce/checkout.tsx
|
|
1073
1323
|
function createMockDriver(provider = "stripe", options = {}) {
|
|
1074
1324
|
const success = options.result ?? { ok: true, amountMinor: 0, currency: "USD" };
|
|
1075
|
-
const offError = typeof options.failOffSession === "object" ? options.failOffSession :
|
|
1325
|
+
const offError = typeof options.failOffSession === "object" ? options.failOffSession : chunkQMAZGLGV_cjs.checkoutError("authentication_required", "off-session charge declined (mock)");
|
|
1076
1326
|
return {
|
|
1077
1327
|
provider,
|
|
1078
1328
|
start: (req) => {
|
|
@@ -1087,7 +1337,7 @@ function createMockDriver(provider = "stripe", options = {}) {
|
|
|
1087
1337
|
onClick: () => {
|
|
1088
1338
|
cb.onStart?.();
|
|
1089
1339
|
if (success.ok) cb.onSuccess(success);
|
|
1090
|
-
else cb.onError(success.error ??
|
|
1340
|
+
else cb.onError(success.error ?? chunkQMAZGLGV_cjs.checkoutError("processing_error", "failed (mock)"));
|
|
1091
1341
|
}
|
|
1092
1342
|
},
|
|
1093
1343
|
`Pay (mock ${req.surface})`
|
|
@@ -1125,12 +1375,25 @@ var FALLBACK_DRIVER = createMockDriver();
|
|
|
1125
1375
|
function useCheckout(opts = {}) {
|
|
1126
1376
|
const driver = useDriver();
|
|
1127
1377
|
const tracker = useTrackerRef();
|
|
1378
|
+
const catalog = useCatalog();
|
|
1128
1379
|
const nav = useNavigation();
|
|
1129
1380
|
const [status, setStatus] = React.useState("idle");
|
|
1130
1381
|
const [error, setError] = React.useState(null);
|
|
1131
1382
|
const intent = opts.intent ?? "purchase";
|
|
1132
1383
|
const kind = opts.kind;
|
|
1133
1384
|
const advance = opts.advanceOnSuccess ?? true;
|
|
1385
|
+
const productFacts = React.useCallback(
|
|
1386
|
+
(product) => {
|
|
1387
|
+
const resolved = product ? catalog.get(product) : void 0;
|
|
1388
|
+
return {
|
|
1389
|
+
productId: resolved?.catalogKey ?? product,
|
|
1390
|
+
amount: resolved ? resolved.priceMinor / 100 : void 0,
|
|
1391
|
+
currency: resolved?.currency,
|
|
1392
|
+
isSubscription: resolved ? resolved.period !== "one-time" : kind === "subscription" || kind === "upgrade"
|
|
1393
|
+
};
|
|
1394
|
+
},
|
|
1395
|
+
[catalog, kind]
|
|
1396
|
+
);
|
|
1134
1397
|
const recovering = React.useRef(false);
|
|
1135
1398
|
const failRef = React.useRef(() => {
|
|
1136
1399
|
});
|
|
@@ -1138,18 +1401,24 @@ function useCheckout(opts = {}) {
|
|
|
1138
1401
|
async () => ({ ok: false })
|
|
1139
1402
|
);
|
|
1140
1403
|
const succeed = React.useCallback(
|
|
1141
|
-
(result) => {
|
|
1404
|
+
(result, product) => {
|
|
1142
1405
|
setStatus("success");
|
|
1406
|
+
const { productId, isSubscription } = productFacts(product);
|
|
1407
|
+
const amount = result.amountMinor != null ? result.amountMinor / 100 : void 0;
|
|
1143
1408
|
tracker.track("purchase.complete", {
|
|
1144
|
-
amount
|
|
1409
|
+
amount,
|
|
1145
1410
|
currency: result.currency,
|
|
1411
|
+
productId,
|
|
1146
1412
|
eventId: result.eventId
|
|
1147
1413
|
// server-minted (browser↔CAPI dedup); undefined if none
|
|
1148
1414
|
});
|
|
1415
|
+
if (isSubscription) {
|
|
1416
|
+
tracker.track("subscription.created", { amount, currency: result.currency, productId, eventId: result.eventId });
|
|
1417
|
+
}
|
|
1149
1418
|
opts.onSuccess?.(result);
|
|
1150
1419
|
if (advance) nav.next();
|
|
1151
1420
|
},
|
|
1152
|
-
[tracker, nav, advance, opts]
|
|
1421
|
+
[tracker, nav, advance, opts, productFacts]
|
|
1153
1422
|
);
|
|
1154
1423
|
const succeedRef = React.useRef(succeed);
|
|
1155
1424
|
succeedRef.current = succeed;
|
|
@@ -1157,12 +1426,19 @@ function useCheckout(opts = {}) {
|
|
|
1157
1426
|
(product, surface) => {
|
|
1158
1427
|
setStatus("loading");
|
|
1159
1428
|
setError(null);
|
|
1160
|
-
|
|
1429
|
+
const { productId, amount, currency } = productFacts(product);
|
|
1430
|
+
tracker.track("checkout.start", { productId, amount, currency, surface, eventId: newEventId() });
|
|
1161
1431
|
},
|
|
1162
|
-
[tracker]
|
|
1432
|
+
[tracker, productFacts]
|
|
1433
|
+
);
|
|
1434
|
+
const paymentAdded = React.useCallback(
|
|
1435
|
+
(product) => {
|
|
1436
|
+
const { productId, amount, currency } = productFacts(product);
|
|
1437
|
+
tracker.track("checkout.payment_added", { productId, amount, currency, eventId: newEventId() });
|
|
1438
|
+
},
|
|
1439
|
+
[tracker, productFacts]
|
|
1163
1440
|
);
|
|
1164
|
-
const
|
|
1165
|
-
const hostInSheet = (surface) => surface === "sheet" || chunkLJYLGLFS_cjs.isInlineSurface(surface) && !!chunkLJYLGLFS_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet;
|
|
1441
|
+
const hostInSheet = (surface) => surface === "sheet" || chunkQMAZGLGV_cjs.isInlineSurface(surface) && !!chunkQMAZGLGV_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet;
|
|
1166
1442
|
const startRecovery = (product, surface) => {
|
|
1167
1443
|
recovering.current = true;
|
|
1168
1444
|
if (hostInSheet(surface)) {
|
|
@@ -1172,9 +1448,9 @@ function useCheckout(opts = {}) {
|
|
|
1172
1448
|
surface: "sheet",
|
|
1173
1449
|
intent,
|
|
1174
1450
|
kind,
|
|
1175
|
-
onSuccess: (r) => succeedRef.current(r),
|
|
1451
|
+
onSuccess: (r) => succeedRef.current(r, product),
|
|
1176
1452
|
onError: (e) => failRef.current(e, product),
|
|
1177
|
-
onPaymentAdded: () => paymentAdded()
|
|
1453
|
+
onPaymentAdded: () => paymentAdded(product)
|
|
1178
1454
|
});
|
|
1179
1455
|
} else {
|
|
1180
1456
|
void attemptRef.current(product, surface);
|
|
@@ -1213,7 +1489,7 @@ function useCheckout(opts = {}) {
|
|
|
1213
1489
|
recovering.current = true;
|
|
1214
1490
|
if (!surface) {
|
|
1215
1491
|
if (intent === "upsell") void attemptRef.current(product);
|
|
1216
|
-
else startRecovery(product,
|
|
1492
|
+
else startRecovery(product, chunkQMAZGLGV_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet ? "sheet" : "popup");
|
|
1217
1493
|
return;
|
|
1218
1494
|
}
|
|
1219
1495
|
startRecovery(product, surface);
|
|
@@ -1228,11 +1504,11 @@ function useCheckout(opts = {}) {
|
|
|
1228
1504
|
begin(product, surface);
|
|
1229
1505
|
try {
|
|
1230
1506
|
const result = await driver.start({ product, intent, surface, kind });
|
|
1231
|
-
if (result.ok) succeedRef.current(result);
|
|
1232
|
-
else failRef.current(result.error ??
|
|
1507
|
+
if (result.ok) succeedRef.current(result, product);
|
|
1508
|
+
else failRef.current(result.error ?? chunkQMAZGLGV_cjs.checkoutError("unknown", "Checkout failed"), product);
|
|
1233
1509
|
return result;
|
|
1234
1510
|
} catch (e) {
|
|
1235
|
-
const err =
|
|
1511
|
+
const err = chunkQMAZGLGV_cjs.checkoutError("processing_error", e instanceof Error ? e.message : "Checkout failed");
|
|
1236
1512
|
failRef.current(err, product);
|
|
1237
1513
|
return { ok: false, error: err };
|
|
1238
1514
|
}
|
|
@@ -1247,9 +1523,9 @@ function useCheckout(opts = {}) {
|
|
|
1247
1523
|
recovering.current = false;
|
|
1248
1524
|
begin(product, surface);
|
|
1249
1525
|
},
|
|
1250
|
-
onSuccess: (result) => succeedRef.current(result),
|
|
1526
|
+
onSuccess: (result) => succeedRef.current(result, product),
|
|
1251
1527
|
onError: (err) => failRef.current(err, product),
|
|
1252
|
-
onPaymentAdded: () => paymentAdded()
|
|
1528
|
+
onPaymentAdded: () => paymentAdded(product)
|
|
1253
1529
|
}),
|
|
1254
1530
|
[begin, paymentAdded]
|
|
1255
1531
|
);
|
|
@@ -1271,7 +1547,7 @@ var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, on
|
|
|
1271
1547
|
if (!hostable) {
|
|
1272
1548
|
resolved.current = true;
|
|
1273
1549
|
onErrorRef.current(
|
|
1274
|
-
|
|
1550
|
+
chunkQMAZGLGV_cjs.checkoutError("processing_error", "This checkout driver has no inline surface to host in the sheet")
|
|
1275
1551
|
);
|
|
1276
1552
|
void modal.hide();
|
|
1277
1553
|
modal.remove();
|
|
@@ -1279,7 +1555,7 @@ var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, on
|
|
|
1279
1555
|
}
|
|
1280
1556
|
return () => {
|
|
1281
1557
|
if (!resolved.current) {
|
|
1282
|
-
onErrorRef.current(
|
|
1558
|
+
onErrorRef.current(chunkQMAZGLGV_cjs.checkoutError("canceled", "Checkout was dismissed before completing"));
|
|
1283
1559
|
}
|
|
1284
1560
|
};
|
|
1285
1561
|
}, []);
|
|
@@ -1324,7 +1600,7 @@ function Checkout({
|
|
|
1324
1600
|
const driver = useDriver();
|
|
1325
1601
|
const checkout = useCheckout(options);
|
|
1326
1602
|
const intent = options.intent ?? "purchase";
|
|
1327
|
-
if (
|
|
1603
|
+
if (chunkQMAZGLGV_cjs.isInlineSurface(surface)) {
|
|
1328
1604
|
return driver.renderInline ? React.createElement(
|
|
1329
1605
|
"div",
|
|
1330
1606
|
{ key: product, className },
|
|
@@ -1386,7 +1662,7 @@ function CheckoutResume(options = {}) {
|
|
|
1386
1662
|
window.history.replaceState(window.history.state, "", url.toString());
|
|
1387
1663
|
cb.onSuccess(result);
|
|
1388
1664
|
} else {
|
|
1389
|
-
cb.onError(result.error ??
|
|
1665
|
+
cb.onError(result.error ?? chunkQMAZGLGV_cjs.checkoutError("unknown", "The checkout could not be verified"));
|
|
1390
1666
|
}
|
|
1391
1667
|
});
|
|
1392
1668
|
}, []);
|
|
@@ -2547,6 +2823,7 @@ var FunnelCtx = React.createContext(null);
|
|
|
2547
2823
|
function FunnelProvider({
|
|
2548
2824
|
config,
|
|
2549
2825
|
sessionValues,
|
|
2826
|
+
deferSessionValues = false,
|
|
2550
2827
|
tracker,
|
|
2551
2828
|
context: contextOpts,
|
|
2552
2829
|
products,
|
|
@@ -2562,7 +2839,12 @@ function FunnelProvider({
|
|
|
2562
2839
|
const [value] = React.useState(() => {
|
|
2563
2840
|
const s = store ?? createFunnelStore(
|
|
2564
2841
|
{ responses: config?.responses, data: config?.data },
|
|
2565
|
-
|
|
2842
|
+
// FLASH-FREE HYDRATION: with deferSessionValues the initial render is seeded with
|
|
2843
|
+
// DEFAULTS ONLY, so the client's first render is byte-identical to the build-time
|
|
2844
|
+
// DEFAULT-state prerender it hydrates against (clean match). The visitor's saved
|
|
2845
|
+
// answers arrive as a post-hydration setMany below. Without the flag, sessionValues
|
|
2846
|
+
// seed the store at construction (SSR + non-prerendered paths).
|
|
2847
|
+
deferSessionValues ? void 0 : sessionValues
|
|
2566
2848
|
);
|
|
2567
2849
|
const ctx = buildContext({
|
|
2568
2850
|
funnelId: config?.id,
|
|
@@ -2580,7 +2862,7 @@ function FunnelProvider({
|
|
|
2580
2862
|
const value2 = s.get("user.email");
|
|
2581
2863
|
return typeof value2 === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value2) ? value2 : void 0;
|
|
2582
2864
|
};
|
|
2583
|
-
const catalog =
|
|
2865
|
+
const catalog = chunkG44BUG6G_cjs.buildCatalog(products ?? []);
|
|
2584
2866
|
return {
|
|
2585
2867
|
funnel: createFunnel(s, ctx, busTracker, experiments ?? []),
|
|
2586
2868
|
store: s,
|
|
@@ -2594,6 +2876,12 @@ function FunnelProvider({
|
|
|
2594
2876
|
};
|
|
2595
2877
|
});
|
|
2596
2878
|
React.useEffect(() => attachBus(value.bus), [value.bus]);
|
|
2879
|
+
React.useEffect(() => {
|
|
2880
|
+
if (!deferSessionValues || !sessionValues) return;
|
|
2881
|
+
const updates = {};
|
|
2882
|
+
for (const [key, v] of Object.entries(sessionValues)) if (v !== void 0) updates[key] = v;
|
|
2883
|
+
if (Object.keys(updates).length > 0) value.store.setMany(updates);
|
|
2884
|
+
}, []);
|
|
2597
2885
|
React.useEffect(() => {
|
|
2598
2886
|
value.tracker.setAcquisition?.(buildAcquisition(value.context));
|
|
2599
2887
|
}, [value]);
|
|
@@ -2629,6 +2917,9 @@ function useFunnelCtx() {
|
|
|
2629
2917
|
function useFunnel() {
|
|
2630
2918
|
return useFunnelCtx().funnel;
|
|
2631
2919
|
}
|
|
2920
|
+
function useBusInternal() {
|
|
2921
|
+
return React.useContext(FunnelCtx)?.bus ?? null;
|
|
2922
|
+
}
|
|
2632
2923
|
function useStoreKey(store, key) {
|
|
2633
2924
|
const subscribe = React.useCallback(
|
|
2634
2925
|
(cb) => store.subscribe(cb, { keys: [key] }),
|
|
@@ -2692,6 +2983,152 @@ function useContextValue(path) {
|
|
|
2692
2983
|
);
|
|
2693
2984
|
}
|
|
2694
2985
|
|
|
2986
|
+
// src/tracking/snapshotPersistence.ts
|
|
2987
|
+
function createSnapshotPersistence(opts) {
|
|
2988
|
+
const baseDelay = opts.baseDelayMs ?? 1e3;
|
|
2989
|
+
const maxDelay = opts.maxDelayMs ?? 3e4;
|
|
2990
|
+
const setT = opts.timers?.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
|
|
2991
|
+
const clearT = opts.timers?.clearTimeout ?? ((h) => clearTimeout(h));
|
|
2992
|
+
const rand = opts.random ?? Math.random;
|
|
2993
|
+
let pending = null;
|
|
2994
|
+
let seq = 0;
|
|
2995
|
+
let pendingSeq = 0;
|
|
2996
|
+
let inFlight = false;
|
|
2997
|
+
let attempt = 0;
|
|
2998
|
+
let retryTimer = null;
|
|
2999
|
+
let disposed = false;
|
|
3000
|
+
let fatalSeq = -1;
|
|
3001
|
+
const clearRetry = () => {
|
|
3002
|
+
if (retryTimer !== null) {
|
|
3003
|
+
clearT(retryTimer);
|
|
3004
|
+
retryTimer = null;
|
|
3005
|
+
}
|
|
3006
|
+
};
|
|
3007
|
+
const buildBody = (snapshot, s) => JSON.stringify({ sessionId: opts.getSessionId(), userData: snapshot, seq: s });
|
|
3008
|
+
const trySend = () => {
|
|
3009
|
+
if (disposed || inFlight || pending === null) return;
|
|
3010
|
+
if (retryTimer !== null) return;
|
|
3011
|
+
const sessionId = opts.getSessionId();
|
|
3012
|
+
if (!sessionId) return;
|
|
3013
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return;
|
|
3014
|
+
if (pendingSeq === fatalSeq) return;
|
|
3015
|
+
const snapshot = pending;
|
|
3016
|
+
const sentSeq = pendingSeq;
|
|
3017
|
+
const body = buildBody(snapshot, sentSeq);
|
|
3018
|
+
inFlight = true;
|
|
3019
|
+
void fetch(opts.url, {
|
|
3020
|
+
method: "POST",
|
|
3021
|
+
headers: { "content-type": "application/json" },
|
|
3022
|
+
body,
|
|
3023
|
+
keepalive: true
|
|
3024
|
+
}).then((res) => {
|
|
3025
|
+
inFlight = false;
|
|
3026
|
+
if (disposed) return;
|
|
3027
|
+
if (res.ok) {
|
|
3028
|
+
attempt = 0;
|
|
3029
|
+
if (pendingSeq === sentSeq) pending = null;
|
|
3030
|
+
else trySend();
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
if (res.status >= 400 && res.status < 500) {
|
|
3034
|
+
if (process.env.NODE_ENV !== "production") {
|
|
3035
|
+
console.warn(
|
|
3036
|
+
`[appfunnel] snapshot persist rejected ${res.status} (dropping snapshot; check session/payload)`
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
fatalSeq = sentSeq;
|
|
3040
|
+
if (pendingSeq === sentSeq) pending = null;
|
|
3041
|
+
else trySend();
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
scheduleRetry();
|
|
3045
|
+
}).catch(() => {
|
|
3046
|
+
inFlight = false;
|
|
3047
|
+
if (disposed) return;
|
|
3048
|
+
scheduleRetry();
|
|
3049
|
+
});
|
|
3050
|
+
};
|
|
3051
|
+
const scheduleRetry = () => {
|
|
3052
|
+
if (disposed || pending === null) return;
|
|
3053
|
+
const exp = Math.min(maxDelay, baseDelay * 2 ** attempt);
|
|
3054
|
+
const delay2 = Math.round(exp * (0.5 + rand() * 0.5));
|
|
3055
|
+
attempt++;
|
|
3056
|
+
clearRetry();
|
|
3057
|
+
retryTimer = setT(() => {
|
|
3058
|
+
retryTimer = null;
|
|
3059
|
+
trySend();
|
|
3060
|
+
}, delay2);
|
|
3061
|
+
};
|
|
3062
|
+
const push = (snapshot) => {
|
|
3063
|
+
if (disposed) return;
|
|
3064
|
+
pending = snapshot;
|
|
3065
|
+
pendingSeq = ++seq;
|
|
3066
|
+
if (fatalSeq !== -1 && pendingSeq !== fatalSeq) fatalSeq = -1;
|
|
3067
|
+
if (retryTimer === null) trySend();
|
|
3068
|
+
};
|
|
3069
|
+
const flushBeacon = () => {
|
|
3070
|
+
if (disposed || pending === null) return;
|
|
3071
|
+
const sessionId = opts.getSessionId();
|
|
3072
|
+
if (!sessionId) return;
|
|
3073
|
+
const body = buildBody(pending, pendingSeq);
|
|
3074
|
+
let delivered = false;
|
|
3075
|
+
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
3076
|
+
try {
|
|
3077
|
+
delivered = navigator.sendBeacon(
|
|
3078
|
+
opts.url,
|
|
3079
|
+
new Blob([body], { type: "application/json" })
|
|
3080
|
+
);
|
|
3081
|
+
} catch {
|
|
3082
|
+
delivered = false;
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
if (!delivered) {
|
|
3086
|
+
try {
|
|
3087
|
+
void fetch(opts.url, {
|
|
3088
|
+
method: "POST",
|
|
3089
|
+
headers: { "content-type": "application/json" },
|
|
3090
|
+
body,
|
|
3091
|
+
keepalive: true
|
|
3092
|
+
}).catch(() => {
|
|
3093
|
+
});
|
|
3094
|
+
delivered = true;
|
|
3095
|
+
} catch {
|
|
3096
|
+
delivered = false;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
if (delivered) pending = null;
|
|
3100
|
+
};
|
|
3101
|
+
const onHidden = () => {
|
|
3102
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
3103
|
+
flushBeacon();
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
const onPageHide = () => flushBeacon();
|
|
3107
|
+
const onOnline = () => trySend();
|
|
3108
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
3109
|
+
try {
|
|
3110
|
+
document.addEventListener("visibilitychange", onHidden);
|
|
3111
|
+
window.addEventListener("pagehide", onPageHide);
|
|
3112
|
+
window.addEventListener("online", onOnline);
|
|
3113
|
+
} catch {
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
const dispose = () => {
|
|
3117
|
+
disposed = true;
|
|
3118
|
+
clearRetry();
|
|
3119
|
+
pending = null;
|
|
3120
|
+
if (typeof window !== "undefined" && typeof window.removeEventListener === "function") {
|
|
3121
|
+
try {
|
|
3122
|
+
document.removeEventListener("visibilitychange", onHidden);
|
|
3123
|
+
window.removeEventListener("pagehide", onPageHide);
|
|
3124
|
+
window.removeEventListener("online", onOnline);
|
|
3125
|
+
} catch {
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
};
|
|
3129
|
+
return { push, flushBeacon, dispose };
|
|
3130
|
+
}
|
|
3131
|
+
|
|
2695
3132
|
// src/tracking/platformTracker.ts
|
|
2696
3133
|
function validClickIds(acq) {
|
|
2697
3134
|
return Object.entries(acq.clickIds ?? {}).filter(
|
|
@@ -2797,32 +3234,35 @@ function createPlatformTracker(cfg) {
|
|
|
2797
3234
|
}
|
|
2798
3235
|
};
|
|
2799
3236
|
const VARS_DEBOUNCE_MS = 800;
|
|
3237
|
+
const dataUrl = `${base}/campaign/${encodeURIComponent(cfg.campaignId)}/event/session/data`;
|
|
2800
3238
|
let pendingVars = null;
|
|
2801
3239
|
let varsTimer = null;
|
|
3240
|
+
const persistence = createSnapshotPersistence({
|
|
3241
|
+
url: dataUrl,
|
|
3242
|
+
getSessionId: () => sessionId
|
|
3243
|
+
});
|
|
2802
3244
|
const flushVars = () => {
|
|
2803
3245
|
if (varsTimer) {
|
|
2804
3246
|
clearTimeout(varsTimer);
|
|
2805
3247
|
varsTimer = null;
|
|
2806
3248
|
}
|
|
2807
3249
|
if (!pendingVars) return;
|
|
2808
|
-
const
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
};
|
|
2821
|
-
if (sessionId) doPost();
|
|
2822
|
-
else chain = chain.then(doPost);
|
|
3250
|
+
const snapshot = pendingVars;
|
|
3251
|
+
pendingVars = null;
|
|
3252
|
+
persistence.push(snapshot);
|
|
3253
|
+
if (!sessionId) chain = chain.then(() => {
|
|
3254
|
+
persistence.push(snapshot);
|
|
3255
|
+
});
|
|
3256
|
+
};
|
|
3257
|
+
const drainOnExit = () => {
|
|
3258
|
+
if (pendingVars) {
|
|
3259
|
+
flushVars();
|
|
3260
|
+
persistence.flushBeacon();
|
|
3261
|
+
}
|
|
2823
3262
|
};
|
|
2824
3263
|
try {
|
|
2825
|
-
window.addEventListener("
|
|
3264
|
+
window.addEventListener("visibilitychange", drainOnExit);
|
|
3265
|
+
window.addEventListener("pagehide", drainOnExit);
|
|
2826
3266
|
} catch {
|
|
2827
3267
|
}
|
|
2828
3268
|
if (cfg.experiment?.id && cfg.experiment.variant) {
|
|
@@ -2865,6 +3305,7 @@ function createFunnelTree({
|
|
|
2865
3305
|
}) {
|
|
2866
3306
|
return function tree(opts) {
|
|
2867
3307
|
const mode = opts.mode ?? "live";
|
|
3308
|
+
const embedEnabled = opts.embed ?? false;
|
|
2868
3309
|
const tracker = opts.tracking ? createPlatformTracker({
|
|
2869
3310
|
...opts.tracking,
|
|
2870
3311
|
visitorId: opts.visitorId,
|
|
@@ -2886,6 +3327,7 @@ function createFunnelTree({
|
|
|
2886
3327
|
React.Fragment,
|
|
2887
3328
|
null,
|
|
2888
3329
|
React.createElement(CheckoutResume, null),
|
|
3330
|
+
embedEnabled ? React.createElement(EmbedBridge, null) : null,
|
|
2889
3331
|
layout ? React.createElement(layout, null, children) : children
|
|
2890
3332
|
);
|
|
2891
3333
|
};
|
|
@@ -2899,6 +3341,7 @@ function createFunnelTree({
|
|
|
2899
3341
|
tracker,
|
|
2900
3342
|
checkout,
|
|
2901
3343
|
sessionValues: opts.sessionValues,
|
|
3344
|
+
deferSessionValues: opts.deferSessionValues,
|
|
2902
3345
|
experiments: opts.experiments,
|
|
2903
3346
|
context: {
|
|
2904
3347
|
mode,
|
|
@@ -2910,6 +3353,13 @@ function createFunnelTree({
|
|
|
2910
3353
|
pages,
|
|
2911
3354
|
layout: layoutWithResume,
|
|
2912
3355
|
initialKey: opts.initialKey,
|
|
3356
|
+
// FLASH-FREE HYDRATION (Option B, FIX 1): trust the SERVER-resolved initialKey instead of
|
|
3357
|
+
// re-running the entry guard. The CLIENT sets this via deferSessionValues (its deferred
|
|
3358
|
+
// snapshot is empty, so re-running the guard would bounce a qualified deep-link and
|
|
3359
|
+
// mismatch the markup). The build-time PRERENDER sets `trustInitialKey` explicitly so a
|
|
3360
|
+
// guarded page prerenders its OWN default-state markup (the renderer picks target-vs-bounce
|
|
3361
|
+
// at serve time). Live SSR fallback leaves both off → the guard runs against real values.
|
|
3362
|
+
trustInitialKey: opts.trustInitialKey ?? opts.deferSessionValues,
|
|
2913
3363
|
prefetch: "off"
|
|
2914
3364
|
// renderer background-modulepreloads all chunks
|
|
2915
3365
|
})
|
|
@@ -3058,147 +3508,147 @@ function Script({
|
|
|
3058
3508
|
|
|
3059
3509
|
Object.defineProperty(exports, "assignVariant", {
|
|
3060
3510
|
enumerable: true,
|
|
3061
|
-
get: function () { return
|
|
3511
|
+
get: function () { return chunkG44BUG6G_cjs.assignVariant; }
|
|
3062
3512
|
});
|
|
3063
3513
|
Object.defineProperty(exports, "bucketingSeed", {
|
|
3064
3514
|
enumerable: true,
|
|
3065
|
-
get: function () { return
|
|
3515
|
+
get: function () { return chunkG44BUG6G_cjs.bucketingSeed; }
|
|
3066
3516
|
});
|
|
3067
3517
|
Object.defineProperty(exports, "buildCatalog", {
|
|
3068
3518
|
enumerable: true,
|
|
3069
|
-
get: function () { return
|
|
3519
|
+
get: function () { return chunkG44BUG6G_cjs.buildCatalog; }
|
|
3070
3520
|
});
|
|
3071
3521
|
Object.defineProperty(exports, "compileManifest", {
|
|
3072
3522
|
enumerable: true,
|
|
3073
|
-
get: function () { return
|
|
3523
|
+
get: function () { return chunkG44BUG6G_cjs.compileManifest; }
|
|
3074
3524
|
});
|
|
3075
3525
|
Object.defineProperty(exports, "currencyExponent", {
|
|
3076
3526
|
enumerable: true,
|
|
3077
|
-
get: function () { return
|
|
3527
|
+
get: function () { return chunkG44BUG6G_cjs.currencyExponent; }
|
|
3078
3528
|
});
|
|
3079
3529
|
Object.defineProperty(exports, "defineFunnel", {
|
|
3080
3530
|
enumerable: true,
|
|
3081
|
-
get: function () { return
|
|
3531
|
+
get: function () { return chunkG44BUG6G_cjs.defineFunnel; }
|
|
3082
3532
|
});
|
|
3083
3533
|
Object.defineProperty(exports, "definePage", {
|
|
3084
3534
|
enumerable: true,
|
|
3085
|
-
get: function () { return
|
|
3535
|
+
get: function () { return chunkG44BUG6G_cjs.definePage; }
|
|
3086
3536
|
});
|
|
3087
3537
|
Object.defineProperty(exports, "entryGuard", {
|
|
3088
3538
|
enumerable: true,
|
|
3089
|
-
get: function () { return
|
|
3539
|
+
get: function () { return chunkG44BUG6G_cjs.entryGuard; }
|
|
3090
3540
|
});
|
|
3091
3541
|
Object.defineProperty(exports, "evaluateCondition", {
|
|
3092
3542
|
enumerable: true,
|
|
3093
|
-
get: function () { return
|
|
3543
|
+
get: function () { return chunkG44BUG6G_cjs.evaluateCondition; }
|
|
3094
3544
|
});
|
|
3095
3545
|
Object.defineProperty(exports, "evaluateGate", {
|
|
3096
3546
|
enumerable: true,
|
|
3097
|
-
get: function () { return
|
|
3547
|
+
get: function () { return chunkG44BUG6G_cjs.evaluateGate; }
|
|
3098
3548
|
});
|
|
3099
3549
|
Object.defineProperty(exports, "expectedPathLength", {
|
|
3100
3550
|
enumerable: true,
|
|
3101
|
-
get: function () { return
|
|
3551
|
+
get: function () { return chunkG44BUG6G_cjs.expectedPathLength; }
|
|
3102
3552
|
});
|
|
3103
3553
|
Object.defineProperty(exports, "fnv1a", {
|
|
3104
3554
|
enumerable: true,
|
|
3105
|
-
get: function () { return
|
|
3555
|
+
get: function () { return chunkG44BUG6G_cjs.fnv1a; }
|
|
3106
3556
|
});
|
|
3107
3557
|
Object.defineProperty(exports, "formatMoney", {
|
|
3108
3558
|
enumerable: true,
|
|
3109
|
-
get: function () { return
|
|
3559
|
+
get: function () { return chunkG44BUG6G_cjs.formatMoney; }
|
|
3110
3560
|
});
|
|
3111
3561
|
Object.defineProperty(exports, "formatProduct", {
|
|
3112
3562
|
enumerable: true,
|
|
3113
|
-
get: function () { return
|
|
3563
|
+
get: function () { return chunkG44BUG6G_cjs.formatProduct; }
|
|
3114
3564
|
});
|
|
3115
3565
|
Object.defineProperty(exports, "hashToUnit", {
|
|
3116
3566
|
enumerable: true,
|
|
3117
|
-
get: function () { return
|
|
3567
|
+
get: function () { return chunkG44BUG6G_cjs.hashToUnit; }
|
|
3118
3568
|
});
|
|
3119
3569
|
Object.defineProperty(exports, "isRtl", {
|
|
3120
3570
|
enumerable: true,
|
|
3121
|
-
get: function () { return
|
|
3571
|
+
get: function () { return chunkG44BUG6G_cjs.isRtl; }
|
|
3122
3572
|
});
|
|
3123
3573
|
Object.defineProperty(exports, "isVariantKey", {
|
|
3124
3574
|
enumerable: true,
|
|
3125
|
-
get: function () { return
|
|
3575
|
+
get: function () { return chunkG44BUG6G_cjs.isVariantKey; }
|
|
3126
3576
|
});
|
|
3127
3577
|
Object.defineProperty(exports, "nextPage", {
|
|
3128
3578
|
enumerable: true,
|
|
3129
|
-
get: function () { return
|
|
3579
|
+
get: function () { return chunkG44BUG6G_cjs.nextPage; }
|
|
3130
3580
|
});
|
|
3131
3581
|
Object.defineProperty(exports, "outgoingKeys", {
|
|
3132
3582
|
enumerable: true,
|
|
3133
|
-
get: function () { return
|
|
3583
|
+
get: function () { return chunkG44BUG6G_cjs.outgoingKeys; }
|
|
3134
3584
|
});
|
|
3135
3585
|
Object.defineProperty(exports, "pageMeta", {
|
|
3136
3586
|
enumerable: true,
|
|
3137
|
-
get: function () { return
|
|
3587
|
+
get: function () { return chunkG44BUG6G_cjs.pageMeta; }
|
|
3138
3588
|
});
|
|
3139
3589
|
Object.defineProperty(exports, "parseSlotKey", {
|
|
3140
3590
|
enumerable: true,
|
|
3141
|
-
get: function () { return
|
|
3591
|
+
get: function () { return chunkG44BUG6G_cjs.parseSlotKey; }
|
|
3142
3592
|
});
|
|
3143
3593
|
Object.defineProperty(exports, "pickByWeight", {
|
|
3144
3594
|
enumerable: true,
|
|
3145
|
-
get: function () { return
|
|
3595
|
+
get: function () { return chunkG44BUG6G_cjs.pickByWeight; }
|
|
3146
3596
|
});
|
|
3147
3597
|
Object.defineProperty(exports, "resolveExperiments", {
|
|
3148
3598
|
enumerable: true,
|
|
3149
|
-
get: function () { return
|
|
3599
|
+
get: function () { return chunkG44BUG6G_cjs.resolveExperiments; }
|
|
3150
3600
|
});
|
|
3151
3601
|
Object.defineProperty(exports, "resolveLocale", {
|
|
3152
3602
|
enumerable: true,
|
|
3153
|
-
get: function () { return
|
|
3603
|
+
get: function () { return chunkG44BUG6G_cjs.resolveLocale; }
|
|
3154
3604
|
});
|
|
3155
3605
|
Object.defineProperty(exports, "resolveProduct", {
|
|
3156
3606
|
enumerable: true,
|
|
3157
|
-
get: function () { return
|
|
3607
|
+
get: function () { return chunkG44BUG6G_cjs.resolveProduct; }
|
|
3158
3608
|
});
|
|
3159
3609
|
Object.defineProperty(exports, "resolveRoute", {
|
|
3160
3610
|
enumerable: true,
|
|
3161
|
-
get: function () { return
|
|
3611
|
+
get: function () { return chunkG44BUG6G_cjs.resolveRoute; }
|
|
3162
3612
|
});
|
|
3163
3613
|
Object.defineProperty(exports, "validateExperiments", {
|
|
3164
3614
|
enumerable: true,
|
|
3165
|
-
get: function () { return
|
|
3615
|
+
get: function () { return chunkG44BUG6G_cjs.validateExperiments; }
|
|
3166
3616
|
});
|
|
3167
3617
|
Object.defineProperty(exports, "INLINE_SURFACES", {
|
|
3168
3618
|
enumerable: true,
|
|
3169
|
-
get: function () { return
|
|
3619
|
+
get: function () { return chunkQMAZGLGV_cjs.INLINE_SURFACES; }
|
|
3170
3620
|
});
|
|
3171
3621
|
Object.defineProperty(exports, "PROVIDER_PROFILES", {
|
|
3172
3622
|
enumerable: true,
|
|
3173
|
-
get: function () { return
|
|
3623
|
+
get: function () { return chunkQMAZGLGV_cjs.PROVIDER_PROFILES; }
|
|
3174
3624
|
});
|
|
3175
3625
|
Object.defineProperty(exports, "checkoutError", {
|
|
3176
3626
|
enumerable: true,
|
|
3177
|
-
get: function () { return
|
|
3627
|
+
get: function () { return chunkQMAZGLGV_cjs.checkoutError; }
|
|
3178
3628
|
});
|
|
3179
3629
|
Object.defineProperty(exports, "isInlineSurface", {
|
|
3180
3630
|
enumerable: true,
|
|
3181
|
-
get: function () { return
|
|
3631
|
+
get: function () { return chunkQMAZGLGV_cjs.isInlineSurface; }
|
|
3182
3632
|
});
|
|
3183
3633
|
Object.defineProperty(exports, "isMerchantOfRecord", {
|
|
3184
3634
|
enumerable: true,
|
|
3185
|
-
get: function () { return
|
|
3635
|
+
get: function () { return chunkQMAZGLGV_cjs.isMerchantOfRecord; }
|
|
3186
3636
|
});
|
|
3187
3637
|
Object.defineProperty(exports, "isOrchestrator", {
|
|
3188
3638
|
enumerable: true,
|
|
3189
|
-
get: function () { return
|
|
3639
|
+
get: function () { return chunkQMAZGLGV_cjs.isOrchestrator; }
|
|
3190
3640
|
});
|
|
3191
3641
|
Object.defineProperty(exports, "surfacesFor", {
|
|
3192
3642
|
enumerable: true,
|
|
3193
|
-
get: function () { return
|
|
3643
|
+
get: function () { return chunkQMAZGLGV_cjs.surfacesFor; }
|
|
3194
3644
|
});
|
|
3195
3645
|
Object.defineProperty(exports, "validateCheckout", {
|
|
3196
3646
|
enumerable: true,
|
|
3197
|
-
get: function () { return
|
|
3647
|
+
get: function () { return chunkQMAZGLGV_cjs.validateCheckout; }
|
|
3198
3648
|
});
|
|
3199
3649
|
Object.defineProperty(exports, "validateUpsell", {
|
|
3200
3650
|
enumerable: true,
|
|
3201
|
-
get: function () { return
|
|
3651
|
+
get: function () { return chunkQMAZGLGV_cjs.validateUpsell; }
|
|
3202
3652
|
});
|
|
3203
3653
|
exports.AssetsContext = AssetsContext;
|
|
3204
3654
|
exports.AssetsProvider = AssetsProvider;
|