@funnelsgrove/runtime 0.7.19 → 0.7.21
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.
|
@@ -118,10 +118,6 @@ type UseFunnelFlowControllerInput<StepId extends string> = {
|
|
|
118
118
|
stepById: Record<string, FunnelStepMeta>;
|
|
119
119
|
stepComponentById: StepComponentRegistry;
|
|
120
120
|
funnelExperiments: readonly FunnelFlowControllerExperiment<StepId>[];
|
|
121
|
-
sessionReplay?: {
|
|
122
|
-
enabled: boolean;
|
|
123
|
-
sampleRate: number;
|
|
124
|
-
};
|
|
125
121
|
getPathForStep: (stepId: StepId) => string;
|
|
126
122
|
getStepIdFromPath: (pathname: string) => StepId | null;
|
|
127
123
|
getSequentialNextStepId: (stepId: StepId) => StepId | null;
|
|
@@ -183,5 +179,5 @@ export declare function resolveRenderedExperimentStepId(input: {
|
|
|
183
179
|
resolveRenderableStepId: (stepId: string | null | undefined) => string | null;
|
|
184
180
|
safeActiveStepId: string;
|
|
185
181
|
}): string;
|
|
186
|
-
export declare function useFunnelFlowController<StepId extends string>({ api, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments,
|
|
182
|
+
export declare function useFunnelFlowController<StepId extends string>({ api, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }: UseFunnelFlowControllerInput<StepId>): UseFunnelFlowControllerResult<StepId>;
|
|
187
183
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { useFunnelRuntimeConfig } from '../components/FunnelRuntimeConfigBoundary.js';
|
|
3
4
|
import { writeStepChoice } from '../sdk/userAnswers.js';
|
|
4
5
|
import { apiService } from '../services/api.service.js';
|
|
5
6
|
import { logger } from '../services/logger.js';
|
|
@@ -278,8 +279,9 @@ export function resolveRenderedExperimentStepId(input) {
|
|
|
278
279
|
const variantStepId = resolveRenderableStepId(assignment.routeToStepId);
|
|
279
280
|
return variantStepId !== null && variantStepId !== void 0 ? variantStepId : safeActiveStepId;
|
|
280
281
|
}
|
|
281
|
-
export function useFunnelFlowController({ api = apiService, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep = false, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments,
|
|
282
|
-
var _a, _b, _c;
|
|
282
|
+
export function useFunnelFlowController({ api = apiService, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep = false, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }) {
|
|
283
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
284
|
+
const publishedRuntime = useFunnelRuntimeConfig();
|
|
283
285
|
const [runtimeMode] = useRuntimeMode();
|
|
284
286
|
const [editorModeEnabled, setEditorModeEnabled] = useState(() => isEditorEnabled());
|
|
285
287
|
const [editorPanelExpanded, setEditorPanelExpanded] = useState(false);
|
|
@@ -287,8 +289,8 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
287
289
|
const [editorVariantOverrides, setEditorVariantOverrides] = useState({});
|
|
288
290
|
const shouldLockToInitialStep = lockToInitialStep || isPreviewStepLockRequested();
|
|
289
291
|
const isPreviewRuntime = useMemo(() => isPreviewFrameRuntime(), []);
|
|
290
|
-
const sessionReplayEnabled =
|
|
291
|
-
const sessionReplaySampleRate =
|
|
292
|
+
const sessionReplayEnabled = (_c = (_b = (_a = publishedRuntime.config) === null || _a === void 0 ? void 0 : _a.sessionReplay) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : false;
|
|
293
|
+
const sessionReplaySampleRate = (_f = (_e = (_d = publishedRuntime.config) === null || _d === void 0 ? void 0 : _d.sessionReplay) === null || _e === void 0 ? void 0 : _e.sampleRate) !== null && _f !== void 0 ? _f : 10;
|
|
292
294
|
const resolveRenderableStepId = useCallback((stepId) => {
|
|
293
295
|
if (!stepId || !isFunnelStepId(stepId)) {
|
|
294
296
|
return null;
|
|
@@ -318,7 +320,7 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
318
320
|
});
|
|
319
321
|
return resolveFallbackStepId(requestedStepId);
|
|
320
322
|
}, [initialStepId, resolveFallbackStepId, resolveRuntimeInitialStepId]);
|
|
321
|
-
const isSubscriptionManagementRuntime = ((
|
|
323
|
+
const isSubscriptionManagementRuntime = ((_g = stepById[safeInitialStepId]) === null || _g === void 0 ? void 0 : _g.kind) === 'manage-subscription';
|
|
322
324
|
const lifecycleEventsSuppressed = isPreviewRuntime || editorModeEnabled || isSubscriptionManagementRuntime;
|
|
323
325
|
const [activeStepId, setActiveStepId] = useState(safeInitialStepId);
|
|
324
326
|
const [attributes, setAttributes] = useState(() => (Object.assign({}, (initialAttributes || {}))));
|
|
@@ -542,12 +544,10 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
542
544
|
distinctId: localUserId,
|
|
543
545
|
projectId: PROJECT_ID,
|
|
544
546
|
funnelId: FUNNEL_ID,
|
|
545
|
-
sessionReplay:
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
: undefined,
|
|
547
|
+
sessionReplay: {
|
|
548
|
+
enabled: sessionReplayEnabled,
|
|
549
|
+
sampleRate: sessionReplaySampleRate,
|
|
550
|
+
},
|
|
551
551
|
})
|
|
552
552
|
.then(() => setPostHogReady(true))
|
|
553
553
|
.catch((error) => {
|
|
@@ -1232,10 +1232,10 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
1232
1232
|
safeInitialStepId,
|
|
1233
1233
|
]);
|
|
1234
1234
|
const choiceTargets = getChoiceTargetsForStep(renderedStepId);
|
|
1235
|
-
const actionBar = (
|
|
1235
|
+
const actionBar = (_h = renderedStepMeta === null || renderedStepMeta === void 0 ? void 0 : renderedStepMeta.actionBar) !== null && _h !== void 0 ? _h : activeStepMeta === null || activeStepMeta === void 0 ? void 0 : activeStepMeta.actionBar;
|
|
1236
1236
|
const isAutoAdvanceStep = typeof (actionBar === null || actionBar === void 0 ? void 0 : actionBar.autoAdvanceMs) === 'number';
|
|
1237
1237
|
const autoAdvanceMs = actionBar === null || actionBar === void 0 ? void 0 : actionBar.autoAdvanceMs;
|
|
1238
|
-
const buttonVariant = (
|
|
1238
|
+
const buttonVariant = (_j = actionBar === null || actionBar === void 0 ? void 0 : actionBar.buttonVariant) !== null && _j !== void 0 ? _j : null;
|
|
1239
1239
|
const buttonText = (actionBar === null || actionBar === void 0 ? void 0 : actionBar.buttonText) || 'Continue';
|
|
1240
1240
|
const hideActionBar = (actionBar === null || actionBar === void 0 ? void 0 : actionBar.hidden) === true;
|
|
1241
1241
|
useEffect(() => {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
const inflightByFunnelId = new Map();
|
|
2
|
+
const TRANSIENT_RUNTIME_CONFIG_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
3
|
+
const RUNTIME_CONFIG_RETRY_DELAYS_MS = [250, 750];
|
|
4
|
+
const wait = (delayMs) => (new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
2
5
|
const isRecord = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
3
6
|
const readString = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
4
7
|
const readNumber = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
|
|
@@ -297,19 +300,38 @@ export const loadFunnelRuntimeConfig = (funnelId, options = {}) => {
|
|
|
297
300
|
}
|
|
298
301
|
const fetcher = options.fetcher || globalThis.fetch;
|
|
299
302
|
const basePath = (options.basePath || '/api/funnel-config').replace(/\/$/, '');
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
303
|
+
const requestUrl = `${basePath}/${encodeURIComponent(normalizedFunnelId)}?versionId=${encodeURIComponent(versionId)}`;
|
|
304
|
+
const request = (async () => {
|
|
305
|
+
for (let attempt = 0;; attempt += 1) {
|
|
306
|
+
let response;
|
|
307
|
+
try {
|
|
308
|
+
response = await fetcher(requestUrl, {
|
|
309
|
+
cache: 'no-store',
|
|
310
|
+
headers: { accept: 'application/json', 'cache-control': 'no-cache' },
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
const retryDelay = RUNTIME_CONFIG_RETRY_DELAYS_MS[attempt];
|
|
315
|
+
if (retryDelay === undefined)
|
|
316
|
+
throw error;
|
|
317
|
+
await wait(retryDelay);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (!response.ok) {
|
|
321
|
+
const retryDelay = RUNTIME_CONFIG_RETRY_DELAYS_MS[attempt];
|
|
322
|
+
if (retryDelay !== undefined && TRANSIENT_RUNTIME_CONFIG_STATUSES.has(response.status)) {
|
|
323
|
+
await wait(retryDelay);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Unable to load funnel runtime config (${response.status})`);
|
|
327
|
+
}
|
|
328
|
+
const published = parsePublishedRuntimeConfig(await response.json());
|
|
329
|
+
if (published.config.funnelId !== normalizedFunnelId) {
|
|
330
|
+
throw new Error('Funnel runtime config belongs to a different funnel');
|
|
331
|
+
}
|
|
332
|
+
return published;
|
|
310
333
|
}
|
|
311
|
-
|
|
312
|
-
}).finally(() => {
|
|
334
|
+
})().finally(() => {
|
|
313
335
|
inflightByFunnelId.delete(cacheKey);
|
|
314
336
|
});
|
|
315
337
|
inflightByFunnelId.set(cacheKey, request);
|