@funnelsgrove/runtime 0.1.48 → 0.1.50
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/runtime/experiment-assignment.d.ts +1 -0
- package/dist/runtime/experiment-assignment.js +10 -3
- package/dist/runtime/funnel-runtime.d.ts +1 -0
- package/dist/runtime/funnel-runtime.js +4 -1
- package/dist/runtime/use-funnel-flow-controller.d.ts +8 -13
- package/dist/runtime/use-funnel-flow-controller.js +69 -65
- package/package.json +1 -1
|
@@ -10,4 +10,5 @@ export type Assignment = {
|
|
|
10
10
|
variantKey: string;
|
|
11
11
|
routeToStepId: string;
|
|
12
12
|
};
|
|
13
|
+
export declare const resolveForcedExperimentAssignment: (input: ResolveInput) => Assignment | null;
|
|
13
14
|
export declare const resolveExperimentAssignment: (input: ResolveInput) => Assignment | null;
|
|
@@ -16,14 +16,21 @@ const resolveForcedVariant = (input) => {
|
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
|
+
export const resolveForcedExperimentAssignment = (input) => {
|
|
20
|
+
const forcedVariant = resolveForcedVariant(input);
|
|
21
|
+
if (!forcedVariant) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return { variantKey: forcedVariant.variantKey, routeToStepId: forcedVariant.routeToStepId };
|
|
25
|
+
};
|
|
19
26
|
export const resolveExperimentAssignment = (input) => {
|
|
20
27
|
var _a;
|
|
21
28
|
if (input.variants.length === 0) {
|
|
22
29
|
return null;
|
|
23
30
|
}
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
return
|
|
31
|
+
const forcedAssignment = resolveForcedExperimentAssignment(input);
|
|
32
|
+
if (forcedAssignment) {
|
|
33
|
+
return forcedAssignment;
|
|
27
34
|
}
|
|
28
35
|
const resolved = resolveExperimentVariant(input.experimentId);
|
|
29
36
|
const match = resolved ? input.variants.find((variant) => variant.variantKey === resolved) : undefined;
|
|
@@ -7,6 +7,7 @@ export type FunnelStepOrderGraph = {
|
|
|
7
7
|
entryPointStepIds?: readonly string[];
|
|
8
8
|
edgesByStepId?: Partial<Record<string, readonly {
|
|
9
9
|
toStepId: string;
|
|
10
|
+
conditionId?: string | null;
|
|
10
11
|
}[]>>;
|
|
11
12
|
};
|
|
12
13
|
export declare const getFunnelStepSequence: (manifest: FunnelManifest) => FunnelStepId[];
|
|
@@ -17,7 +17,10 @@ export const getFunnelStepIdsByEdgeOrder = (graph) => {
|
|
|
17
17
|
}
|
|
18
18
|
visitedStepIds.add(stepId);
|
|
19
19
|
orderedStepIds.push(stepId);
|
|
20
|
-
|
|
20
|
+
const edges = [...(((_a = graph.edgesByStepId) === null || _a === void 0 ? void 0 : _a[stepId]) || [])].sort((left, right) => {
|
|
21
|
+
return Number(Boolean(left.conditionId)) - Number(Boolean(right.conditionId));
|
|
22
|
+
});
|
|
23
|
+
for (const edge of edges) {
|
|
21
24
|
visit(edge.toStepId);
|
|
22
25
|
}
|
|
23
26
|
};
|
|
@@ -31,18 +31,6 @@ type FunnelFlowAnalyticsAdapter = {
|
|
|
31
31
|
selected?: Record<string, unknown>;
|
|
32
32
|
featureFlags?: Record<string, string>;
|
|
33
33
|
}) => string | null;
|
|
34
|
-
trackStepEngaged?: (input: {
|
|
35
|
-
userId?: string;
|
|
36
|
-
environment?: 'test' | 'live';
|
|
37
|
-
stepId: string;
|
|
38
|
-
stepName: string;
|
|
39
|
-
stepType?: string;
|
|
40
|
-
startedAt: string;
|
|
41
|
-
engagedAt: string;
|
|
42
|
-
durationMs: number;
|
|
43
|
-
engagementThresholdMs: number;
|
|
44
|
-
featureFlags?: Record<string, string>;
|
|
45
|
-
}) => string | null;
|
|
46
34
|
trackFunnelStarted?: (input: {
|
|
47
35
|
userId?: string;
|
|
48
36
|
environment?: 'test' | 'live';
|
|
@@ -116,8 +104,15 @@ export declare function computeRenderSuspended(input: {
|
|
|
116
104
|
postHogReady: boolean;
|
|
117
105
|
experiments: ReadonlyArray<{
|
|
118
106
|
stepId: string;
|
|
119
|
-
}
|
|
107
|
+
} & Partial<Pick<FunnelManifestExperiment, 'experimentId' | 'variants'>>>;
|
|
120
108
|
isPreviewRuntime?: boolean;
|
|
121
109
|
}): boolean;
|
|
110
|
+
export declare function resolveRenderedExperimentStepId(input: {
|
|
111
|
+
activeExperiment: FunnelManifestExperiment | null;
|
|
112
|
+
editorVariantOverrides: Record<string, string>;
|
|
113
|
+
postHogReady: boolean;
|
|
114
|
+
resolveRenderableStepId: (stepId: string | null | undefined) => string | null;
|
|
115
|
+
safeActiveStepId: string;
|
|
116
|
+
}): string;
|
|
122
117
|
export declare function useFunnelFlowController<StepId extends string>({ analytics, initialStepId, initialAttributes, lockToInitialStep, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }: UseFunnelFlowControllerInput<StepId>): UseFunnelFlowControllerResult<StepId>;
|
|
123
118
|
export {};
|
|
@@ -7,18 +7,63 @@ import { FUNNEL_ID, POSTHOG_API_HOST, POSTHOG_PROJECT_API_KEY, PROJECT_ID, } fro
|
|
|
7
7
|
import { buildHostedStepLocation, dispatchWindowCustomEvent, } from './browser-helpers.js';
|
|
8
8
|
import { isPreviewStepLockRequested, resolveNextStepFromContext, shouldRunAutoAdvanceTimer, } from './funnel-flow.js';
|
|
9
9
|
import { usePreviewBridge } from './preview-bridge.js';
|
|
10
|
-
import { resolveExperimentAssignment } from './experiment-assignment.js';
|
|
10
|
+
import { resolveExperimentAssignment, resolveForcedExperimentAssignment, } from './experiment-assignment.js';
|
|
11
11
|
import { collectCurrentFunnelAttribution } from './funnel-attribution.js';
|
|
12
12
|
import { resolveUrlUserAttributes } from './url-user-attributes.js';
|
|
13
13
|
import { bootstrapPostHog, identifyPostHog, isPostHogReady, resolveExperimentVariant, } from './posthog-flags.js';
|
|
14
14
|
import { getRuntimeMode, isEditorEnabled, useRuntimeMode } from '../services/runtime-mode.service.js';
|
|
15
15
|
import { isPreviewFrameRuntime } from '../services/preview-frame.service.js';
|
|
16
|
-
const FIRST_STEP_ENGAGEMENT_THRESHOLD_MS = 1000;
|
|
17
16
|
export function computeRenderSuspended(input) {
|
|
17
|
+
var _a;
|
|
18
18
|
if (input.isPreviewRuntime || input.postHogReady) {
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
const activeExperiment = input.experiments.find((experiment) => experiment.stepId === input.activeStepId);
|
|
22
|
+
if (!activeExperiment) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (activeExperiment.experimentId && ((_a = activeExperiment.variants) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
26
|
+
const forcedAssignment = resolveForcedExperimentAssignment({
|
|
27
|
+
experimentId: activeExperiment.experimentId,
|
|
28
|
+
variants: activeExperiment.variants,
|
|
29
|
+
});
|
|
30
|
+
if (forcedAssignment) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
export function resolveRenderedExperimentStepId(input) {
|
|
37
|
+
const { activeExperiment, editorVariantOverrides, postHogReady, resolveRenderableStepId, safeActiveStepId, } = input;
|
|
38
|
+
if (!activeExperiment) {
|
|
39
|
+
return safeActiveStepId;
|
|
40
|
+
}
|
|
41
|
+
const override = editorVariantOverrides[activeExperiment.experimentId];
|
|
42
|
+
if (override) {
|
|
43
|
+
const matchedVariant = activeExperiment.variants.find((variant) => variant.variantKey === override);
|
|
44
|
+
const variantStepId = resolveRenderableStepId((matchedVariant === null || matchedVariant === void 0 ? void 0 : matchedVariant.routeToStepId) || null);
|
|
45
|
+
return variantStepId !== null && variantStepId !== void 0 ? variantStepId : safeActiveStepId;
|
|
46
|
+
}
|
|
47
|
+
const forcedAssignment = resolveForcedExperimentAssignment({
|
|
48
|
+
experimentId: activeExperiment.experimentId,
|
|
49
|
+
variants: activeExperiment.variants,
|
|
50
|
+
});
|
|
51
|
+
if (forcedAssignment) {
|
|
52
|
+
const forcedStepId = resolveRenderableStepId(forcedAssignment.routeToStepId);
|
|
53
|
+
return forcedStepId !== null && forcedStepId !== void 0 ? forcedStepId : safeActiveStepId;
|
|
54
|
+
}
|
|
55
|
+
if (!postHogReady) {
|
|
56
|
+
return safeActiveStepId;
|
|
57
|
+
}
|
|
58
|
+
const assignment = resolveExperimentAssignment({
|
|
59
|
+
experimentId: activeExperiment.experimentId,
|
|
60
|
+
variants: activeExperiment.variants,
|
|
61
|
+
});
|
|
62
|
+
if (!assignment) {
|
|
63
|
+
return safeActiveStepId;
|
|
64
|
+
}
|
|
65
|
+
const variantStepId = resolveRenderableStepId(assignment.routeToStepId);
|
|
66
|
+
return variantStepId !== null && variantStepId !== void 0 ? variantStepId : safeActiveStepId;
|
|
22
67
|
}
|
|
23
68
|
export function useFunnelFlowController({ analytics, initialStepId, initialAttributes, lockToInitialStep = false, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }) {
|
|
24
69
|
var _a, _b;
|
|
@@ -75,8 +120,6 @@ export function useFunnelFlowController({ analytics, initialStepId, initialAttri
|
|
|
75
120
|
const prevStepIdRef = useRef(null);
|
|
76
121
|
const firstStepViewedTrackedRef = useRef(false);
|
|
77
122
|
const stepStartedAtByIdRef = useRef({});
|
|
78
|
-
const engagedStepIdsRef = useRef(new Set());
|
|
79
|
-
const firstStepEngagementTimerRef = useRef(null);
|
|
80
123
|
const currentUserIdRef = useRef(user.id);
|
|
81
124
|
const safeActiveStepId = useMemo(() => { var _a; return (_a = resolveRenderableStepId(activeStepId)) !== null && _a !== void 0 ? _a : safeInitialStepId; }, [activeStepId, resolveRenderableStepId, safeInitialStepId]);
|
|
82
125
|
const activeStepMeta = stepById[safeActiveStepId];
|
|
@@ -92,35 +135,26 @@ export function useFunnelFlowController({ analytics, initialStepId, initialAttri
|
|
|
92
135
|
if (override) {
|
|
93
136
|
return override;
|
|
94
137
|
}
|
|
138
|
+
const forcedAssignment = resolveForcedExperimentAssignment({
|
|
139
|
+
experimentId: experiment.experimentId,
|
|
140
|
+
variants: experiment.variants,
|
|
141
|
+
});
|
|
142
|
+
if (forcedAssignment) {
|
|
143
|
+
return forcedAssignment.variantKey;
|
|
144
|
+
}
|
|
95
145
|
if (!postHogReady) {
|
|
96
146
|
return null;
|
|
97
147
|
}
|
|
98
148
|
return (_a = resolveExperimentVariant(experiment.experimentId)) !== null && _a !== void 0 ? _a : null;
|
|
99
149
|
}, [editorVariantOverrides, postHogReady]);
|
|
100
150
|
const activeExperimentForStep = useMemo(() => funnelExperiments.find((candidate) => candidate.stepId === safeActiveStepId) || null, [funnelExperiments, safeActiveStepId]);
|
|
101
|
-
const renderedStepId = useMemo(() => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const variantStepId = resolveRenderableStepId((matchedVariant === null || matchedVariant === void 0 ? void 0 : matchedVariant.routeToStepId) || null);
|
|
109
|
-
return variantStepId !== null && variantStepId !== void 0 ? variantStepId : safeActiveStepId;
|
|
110
|
-
}
|
|
111
|
-
if (!postHogReady) {
|
|
112
|
-
return safeActiveStepId;
|
|
113
|
-
}
|
|
114
|
-
const assignment = resolveExperimentAssignment({
|
|
115
|
-
experimentId: activeExperimentForStep.experimentId,
|
|
116
|
-
variants: activeExperimentForStep.variants,
|
|
117
|
-
});
|
|
118
|
-
if (!assignment) {
|
|
119
|
-
return safeActiveStepId;
|
|
120
|
-
}
|
|
121
|
-
const variantStepId = resolveRenderableStepId(assignment.routeToStepId);
|
|
122
|
-
return variantStepId !== null && variantStepId !== void 0 ? variantStepId : safeActiveStepId;
|
|
123
|
-
}, [activeExperimentForStep, editorVariantOverrides, postHogReady, resolveRenderableStepId, safeActiveStepId]);
|
|
151
|
+
const renderedStepId = useMemo(() => resolveRenderedExperimentStepId({
|
|
152
|
+
activeExperiment: activeExperimentForStep,
|
|
153
|
+
editorVariantOverrides,
|
|
154
|
+
postHogReady,
|
|
155
|
+
resolveRenderableStepId,
|
|
156
|
+
safeActiveStepId,
|
|
157
|
+
}), [activeExperimentForStep, editorVariantOverrides, postHogReady, resolveRenderableStepId, safeActiveStepId]);
|
|
124
158
|
const renderedStepMeta = stepById[renderedStepId];
|
|
125
159
|
const activeStepComponent = stepComponentById[renderedStepId];
|
|
126
160
|
const postHogFeatureFlags = useMemo(() => {
|
|
@@ -416,37 +450,6 @@ export function useFunnelFlowController({ analytics, initialStepId, initialAttri
|
|
|
416
450
|
occurredAt: startedAt,
|
|
417
451
|
featureFlags: postHogFeatureFlagsRef.current,
|
|
418
452
|
});
|
|
419
|
-
if (!engagedStepIdsRef.current.has(renderedStepId)) {
|
|
420
|
-
if (firstStepEngagementTimerRef.current !== null) {
|
|
421
|
-
window.clearTimeout(firstStepEngagementTimerRef.current);
|
|
422
|
-
}
|
|
423
|
-
const engagedStepId = renderedStepId;
|
|
424
|
-
const engagedStartedAt = startedAt;
|
|
425
|
-
const engagedStepName = stepName;
|
|
426
|
-
const engagedStepType = stepType;
|
|
427
|
-
firstStepEngagementTimerRef.current = window.setTimeout(() => {
|
|
428
|
-
var _a;
|
|
429
|
-
firstStepEngagementTimerRef.current = null;
|
|
430
|
-
if (prevStepIdRef.current !== engagedStepId ||
|
|
431
|
-
engagedStepIdsRef.current.has(engagedStepId)) {
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
engagedStepIdsRef.current.add(engagedStepId);
|
|
435
|
-
const engagedAt = new Date().toISOString();
|
|
436
|
-
(_a = analytics === null || analytics === void 0 ? void 0 : analytics.trackStepEngaged) === null || _a === void 0 ? void 0 : _a.call(analytics, {
|
|
437
|
-
userId: currentUserIdRef.current,
|
|
438
|
-
environment: analyticsRuntimeMode,
|
|
439
|
-
stepId: engagedStepId,
|
|
440
|
-
stepName: engagedStepName,
|
|
441
|
-
stepType: engagedStepType,
|
|
442
|
-
startedAt: engagedStartedAt,
|
|
443
|
-
engagedAt,
|
|
444
|
-
durationMs: FIRST_STEP_ENGAGEMENT_THRESHOLD_MS,
|
|
445
|
-
engagementThresholdMs: FIRST_STEP_ENGAGEMENT_THRESHOLD_MS,
|
|
446
|
-
featureFlags: postHogFeatureFlagsRef.current,
|
|
447
|
-
});
|
|
448
|
-
}, FIRST_STEP_ENGAGEMENT_THRESHOLD_MS);
|
|
449
|
-
}
|
|
450
453
|
}
|
|
451
454
|
}
|
|
452
455
|
attributesAtStepStart.current = Object.assign({}, attributesRef.current);
|
|
@@ -464,12 +467,6 @@ export function useFunnelFlowController({ analytics, initialStepId, initialAttri
|
|
|
464
467
|
renderedStepMeta === null || renderedStepMeta === void 0 ? void 0 : renderedStepMeta.type,
|
|
465
468
|
safeActiveStepId,
|
|
466
469
|
]);
|
|
467
|
-
useEffect(() => () => {
|
|
468
|
-
if (firstStepEngagementTimerRef.current !== null) {
|
|
469
|
-
window.clearTimeout(firstStepEngagementTimerRef.current);
|
|
470
|
-
firstStepEngagementTimerRef.current = null;
|
|
471
|
-
}
|
|
472
|
-
}, []);
|
|
473
470
|
useEffect(() => {
|
|
474
471
|
setActiveStepId(safeInitialStepId);
|
|
475
472
|
}, [safeInitialStepId]);
|
|
@@ -584,6 +581,13 @@ export function useFunnelFlowController({ analytics, initialStepId, initialAttri
|
|
|
584
581
|
if (override) {
|
|
585
582
|
return override;
|
|
586
583
|
}
|
|
584
|
+
const forcedAssignment = resolveForcedExperimentAssignment({
|
|
585
|
+
experimentId: experiment.experimentId,
|
|
586
|
+
variants: experiment.variants,
|
|
587
|
+
});
|
|
588
|
+
if (forcedAssignment) {
|
|
589
|
+
return forcedAssignment.variantKey;
|
|
590
|
+
}
|
|
587
591
|
if (postHogReady) {
|
|
588
592
|
const assignment = resolveExperimentAssignment({
|
|
589
593
|
experimentId: experiment.experimentId,
|