@funnelsgrove/runtime 0.1.12 → 0.1.14

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.
@@ -18,6 +18,7 @@ export type FunnelUser = {
18
18
  };
19
19
  export type FunnelContextValue = {
20
20
  activeStepId: FunnelStepId;
21
+ featureFlags: Record<string, string>;
21
22
  isBuilder: boolean;
22
23
  goToStep: (stepId: string) => void;
23
24
  goNext: () => void;
@@ -33,6 +33,10 @@ export declare const RUNTIME_ENV_KEYS: {
33
33
  readonly stripeLivePublishableKey: "NEXT_PUBLIC_STRIPE_LIVE_PUBLISHABLE_KEY";
34
34
  readonly metaPixelEnabled: "NEXT_PUBLIC_META_PIXEL_ENABLED";
35
35
  readonly metaPixelId: "NEXT_PUBLIC_META_PIXEL_ID";
36
+ readonly googleTagEnabled: "NEXT_PUBLIC_GOOGLE_TAG_ENABLED";
37
+ readonly googleAnalyticsId: "NEXT_PUBLIC_GOOGLE_ANALYTICS_ID";
38
+ readonly googleTagManagerId: "NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID";
39
+ readonly googleTagEventTarget: "NEXT_PUBLIC_GOOGLE_TAG_EVENT_TARGET";
36
40
  readonly funnelCustomDomain: "FUNNEL_CUSTOM_DOMAIN";
37
41
  readonly claimbeeWeeklyPriceId: "NEXT_PUBLIC_CLAIMBEE_WEEKLY_PRICE_ID";
38
42
  readonly claimbeeMonthlyPriceId: "NEXT_PUBLIC_CLAIMBEE_MONTHLY_PRICE_ID";
@@ -44,6 +44,10 @@ export const RUNTIME_ENV_KEYS = {
44
44
  stripeLivePublishableKey: 'NEXT_PUBLIC_STRIPE_LIVE_PUBLISHABLE_KEY',
45
45
  metaPixelEnabled: 'NEXT_PUBLIC_META_PIXEL_ENABLED',
46
46
  metaPixelId: 'NEXT_PUBLIC_META_PIXEL_ID',
47
+ googleTagEnabled: 'NEXT_PUBLIC_GOOGLE_TAG_ENABLED',
48
+ googleAnalyticsId: 'NEXT_PUBLIC_GOOGLE_ANALYTICS_ID',
49
+ googleTagManagerId: 'NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID',
50
+ googleTagEventTarget: 'NEXT_PUBLIC_GOOGLE_TAG_EVENT_TARGET',
47
51
  funnelCustomDomain: 'FUNNEL_CUSTOM_DOMAIN',
48
52
  claimbeeWeeklyPriceId: 'NEXT_PUBLIC_CLAIMBEE_WEEKLY_PRICE_ID',
49
53
  claimbeeMonthlyPriceId: 'NEXT_PUBLIC_CLAIMBEE_MONTHLY_PRICE_ID',
@@ -97,6 +101,10 @@ const RUNTIME_ENV_VALUES = {
97
101
  NEXT_PUBLIC_STRIPE_LIVE_PUBLISHABLE_KEY: readRuntimeEnv(() => process.env.NEXT_PUBLIC_STRIPE_LIVE_PUBLISHABLE_KEY),
98
102
  NEXT_PUBLIC_META_PIXEL_ENABLED: readRuntimeEnv(() => process.env.NEXT_PUBLIC_META_PIXEL_ENABLED),
99
103
  NEXT_PUBLIC_META_PIXEL_ID: readRuntimeEnv(() => process.env.NEXT_PUBLIC_META_PIXEL_ID),
104
+ NEXT_PUBLIC_GOOGLE_TAG_ENABLED: readRuntimeEnv(() => process.env.NEXT_PUBLIC_GOOGLE_TAG_ENABLED),
105
+ NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: readRuntimeEnv(() => process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID),
106
+ NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID: readRuntimeEnv(() => process.env.NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID),
107
+ NEXT_PUBLIC_GOOGLE_TAG_EVENT_TARGET: readRuntimeEnv(() => process.env.NEXT_PUBLIC_GOOGLE_TAG_EVENT_TARGET),
100
108
  NEXT_PUBLIC_CLAIMBEE_WEEKLY_PRICE_ID: readRuntimeEnv(() => process.env.NEXT_PUBLIC_CLAIMBEE_WEEKLY_PRICE_ID),
101
109
  NEXT_PUBLIC_CLAIMBEE_MONTHLY_PRICE_ID: readRuntimeEnv(() => process.env.NEXT_PUBLIC_CLAIMBEE_MONTHLY_PRICE_ID),
102
110
  NEXT_PUBLIC_CLAIMBEE_YEARLY_PRICE_ID: readRuntimeEnv(() => process.env.NEXT_PUBLIC_CLAIMBEE_YEARLY_PRICE_ID),
@@ -0,0 +1,18 @@
1
+ import type { FunnelManifestExperiment } from './funnel.manifest.types.js';
2
+ export type PaywallExperimentDefinition = {
3
+ id: string;
4
+ name: string;
5
+ type: 'paywall';
6
+ launchDate: string;
7
+ control: {
8
+ stepId: string;
9
+ trafficPercent: number;
10
+ };
11
+ variant: {
12
+ stepId: string;
13
+ trafficPercent: number;
14
+ };
15
+ };
16
+ export type FunnelExperimentDefinition = PaywallExperimentDefinition;
17
+ export declare const defineFunnelExperiments: <const T extends readonly FunnelExperimentDefinition[]>(experiments: T) => T;
18
+ export declare const toManifestExperiments: (experiments: readonly FunnelExperimentDefinition[]) => FunnelManifestExperiment[];
@@ -0,0 +1,23 @@
1
+ export const defineFunnelExperiments = (experiments) => {
2
+ for (const experiment of experiments) {
3
+ const trafficTotal = experiment.control.trafficPercent + experiment.variant.trafficPercent;
4
+ if (trafficTotal !== 100) {
5
+ throw new Error(`Experiment "${experiment.id}" traffic must sum to 100`);
6
+ }
7
+ }
8
+ return experiments;
9
+ };
10
+ export const toManifestExperiments = (experiments) => experiments.map((experiment) => ({
11
+ experimentId: experiment.id,
12
+ stepId: experiment.control.stepId,
13
+ variants: [
14
+ {
15
+ variantKey: 'control',
16
+ routeToStepId: experiment.control.stepId,
17
+ },
18
+ {
19
+ variantKey: 'variant_b',
20
+ routeToStepId: experiment.variant.stepId,
21
+ },
22
+ ],
23
+ }));
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './config/builder-preview.protocol.js';
2
2
  export * from './config/env.config.js';
3
3
  export * from './config/funnel.manifest.types.js';
4
+ export * from './config/funnel.experiments.types.js';
4
5
  export * from './config/funnel-theme.js';
5
6
  export * from './config/font-config.js';
6
7
  export * from './content/step-content.js';
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './config/builder-preview.protocol.js';
2
2
  export * from './config/env.config.js';
3
3
  export * from './config/funnel.manifest.types.js';
4
+ export * from './config/funnel.experiments.types.js';
4
5
  export * from './config/funnel-theme.js';
5
6
  export * from './config/font-config.js';
6
7
  export * from './content/step-content.js';
@@ -7,6 +7,7 @@ const INTERNAL_ATTRIBUTION_QUERY_KEYS = new Set([
7
7
  'user_id',
8
8
  ]);
9
9
  const POSTHOG_CONTEXT_MAPPINGS = {
10
+ $geoip_city_name: 'cityName',
10
11
  $geoip_country_code: 'countryCode',
11
12
  $geoip_country_name: 'countryName',
12
13
  $browser: 'browser',
@@ -10,7 +10,7 @@ import { usePreviewBridge } from './preview-bridge.js';
10
10
  import { resolveExperimentAssignment } from './experiment-assignment.js';
11
11
  import { collectCurrentFunnelAttribution } from './funnel-attribution.js';
12
12
  import { resolveUrlUserAttributes } from './url-user-attributes.js';
13
- import { bootstrapPostHog, getPostHog, identifyPostHog, isPostHogReady, resolveExperimentVariant, } from './posthog-flags.js';
13
+ import { bootstrapPostHog, identifyPostHog, isPostHogReady, resolveExperimentVariant, } from './posthog-flags.js';
14
14
  import { isEditorEnabled, useRuntimeMode } from '../services/runtime-mode.service.js';
15
15
  import { isPreviewFrameRuntime } from '../services/preview-frame.service.js';
16
16
  const FIRST_STEP_ENGAGEMENT_THRESHOLD_MS = 1000;
@@ -18,9 +18,6 @@ const buildFeatureFlagProperties = (featureFlags) => {
18
18
  return Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [`$feature/${key}`, value]));
19
19
  };
20
20
  const capturePostHogStepEvent = (event, properties) => {
21
- if (!getPostHog() || !POSTHOG_API_HOST || !POSTHOG_PROJECT_API_KEY || !PROJECT_ID) {
22
- return;
23
- }
24
21
  const payload = JSON.stringify({
25
22
  api_key: POSTHOG_PROJECT_API_KEY,
26
23
  batch: [
@@ -366,29 +363,29 @@ export function useFunnelFlowController({ initialStepId, lockToInitialStep = fal
366
363
  return;
367
364
  }
368
365
  const prevId = prevStepIdRef.current;
369
- if (prevId && prevId !== safeActiveStepId) {
366
+ if (prevId && prevId !== renderedStepId) {
370
367
  recordStepCompletion(prevId);
371
368
  }
372
369
  const startedAt = new Date().toISOString();
373
- const stepName = (activeStepMeta === null || activeStepMeta === void 0 ? void 0 : activeStepMeta.name) || (activeStepMeta === null || activeStepMeta === void 0 ? void 0 : activeStepMeta.title) || safeActiveStepId;
374
- stepStartedAtByIdRef.current[safeActiveStepId] = startedAt;
370
+ const stepName = (renderedStepMeta === null || renderedStepMeta === void 0 ? void 0 : renderedStepMeta.name) || (renderedStepMeta === null || renderedStepMeta === void 0 ? void 0 : renderedStepMeta.title) || renderedStepId;
371
+ stepStartedAtByIdRef.current[renderedStepId] = startedAt;
375
372
  if (!isPreviewRuntime) {
376
373
  apiService.trackStepStarted({
377
374
  userId: currentUserIdRef.current,
378
- stepId: safeActiveStepId,
375
+ stepId: renderedStepId,
379
376
  stepName,
380
377
  startedAt,
381
378
  });
382
- capturePostHogStepEvent('step_started', Object.assign({ distinct_id: currentUserIdRef.current, funnel_id: FUNNEL_ID || undefined, funnel_version_id: FUNNEL_VERSION_ID || undefined, project_id: PROJECT_ID || undefined, step_id: safeActiveStepId, step_name: stepName, started_at: startedAt }, buildFeatureFlagProperties(postHogFeatureFlagsRef.current)));
379
+ capturePostHogStepEvent('step_started', Object.assign({ distinct_id: currentUserIdRef.current, funnel_id: FUNNEL_ID || undefined, funnel_version_id: FUNNEL_VERSION_ID || undefined, project_id: PROJECT_ID || undefined, step_id: renderedStepId, step_name: stepName, started_at: startedAt }, buildFeatureFlagProperties(postHogFeatureFlagsRef.current)));
383
380
  }
384
381
  attributesAtStepStart.current = Object.assign({}, attributesRef.current);
385
- prevStepIdRef.current = safeActiveStepId;
382
+ prevStepIdRef.current = renderedStepId;
386
383
  if (isPreviewRuntime ||
387
- safeActiveStepId !== defaultStepId ||
388
- engagedStepIdsRef.current.has(safeActiveStepId)) {
384
+ renderedStepId !== defaultStepId ||
385
+ engagedStepIdsRef.current.has(renderedStepId)) {
389
386
  return;
390
387
  }
391
- const engagedStepId = safeActiveStepId;
388
+ const engagedStepId = renderedStepId;
392
389
  const engagedStartedAt = startedAt;
393
390
  const engagedStepName = stepName;
394
391
  const timer = window.setTimeout(() => {
@@ -414,12 +411,13 @@ export function useFunnelFlowController({ initialStepId, lockToInitialStep = fal
414
411
  return () => window.clearTimeout(timer);
415
412
  }, [
416
413
  activeStepId,
417
- activeStepMeta === null || activeStepMeta === void 0 ? void 0 : activeStepMeta.name,
418
- activeStepMeta === null || activeStepMeta === void 0 ? void 0 : activeStepMeta.title,
419
414
  defaultStepId,
420
415
  isPreviewRuntime,
421
416
  recordStepCompletion,
422
417
  renderSuspended,
418
+ renderedStepId,
419
+ renderedStepMeta === null || renderedStepMeta === void 0 ? void 0 : renderedStepMeta.name,
420
+ renderedStepMeta === null || renderedStepMeta === void 0 ? void 0 : renderedStepMeta.title,
423
421
  safeActiveStepId,
424
422
  ]);
425
423
  useEffect(() => {
@@ -535,6 +533,7 @@ export function useFunnelFlowController({ initialStepId, lockToInitialStep = fal
535
533
  }, [editorVariantOverrides, postHogReady]);
536
534
  const contextValue = useMemo(() => ({
537
535
  activeStepId: safeActiveStepId,
536
+ featureFlags: postHogFeatureFlags,
538
537
  isBuilder: isPreviewRuntime,
539
538
  goToStep: goToStepFromContext,
540
539
  goNext,
@@ -561,6 +560,7 @@ export function useFunnelFlowController({ initialStepId, lockToInitialStep = fal
561
560
  goNext,
562
561
  goToStepFromContext,
563
562
  isPreviewRuntime,
563
+ postHogFeatureFlags,
564
564
  resolveRenderableStepId,
565
565
  safeActiveStepId,
566
566
  setAnswer,
@@ -33,6 +33,10 @@ export declare const PROJECT_ENV_STANDARD_KEYS: {
33
33
  readonly metaPixelId: "NEXT_PUBLIC_META_PIXEL_ID";
34
34
  readonly metaConversionsAccessToken: "META_CONVERSIONS_ACCESS_TOKEN";
35
35
  readonly metaTestEventCode: "META_TEST_EVENT_CODE";
36
+ readonly googleTagEnabled: "NEXT_PUBLIC_GOOGLE_TAG_ENABLED";
37
+ readonly googleAnalyticsId: "NEXT_PUBLIC_GOOGLE_ANALYTICS_ID";
38
+ readonly googleTagManagerId: "NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID";
39
+ readonly googleTagEventTarget: "NEXT_PUBLIC_GOOGLE_TAG_EVENT_TARGET";
36
40
  };
37
41
  export type ProjectEnvStandardKey = keyof typeof PROJECT_ENV_STANDARD_KEYS;
38
42
  export type ProjectEnvStandardValues = Record<ProjectEnvStandardKey, string>;
@@ -33,6 +33,10 @@ export const PROJECT_ENV_STANDARD_KEYS = {
33
33
  metaPixelId: 'NEXT_PUBLIC_META_PIXEL_ID',
34
34
  metaConversionsAccessToken: 'META_CONVERSIONS_ACCESS_TOKEN',
35
35
  metaTestEventCode: 'META_TEST_EVENT_CODE',
36
+ googleTagEnabled: 'NEXT_PUBLIC_GOOGLE_TAG_ENABLED',
37
+ googleAnalyticsId: 'NEXT_PUBLIC_GOOGLE_ANALYTICS_ID',
38
+ googleTagManagerId: 'NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID',
39
+ googleTagEventTarget: 'NEXT_PUBLIC_GOOGLE_TAG_EVENT_TARGET',
36
40
  };
37
41
  export const normalizeProjectEnvValue = (value) => {
38
42
  return (value === null || value === void 0 ? void 0 : value.trim()) || '';
@@ -100,6 +104,10 @@ const buildProjectStandardValues = (envMap) => {
100
104
  metaPixelId: getProjectEnvValue(envMap, PROJECT_ENV_STANDARD_KEYS.metaPixelId),
101
105
  metaConversionsAccessToken: getProjectEnvValue(envMap, PROJECT_ENV_STANDARD_KEYS.metaConversionsAccessToken),
102
106
  metaTestEventCode: getProjectEnvValue(envMap, PROJECT_ENV_STANDARD_KEYS.metaTestEventCode),
107
+ googleTagEnabled: getProjectEnvValue(envMap, PROJECT_ENV_STANDARD_KEYS.googleTagEnabled),
108
+ googleAnalyticsId: getProjectEnvValue(envMap, PROJECT_ENV_STANDARD_KEYS.googleAnalyticsId),
109
+ googleTagManagerId: getProjectEnvValue(envMap, PROJECT_ENV_STANDARD_KEYS.googleTagManagerId),
110
+ googleTagEventTarget: getProjectEnvValue(envMap, PROJECT_ENV_STANDARD_KEYS.googleTagEventTarget),
103
111
  };
104
112
  };
105
113
  export const parseProjectEnvVariables = (variables) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/runtime",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.js",