@founderhq/journeys 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/_tsup-dts-rollup.d.cts +10 -4
- package/dist/_tsup-dts-rollup.d.ts +10 -4
- package/dist/index.cjs +216 -85
- package/dist/index.js +216 -85
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -208,6 +208,12 @@ Capture is enabled by default after access validation succeeds. Pass `capture={f
|
|
|
208
208
|
|
|
209
209
|
`apiKey`, `journeyId`, and the FounderHQ API URL are inherited from the root props and are not repeated inside `capture`.
|
|
210
210
|
|
|
211
|
+
Capture events are batched and persisted locally with a TTL. The runtime flushes
|
|
212
|
+
immediately for completion events, and browser embeds also try an exit-safe
|
|
213
|
+
`sendBeacon`/`keepalive` flush on `visibilitychange` and `pagehide`. Beacon
|
|
214
|
+
delivery is treated as best-effort; FounderHQ uses event IDs server-side so a
|
|
215
|
+
later retry can safely duplicate a queued event.
|
|
216
|
+
|
|
211
217
|
## Config Shape
|
|
212
218
|
|
|
213
219
|
A journey config is centered around:
|
|
@@ -323,7 +323,7 @@ declare type BlockPropsMap = {
|
|
|
323
323
|
export { BlockPropsMap }
|
|
324
324
|
export { BlockPropsMap as BlockPropsMap_alias_1 }
|
|
325
325
|
|
|
326
|
-
export declare function BlockRenderer({ blocks, layout, answers, onNext, onBack, onGoToStep, scrollViewportRef, wrapInScrollViewport, }: BlockRendererProps): JSX.Element;
|
|
326
|
+
export declare function BlockRenderer({ blocks, layout, answers, onNext, onBack, onGoToStep, isLastStep, scrollViewportRef, wrapInScrollViewport, }: BlockRendererProps): JSX.Element;
|
|
327
327
|
|
|
328
328
|
declare type BlockRendererProps = {
|
|
329
329
|
blocks: BlockConfig[];
|
|
@@ -332,6 +332,7 @@ declare type BlockRendererProps = {
|
|
|
332
332
|
onNext: () => void;
|
|
333
333
|
onBack: () => void;
|
|
334
334
|
onGoToStep?: (stepId: string) => void;
|
|
335
|
+
isLastStep?: boolean;
|
|
335
336
|
scrollViewportRef?: Ref<HTMLDivElement>;
|
|
336
337
|
wrapInScrollViewport?: boolean;
|
|
337
338
|
};
|
|
@@ -355,6 +356,8 @@ declare type ButtonAction = {
|
|
|
355
356
|
type: "link";
|
|
356
357
|
url: string;
|
|
357
358
|
external?: boolean;
|
|
359
|
+
/** Final-step links complete before navigating by default. Set false to opt out, or true to force this on non-final steps. */
|
|
360
|
+
completeBeforeNavigate?: boolean;
|
|
358
361
|
} | {
|
|
359
362
|
type: "purchase";
|
|
360
363
|
planVariable?: string;
|
|
@@ -370,7 +373,7 @@ declare type ButtonAction = {
|
|
|
370
373
|
export { ButtonAction }
|
|
371
374
|
export { ButtonAction as ButtonAction_alias_1 }
|
|
372
375
|
|
|
373
|
-
export declare function ButtonBlock({ label, action, variant, className, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, disabled, }: ButtonBlockComponentProps): JSX.Element;
|
|
376
|
+
export declare function ButtonBlock({ label, action, variant, className, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, disabled, isLastStep, }: ButtonBlockComponentProps): JSX.Element;
|
|
374
377
|
|
|
375
378
|
declare type ButtonBlockComponentProps = ButtonBlockProps & {
|
|
376
379
|
onNext?: () => void;
|
|
@@ -383,6 +386,7 @@ declare type ButtonBlockComponentProps = ButtonBlockProps & {
|
|
|
383
386
|
onOpenDiscountCode?: () => void;
|
|
384
387
|
/** Injected by BlockRenderer when sibling input blocks have unmet validation. */
|
|
385
388
|
disabled?: boolean;
|
|
389
|
+
isLastStep?: boolean;
|
|
386
390
|
};
|
|
387
391
|
|
|
388
392
|
declare type ButtonBlockProps = {
|
|
@@ -572,13 +576,14 @@ declare type ColumnConfig = {
|
|
|
572
576
|
export { ColumnConfig }
|
|
573
577
|
export { ColumnConfig as ColumnConfig_alias_1 }
|
|
574
578
|
|
|
575
|
-
export declare function ColumnsBlock({ columns, gap, responsive, className, answers, onNext, onBack, onGoToStep, }: ColumnsBlockComponentProps): JSX.Element;
|
|
579
|
+
export declare function ColumnsBlock({ columns, gap, responsive, className, answers, onNext, onBack, onGoToStep, isLastStep, }: ColumnsBlockComponentProps): JSX.Element;
|
|
576
580
|
|
|
577
581
|
declare type ColumnsBlockComponentProps = ColumnsBlockProps & {
|
|
578
582
|
answers?: JourneyAnswers;
|
|
579
583
|
onNext?: () => void;
|
|
580
584
|
onBack?: () => void;
|
|
581
585
|
onGoToStep?: (stepId: string) => void;
|
|
586
|
+
isLastStep?: boolean;
|
|
582
587
|
};
|
|
583
588
|
|
|
584
589
|
declare type ColumnsBlockProps = {
|
|
@@ -979,7 +984,7 @@ declare type ImageBlockProps = {
|
|
|
979
984
|
export { ImageBlockProps }
|
|
980
985
|
export { ImageBlockProps as ImageBlockProps_alias_1 }
|
|
981
986
|
|
|
982
|
-
export declare function InfoPageStep({ config, onNext }: StepComponentProps): JSX.Element | null;
|
|
987
|
+
export declare function InfoPageStep({ config, onNext, isLastStep, }: StepComponentProps): JSX.Element | null;
|
|
983
988
|
|
|
984
989
|
export declare function Input({ className, type, ...props }: React_2.ComponentProps<"input">): React_2.JSX.Element;
|
|
985
990
|
|
|
@@ -1863,6 +1868,7 @@ declare type StepComponentProps = {
|
|
|
1863
1868
|
answer: StepAnswer | undefined;
|
|
1864
1869
|
onAnswer: (answer: StepAnswer) => void;
|
|
1865
1870
|
onNext: () => void;
|
|
1871
|
+
isLastStep?: boolean;
|
|
1866
1872
|
};
|
|
1867
1873
|
export { StepComponentProps }
|
|
1868
1874
|
export { StepComponentProps as StepComponentProps_alias_1 }
|
|
@@ -323,7 +323,7 @@ declare type BlockPropsMap = {
|
|
|
323
323
|
export { BlockPropsMap }
|
|
324
324
|
export { BlockPropsMap as BlockPropsMap_alias_1 }
|
|
325
325
|
|
|
326
|
-
export declare function BlockRenderer({ blocks, layout, answers, onNext, onBack, onGoToStep, scrollViewportRef, wrapInScrollViewport, }: BlockRendererProps): JSX.Element;
|
|
326
|
+
export declare function BlockRenderer({ blocks, layout, answers, onNext, onBack, onGoToStep, isLastStep, scrollViewportRef, wrapInScrollViewport, }: BlockRendererProps): JSX.Element;
|
|
327
327
|
|
|
328
328
|
declare type BlockRendererProps = {
|
|
329
329
|
blocks: BlockConfig[];
|
|
@@ -332,6 +332,7 @@ declare type BlockRendererProps = {
|
|
|
332
332
|
onNext: () => void;
|
|
333
333
|
onBack: () => void;
|
|
334
334
|
onGoToStep?: (stepId: string) => void;
|
|
335
|
+
isLastStep?: boolean;
|
|
335
336
|
scrollViewportRef?: Ref<HTMLDivElement>;
|
|
336
337
|
wrapInScrollViewport?: boolean;
|
|
337
338
|
};
|
|
@@ -355,6 +356,8 @@ declare type ButtonAction = {
|
|
|
355
356
|
type: "link";
|
|
356
357
|
url: string;
|
|
357
358
|
external?: boolean;
|
|
359
|
+
/** Final-step links complete before navigating by default. Set false to opt out, or true to force this on non-final steps. */
|
|
360
|
+
completeBeforeNavigate?: boolean;
|
|
358
361
|
} | {
|
|
359
362
|
type: "purchase";
|
|
360
363
|
planVariable?: string;
|
|
@@ -370,7 +373,7 @@ declare type ButtonAction = {
|
|
|
370
373
|
export { ButtonAction }
|
|
371
374
|
export { ButtonAction as ButtonAction_alias_1 }
|
|
372
375
|
|
|
373
|
-
export declare function ButtonBlock({ label, action, variant, className, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, disabled, }: ButtonBlockComponentProps): JSX.Element;
|
|
376
|
+
export declare function ButtonBlock({ label, action, variant, className, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, disabled, isLastStep, }: ButtonBlockComponentProps): JSX.Element;
|
|
374
377
|
|
|
375
378
|
declare type ButtonBlockComponentProps = ButtonBlockProps & {
|
|
376
379
|
onNext?: () => void;
|
|
@@ -383,6 +386,7 @@ declare type ButtonBlockComponentProps = ButtonBlockProps & {
|
|
|
383
386
|
onOpenDiscountCode?: () => void;
|
|
384
387
|
/** Injected by BlockRenderer when sibling input blocks have unmet validation. */
|
|
385
388
|
disabled?: boolean;
|
|
389
|
+
isLastStep?: boolean;
|
|
386
390
|
};
|
|
387
391
|
|
|
388
392
|
declare type ButtonBlockProps = {
|
|
@@ -572,13 +576,14 @@ declare type ColumnConfig = {
|
|
|
572
576
|
export { ColumnConfig }
|
|
573
577
|
export { ColumnConfig as ColumnConfig_alias_1 }
|
|
574
578
|
|
|
575
|
-
export declare function ColumnsBlock({ columns, gap, responsive, className, answers, onNext, onBack, onGoToStep, }: ColumnsBlockComponentProps): JSX.Element;
|
|
579
|
+
export declare function ColumnsBlock({ columns, gap, responsive, className, answers, onNext, onBack, onGoToStep, isLastStep, }: ColumnsBlockComponentProps): JSX.Element;
|
|
576
580
|
|
|
577
581
|
declare type ColumnsBlockComponentProps = ColumnsBlockProps & {
|
|
578
582
|
answers?: JourneyAnswers;
|
|
579
583
|
onNext?: () => void;
|
|
580
584
|
onBack?: () => void;
|
|
581
585
|
onGoToStep?: (stepId: string) => void;
|
|
586
|
+
isLastStep?: boolean;
|
|
582
587
|
};
|
|
583
588
|
|
|
584
589
|
declare type ColumnsBlockProps = {
|
|
@@ -979,7 +984,7 @@ declare type ImageBlockProps = {
|
|
|
979
984
|
export { ImageBlockProps }
|
|
980
985
|
export { ImageBlockProps as ImageBlockProps_alias_1 }
|
|
981
986
|
|
|
982
|
-
export declare function InfoPageStep({ config, onNext }: StepComponentProps): JSX.Element | null;
|
|
987
|
+
export declare function InfoPageStep({ config, onNext, isLastStep, }: StepComponentProps): JSX.Element | null;
|
|
983
988
|
|
|
984
989
|
export declare function Input({ className, type, ...props }: React_2.ComponentProps<"input">): React_2.JSX.Element;
|
|
985
990
|
|
|
@@ -1863,6 +1868,7 @@ declare type StepComponentProps = {
|
|
|
1863
1868
|
answer: StepAnswer | undefined;
|
|
1864
1869
|
onAnswer: (answer: StepAnswer) => void;
|
|
1865
1870
|
onNext: () => void;
|
|
1871
|
+
isLastStep?: boolean;
|
|
1866
1872
|
};
|
|
1867
1873
|
export { StepComponentProps }
|
|
1868
1874
|
export { StepComponentProps as StepComponentProps_alias_1 }
|
package/dist/index.cjs
CHANGED
|
@@ -2698,9 +2698,11 @@ function ButtonBlock({
|
|
|
2698
2698
|
onGoToStep,
|
|
2699
2699
|
onPurchase,
|
|
2700
2700
|
onOpenDiscountCode,
|
|
2701
|
-
disabled
|
|
2701
|
+
disabled,
|
|
2702
|
+
isLastStep
|
|
2702
2703
|
}) {
|
|
2703
2704
|
const handleClick = () => {
|
|
2705
|
+
var _a;
|
|
2704
2706
|
if (!action) {
|
|
2705
2707
|
onNext == null ? void 0 : onNext();
|
|
2706
2708
|
return;
|
|
@@ -2720,6 +2722,9 @@ function ButtonBlock({
|
|
|
2720
2722
|
}
|
|
2721
2723
|
break;
|
|
2722
2724
|
case "link":
|
|
2725
|
+
if ((_a = action.completeBeforeNavigate) != null ? _a : isLastStep) {
|
|
2726
|
+
onNext == null ? void 0 : onNext();
|
|
2727
|
+
}
|
|
2723
2728
|
if (action.external) {
|
|
2724
2729
|
window.open(action.url, "_blank", "noopener,noreferrer");
|
|
2725
2730
|
} else {
|
|
@@ -4210,7 +4215,8 @@ function ColumnsBlock({
|
|
|
4210
4215
|
answers,
|
|
4211
4216
|
onNext,
|
|
4212
4217
|
onBack,
|
|
4213
|
-
onGoToStep
|
|
4218
|
+
onGoToStep,
|
|
4219
|
+
isLastStep
|
|
4214
4220
|
}) {
|
|
4215
4221
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "jy-columns-container", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4216
4222
|
"div",
|
|
@@ -4236,6 +4242,7 @@ function ColumnsBlock({
|
|
|
4236
4242
|
onBack: onBack != null ? onBack : (() => {
|
|
4237
4243
|
}),
|
|
4238
4244
|
onGoToStep,
|
|
4245
|
+
isLastStep,
|
|
4239
4246
|
wrapInScrollViewport: false
|
|
4240
4247
|
}
|
|
4241
4248
|
)
|
|
@@ -7911,7 +7918,11 @@ function HiddenBlockSlot({ block }) {
|
|
|
7911
7918
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7912
7919
|
"div",
|
|
7913
7920
|
{
|
|
7914
|
-
className: cn(
|
|
7921
|
+
className: cn(
|
|
7922
|
+
widthClass,
|
|
7923
|
+
resolvedMaxWidth ? "mx-auto" : void 0,
|
|
7924
|
+
block.className
|
|
7925
|
+
),
|
|
7915
7926
|
style: __spreadProps(__spreadValues(__spreadValues({}, resolvedMaxWidth ? { maxWidth: resolvedMaxWidth } : null), block.style), {
|
|
7916
7927
|
visibility: "hidden"
|
|
7917
7928
|
}),
|
|
@@ -8094,7 +8105,7 @@ function resolveAppliedDiscountForDialog(answers, discountVariable, planId) {
|
|
|
8094
8105
|
if (!planId) return answer.planId ? void 0 : answer;
|
|
8095
8106
|
return discountAppliesToPlan(answer, planId) ? answer : void 0;
|
|
8096
8107
|
}
|
|
8097
|
-
function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, inputsValid) {
|
|
8108
|
+
function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, inputsValid, isLastStep) {
|
|
8098
8109
|
var _a, _b;
|
|
8099
8110
|
const conditionMet = !block.condition || evaluateCondition(block.condition, answers);
|
|
8100
8111
|
const hasExitAnim = ((_a = block.exitAnimation) == null ? void 0 : _a.preset) && block.exitAnimation.preset !== "none";
|
|
@@ -8121,8 +8132,9 @@ function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToS
|
|
|
8121
8132
|
onGoToStep,
|
|
8122
8133
|
onPurchase,
|
|
8123
8134
|
onOpenDiscountCode,
|
|
8124
|
-
disabled: buttonDisabledForValidity(resolvedProps, inputsValid)
|
|
8125
|
-
|
|
8135
|
+
disabled: buttonDisabledForValidity(resolvedProps, inputsValid),
|
|
8136
|
+
isLastStep
|
|
8137
|
+
}) : block.type === "columns" ? __spreadProps(__spreadValues({}, resolvedProps), { answers, onNext, onBack, onGoToStep, isLastStep }) : block.type === "gravity_bin" ? __spreadProps(__spreadValues({}, resolvedProps), { answers }) : resolvedProps;
|
|
8126
8138
|
const idx = visibleIndexRef.current;
|
|
8127
8139
|
visibleIndexRef.current++;
|
|
8128
8140
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -8144,6 +8156,7 @@ function BlockRenderer({
|
|
|
8144
8156
|
onNext,
|
|
8145
8157
|
onBack,
|
|
8146
8158
|
onGoToStep,
|
|
8159
|
+
isLastStep,
|
|
8147
8160
|
scrollViewportRef,
|
|
8148
8161
|
wrapInScrollViewport = true
|
|
8149
8162
|
}) {
|
|
@@ -8229,7 +8242,8 @@ function BlockRenderer({
|
|
|
8229
8242
|
onGoToStep,
|
|
8230
8243
|
onPurchase,
|
|
8231
8244
|
onOpenDiscountCode,
|
|
8232
|
-
inputsValid
|
|
8245
|
+
inputsValid,
|
|
8246
|
+
isLastStep
|
|
8233
8247
|
);
|
|
8234
8248
|
};
|
|
8235
8249
|
if (!wrapInScrollViewport && stickyBlocks.length === 0) {
|
|
@@ -8252,7 +8266,11 @@ function BlockRenderer({
|
|
|
8252
8266
|
"div",
|
|
8253
8267
|
{
|
|
8254
8268
|
className: layoutClasses(layout),
|
|
8255
|
-
style: {
|
|
8269
|
+
style: {
|
|
8270
|
+
gap: `${gap}rem`,
|
|
8271
|
+
maxWidth: layout == null ? void 0 : layout.maxWidth,
|
|
8272
|
+
width: "100%"
|
|
8273
|
+
},
|
|
8256
8274
|
children: contentBlocks.map((block, i) => renderOne(block, i))
|
|
8257
8275
|
}
|
|
8258
8276
|
)
|
|
@@ -8263,7 +8281,11 @@ function BlockRenderer({
|
|
|
8263
8281
|
) }) : null
|
|
8264
8282
|
] });
|
|
8265
8283
|
}
|
|
8266
|
-
function InfoPageStep({
|
|
8284
|
+
function InfoPageStep({
|
|
8285
|
+
config,
|
|
8286
|
+
onNext,
|
|
8287
|
+
isLastStep
|
|
8288
|
+
}) {
|
|
8267
8289
|
var _a;
|
|
8268
8290
|
const { answers } = useJourneyState();
|
|
8269
8291
|
const { goBack, goToStep } = useJourneyActions();
|
|
@@ -8271,9 +8293,7 @@ function InfoPageStep({ config, onNext }) {
|
|
|
8271
8293
|
const sortedTimelineEvents = React.useMemo(
|
|
8272
8294
|
() => {
|
|
8273
8295
|
var _a2, _b;
|
|
8274
|
-
return [...(_b = (_a2 = config.scrollTimeline) == null ? void 0 : _a2.events) != null ? _b : []].sort(
|
|
8275
|
-
(a, b) => a.at - b.at
|
|
8276
|
-
);
|
|
8296
|
+
return [...(_b = (_a2 = config.scrollTimeline) == null ? void 0 : _a2.events) != null ? _b : []].sort((a, b) => a.at - b.at);
|
|
8277
8297
|
},
|
|
8278
8298
|
[(_a = config.scrollTimeline) == null ? void 0 : _a.events]
|
|
8279
8299
|
);
|
|
@@ -8453,6 +8473,7 @@ function InfoPageStep({ config, onNext }) {
|
|
|
8453
8473
|
onNext,
|
|
8454
8474
|
onBack: goBack,
|
|
8455
8475
|
onGoToStep: goToStep,
|
|
8476
|
+
isLastStep,
|
|
8456
8477
|
scrollViewportRef
|
|
8457
8478
|
}
|
|
8458
8479
|
);
|
|
@@ -9320,7 +9341,7 @@ var STEP_REGISTRY = {
|
|
|
9320
9341
|
function StepRenderer({ config }) {
|
|
9321
9342
|
var _a, _b;
|
|
9322
9343
|
const { answers } = useJourneyState();
|
|
9323
|
-
const { setAnswer, goNext } = useJourneyActions();
|
|
9344
|
+
const { setAnswer, goNext, isLastStep } = useJourneyActions();
|
|
9324
9345
|
const resolvedConfig = React.useMemo(() => {
|
|
9325
9346
|
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
9326
9347
|
const hasTemplates = ((_a2 = config.preface) == null ? void 0 : _a2.includes("${")) || ((_b2 = config.question) == null ? void 0 : _b2.includes("${")) || ((_c = config.description) == null ? void 0 : _c.includes("${")) || ((_d = config.buttonText) == null ? void 0 : _d.includes("${")) || ((_e = config.footerText) == null ? void 0 : _e.includes("${")) || ((_g = (_f = config.swipeLabels) == null ? void 0 : _f.yes) == null ? void 0 : _g.includes("${")) || ((_i = (_h = config.swipeLabels) == null ? void 0 : _h.no) == null ? void 0 : _i.includes("${")) || ((_j = config.swipeCards) != null ? _j : []).some(
|
|
@@ -9373,7 +9394,8 @@ function StepRenderer({ config }) {
|
|
|
9373
9394
|
}
|
|
9374
9395
|
setAnswer(answerKey, answer);
|
|
9375
9396
|
},
|
|
9376
|
-
onNext: () => goNext()
|
|
9397
|
+
onNext: () => goNext(),
|
|
9398
|
+
isLastStep: isLastStep()
|
|
9377
9399
|
}
|
|
9378
9400
|
);
|
|
9379
9401
|
}
|
|
@@ -9671,20 +9693,15 @@ function JourneyShell({ className, theme } = {}) {
|
|
|
9671
9693
|
] });
|
|
9672
9694
|
}
|
|
9673
9695
|
var JOURNEY_LIBRARY_NAME = "@founderhq/journeys";
|
|
9674
|
-
var JOURNEY_LIBRARY_VERSION = "0.4.
|
|
9696
|
+
var JOURNEY_LIBRARY_VERSION = "0.4.2";
|
|
9697
|
+
var DEFAULT_QUEUE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9698
|
+
var MAX_QUEUED_CAPTURE_EVENTS = 100;
|
|
9699
|
+
var MAX_BEACON_BYTES = 60 * 1024;
|
|
9675
9700
|
function randomId(prefix) {
|
|
9676
9701
|
const cryptoRef = globalThis.crypto;
|
|
9677
9702
|
if (cryptoRef == null ? void 0 : cryptoRef.randomUUID) return `${prefix}_${cryptoRef.randomUUID()}`;
|
|
9678
9703
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
9679
9704
|
}
|
|
9680
|
-
function readJson(storage, key, fallback) {
|
|
9681
|
-
try {
|
|
9682
|
-
const raw = storage.getItem(key);
|
|
9683
|
-
return raw ? JSON.parse(raw) : fallback;
|
|
9684
|
-
} catch (e) {
|
|
9685
|
-
return fallback;
|
|
9686
|
-
}
|
|
9687
|
-
}
|
|
9688
9705
|
function getStoredId(storage, key, prefix) {
|
|
9689
9706
|
try {
|
|
9690
9707
|
const existing = storage.getItem(key);
|
|
@@ -9696,6 +9713,64 @@ function getStoredId(storage, key, prefix) {
|
|
|
9696
9713
|
return randomId(prefix);
|
|
9697
9714
|
}
|
|
9698
9715
|
}
|
|
9716
|
+
function safeBrowserStorage(name) {
|
|
9717
|
+
var _a;
|
|
9718
|
+
if (typeof window === "undefined") return null;
|
|
9719
|
+
try {
|
|
9720
|
+
return (_a = window[name]) != null ? _a : null;
|
|
9721
|
+
} catch (e) {
|
|
9722
|
+
return null;
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
function isQueuedCaptureEvent(value) {
|
|
9726
|
+
if (!isRecord(value)) return false;
|
|
9727
|
+
const event = value.event;
|
|
9728
|
+
return isRecord(event) && typeof event.id === "string" && typeof event.type === "string" && typeof event.occurredAt === "string" && typeof value.attempts === "number" && typeof value.createdAt === "number";
|
|
9729
|
+
}
|
|
9730
|
+
function pruneQueuedEvents(queue, now = Date.now(), ttlMs = DEFAULT_QUEUE_TTL_MS) {
|
|
9731
|
+
const cutoff = now - ttlMs;
|
|
9732
|
+
return queue.filter((item) => item.createdAt >= cutoff);
|
|
9733
|
+
}
|
|
9734
|
+
function readPersistedCaptureQueue(storage, key) {
|
|
9735
|
+
if (!storage) return null;
|
|
9736
|
+
try {
|
|
9737
|
+
const raw = storage.getItem(key);
|
|
9738
|
+
if (!raw) return null;
|
|
9739
|
+
const parsed = JSON.parse(raw);
|
|
9740
|
+
if (typeof parsed.visitorId !== "string" || typeof parsed.clientSessionId !== "string") {
|
|
9741
|
+
return null;
|
|
9742
|
+
}
|
|
9743
|
+
const queue = Array.isArray(parsed.queue) ? pruneQueuedEvents(parsed.queue.filter(isQueuedCaptureEvent)) : [];
|
|
9744
|
+
return {
|
|
9745
|
+
visitorId: parsed.visitorId,
|
|
9746
|
+
clientSessionId: parsed.clientSessionId,
|
|
9747
|
+
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
|
|
9748
|
+
queue
|
|
9749
|
+
};
|
|
9750
|
+
} catch (e) {
|
|
9751
|
+
return null;
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9754
|
+
function persistCaptureQueue(storage, runtime, queue) {
|
|
9755
|
+
if (!storage) return;
|
|
9756
|
+
try {
|
|
9757
|
+
const pruned = pruneQueuedEvents(queue).slice(-MAX_QUEUED_CAPTURE_EVENTS);
|
|
9758
|
+
if (pruned.length === 0) {
|
|
9759
|
+
storage.removeItem(runtime.queueKey);
|
|
9760
|
+
return;
|
|
9761
|
+
}
|
|
9762
|
+
storage.setItem(
|
|
9763
|
+
runtime.queueKey,
|
|
9764
|
+
JSON.stringify({
|
|
9765
|
+
visitorId: runtime.visitorId,
|
|
9766
|
+
clientSessionId: runtime.clientSessionId,
|
|
9767
|
+
updatedAt: Date.now(),
|
|
9768
|
+
queue: pruned
|
|
9769
|
+
})
|
|
9770
|
+
);
|
|
9771
|
+
} catch (e) {
|
|
9772
|
+
}
|
|
9773
|
+
}
|
|
9699
9774
|
function isRecord(value) {
|
|
9700
9775
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9701
9776
|
}
|
|
@@ -9796,6 +9871,54 @@ function captureContext(capture, runtime) {
|
|
|
9796
9871
|
screen: screenRef ? { width: screenRef.width, height: screenRef.height } : void 0
|
|
9797
9872
|
}, customContext));
|
|
9798
9873
|
}
|
|
9874
|
+
function captureUrl(capture) {
|
|
9875
|
+
var _a;
|
|
9876
|
+
const baseUrl = (_a = capture.baseUrl) != null ? _a : "https://getfounderhq.com";
|
|
9877
|
+
return `${baseUrl}/api/v1/journeys/${encodeURIComponent(
|
|
9878
|
+
capture.journeyId
|
|
9879
|
+
)}/capture`;
|
|
9880
|
+
}
|
|
9881
|
+
function byteLength(value) {
|
|
9882
|
+
try {
|
|
9883
|
+
return new TextEncoder().encode(value).byteLength;
|
|
9884
|
+
} catch (e) {
|
|
9885
|
+
return value.length;
|
|
9886
|
+
}
|
|
9887
|
+
}
|
|
9888
|
+
function safeSendBeacon() {
|
|
9889
|
+
if (typeof navigator === "undefined") return null;
|
|
9890
|
+
const beacon = navigator.sendBeacon;
|
|
9891
|
+
return typeof beacon === "function" ? beacon.bind(navigator) : null;
|
|
9892
|
+
}
|
|
9893
|
+
async function sendCaptureBatch(capture, runtime, batch, options = {}) {
|
|
9894
|
+
const body = JSON.stringify(__spreadProps(__spreadValues({}, options.preferBeacon ? { apiKey: capture.apiKey } : {}), {
|
|
9895
|
+
clientSessionId: runtime.clientSessionId,
|
|
9896
|
+
visitorId: runtime.visitorId,
|
|
9897
|
+
context: captureContext(capture, runtime),
|
|
9898
|
+
events: batch.map((item) => item.event)
|
|
9899
|
+
}));
|
|
9900
|
+
const url = captureUrl(capture);
|
|
9901
|
+
if (options.preferBeacon && byteLength(body) <= MAX_BEACON_BYTES) {
|
|
9902
|
+
const beacon = safeSendBeacon();
|
|
9903
|
+
if (beacon == null ? void 0 : beacon(url, body)) {
|
|
9904
|
+
return "queued";
|
|
9905
|
+
}
|
|
9906
|
+
}
|
|
9907
|
+
try {
|
|
9908
|
+
const response = await fetch(url, {
|
|
9909
|
+
method: "POST",
|
|
9910
|
+
headers: {
|
|
9911
|
+
Authorization: `Bearer ${capture.apiKey}`,
|
|
9912
|
+
"Content-Type": "application/json"
|
|
9913
|
+
},
|
|
9914
|
+
body,
|
|
9915
|
+
keepalive: batch.length <= 5 || options.preferBeacon === true
|
|
9916
|
+
});
|
|
9917
|
+
return response.ok ? "acknowledged" : "failed";
|
|
9918
|
+
} catch (e) {
|
|
9919
|
+
return "failed";
|
|
9920
|
+
}
|
|
9921
|
+
}
|
|
9799
9922
|
function toJsonRecord(value) {
|
|
9800
9923
|
return isRecord(value) ? value : {};
|
|
9801
9924
|
}
|
|
@@ -9941,30 +10064,34 @@ function useJourneyCapture(params) {
|
|
|
9941
10064
|
captureRef.current = params.capture;
|
|
9942
10065
|
const persistQueue = React.useCallback(() => {
|
|
9943
10066
|
const runtime = runtimeRef.current;
|
|
9944
|
-
if (!runtime
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
} catch (e) {
|
|
9951
|
-
}
|
|
10067
|
+
if (!runtime) return;
|
|
10068
|
+
persistCaptureQueue(
|
|
10069
|
+
safeBrowserStorage("localStorage"),
|
|
10070
|
+
runtime,
|
|
10071
|
+
queueRef.current
|
|
10072
|
+
);
|
|
9952
10073
|
}, []);
|
|
9953
10074
|
const ensureRuntime = React.useCallback((capture) => {
|
|
10075
|
+
var _a, _b;
|
|
9954
10076
|
if (typeof window === "undefined") return null;
|
|
10077
|
+
const queueKey = `fhq_journey_queue:v2:${capture.apiKey.slice(
|
|
10078
|
+
0,
|
|
10079
|
+
16
|
|
10080
|
+
)}:${capture.journeyId}`;
|
|
9955
10081
|
const existing = runtimeRef.current;
|
|
9956
|
-
if ((existing == null ? void 0 : existing.journeyId) === capture.journeyId
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
);
|
|
9962
|
-
const
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
10082
|
+
if ((existing == null ? void 0 : existing.journeyId) === capture.journeyId && existing.queueKey === queueKey) {
|
|
10083
|
+
return existing;
|
|
10084
|
+
}
|
|
10085
|
+
const local = safeBrowserStorage("localStorage");
|
|
10086
|
+
const session = safeBrowserStorage("sessionStorage");
|
|
10087
|
+
const persisted = readPersistedCaptureQueue(local, queueKey);
|
|
10088
|
+
const visitorId = (_a = persisted == null ? void 0 : persisted.visitorId) != null ? _a : local ? getStoredId(local, "fhq_journey_visitor_id", "fhqv") : randomId("fhqv");
|
|
10089
|
+
const sessionKey = `fhq_journey_session:${capture.journeyId}`;
|
|
10090
|
+
const clientSessionId = persisted && persisted.queue.length > 0 ? persisted.clientSessionId : session ? getStoredId(session, sessionKey, "fhqs") : randomId("fhqs");
|
|
10091
|
+
try {
|
|
10092
|
+
session == null ? void 0 : session.setItem(sessionKey, clientSessionId);
|
|
10093
|
+
} catch (e) {
|
|
10094
|
+
}
|
|
9968
10095
|
const runtime = {
|
|
9969
10096
|
journeyId: capture.journeyId,
|
|
9970
10097
|
visitorId,
|
|
@@ -9972,51 +10099,46 @@ function useJourneyCapture(params) {
|
|
|
9972
10099
|
queueKey
|
|
9973
10100
|
};
|
|
9974
10101
|
runtimeRef.current = runtime;
|
|
9975
|
-
queueRef.current =
|
|
9976
|
-
sessionStorage,
|
|
9977
|
-
queueKey,
|
|
9978
|
-
[]
|
|
9979
|
-
);
|
|
10102
|
+
queueRef.current = (_b = persisted == null ? void 0 : persisted.queue) != null ? _b : [];
|
|
9980
10103
|
return runtime;
|
|
9981
10104
|
}, []);
|
|
9982
|
-
const flush = React.useCallback(async () => {
|
|
9983
|
-
var _a, _b
|
|
10105
|
+
const flush = React.useCallback(async (options = {}) => {
|
|
10106
|
+
var _a, _b;
|
|
9984
10107
|
const capture = captureRef.current;
|
|
9985
|
-
if (!capture || !configRef.current
|
|
10108
|
+
if (!capture || !configRef.current) return;
|
|
10109
|
+
if (!options.preferBeacon && flushingRef.current) return;
|
|
9986
10110
|
const runtime = ensureRuntime(capture);
|
|
9987
10111
|
if (!runtime || queueRef.current.length === 0) return;
|
|
9988
|
-
|
|
10112
|
+
queueRef.current = pruneQueuedEvents(queueRef.current);
|
|
9989
10113
|
const batchSize = Math.max(1, (_a = capture.batchSize) != null ? _a : 10);
|
|
9990
10114
|
const maxRetries = Math.max(1, (_b = capture.maxRetries) != null ? _b : 3);
|
|
9991
|
-
const batch = queueRef.current.slice(
|
|
9992
|
-
|
|
10115
|
+
const batch = queueRef.current.slice(
|
|
10116
|
+
0,
|
|
10117
|
+
options.preferBeacon ? Math.min(batchSize, 5) : batchSize
|
|
10118
|
+
);
|
|
10119
|
+
if (batch.length === 0) {
|
|
10120
|
+
persistQueue();
|
|
10121
|
+
return;
|
|
10122
|
+
}
|
|
10123
|
+
if (options.preferBeacon) {
|
|
10124
|
+
await sendCaptureBatch(capture, runtime, batch, options);
|
|
10125
|
+
persistQueue();
|
|
10126
|
+
return;
|
|
10127
|
+
}
|
|
10128
|
+
flushingRef.current = true;
|
|
9993
10129
|
try {
|
|
9994
|
-
const
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
},
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
visitorId: runtime.visitorId,
|
|
10007
|
-
context: captureContext(capture, runtime),
|
|
10008
|
-
events: batch.map((item) => item.event)
|
|
10009
|
-
}),
|
|
10010
|
-
keepalive: batch.length <= 5
|
|
10011
|
-
}
|
|
10012
|
-
);
|
|
10013
|
-
if (!response.ok) throw new Error(`Capture failed: ${response.status}`);
|
|
10014
|
-
queueRef.current = queueRef.current.slice(batch.length);
|
|
10015
|
-
} catch (e) {
|
|
10016
|
-
const failedIds = new Set(batch.map((item) => item.event.id));
|
|
10017
|
-
queueRef.current = queueRef.current.map(
|
|
10018
|
-
(item) => failedIds.has(item.event.id) ? __spreadProps(__spreadValues({}, item), { attempts: item.attempts + 1 }) : item
|
|
10019
|
-
).filter((item) => item.attempts < maxRetries);
|
|
10130
|
+
const result = await sendCaptureBatch(capture, runtime, batch);
|
|
10131
|
+
if (result === "acknowledged") {
|
|
10132
|
+
const sentIds = new Set(batch.map((item) => item.event.id));
|
|
10133
|
+
queueRef.current = queueRef.current.filter(
|
|
10134
|
+
(item) => !sentIds.has(item.event.id)
|
|
10135
|
+
);
|
|
10136
|
+
} else {
|
|
10137
|
+
const failedIds = new Set(batch.map((item) => item.event.id));
|
|
10138
|
+
queueRef.current = queueRef.current.map(
|
|
10139
|
+
(item) => failedIds.has(item.event.id) ? __spreadProps(__spreadValues({}, item), { attempts: item.attempts + 1 }) : item
|
|
10140
|
+
).filter((item) => item.attempts < maxRetries);
|
|
10141
|
+
}
|
|
10020
10142
|
} finally {
|
|
10021
10143
|
persistQueue();
|
|
10022
10144
|
flushingRef.current = false;
|
|
@@ -10032,15 +10154,18 @@ function useJourneyCapture(params) {
|
|
|
10032
10154
|
Math.max(1e3, (_a = capture.flushIntervalMs) != null ? _a : 3e3)
|
|
10033
10155
|
);
|
|
10034
10156
|
const handleVisibility = () => {
|
|
10035
|
-
if (document.visibilityState === "hidden")
|
|
10157
|
+
if (document.visibilityState === "hidden") {
|
|
10158
|
+
void flush({ preferBeacon: true });
|
|
10159
|
+
}
|
|
10036
10160
|
};
|
|
10037
|
-
|
|
10161
|
+
const handlePageHide = () => void flush({ preferBeacon: true });
|
|
10038
10162
|
document.addEventListener("visibilitychange", handleVisibility);
|
|
10163
|
+
window.addEventListener("pagehide", handlePageHide);
|
|
10039
10164
|
void flush();
|
|
10040
10165
|
return () => {
|
|
10041
10166
|
window.clearInterval(interval);
|
|
10042
|
-
window.removeEventListener("beforeunload", persistQueue);
|
|
10043
10167
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
10168
|
+
window.removeEventListener("pagehide", handlePageHide);
|
|
10044
10169
|
persistQueue();
|
|
10045
10170
|
};
|
|
10046
10171
|
}, [ensureRuntime, flush, persistQueue, params.capture]);
|
|
@@ -10056,11 +10181,17 @@ function useJourneyCapture(params) {
|
|
|
10056
10181
|
sequenceRef.current += 1;
|
|
10057
10182
|
queueRef.current.push({
|
|
10058
10183
|
event: toCaptureEvent(config, event, sequenceRef.current, runtime),
|
|
10059
|
-
attempts: 0
|
|
10184
|
+
attempts: 0,
|
|
10185
|
+
createdAt: Date.now()
|
|
10060
10186
|
});
|
|
10187
|
+
queueRef.current = pruneQueuedEvents(queueRef.current).slice(
|
|
10188
|
+
-MAX_QUEUED_CAPTURE_EVENTS
|
|
10189
|
+
);
|
|
10061
10190
|
persistQueue();
|
|
10062
10191
|
const batchSize = Math.max(1, (_b = capture.batchSize) != null ? _b : 10);
|
|
10063
|
-
if (queueRef.current.length >= batchSize)
|
|
10192
|
+
if (event.type === "complete" || queueRef.current.length >= batchSize) {
|
|
10193
|
+
void flush();
|
|
10194
|
+
}
|
|
10064
10195
|
},
|
|
10065
10196
|
[ensureRuntime, flush, persistQueue]
|
|
10066
10197
|
);
|
package/dist/index.js
CHANGED
|
@@ -2673,9 +2673,11 @@ function ButtonBlock({
|
|
|
2673
2673
|
onGoToStep,
|
|
2674
2674
|
onPurchase,
|
|
2675
2675
|
onOpenDiscountCode,
|
|
2676
|
-
disabled
|
|
2676
|
+
disabled,
|
|
2677
|
+
isLastStep
|
|
2677
2678
|
}) {
|
|
2678
2679
|
const handleClick = () => {
|
|
2680
|
+
var _a;
|
|
2679
2681
|
if (!action) {
|
|
2680
2682
|
onNext == null ? void 0 : onNext();
|
|
2681
2683
|
return;
|
|
@@ -2695,6 +2697,9 @@ function ButtonBlock({
|
|
|
2695
2697
|
}
|
|
2696
2698
|
break;
|
|
2697
2699
|
case "link":
|
|
2700
|
+
if ((_a = action.completeBeforeNavigate) != null ? _a : isLastStep) {
|
|
2701
|
+
onNext == null ? void 0 : onNext();
|
|
2702
|
+
}
|
|
2698
2703
|
if (action.external) {
|
|
2699
2704
|
window.open(action.url, "_blank", "noopener,noreferrer");
|
|
2700
2705
|
} else {
|
|
@@ -4185,7 +4190,8 @@ function ColumnsBlock({
|
|
|
4185
4190
|
answers,
|
|
4186
4191
|
onNext,
|
|
4187
4192
|
onBack,
|
|
4188
|
-
onGoToStep
|
|
4193
|
+
onGoToStep,
|
|
4194
|
+
isLastStep
|
|
4189
4195
|
}) {
|
|
4190
4196
|
return /* @__PURE__ */ jsx("div", { className: "jy-columns-container", children: /* @__PURE__ */ jsx(
|
|
4191
4197
|
"div",
|
|
@@ -4211,6 +4217,7 @@ function ColumnsBlock({
|
|
|
4211
4217
|
onBack: onBack != null ? onBack : (() => {
|
|
4212
4218
|
}),
|
|
4213
4219
|
onGoToStep,
|
|
4220
|
+
isLastStep,
|
|
4214
4221
|
wrapInScrollViewport: false
|
|
4215
4222
|
}
|
|
4216
4223
|
)
|
|
@@ -7886,7 +7893,11 @@ function HiddenBlockSlot({ block }) {
|
|
|
7886
7893
|
return /* @__PURE__ */ jsx(
|
|
7887
7894
|
"div",
|
|
7888
7895
|
{
|
|
7889
|
-
className: cn(
|
|
7896
|
+
className: cn(
|
|
7897
|
+
widthClass,
|
|
7898
|
+
resolvedMaxWidth ? "mx-auto" : void 0,
|
|
7899
|
+
block.className
|
|
7900
|
+
),
|
|
7890
7901
|
style: __spreadProps(__spreadValues(__spreadValues({}, resolvedMaxWidth ? { maxWidth: resolvedMaxWidth } : null), block.style), {
|
|
7891
7902
|
visibility: "hidden"
|
|
7892
7903
|
}),
|
|
@@ -8069,7 +8080,7 @@ function resolveAppliedDiscountForDialog(answers, discountVariable, planId) {
|
|
|
8069
8080
|
if (!planId) return answer.planId ? void 0 : answer;
|
|
8070
8081
|
return discountAppliesToPlan(answer, planId) ? answer : void 0;
|
|
8071
8082
|
}
|
|
8072
|
-
function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, inputsValid) {
|
|
8083
|
+
function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, inputsValid, isLastStep) {
|
|
8073
8084
|
var _a, _b;
|
|
8074
8085
|
const conditionMet = !block.condition || evaluateCondition(block.condition, answers);
|
|
8075
8086
|
const hasExitAnim = ((_a = block.exitAnimation) == null ? void 0 : _a.preset) && block.exitAnimation.preset !== "none";
|
|
@@ -8096,8 +8107,9 @@ function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToS
|
|
|
8096
8107
|
onGoToStep,
|
|
8097
8108
|
onPurchase,
|
|
8098
8109
|
onOpenDiscountCode,
|
|
8099
|
-
disabled: buttonDisabledForValidity(resolvedProps, inputsValid)
|
|
8100
|
-
|
|
8110
|
+
disabled: buttonDisabledForValidity(resolvedProps, inputsValid),
|
|
8111
|
+
isLastStep
|
|
8112
|
+
}) : block.type === "columns" ? __spreadProps(__spreadValues({}, resolvedProps), { answers, onNext, onBack, onGoToStep, isLastStep }) : block.type === "gravity_bin" ? __spreadProps(__spreadValues({}, resolvedProps), { answers }) : resolvedProps;
|
|
8101
8113
|
const idx = visibleIndexRef.current;
|
|
8102
8114
|
visibleIndexRef.current++;
|
|
8103
8115
|
return /* @__PURE__ */ jsx(
|
|
@@ -8119,6 +8131,7 @@ function BlockRenderer({
|
|
|
8119
8131
|
onNext,
|
|
8120
8132
|
onBack,
|
|
8121
8133
|
onGoToStep,
|
|
8134
|
+
isLastStep,
|
|
8122
8135
|
scrollViewportRef,
|
|
8123
8136
|
wrapInScrollViewport = true
|
|
8124
8137
|
}) {
|
|
@@ -8204,7 +8217,8 @@ function BlockRenderer({
|
|
|
8204
8217
|
onGoToStep,
|
|
8205
8218
|
onPurchase,
|
|
8206
8219
|
onOpenDiscountCode,
|
|
8207
|
-
inputsValid
|
|
8220
|
+
inputsValid,
|
|
8221
|
+
isLastStep
|
|
8208
8222
|
);
|
|
8209
8223
|
};
|
|
8210
8224
|
if (!wrapInScrollViewport && stickyBlocks.length === 0) {
|
|
@@ -8227,7 +8241,11 @@ function BlockRenderer({
|
|
|
8227
8241
|
"div",
|
|
8228
8242
|
{
|
|
8229
8243
|
className: layoutClasses(layout),
|
|
8230
|
-
style: {
|
|
8244
|
+
style: {
|
|
8245
|
+
gap: `${gap}rem`,
|
|
8246
|
+
maxWidth: layout == null ? void 0 : layout.maxWidth,
|
|
8247
|
+
width: "100%"
|
|
8248
|
+
},
|
|
8231
8249
|
children: contentBlocks.map((block, i) => renderOne(block, i))
|
|
8232
8250
|
}
|
|
8233
8251
|
)
|
|
@@ -8238,7 +8256,11 @@ function BlockRenderer({
|
|
|
8238
8256
|
) }) : null
|
|
8239
8257
|
] });
|
|
8240
8258
|
}
|
|
8241
|
-
function InfoPageStep({
|
|
8259
|
+
function InfoPageStep({
|
|
8260
|
+
config,
|
|
8261
|
+
onNext,
|
|
8262
|
+
isLastStep
|
|
8263
|
+
}) {
|
|
8242
8264
|
var _a;
|
|
8243
8265
|
const { answers } = useJourneyState();
|
|
8244
8266
|
const { goBack, goToStep } = useJourneyActions();
|
|
@@ -8246,9 +8268,7 @@ function InfoPageStep({ config, onNext }) {
|
|
|
8246
8268
|
const sortedTimelineEvents = useMemo(
|
|
8247
8269
|
() => {
|
|
8248
8270
|
var _a2, _b;
|
|
8249
|
-
return [...(_b = (_a2 = config.scrollTimeline) == null ? void 0 : _a2.events) != null ? _b : []].sort(
|
|
8250
|
-
(a, b) => a.at - b.at
|
|
8251
|
-
);
|
|
8271
|
+
return [...(_b = (_a2 = config.scrollTimeline) == null ? void 0 : _a2.events) != null ? _b : []].sort((a, b) => a.at - b.at);
|
|
8252
8272
|
},
|
|
8253
8273
|
[(_a = config.scrollTimeline) == null ? void 0 : _a.events]
|
|
8254
8274
|
);
|
|
@@ -8428,6 +8448,7 @@ function InfoPageStep({ config, onNext }) {
|
|
|
8428
8448
|
onNext,
|
|
8429
8449
|
onBack: goBack,
|
|
8430
8450
|
onGoToStep: goToStep,
|
|
8451
|
+
isLastStep,
|
|
8431
8452
|
scrollViewportRef
|
|
8432
8453
|
}
|
|
8433
8454
|
);
|
|
@@ -9295,7 +9316,7 @@ var STEP_REGISTRY = {
|
|
|
9295
9316
|
function StepRenderer({ config }) {
|
|
9296
9317
|
var _a, _b;
|
|
9297
9318
|
const { answers } = useJourneyState();
|
|
9298
|
-
const { setAnswer, goNext } = useJourneyActions();
|
|
9319
|
+
const { setAnswer, goNext, isLastStep } = useJourneyActions();
|
|
9299
9320
|
const resolvedConfig = useMemo(() => {
|
|
9300
9321
|
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
9301
9322
|
const hasTemplates = ((_a2 = config.preface) == null ? void 0 : _a2.includes("${")) || ((_b2 = config.question) == null ? void 0 : _b2.includes("${")) || ((_c = config.description) == null ? void 0 : _c.includes("${")) || ((_d = config.buttonText) == null ? void 0 : _d.includes("${")) || ((_e = config.footerText) == null ? void 0 : _e.includes("${")) || ((_g = (_f = config.swipeLabels) == null ? void 0 : _f.yes) == null ? void 0 : _g.includes("${")) || ((_i = (_h = config.swipeLabels) == null ? void 0 : _h.no) == null ? void 0 : _i.includes("${")) || ((_j = config.swipeCards) != null ? _j : []).some(
|
|
@@ -9348,7 +9369,8 @@ function StepRenderer({ config }) {
|
|
|
9348
9369
|
}
|
|
9349
9370
|
setAnswer(answerKey, answer);
|
|
9350
9371
|
},
|
|
9351
|
-
onNext: () => goNext()
|
|
9372
|
+
onNext: () => goNext(),
|
|
9373
|
+
isLastStep: isLastStep()
|
|
9352
9374
|
}
|
|
9353
9375
|
);
|
|
9354
9376
|
}
|
|
@@ -9646,20 +9668,15 @@ function JourneyShell({ className, theme } = {}) {
|
|
|
9646
9668
|
] });
|
|
9647
9669
|
}
|
|
9648
9670
|
var JOURNEY_LIBRARY_NAME = "@founderhq/journeys";
|
|
9649
|
-
var JOURNEY_LIBRARY_VERSION = "0.4.
|
|
9671
|
+
var JOURNEY_LIBRARY_VERSION = "0.4.2";
|
|
9672
|
+
var DEFAULT_QUEUE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9673
|
+
var MAX_QUEUED_CAPTURE_EVENTS = 100;
|
|
9674
|
+
var MAX_BEACON_BYTES = 60 * 1024;
|
|
9650
9675
|
function randomId(prefix) {
|
|
9651
9676
|
const cryptoRef = globalThis.crypto;
|
|
9652
9677
|
if (cryptoRef == null ? void 0 : cryptoRef.randomUUID) return `${prefix}_${cryptoRef.randomUUID()}`;
|
|
9653
9678
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
9654
9679
|
}
|
|
9655
|
-
function readJson(storage, key, fallback) {
|
|
9656
|
-
try {
|
|
9657
|
-
const raw = storage.getItem(key);
|
|
9658
|
-
return raw ? JSON.parse(raw) : fallback;
|
|
9659
|
-
} catch (e) {
|
|
9660
|
-
return fallback;
|
|
9661
|
-
}
|
|
9662
|
-
}
|
|
9663
9680
|
function getStoredId(storage, key, prefix) {
|
|
9664
9681
|
try {
|
|
9665
9682
|
const existing = storage.getItem(key);
|
|
@@ -9671,6 +9688,64 @@ function getStoredId(storage, key, prefix) {
|
|
|
9671
9688
|
return randomId(prefix);
|
|
9672
9689
|
}
|
|
9673
9690
|
}
|
|
9691
|
+
function safeBrowserStorage(name) {
|
|
9692
|
+
var _a;
|
|
9693
|
+
if (typeof window === "undefined") return null;
|
|
9694
|
+
try {
|
|
9695
|
+
return (_a = window[name]) != null ? _a : null;
|
|
9696
|
+
} catch (e) {
|
|
9697
|
+
return null;
|
|
9698
|
+
}
|
|
9699
|
+
}
|
|
9700
|
+
function isQueuedCaptureEvent(value) {
|
|
9701
|
+
if (!isRecord(value)) return false;
|
|
9702
|
+
const event = value.event;
|
|
9703
|
+
return isRecord(event) && typeof event.id === "string" && typeof event.type === "string" && typeof event.occurredAt === "string" && typeof value.attempts === "number" && typeof value.createdAt === "number";
|
|
9704
|
+
}
|
|
9705
|
+
function pruneQueuedEvents(queue, now = Date.now(), ttlMs = DEFAULT_QUEUE_TTL_MS) {
|
|
9706
|
+
const cutoff = now - ttlMs;
|
|
9707
|
+
return queue.filter((item) => item.createdAt >= cutoff);
|
|
9708
|
+
}
|
|
9709
|
+
function readPersistedCaptureQueue(storage, key) {
|
|
9710
|
+
if (!storage) return null;
|
|
9711
|
+
try {
|
|
9712
|
+
const raw = storage.getItem(key);
|
|
9713
|
+
if (!raw) return null;
|
|
9714
|
+
const parsed = JSON.parse(raw);
|
|
9715
|
+
if (typeof parsed.visitorId !== "string" || typeof parsed.clientSessionId !== "string") {
|
|
9716
|
+
return null;
|
|
9717
|
+
}
|
|
9718
|
+
const queue = Array.isArray(parsed.queue) ? pruneQueuedEvents(parsed.queue.filter(isQueuedCaptureEvent)) : [];
|
|
9719
|
+
return {
|
|
9720
|
+
visitorId: parsed.visitorId,
|
|
9721
|
+
clientSessionId: parsed.clientSessionId,
|
|
9722
|
+
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
|
|
9723
|
+
queue
|
|
9724
|
+
};
|
|
9725
|
+
} catch (e) {
|
|
9726
|
+
return null;
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
9729
|
+
function persistCaptureQueue(storage, runtime, queue) {
|
|
9730
|
+
if (!storage) return;
|
|
9731
|
+
try {
|
|
9732
|
+
const pruned = pruneQueuedEvents(queue).slice(-MAX_QUEUED_CAPTURE_EVENTS);
|
|
9733
|
+
if (pruned.length === 0) {
|
|
9734
|
+
storage.removeItem(runtime.queueKey);
|
|
9735
|
+
return;
|
|
9736
|
+
}
|
|
9737
|
+
storage.setItem(
|
|
9738
|
+
runtime.queueKey,
|
|
9739
|
+
JSON.stringify({
|
|
9740
|
+
visitorId: runtime.visitorId,
|
|
9741
|
+
clientSessionId: runtime.clientSessionId,
|
|
9742
|
+
updatedAt: Date.now(),
|
|
9743
|
+
queue: pruned
|
|
9744
|
+
})
|
|
9745
|
+
);
|
|
9746
|
+
} catch (e) {
|
|
9747
|
+
}
|
|
9748
|
+
}
|
|
9674
9749
|
function isRecord(value) {
|
|
9675
9750
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9676
9751
|
}
|
|
@@ -9771,6 +9846,54 @@ function captureContext(capture, runtime) {
|
|
|
9771
9846
|
screen: screenRef ? { width: screenRef.width, height: screenRef.height } : void 0
|
|
9772
9847
|
}, customContext));
|
|
9773
9848
|
}
|
|
9849
|
+
function captureUrl(capture) {
|
|
9850
|
+
var _a;
|
|
9851
|
+
const baseUrl = (_a = capture.baseUrl) != null ? _a : "https://getfounderhq.com";
|
|
9852
|
+
return `${baseUrl}/api/v1/journeys/${encodeURIComponent(
|
|
9853
|
+
capture.journeyId
|
|
9854
|
+
)}/capture`;
|
|
9855
|
+
}
|
|
9856
|
+
function byteLength(value) {
|
|
9857
|
+
try {
|
|
9858
|
+
return new TextEncoder().encode(value).byteLength;
|
|
9859
|
+
} catch (e) {
|
|
9860
|
+
return value.length;
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
function safeSendBeacon() {
|
|
9864
|
+
if (typeof navigator === "undefined") return null;
|
|
9865
|
+
const beacon = navigator.sendBeacon;
|
|
9866
|
+
return typeof beacon === "function" ? beacon.bind(navigator) : null;
|
|
9867
|
+
}
|
|
9868
|
+
async function sendCaptureBatch(capture, runtime, batch, options = {}) {
|
|
9869
|
+
const body = JSON.stringify(__spreadProps(__spreadValues({}, options.preferBeacon ? { apiKey: capture.apiKey } : {}), {
|
|
9870
|
+
clientSessionId: runtime.clientSessionId,
|
|
9871
|
+
visitorId: runtime.visitorId,
|
|
9872
|
+
context: captureContext(capture, runtime),
|
|
9873
|
+
events: batch.map((item) => item.event)
|
|
9874
|
+
}));
|
|
9875
|
+
const url = captureUrl(capture);
|
|
9876
|
+
if (options.preferBeacon && byteLength(body) <= MAX_BEACON_BYTES) {
|
|
9877
|
+
const beacon = safeSendBeacon();
|
|
9878
|
+
if (beacon == null ? void 0 : beacon(url, body)) {
|
|
9879
|
+
return "queued";
|
|
9880
|
+
}
|
|
9881
|
+
}
|
|
9882
|
+
try {
|
|
9883
|
+
const response = await fetch(url, {
|
|
9884
|
+
method: "POST",
|
|
9885
|
+
headers: {
|
|
9886
|
+
Authorization: `Bearer ${capture.apiKey}`,
|
|
9887
|
+
"Content-Type": "application/json"
|
|
9888
|
+
},
|
|
9889
|
+
body,
|
|
9890
|
+
keepalive: batch.length <= 5 || options.preferBeacon === true
|
|
9891
|
+
});
|
|
9892
|
+
return response.ok ? "acknowledged" : "failed";
|
|
9893
|
+
} catch (e) {
|
|
9894
|
+
return "failed";
|
|
9895
|
+
}
|
|
9896
|
+
}
|
|
9774
9897
|
function toJsonRecord(value) {
|
|
9775
9898
|
return isRecord(value) ? value : {};
|
|
9776
9899
|
}
|
|
@@ -9916,30 +10039,34 @@ function useJourneyCapture(params) {
|
|
|
9916
10039
|
captureRef.current = params.capture;
|
|
9917
10040
|
const persistQueue = useCallback(() => {
|
|
9918
10041
|
const runtime = runtimeRef.current;
|
|
9919
|
-
if (!runtime
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
} catch (e) {
|
|
9926
|
-
}
|
|
10042
|
+
if (!runtime) return;
|
|
10043
|
+
persistCaptureQueue(
|
|
10044
|
+
safeBrowserStorage("localStorage"),
|
|
10045
|
+
runtime,
|
|
10046
|
+
queueRef.current
|
|
10047
|
+
);
|
|
9927
10048
|
}, []);
|
|
9928
10049
|
const ensureRuntime = useCallback((capture) => {
|
|
10050
|
+
var _a, _b;
|
|
9929
10051
|
if (typeof window === "undefined") return null;
|
|
10052
|
+
const queueKey = `fhq_journey_queue:v2:${capture.apiKey.slice(
|
|
10053
|
+
0,
|
|
10054
|
+
16
|
|
10055
|
+
)}:${capture.journeyId}`;
|
|
9930
10056
|
const existing = runtimeRef.current;
|
|
9931
|
-
if ((existing == null ? void 0 : existing.journeyId) === capture.journeyId
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
);
|
|
9937
|
-
const
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
10057
|
+
if ((existing == null ? void 0 : existing.journeyId) === capture.journeyId && existing.queueKey === queueKey) {
|
|
10058
|
+
return existing;
|
|
10059
|
+
}
|
|
10060
|
+
const local = safeBrowserStorage("localStorage");
|
|
10061
|
+
const session = safeBrowserStorage("sessionStorage");
|
|
10062
|
+
const persisted = readPersistedCaptureQueue(local, queueKey);
|
|
10063
|
+
const visitorId = (_a = persisted == null ? void 0 : persisted.visitorId) != null ? _a : local ? getStoredId(local, "fhq_journey_visitor_id", "fhqv") : randomId("fhqv");
|
|
10064
|
+
const sessionKey = `fhq_journey_session:${capture.journeyId}`;
|
|
10065
|
+
const clientSessionId = persisted && persisted.queue.length > 0 ? persisted.clientSessionId : session ? getStoredId(session, sessionKey, "fhqs") : randomId("fhqs");
|
|
10066
|
+
try {
|
|
10067
|
+
session == null ? void 0 : session.setItem(sessionKey, clientSessionId);
|
|
10068
|
+
} catch (e) {
|
|
10069
|
+
}
|
|
9943
10070
|
const runtime = {
|
|
9944
10071
|
journeyId: capture.journeyId,
|
|
9945
10072
|
visitorId,
|
|
@@ -9947,51 +10074,46 @@ function useJourneyCapture(params) {
|
|
|
9947
10074
|
queueKey
|
|
9948
10075
|
};
|
|
9949
10076
|
runtimeRef.current = runtime;
|
|
9950
|
-
queueRef.current =
|
|
9951
|
-
sessionStorage,
|
|
9952
|
-
queueKey,
|
|
9953
|
-
[]
|
|
9954
|
-
);
|
|
10077
|
+
queueRef.current = (_b = persisted == null ? void 0 : persisted.queue) != null ? _b : [];
|
|
9955
10078
|
return runtime;
|
|
9956
10079
|
}, []);
|
|
9957
|
-
const flush = useCallback(async () => {
|
|
9958
|
-
var _a, _b
|
|
10080
|
+
const flush = useCallback(async (options = {}) => {
|
|
10081
|
+
var _a, _b;
|
|
9959
10082
|
const capture = captureRef.current;
|
|
9960
|
-
if (!capture || !configRef.current
|
|
10083
|
+
if (!capture || !configRef.current) return;
|
|
10084
|
+
if (!options.preferBeacon && flushingRef.current) return;
|
|
9961
10085
|
const runtime = ensureRuntime(capture);
|
|
9962
10086
|
if (!runtime || queueRef.current.length === 0) return;
|
|
9963
|
-
|
|
10087
|
+
queueRef.current = pruneQueuedEvents(queueRef.current);
|
|
9964
10088
|
const batchSize = Math.max(1, (_a = capture.batchSize) != null ? _a : 10);
|
|
9965
10089
|
const maxRetries = Math.max(1, (_b = capture.maxRetries) != null ? _b : 3);
|
|
9966
|
-
const batch = queueRef.current.slice(
|
|
9967
|
-
|
|
10090
|
+
const batch = queueRef.current.slice(
|
|
10091
|
+
0,
|
|
10092
|
+
options.preferBeacon ? Math.min(batchSize, 5) : batchSize
|
|
10093
|
+
);
|
|
10094
|
+
if (batch.length === 0) {
|
|
10095
|
+
persistQueue();
|
|
10096
|
+
return;
|
|
10097
|
+
}
|
|
10098
|
+
if (options.preferBeacon) {
|
|
10099
|
+
await sendCaptureBatch(capture, runtime, batch, options);
|
|
10100
|
+
persistQueue();
|
|
10101
|
+
return;
|
|
10102
|
+
}
|
|
10103
|
+
flushingRef.current = true;
|
|
9968
10104
|
try {
|
|
9969
|
-
const
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
},
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
visitorId: runtime.visitorId,
|
|
9982
|
-
context: captureContext(capture, runtime),
|
|
9983
|
-
events: batch.map((item) => item.event)
|
|
9984
|
-
}),
|
|
9985
|
-
keepalive: batch.length <= 5
|
|
9986
|
-
}
|
|
9987
|
-
);
|
|
9988
|
-
if (!response.ok) throw new Error(`Capture failed: ${response.status}`);
|
|
9989
|
-
queueRef.current = queueRef.current.slice(batch.length);
|
|
9990
|
-
} catch (e) {
|
|
9991
|
-
const failedIds = new Set(batch.map((item) => item.event.id));
|
|
9992
|
-
queueRef.current = queueRef.current.map(
|
|
9993
|
-
(item) => failedIds.has(item.event.id) ? __spreadProps(__spreadValues({}, item), { attempts: item.attempts + 1 }) : item
|
|
9994
|
-
).filter((item) => item.attempts < maxRetries);
|
|
10105
|
+
const result = await sendCaptureBatch(capture, runtime, batch);
|
|
10106
|
+
if (result === "acknowledged") {
|
|
10107
|
+
const sentIds = new Set(batch.map((item) => item.event.id));
|
|
10108
|
+
queueRef.current = queueRef.current.filter(
|
|
10109
|
+
(item) => !sentIds.has(item.event.id)
|
|
10110
|
+
);
|
|
10111
|
+
} else {
|
|
10112
|
+
const failedIds = new Set(batch.map((item) => item.event.id));
|
|
10113
|
+
queueRef.current = queueRef.current.map(
|
|
10114
|
+
(item) => failedIds.has(item.event.id) ? __spreadProps(__spreadValues({}, item), { attempts: item.attempts + 1 }) : item
|
|
10115
|
+
).filter((item) => item.attempts < maxRetries);
|
|
10116
|
+
}
|
|
9995
10117
|
} finally {
|
|
9996
10118
|
persistQueue();
|
|
9997
10119
|
flushingRef.current = false;
|
|
@@ -10007,15 +10129,18 @@ function useJourneyCapture(params) {
|
|
|
10007
10129
|
Math.max(1e3, (_a = capture.flushIntervalMs) != null ? _a : 3e3)
|
|
10008
10130
|
);
|
|
10009
10131
|
const handleVisibility = () => {
|
|
10010
|
-
if (document.visibilityState === "hidden")
|
|
10132
|
+
if (document.visibilityState === "hidden") {
|
|
10133
|
+
void flush({ preferBeacon: true });
|
|
10134
|
+
}
|
|
10011
10135
|
};
|
|
10012
|
-
|
|
10136
|
+
const handlePageHide = () => void flush({ preferBeacon: true });
|
|
10013
10137
|
document.addEventListener("visibilitychange", handleVisibility);
|
|
10138
|
+
window.addEventListener("pagehide", handlePageHide);
|
|
10014
10139
|
void flush();
|
|
10015
10140
|
return () => {
|
|
10016
10141
|
window.clearInterval(interval);
|
|
10017
|
-
window.removeEventListener("beforeunload", persistQueue);
|
|
10018
10142
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
10143
|
+
window.removeEventListener("pagehide", handlePageHide);
|
|
10019
10144
|
persistQueue();
|
|
10020
10145
|
};
|
|
10021
10146
|
}, [ensureRuntime, flush, persistQueue, params.capture]);
|
|
@@ -10031,11 +10156,17 @@ function useJourneyCapture(params) {
|
|
|
10031
10156
|
sequenceRef.current += 1;
|
|
10032
10157
|
queueRef.current.push({
|
|
10033
10158
|
event: toCaptureEvent(config, event, sequenceRef.current, runtime),
|
|
10034
|
-
attempts: 0
|
|
10159
|
+
attempts: 0,
|
|
10160
|
+
createdAt: Date.now()
|
|
10035
10161
|
});
|
|
10162
|
+
queueRef.current = pruneQueuedEvents(queueRef.current).slice(
|
|
10163
|
+
-MAX_QUEUED_CAPTURE_EVENTS
|
|
10164
|
+
);
|
|
10036
10165
|
persistQueue();
|
|
10037
10166
|
const batchSize = Math.max(1, (_b = capture.batchSize) != null ? _b : 10);
|
|
10038
|
-
if (queueRef.current.length >= batchSize)
|
|
10167
|
+
if (event.type === "complete" || queueRef.current.length >= batchSize) {
|
|
10168
|
+
void flush();
|
|
10169
|
+
}
|
|
10039
10170
|
},
|
|
10040
10171
|
[ensureRuntime, flush, persistQueue]
|
|
10041
10172
|
);
|