@funnelsgrove/runtime 0.7.13 → 0.7.15
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/posthog-flags.d.ts +5 -0
- package/dist/runtime/posthog-flags.js +19 -1
- package/dist/runtime/use-funnel-flow-controller.d.ts +5 -1
- package/dist/runtime/use-funnel-flow-controller.js +16 -2
- package/dist/services/funnel-runtime-config.d.ts +9 -0
- package/dist/services/funnel-runtime-config.js +14 -0
- package/package.json +1 -1
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { PostHog } from 'posthog-js';
|
|
2
2
|
export declare const POSTHOG_FEATURE_FLAG_READY_TIMEOUT_MS = 2500;
|
|
3
3
|
export declare const POSTHOG_SCOPED_FLAG_RELOAD_RETRY_MS = 300;
|
|
4
|
+
export declare const shouldRecordPostHogSession: (distinctId: string, sampleRate: number) => boolean;
|
|
4
5
|
type BootstrapConfig = {
|
|
5
6
|
apiKey: string;
|
|
6
7
|
apiHost: string;
|
|
7
8
|
distinctId: string;
|
|
8
9
|
projectId?: string;
|
|
9
10
|
funnelId?: string;
|
|
11
|
+
sessionReplay?: {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
sampleRate: number;
|
|
14
|
+
};
|
|
10
15
|
};
|
|
11
16
|
export declare const bootstrapPostHog: (config: BootstrapConfig) => Promise<void>;
|
|
12
17
|
export declare const isPostHogReady: () => boolean;
|
|
@@ -5,6 +5,15 @@ let readyPromise = null;
|
|
|
5
5
|
export const POSTHOG_FEATURE_FLAG_READY_TIMEOUT_MS = 2500;
|
|
6
6
|
export const POSTHOG_SCOPED_FLAG_RELOAD_RETRY_MS = 300;
|
|
7
7
|
const STORED_PERSON_PROPERTIES_KEY = '$stored_person_properties';
|
|
8
|
+
export const shouldRecordPostHogSession = (distinctId, sampleRate) => {
|
|
9
|
+
const normalizedRate = Math.min(100, Math.max(0, Math.round(sampleRate)));
|
|
10
|
+
let hash = 2166136261;
|
|
11
|
+
for (let index = 0; index < distinctId.length; index += 1) {
|
|
12
|
+
hash ^= distinctId.charCodeAt(index);
|
|
13
|
+
hash = Math.imul(hash, 16777619);
|
|
14
|
+
}
|
|
15
|
+
return (hash >>> 0) % 100 < normalizedRate;
|
|
16
|
+
};
|
|
8
17
|
const buildFlagScopeProperties = (config) => {
|
|
9
18
|
var _a, _b;
|
|
10
19
|
const properties = {};
|
|
@@ -59,6 +68,7 @@ export const bootstrapPostHog = (config) => {
|
|
|
59
68
|
return readyPromise;
|
|
60
69
|
}
|
|
61
70
|
readyPromise = new Promise((resolve) => {
|
|
71
|
+
var _a;
|
|
62
72
|
if (!config.apiKey || !config.apiHost) {
|
|
63
73
|
resolve();
|
|
64
74
|
return;
|
|
@@ -76,6 +86,8 @@ export const bootstrapPostHog = (config) => {
|
|
|
76
86
|
};
|
|
77
87
|
readyTimeoutId = setTimeout(markReady, POSTHOG_FEATURE_FLAG_READY_TIMEOUT_MS);
|
|
78
88
|
const scopeProperties = buildFlagScopeProperties(config);
|
|
89
|
+
const sessionReplayEnabled = ((_a = config.sessionReplay) === null || _a === void 0 ? void 0 : _a.enabled) === true
|
|
90
|
+
&& shouldRecordPostHogSession(config.distinctId, config.sessionReplay.sampleRate);
|
|
79
91
|
primePostHogFlagScopePersistence(config.apiKey, scopeProperties);
|
|
80
92
|
posthog.init(config.apiKey, {
|
|
81
93
|
api_host: config.apiHost,
|
|
@@ -89,11 +101,17 @@ export const bootstrapPostHog = (config) => {
|
|
|
89
101
|
capture_exceptions: false,
|
|
90
102
|
capture_heatmaps: false,
|
|
91
103
|
capture_performance: false,
|
|
92
|
-
disable_session_recording:
|
|
104
|
+
disable_session_recording: !sessionReplayEnabled,
|
|
105
|
+
session_recording: {
|
|
106
|
+
maskAllInputs: true,
|
|
107
|
+
},
|
|
93
108
|
disable_surveys: true,
|
|
94
109
|
advanced_disable_feature_flags_on_first_load: true,
|
|
95
110
|
bootstrap: { distinctID: config.distinctId },
|
|
96
111
|
loaded: () => {
|
|
112
|
+
if (Object.keys(scopeProperties).length > 0) {
|
|
113
|
+
posthog.register(scopeProperties);
|
|
114
|
+
}
|
|
97
115
|
let unsubscribe = null;
|
|
98
116
|
let scopedReloadRetried = false;
|
|
99
117
|
unsubscribe = posthog.onFeatureFlags((flags) => {
|
|
@@ -118,6 +118,10 @@ 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
|
+
};
|
|
121
125
|
getPathForStep: (stepId: StepId) => string;
|
|
122
126
|
getStepIdFromPath: (pathname: string) => StepId | null;
|
|
123
127
|
getSequentialNextStepId: (stepId: StepId) => StepId | null;
|
|
@@ -179,5 +183,5 @@ export declare function resolveRenderedExperimentStepId(input: {
|
|
|
179
183
|
resolveRenderableStepId: (stepId: string | null | undefined) => string | null;
|
|
180
184
|
safeActiveStepId: string;
|
|
181
185
|
}): string;
|
|
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>;
|
|
186
|
+
export declare function useFunnelFlowController<StepId extends string>({ api, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, sessionReplay, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }: UseFunnelFlowControllerInput<StepId>): UseFunnelFlowControllerResult<StepId>;
|
|
183
187
|
export {};
|
|
@@ -278,7 +278,7 @@ export function resolveRenderedExperimentStepId(input) {
|
|
|
278
278
|
const variantStepId = resolveRenderableStepId(assignment.routeToStepId);
|
|
279
279
|
return variantStepId !== null && variantStepId !== void 0 ? variantStepId : safeActiveStepId;
|
|
280
280
|
}
|
|
281
|
-
export function useFunnelFlowController({ api = apiService, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep = false, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }) {
|
|
281
|
+
export function useFunnelFlowController({ api = apiService, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep = false, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, sessionReplay, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }) {
|
|
282
282
|
var _a, _b, _c;
|
|
283
283
|
const [runtimeMode] = useRuntimeMode();
|
|
284
284
|
const [editorModeEnabled, setEditorModeEnabled] = useState(() => isEditorEnabled());
|
|
@@ -287,6 +287,8 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
287
287
|
const [editorVariantOverrides, setEditorVariantOverrides] = useState({});
|
|
288
288
|
const shouldLockToInitialStep = lockToInitialStep || isPreviewStepLockRequested();
|
|
289
289
|
const isPreviewRuntime = useMemo(() => isPreviewFrameRuntime(), []);
|
|
290
|
+
const sessionReplayEnabled = sessionReplay === null || sessionReplay === void 0 ? void 0 : sessionReplay.enabled;
|
|
291
|
+
const sessionReplaySampleRate = sessionReplay === null || sessionReplay === void 0 ? void 0 : sessionReplay.sampleRate;
|
|
290
292
|
const resolveRenderableStepId = useCallback((stepId) => {
|
|
291
293
|
if (!stepId || !isFunnelStepId(stepId)) {
|
|
292
294
|
return null;
|
|
@@ -540,6 +542,12 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
540
542
|
distinctId: localUserId,
|
|
541
543
|
projectId: PROJECT_ID,
|
|
542
544
|
funnelId: FUNNEL_ID,
|
|
545
|
+
sessionReplay: sessionReplayEnabled !== undefined && sessionReplaySampleRate !== undefined
|
|
546
|
+
? {
|
|
547
|
+
enabled: sessionReplayEnabled,
|
|
548
|
+
sampleRate: sessionReplaySampleRate,
|
|
549
|
+
}
|
|
550
|
+
: undefined,
|
|
543
551
|
})
|
|
544
552
|
.then(() => setPostHogReady(true))
|
|
545
553
|
.catch((error) => {
|
|
@@ -598,7 +606,13 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
598
606
|
logger.error('Failed to identify PostHog user:', error);
|
|
599
607
|
});
|
|
600
608
|
return cancelAttributionFailOpen;
|
|
601
|
-
}, [
|
|
609
|
+
}, [
|
|
610
|
+
api,
|
|
611
|
+
isPreviewRuntime,
|
|
612
|
+
isSubscriptionManagementRuntime,
|
|
613
|
+
sessionReplayEnabled,
|
|
614
|
+
sessionReplaySampleRate,
|
|
615
|
+
]);
|
|
602
616
|
const recordStepCompletion = useCallback((intent, options = {}) => {
|
|
603
617
|
var _a;
|
|
604
618
|
const meta = stepById[intent.visit.stepId];
|
|
@@ -4,14 +4,23 @@ export type FunnelRuntimeConfig = {
|
|
|
4
4
|
schemaVersion: 2;
|
|
5
5
|
funnelId: string;
|
|
6
6
|
projectId: string;
|
|
7
|
+
sessionReplay?: {
|
|
8
|
+
enabled?: unknown;
|
|
9
|
+
sampleRate?: unknown;
|
|
10
|
+
};
|
|
7
11
|
offerSets: unknown[];
|
|
8
12
|
experiments: unknown[];
|
|
9
13
|
};
|
|
14
|
+
export type FunnelSessionReplayConfig = {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
sampleRate: number;
|
|
17
|
+
};
|
|
10
18
|
export type ResolvedFunnelRuntimeConfig = {
|
|
11
19
|
revisionId: string;
|
|
12
20
|
sourceDeploymentId: string;
|
|
13
21
|
sourceVersionId: string;
|
|
14
22
|
publishedAt: string;
|
|
23
|
+
sessionReplay?: FunnelSessionReplayConfig;
|
|
15
24
|
offerSets: readonly GeneratedOfferSet[];
|
|
16
25
|
experiments: readonly FunnelExperimentDefinition[];
|
|
17
26
|
};
|
|
@@ -26,6 +26,19 @@ const pick = (record, ...keys) => {
|
|
|
26
26
|
const pickString = (record, ...keys) => (readString(pick(record, ...keys)));
|
|
27
27
|
const pickNumber = (record, ...keys) => (readNumber(pick(record, ...keys)));
|
|
28
28
|
const pickBoolean = (record, ...keys) => (readBoolean(pick(record, ...keys)));
|
|
29
|
+
const normalizeSessionReplay = (value) => {
|
|
30
|
+
var _a;
|
|
31
|
+
if (!isRecord(value)) {
|
|
32
|
+
return { enabled: false, sampleRate: 10 };
|
|
33
|
+
}
|
|
34
|
+
const sampleRate = readNumber(value.sampleRate);
|
|
35
|
+
return {
|
|
36
|
+
enabled: (_a = readBoolean(value.enabled)) !== null && _a !== void 0 ? _a : false,
|
|
37
|
+
sampleRate: sampleRate === null
|
|
38
|
+
? 10
|
|
39
|
+
: Math.min(100, Math.max(1, Math.round(sampleRate))),
|
|
40
|
+
};
|
|
41
|
+
};
|
|
29
42
|
const deepFreeze = (value) => {
|
|
30
43
|
if (!value || typeof value !== 'object' || Object.isFrozen(value)) {
|
|
31
44
|
return value;
|
|
@@ -234,6 +247,7 @@ export const resolvePublishedFunnelRuntimeConfig = (published, options = {}) =>
|
|
|
234
247
|
sourceDeploymentId: published.sourceDeploymentId,
|
|
235
248
|
sourceVersionId: published.sourceVersionId,
|
|
236
249
|
publishedAt: published.publishedAt,
|
|
250
|
+
sessionReplay: normalizeSessionReplay(published.config.sessionReplay),
|
|
237
251
|
offerSets,
|
|
238
252
|
experiments: normalizePublishedExperiments(published.config, published.publishedAt, offerSets),
|
|
239
253
|
});
|