@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.
@@ -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: true,
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
- }, [api, isPreviewRuntime, isSubscriptionManagementRuntime]);
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/runtime",
3
- "version": "0.7.13",
3
+ "version": "0.7.15",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",