@v-tilt/browser 1.11.0 → 1.13.0

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.
Files changed (90) hide show
  1. package/dist/all-external-dependencies.js +1 -1
  2. package/dist/all-external-dependencies.js.map +1 -1
  3. package/dist/array.full.js +1 -1
  4. package/dist/array.full.js.map +1 -1
  5. package/dist/array.js +1 -1
  6. package/dist/array.js.map +1 -1
  7. package/dist/array.no-external.js +1 -1
  8. package/dist/array.no-external.js.map +1 -1
  9. package/dist/autocapture-types.d.ts +17 -0
  10. package/dist/autocapture-utils.d.ts +24 -1
  11. package/dist/autocapture.d.ts +94 -5
  12. package/dist/chat.js +1 -1
  13. package/dist/chat.js.map +1 -1
  14. package/dist/config.d.ts +8 -1
  15. package/dist/constants.d.ts +19 -13
  16. package/dist/core/capture.d.ts +15 -5
  17. package/dist/core/config-utils.d.ts +15 -0
  18. package/dist/core/consent.d.ts +62 -0
  19. package/dist/core/event-buffer.d.ts +60 -0
  20. package/dist/core/fb-cookies.d.ts +32 -0
  21. package/dist/core/feature-manager.d.ts +61 -69
  22. package/dist/core/fifo-queue.d.ts +23 -0
  23. package/dist/core/identity.d.ts +23 -33
  24. package/dist/core/index.d.ts +7 -1
  25. package/dist/core/page-lifecycle.d.ts +41 -0
  26. package/dist/core/remote-config.d.ts +14 -17
  27. package/dist/extensions/chat/bubble-drag.d.ts +30 -0
  28. package/dist/extensions/chat/chat-api.d.ts +15 -0
  29. package/dist/extensions/chat/chat-styles.d.ts +27 -0
  30. package/dist/extensions/chat/chat-wrapper.d.ts +20 -145
  31. package/dist/extensions/chat/chat.d.ts +261 -14
  32. package/dist/extensions/chat/message-content-styles.d.ts +1 -0
  33. package/dist/extensions/chat/message-html.d.ts +6 -0
  34. package/dist/extensions/chat/message-markdown.d.ts +8 -0
  35. package/dist/extensions/chat/normalize-send-content.d.ts +24 -0
  36. package/dist/extensions/chat/types.d.ts +19 -57
  37. package/dist/extensions/chat/widget-registry.d.ts +53 -0
  38. package/dist/extensions/chat/widgets/collect-email.d.ts +6 -0
  39. package/dist/extensions/chat/widgets/escalate-to-human.d.ts +6 -0
  40. package/dist/extensions/ga4-proxy.d.ts +59 -0
  41. package/dist/extensions/google-tag-gateway/consent-bridge.d.ts +27 -0
  42. package/dist/extensions/google-tag-gateway/enhanced-conversions.d.ts +35 -0
  43. package/dist/extensions/google-tag-gateway/event-bridge.d.ts +74 -0
  44. package/dist/extensions/google-tag-gateway/google-tag-gateway.d.ts +95 -0
  45. package/dist/extensions/google-tag-gateway/gtag-loader.d.ts +85 -0
  46. package/dist/extensions/google-tag-gateway/index.d.ts +7 -0
  47. package/dist/extensions/google-tag-gateway/normalize.d.ts +28 -0
  48. package/dist/extensions/google-tag-gateway/public-api.d.ts +23 -0
  49. package/dist/extensions/history-autocapture.d.ts +2 -2
  50. package/dist/extensions/replay/index.d.ts +1 -1
  51. package/dist/extensions/replay/session-recording-utils.d.ts +13 -43
  52. package/dist/extensions/replay/session-recording-wrapper.d.ts +10 -66
  53. package/dist/extensions/replay/session-recording.d.ts +53 -1
  54. package/dist/extensions/replay/types.d.ts +6 -1
  55. package/dist/extensions/web-vitals/web-vitals-manager.d.ts +14 -43
  56. package/dist/external-scripts-loader.js +1 -1
  57. package/dist/external-scripts-loader.js.map +1 -1
  58. package/dist/feature.d.ts +54 -172
  59. package/dist/main.js +1 -1
  60. package/dist/main.js.map +1 -1
  61. package/dist/module.d.ts +728 -753
  62. package/dist/module.js +1 -1
  63. package/dist/module.js.map +1 -1
  64. package/dist/module.no-external.d.ts +728 -753
  65. package/dist/module.no-external.js +1 -1
  66. package/dist/module.no-external.js.map +1 -1
  67. package/dist/rate-limiter.d.ts +0 -1
  68. package/dist/recorder.js +1 -1
  69. package/dist/recorder.js.map +1 -1
  70. package/dist/request.d.ts +34 -20
  71. package/dist/scroll-depth-tracker.d.ts +42 -0
  72. package/dist/server.d.ts +114 -0
  73. package/dist/server.js +1 -1
  74. package/dist/server.js.map +1 -1
  75. package/dist/session.d.ts +12 -0
  76. package/dist/types.d.ts +204 -9
  77. package/dist/user-manager.d.ts +26 -52
  78. package/dist/utils/base64.d.ts +30 -0
  79. package/dist/utils/bot-detection.d.ts +28 -0
  80. package/dist/utils/endpoint-url.d.ts +36 -0
  81. package/dist/utils/event-emitter.d.ts +1 -0
  82. package/dist/utils/globals.d.ts +71 -2
  83. package/dist/utils/index.d.ts +20 -5
  84. package/dist/utils/logger.d.ts +66 -0
  85. package/dist/utils/request-utils.d.ts +5 -0
  86. package/dist/utils/safewrap.d.ts +6 -1
  87. package/dist/utils/transport-health.d.ts +55 -0
  88. package/dist/vtilt.d.ts +85 -25
  89. package/dist/web-vitals.js.map +1 -1
  90. package/package.json +71 -66
@@ -6,6 +6,48 @@
6
6
  */
7
7
  type PersonProfilesMode = "always" | "identified_only" | "never";
8
8
 
9
+ /**
10
+ * First argument to `vt.sendChatMessage()` / {@link LazyLoadedChatInterface.sendMessage}.
11
+ *
12
+ * Plain strings stay plain text for backward compatibility. Use `{ markdown: '...' }`,
13
+ * `{ html: '...' }`, or `options.format` for rich content.
14
+ */
15
+ type SendChatMessageContent = string | {
16
+ text: string;
17
+ } | {
18
+ markdown: string;
19
+ } | {
20
+ html: string;
21
+ };
22
+ /**
23
+ * Options for `vt.sendChatMessage()` / {@link LazyLoadedChatInterface.sendMessage}.
24
+ */
25
+ interface SendChatMessageOptions {
26
+ /**
27
+ * Target conversation: `'new'` creates a channel, a UUID selects an existing one,
28
+ * omit to use the active conversation (must already be in conversation view).
29
+ */
30
+ channel?: "new" | string;
31
+ /**
32
+ * Open the widget panel before sending. Default: true. Pass `open: false` to send without opening.
33
+ */
34
+ open?: boolean;
35
+ /**
36
+ * Applies when the first argument is a plain string. Default: `text`.
37
+ * `markdown` and `html` use the same sanitized rendering as AI/agent messages.
38
+ */
39
+ format?: "text" | "markdown" | "html";
40
+ }
41
+ /**
42
+ * Chat bubble appearance and behavior configuration
43
+ */
44
+ interface BubbleConfig {
45
+ /** Allow user to drag the bubble to reposition (default: false) */
46
+ draggable?: boolean;
47
+ /** Show the bubble on load (default: true). Set to false to control via vt.chat.show() */
48
+ visible?: boolean;
49
+ }
50
+
9
51
  /**
10
52
  * VTilt Types
11
53
  *
@@ -16,7 +58,11 @@ type PersonProfilesMode = "always" | "identified_only" | "never";
16
58
  interface VTiltConfig {
17
59
  /** Project identifier (required) */
18
60
  token: string;
19
- /** API host for tracking (default: https://api.vtilt.io) */
61
+ /**
62
+ * API host for all SDK requests (events, config, recordings, chat).
63
+ * Set this to your own domain when using a reverse proxy.
64
+ * If not set, SDK uses relative URLs (same-origin).
65
+ */
20
66
  api_host?: string;
21
67
  /** UI host for dashboard links */
22
68
  ui_host?: string | null;
@@ -26,10 +72,6 @@ interface VTiltConfig {
26
72
  * If not set, falls back to {api_host}/dist/{script}.js
27
73
  */
28
74
  script_host?: string;
29
- /** Proxy domain for tracking requests */
30
- proxy?: string;
31
- /** Full proxy URL for tracking requests */
32
- proxyUrl?: string;
33
75
  /** Instance name (for multiple instances) */
34
76
  name?: string;
35
77
  /** Domain to track (auto-detected if not provided) - used in event properties */
@@ -83,8 +125,25 @@ interface VTiltConfig {
83
125
  mask_all_element_attributes?: boolean;
84
126
  /** Respect Do Not Track browser setting */
85
127
  respect_dnt?: boolean;
128
+ /**
129
+ * When false (default), events are not sent when the browser looks like a bot
130
+ * (known crawler user agents, `navigator.webdriver`, or blocked CH brands).
131
+ * Set to true to disable bot filtering (PostHog: `opt_out_useragent_filter`).
132
+ */
133
+ opt_out_useragent_filter?: boolean;
134
+ /**
135
+ * Extra user-agent substrings to treat as bots (case-insensitive), merged with
136
+ * the built-in list aligned with PostHog (PostHog: `custom_blocked_useragents`).
137
+ */
138
+ custom_blocked_useragents?: string[];
86
139
  /** Opt users out by default */
87
140
  opt_out_capturing_by_default?: boolean;
141
+ /**
142
+ * When true, non-essential events are blocked until setConsent() is called.
143
+ * Essential events ($identify, $alias, $set) are always allowed.
144
+ * Use with vt.setConsent({ analytics: true, marketing: true, advertising: true }).
145
+ */
146
+ require_consent?: boolean;
88
147
  /** Session recording configuration */
89
148
  session_recording?: SessionRecordingOptions;
90
149
  /** Disable session recording (convenience flag) */
@@ -93,6 +152,46 @@ interface VTiltConfig {
93
152
  chat?: ChatWidgetConfig;
94
153
  /** Disable chat (convenience flag) */
95
154
  disable_chat?: boolean;
155
+ /**
156
+ * Google Tag Gateway feature config (merged `ga4_gtag` + `google_ads_gtag`
157
+ * rows). Property name is the stable SDK config key; populated from `/decide`
158
+ * or set for SSR/tests.
159
+ */
160
+ google_tag?: GoogleTagClientConfig;
161
+ /** Disable the Google Tag Gateway feature (convenience flag) */
162
+ disable_google_tag?: boolean;
163
+ /**
164
+ * Console log level for SDK output prefixed with `[vTilt]`.
165
+ *
166
+ * Levels (each includes the levels above it):
167
+ * - 'none' — silence everything (escape hatch for shared kiosks etc.)
168
+ * - 'error' — SDK errors only
169
+ * - 'warn' — errors + warnings (default)
170
+ * - 'info' — + lifecycle events (init, autocapture started, replay started)
171
+ * - 'debug' — + per-event trace (every captured event, autocapture skips, requests)
172
+ *
173
+ * Default is 'warn' so SDK errors and warnings always surface without opt-in
174
+ * (matches Amplitude/PostHog/Sentry).
175
+ *
176
+ * NOT to be confused with the per-event `$debug` flag — that is a separate
177
+ * marker on the event payload itself, controlled by `debug: true` (below)
178
+ * or the `?vtilt_debug=1` URL parameter, and consumed by the Debug View in
179
+ * the dashboard. See the public docs for details.
180
+ *
181
+ * Setting `log_level` (or `debug: true`) at init time pins the level — it
182
+ * cannot be overridden by remote config / Default Configuration. Leave it
183
+ * unset to let project admins control verbosity from the dashboard.
184
+ */
185
+ log_level?: "none" | "error" | "warn" | "info" | "debug";
186
+ /**
187
+ * PostHog-style shorthand: when true, equivalent to `log_level: 'debug'`.
188
+ * Ignored if `log_level` is also set.
189
+ *
190
+ * Setting this to true ALSO marks every captured event with `$debug: true`
191
+ * so the dashboard's Debug View highlights it. The `?vtilt_debug=1` URL
192
+ * parameter sets the event flag without raising the console log level.
193
+ */
194
+ debug?: boolean;
96
195
  /** Global attributes added to all events */
97
196
  globalAttributes?: Record<string, string>;
98
197
  /** Bootstrap data for initialization (server-side rendering) */
@@ -107,6 +206,8 @@ interface VTiltConfig {
107
206
  before_send?: (event: CaptureResult) => CaptureResult | null;
108
207
  /** Loaded callback */
109
208
  loaded?: (vtilt: any) => void;
209
+ /** @internal Set by RemoteConfigManager after a fresh /decide response (or fetch failure). */
210
+ __remote_config_loaded?: boolean;
110
211
  }
111
212
  interface EventPayload {
112
213
  [key: string]: any;
@@ -175,9 +276,13 @@ interface UserIdentity {
175
276
  interface UserProperties {
176
277
  [key: string]: any;
177
278
  }
178
- interface AliasEvent {
179
- distinct_id: string;
180
- original: string;
279
+ /** Batched identity state change — applied atomically with a single save. */
280
+ interface IdentityUpdate {
281
+ distinct_id?: string;
282
+ user_state?: "anonymous" | "identified";
283
+ device_id?: string;
284
+ properties_set?: Properties;
285
+ properties_set_once?: Properties;
181
286
  }
182
287
  /** Supported Web Vitals metrics */
183
288
  type SupportedWebVitalsMetric = "LCP" | "CLS" | "FCP" | "INP" | "TTFB";
@@ -315,6 +420,16 @@ interface AutocaptureOptions {
315
420
  * @default false
316
421
  */
317
422
  capture_element_values?: boolean;
423
+ /**
424
+ * Scroll depth autocapture — two independently opt-in modes (both default off).
425
+ */
426
+ scroll_depth?: {
427
+ milestones?: boolean | {
428
+ thresholds?: number[];
429
+ };
430
+ pageleave?: boolean;
431
+ scroll_root_selector?: string | string[];
432
+ };
318
433
  }
319
434
  /** Mask options for input elements in session recording */
320
435
  interface SessionRecordingMaskInputOptions {
@@ -361,6 +476,11 @@ interface SessionRecordingOptions {
361
476
  blockClass?: string;
362
477
  /** Block selector for elements to hide */
363
478
  blockSelector?: string;
479
+ /**
480
+ * When `true`, skip built-in `blockSelector` entries for common invisible
481
+ * template nodes (screen-reader-only, Webflow CMS placeholders, etc.).
482
+ */
483
+ skipDefaultInvisibleBlocking?: boolean;
364
484
  /** Ignore class for input masking (default: 'vt-ignore-input') */
365
485
  ignoreClass?: string;
366
486
  /** Mask text class (default: 'vt-mask') */
@@ -381,7 +501,7 @@ interface SessionRecordingOptions {
381
501
  recordHeaders?: boolean;
382
502
  /** Record body in network requests */
383
503
  recordBody?: boolean;
384
- /** Compress events before sending (default: true) */
504
+ /** Gzip-compress the request body before sending (default: true) */
385
505
  compressEvents?: boolean;
386
506
  /** Internal: Mutation throttler refill rate */
387
507
  __mutationThrottlerRefillRate?: number;
@@ -410,6 +530,104 @@ interface ChatWidgetConfig {
410
530
  offlineMessage?: string;
411
531
  /** Collect email when offline */
412
532
  collectEmailOffline?: boolean;
533
+ /** Bubble appearance and behavior */
534
+ bubble?: BubbleConfig;
535
+ }
536
+ type GoogleConsentValue = "follow_advertising" | "follow_analytics" | "granted" | "denied";
537
+ interface GoogleConsentOverride {
538
+ ad_storage?: GoogleConsentValue;
539
+ ad_user_data?: GoogleConsentValue;
540
+ ad_personalization?: GoogleConsentValue;
541
+ analytics_storage?: GoogleConsentValue;
542
+ }
543
+ interface GoogleAdsConversionMapping {
544
+ event_name: string;
545
+ send_to: string;
546
+ value_param_path?: string;
547
+ currency_param_path?: string;
548
+ default_currency?: string;
549
+ send_transaction_id?: boolean;
550
+ transaction_id_param_path?: string;
551
+ }
552
+ /**
553
+ * How gtag.js is loaded — mirrors the server-side `proxy_mode` destination
554
+ * setting:
555
+ *
556
+ * - `proxied` (default) — route through `api_host/gt/*`. `api_host` is the
557
+ * SDK-level proxy setting; self-hosted customers point it at their own
558
+ * domain (which must implement the `/gt/*` spec).
559
+ * - `direct` — no proxy; gtag.js loads straight from
560
+ * `https://www.googletagmanager.com/gtag/js`.
561
+ */
562
+ type GoogleTagProxyMode = "proxied" | "direct";
563
+ /**
564
+ * Configuration for the Google Tag Gateway SDK feature. Ships under the
565
+ * `googleTag` key of /decide when at least one `ga4_gtag` or `google_ads_gtag` destination is
566
+ * enabled for the project.
567
+ */
568
+ interface GoogleTagClientConfig {
569
+ destinationId: string;
570
+ /**
571
+ * Proxy mode. When absent the SDK falls back to `proxied` so older
572
+ * `/decide` responses and the zero-config default remain compatible.
573
+ */
574
+ proxyMode?: GoogleTagProxyMode;
575
+ tagIds: string[];
576
+ conversions: GoogleAdsConversionMapping[];
577
+ enhancedConversions: boolean;
578
+ conversionLinker: boolean;
579
+ linkerDomains: string[];
580
+ capturePageview: boolean;
581
+ debugMode: boolean;
582
+ consentOverride?: GoogleConsentOverride;
583
+ /**
584
+ * Master switch for forwarding every `vt.capture()` event to GA4 via
585
+ * `gtag('event', name, params)`. Defaults to `true` when absent. When
586
+ * `false`, only events that match an explicit row in `eventMappings` — or
587
+ * an Ads `conversions` mapping — fire; everything else is left to the
588
+ * Google tag's own behavior (page_view, enhanced measurement, etc.).
589
+ */
590
+ autoForward?: boolean;
591
+ /**
592
+ * Whether to fire the direct Ads conversion beacon
593
+ * (`gtag('event', 'conversion', {send_to: 'AW-.../...'})`) for rows in
594
+ * `conversions`. Defaults to `true` when absent. Turn off when GA4 is
595
+ * linked to Google Ads in the Ads account — in that mode the GA4 event
596
+ * (marked as a conversion in GA4) propagates to Ads automatically and
597
+ * the direct beacon would duplicate it. Has no effect when `conversions`
598
+ * is empty.
599
+ */
600
+ sendAdsConversions?: boolean;
601
+ /**
602
+ * Admin-configured event renames and param remappings. Mirrors the
603
+ * generic `event_mappings` configured on the destination. Applied after
604
+ * the filter check and before firing `gtag('event', ...)`.
605
+ */
606
+ eventMappings?: GoogleTagEventMapping[];
607
+ /**
608
+ * Admin-configured include/exclude lists. Mirrors the generic
609
+ * `event_filter` on the destination. Evaluated before any mapping.
610
+ */
611
+ eventFilter?: {
612
+ include?: string[];
613
+ exclude?: string[];
614
+ };
615
+ }
616
+ /**
617
+ * Rename an event and optionally remap payload paths to gtag param paths.
618
+ * Structurally identical to the server-side `EventMapping` type but lives
619
+ * in the browser package to avoid cross-package imports. Source paths use
620
+ * dotted notation into the captured payload (e.g. `order.total`); dest
621
+ * paths use dotted notation into the outgoing gtag `params` object.
622
+ */
623
+ interface GoogleTagEventMapping {
624
+ source: string;
625
+ destination: string;
626
+ param_mappings?: GoogleTagParamMapping[];
627
+ }
628
+ interface GoogleTagParamMapping {
629
+ source: string;
630
+ destination: string;
413
631
  }
414
632
  interface RemoteConfig {
415
633
  sessionRecording?: {
@@ -431,6 +649,8 @@ interface RemoteConfig {
431
649
  enabled?: boolean;
432
650
  widgetPosition?: "bottom-right" | "bottom-left";
433
651
  widgetColor?: string;
652
+ bubbleDraggable?: boolean;
653
+ bubbleVisible?: boolean;
434
654
  };
435
655
  chatTracking?: {
436
656
  trackUserMessages?: boolean;
@@ -441,17 +661,33 @@ interface RemoteConfig {
441
661
  capturePageleave?: boolean;
442
662
  capturePerformance?: boolean;
443
663
  autocapture?: boolean;
664
+ scrollDepthMilestones?: boolean;
665
+ scrollDepthPageleave?: boolean;
444
666
  };
445
667
  privacy?: {
446
668
  respectDnt?: boolean;
447
669
  requireConsent?: boolean;
448
670
  ipAnonymization?: boolean;
449
671
  };
672
+ /**
673
+ * Diagnostics — runtime-tunable console verbosity. Applied by the SDK only
674
+ * when the integrator did NOT pass `log_level` / `debug` to `vt.init()`.
675
+ * Set from the dashboard's Default Configuration → Diagnostics tab.
676
+ */
677
+ diagnostics?: {
678
+ defaultLogLevel?: "none" | "error" | "warn" | "info" | "debug";
679
+ };
450
680
  featureFlags?: FeatureFlagsConfig;
451
681
  /** Whether to send elements as chain string (PostHog compatibility) */
452
682
  elementsChainAsString?: boolean;
453
683
  /** Server-side autocapture opt-out */
454
684
  autocapture_opt_out?: boolean;
685
+ /**
686
+ * Google Tag Gateway config (absent when no client Google gtag destination is
687
+ * enabled for the project). Drives the gtag.js proxy loader, Consent Mode
688
+ * v2 bridge, and event → conversion mappings.
689
+ */
690
+ googleTag?: GoogleTagClientConfig;
455
691
  }
456
692
 
457
693
  /**
@@ -466,7 +702,13 @@ interface RemoteConfig {
466
702
  declare class SessionManager {
467
703
  private storage;
468
704
  private _windowId;
705
+ private _isNewSession;
469
706
  constructor(storageMethod?: PersistenceMethod, cross_subdomain?: boolean);
707
+ /**
708
+ * Hydrate window ID from sessionStorage.
709
+ * Called by VTilt._boot() once the browser environment is ready.
710
+ */
711
+ hydrateFromStorage(): void;
470
712
  /**
471
713
  * Get session ID (always returns a value, generates if needed)
472
714
  */
@@ -480,6 +722,12 @@ declare class SessionManager {
480
722
  * Reset session ID (generates new session on reset)
481
723
  */
482
724
  resetSessionId(): void;
725
+ /**
726
+ * Returns true if the current session was just started (new session ID generated
727
+ * because the session cookie expired or didn't exist). Resets after first call.
728
+ * Matches GA4's _ss=1 semantics.
729
+ */
730
+ consumeSessionStart(): boolean;
483
731
  /**
484
732
  * Get session ID from storage (raw, can return null)
485
733
  * Cookie Max-Age handles expiration automatically
@@ -537,8 +785,22 @@ declare class SessionManager {
537
785
  declare class UserManager {
538
786
  private storage;
539
787
  private userIdentity;
540
- private _cachedPersonProperties;
788
+ private _isFirstVisit;
541
789
  constructor(storageMethod?: PersistenceMethod, cross_subdomain?: boolean);
790
+ /**
791
+ * Hydrate identity from persistent storage.
792
+ *
793
+ * Called by VTilt._boot() once the browser environment is guaranteed ready
794
+ * (DOMContentLoaded). This is the only code path that reads from
795
+ * localStorage / cookies. If storage is empty (first visit), the generated
796
+ * IDs are persisted so they survive the next page load.
797
+ */
798
+ hydrateFromStorage(): void;
799
+ /**
800
+ * Generate an ephemeral in-memory identity with no storage access.
801
+ * Used by the constructor so the SDK is safe to instantiate in SSR.
802
+ */
803
+ private generateEphemeralIdentity;
542
804
  /**
543
805
  * Get current user identity
544
806
  */
@@ -568,42 +830,20 @@ declare class UserManager {
568
830
  */
569
831
  getUserState(): "anonymous" | "identified";
570
832
  /**
571
- * Identify a user with distinct ID and properties
572
- */
573
- identify(newDistinctId?: string, userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
574
- /**
575
- * Set user properties without changing distinct ID
833
+ * Returns true if this is the user's first visit (no prior anonymous_id
834
+ * in storage when the SDK booted). Resets after first call.
835
+ * Matches GA4's _fv=1 semantics.
576
836
  */
577
- setUserProperties(userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): boolean;
837
+ consumeFirstVisit(): boolean;
578
838
  /**
579
- * Reset user identity (logout)
580
- * Generates new anonymous ID, clears user data, optionally resets device ID
839
+ * Apply batched identity changes with a single save.
840
+ * Replaces individual setters to avoid multiple storage writes per operation.
581
841
  */
582
- reset(reset_device_id?: boolean): void;
842
+ applyUpdate(update: IdentityUpdate): void;
583
843
  /**
584
- * Update distinct ID (internal use - for VTilt)
844
+ * Reset identity to anonymous state. Generates new IDs and clears user data.
585
845
  */
586
- setDistinctId(distinctId: string): void;
587
- /**
588
- * Update user state (internal use - for VTilt)
589
- */
590
- setUserState(state: "anonymous" | "identified"): void;
591
- /**
592
- * Update user properties (internal use - for VTilt)
593
- */
594
- updateUserProperties(userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
595
- /**
596
- * Set device ID if not already set (internal use - for VTilt)
597
- */
598
- ensureDeviceId(deviceId: string): void;
599
- /**
600
- * Create an alias to link two distinct IDs
601
- */
602
- createAlias(alias: string, original?: string): AliasEvent | null;
603
- /**
604
- * Check if distinct ID is string-like (hardcoded string) - public for validation
605
- */
606
- isDistinctIdStringLikePublic(distinctId: string): boolean;
846
+ reset(resetDeviceId?: boolean): void;
607
847
  /**
608
848
  * Set initial person info
609
849
  */
@@ -650,275 +890,95 @@ declare class UserManager {
650
890
  * Register a value once (only if not already set)
651
891
  */
652
892
  private register_once;
653
- /**
654
- * Generate a new anonymous ID
655
- */
656
893
  private generateAnonymousId;
657
- /**
658
- * Generate a new device ID
659
- */
660
894
  private generateDeviceId;
661
895
  /**
662
- * Get hash for person properties (for deduplication)
663
- */
664
- private getPersonPropertiesHash;
665
- /**
666
- * Validate distinct ID
667
- */
668
- private isValidDistinctId;
669
- /**
670
- * Check if distinct ID is string-like (hardcoded string)
671
- */
672
- private isDistinctIdStringLike;
673
- /**
674
- * Update storage method at runtime
896
+ * Update storage method at runtime.
675
897
  */
676
898
  updateStorageMethod(method: PersistenceMethod, cross_subdomain?: boolean): void;
677
899
  }
678
900
 
679
901
  /**
680
- * Feature Interface & Base Class
902
+ * Feature Interface & Base Classes
681
903
  *
682
- * Standard interface that all vTilt features implement.
683
- * Provides consistent lifecycle management and self-contained configuration.
904
+ * Standard interfaces and abstract base classes for vTilt SDK features.
684
905
  *
685
- * Design Principles:
686
- * 1. Features are self-contained - they extract their own config
687
- * 2. Features implement a common interface - vtilt.ts doesn't need to know details
688
- * 3. Features handle their own errors - using safewrap utilities
689
- * 4. Features can communicate via events - using the event emitter
906
+ * Hierarchy:
907
+ * Feature (interface) — eager features (Autocapture, HistoryAutocapture)
908
+ * ToggleableFeature (interface)— features that actively stop when disabled
909
+ * LazyFeature (abstract) — lazy-loaded features (WebVitals, Chat)
910
+ * ToggleableLazyFeature — lazy-loaded + toggle (SessionRecording)
690
911
  *
691
- * Based on PostHog's convention patterns, formalized as TypeScript interfaces.
912
+ * @see docs/patterns/tracker-feature-lifecycle.md
692
913
  */
693
914
 
694
915
  /**
695
916
  * Feature interface that all vTilt features should implement.
696
917
  * Provides a consistent lifecycle for feature initialization and management.
697
- *
698
- * @example
699
- * ```typescript
700
- * class MyFeature implements Feature {
701
- * static extractConfig(config: VTiltConfig): MyFeatureConfig {
702
- * return { enabled: !!config.myFeature };
703
- * }
704
- *
705
- * get isEnabled(): boolean { return this._config.enabled; }
706
- * get isStarted(): boolean { return this._started; }
707
- *
708
- * startIfEnabled(): void {
709
- * if (this.isEnabled && !this._started) {
710
- * this._start();
711
- * }
712
- * }
713
- *
714
- * stop(): void {
715
- * if (this._started) {
716
- * this._cleanup();
717
- * this._started = false;
718
- * }
719
- * }
720
- * }
721
- * ```
722
918
  */
723
919
  interface Feature {
724
- /**
725
- * Feature name for logging and debugging.
726
- */
727
920
  readonly name: string;
728
- /**
729
- * Check if the feature is enabled based on current configuration.
730
- * Features should check both local config and remote config.
731
- */
732
921
  readonly isEnabled: boolean;
733
- /**
734
- * Check if the feature is currently started/active.
735
- */
736
922
  readonly isStarted: boolean;
737
- /**
738
- * Start the feature if it's enabled, otherwise do nothing.
739
- * This is the primary lifecycle method called during initialization.
740
- *
741
- * Safe to call multiple times - should be idempotent.
742
- */
743
923
  startIfEnabled(): void;
744
- /**
745
- * Stop the feature and clean up any resources (event listeners, timers, etc.).
746
- * Safe to call multiple times - should be idempotent.
747
- */
748
924
  stop(): void;
749
925
  /**
750
926
  * Handle VTilt configuration updates.
751
927
  * Called when the main VTilt config is updated via updateConfig().
752
- *
753
928
  * Features should re-evaluate their enabled state and start/stop accordingly.
754
- *
755
- * @param config - The new VTilt configuration
756
929
  */
757
930
  onConfigUpdate?(config: VTiltConfig): void;
758
- /**
759
- * Handle remote configuration updates from /decide endpoint.
760
- * Called when new remote config is received from the server.
761
- *
762
- * Features can use this to enable/disable themselves based on server settings.
763
- *
764
- * @param remoteConfig - The remote configuration response
765
- */
766
- onRemoteConfig?(remoteConfig: RemoteConfig): void;
767
931
  }
768
- /**
769
- * Feature with the startIfEnabledOrStop pattern.
770
- * Used for features that should stop when disabled (not just stay stopped).
771
- *
772
- * Example: Session recording should stop if disabled mid-session.
773
- */
774
- interface ToggleableFeature extends Feature {
775
- /**
776
- * Start the feature if enabled, or stop it if disabled.
777
- * More aggressive than startIfEnabled - actively stops if conditions change.
778
- *
779
- * @param trigger - Optional trigger name for debugging
780
- */
781
- startIfEnabledOrStop(trigger?: string): void;
782
- }
783
- /**
784
- * Base configuration that all features receive.
785
- * Features extend this for their specific config needs.
786
- */
787
932
  interface FeatureConfig {
788
- /** Whether the feature is enabled */
789
933
  enabled?: boolean;
790
934
  }
791
935
 
792
936
  /**
793
- * Web Vitals Manager
794
- *
795
- * Captures Core Web Vitals (LCP, CLS, FCP, INP, TTFB) and sends them
796
- * as batched $web_vitals events. Implements the Feature interface for
797
- * consistent lifecycle management.
798
- *
799
- * Follows PostHog's approach with:
800
- * 1. Extension pattern - checks for callbacks registered on __VTiltExtensions__
801
- * 2. Lazy loading - loads web-vitals.js on demand if not bundled
802
- * 3. Configurable metrics - choose which metrics to capture
803
- * 4. Smart buffering - collects metrics per page, flushes on navigation or timeout
804
- * 5. Session context - includes session_id and window_id for correlation
937
+ * Autocapture
805
938
  *
806
- * Event structure:
807
- * - event: "$web_vitals"
808
- * - properties:
809
- * - $web_vitals_LCP_value: number
810
- * - $web_vitals_LCP_event: { name, value, delta, rating, ... }
811
- * - $pathname: string
812
- * - $current_url: string
813
- */
814
-
815
- declare class WebVitalsManager implements Feature {
816
- readonly name = "WebVitals";
817
- private _instance;
818
- private _buffer;
819
- private _flushTimer;
820
- private _isStarted;
821
- private _config;
822
- /**
823
- * Extract web vitals config from VTiltConfig (self-contained)
824
- */
825
- static extractConfig(config: VTiltConfig): CapturePerformanceConfig;
826
- constructor(instance: VTilt);
827
- /**
828
- * Check if web vitals capture is enabled
829
- */
830
- get isEnabled(): boolean;
831
- /**
832
- * Check if capturing has started
833
- */
834
- get isStarted(): boolean;
835
- /**
836
- * Start capturing if enabled
837
- */
838
- startIfEnabled(): void;
839
- /**
840
- * Stop capturing (flushes any pending metrics)
841
- */
842
- stop(): void;
843
- /**
844
- * Handle config update
845
- */
846
- onConfigUpdate(config: VTiltConfig): void;
847
- /**
848
- * Get the list of metrics to capture
849
- */
850
- get allowedMetrics(): SupportedWebVitalsMetric[];
851
- /**
852
- * Get flush timeout in ms
853
- */
854
- get flushTimeoutMs(): number;
855
- /**
856
- * Get maximum allowed metric value
857
- */
858
- get maxAllowedValue(): number;
859
- private _createEmptyBuffer;
860
- private _getWebVitalsCallbacks;
861
- private _loadWebVitals;
862
- private _startCapturing;
863
- private _getCurrentUrl;
864
- private _getCurrentPathname;
865
- private _addToBuffer;
866
- private _cleanAttribution;
867
- private _getWindowId;
868
- private _scheduleFlush;
869
- private _flush;
870
- }
871
-
872
- /**
873
- * History Autocapture
939
+ * Automatic DOM event capture for clicks, form submissions, and input changes.
940
+ * Privacy-first approach with element chain tracking and sensitive data filtering.
874
941
  *
875
- * Captures pageview events when the user navigates using the History API
876
- * (pushState, replaceState) or browser back/forward buttons.
942
+ * Lifecycle (see docs/patterns/tracker-feature-lifecycle.md):
943
+ * - Construction: created by FeatureManager when not hard-disabled.
944
+ * - startIfEnabled: attaches DOM listeners when isEnabled becomes true.
945
+ * - stop: detaches DOM listeners. Subsequent startIfEnabled re-attaches.
946
+ * - onConfigUpdate: re-evaluates isEnabled on every config change and starts or
947
+ * stops accordingly. This is what flips autocapture on when the
948
+ * /decide endpoint returns `analytics.autocapture: true`.
877
949
  *
878
- * Implements the Feature interface for consistent lifecycle management.
950
+ * Enabled state precedence (highest first):
951
+ * 1. _userOverride === false → off (set by vt.stopAutocapture())
952
+ * 2. _isDisabledServerSide === true → off (set by remote autocapture_opt_out)
953
+ * 3. _userOverride === true → on (set by vt.startAutocapture())
954
+ * 4. config.autocapture truthy → on
955
+ * 5. otherwise → off
879
956
  */
880
957
 
881
- interface HistoryAutocaptureConfig extends FeatureConfig {
882
- /** Whether to capture pageviews on history changes */
883
- enabled?: boolean;
884
- }
885
958
  /**
886
- * History Autocapture Feature
887
- *
888
- * Automatically captures $pageview events on SPA navigation.
889
- * Implements the Feature interface for consistent lifecycle management.
959
+ * Reasons isEnabled may evaluate to false. Surfaced by getDiagnostics() so that
960
+ * integrators can pinpoint why no `$autocapture` events are flowing.
890
961
  */
891
- declare class HistoryAutocapture implements Feature {
892
- readonly name = "HistoryAutocapture";
893
- private _instance;
894
- private _config;
895
- private _isStarted;
896
- private _popstateListener;
897
- private _lastPathname;
898
- constructor(instance: VTilt, config?: HistoryAutocaptureConfig);
899
- /**
900
- * Extract history autocapture config from VTiltConfig.
901
- * Self-contained - vtilt.ts doesn't need to know config details.
902
- */
903
- static extractConfig(config: VTiltConfig): HistoryAutocaptureConfig;
904
- get isEnabled(): boolean;
905
- get isStarted(): boolean;
906
- startIfEnabled(): void;
907
- stop(): void;
908
- onConfigUpdate(config: VTiltConfig): void;
909
- private _start;
910
- private _patchHistoryMethods;
911
- private _setupPopstateListener;
912
- private _capturePageview;
962
+ type AutocaptureDisabledReason = "user_stop_called" | "server_opt_out" | "config_autocapture_false" | "config_autocapture_undefined";
963
+ interface AutocaptureDiagnostics {
964
+ /** Whether the feature is enabled given current config and overrides. */
965
+ isEnabled: boolean;
966
+ /** Whether DOM listeners are currently attached. */
967
+ isStarted: boolean;
968
+ /** Why the feature is disabled (if any). */
969
+ disabledReason: AutocaptureDisabledReason | null;
970
+ /** Snapshot of inputs that drove the decision. */
971
+ inputs: {
972
+ configAutocapture: unknown;
973
+ isDisabledServerSide: boolean;
974
+ userOverride: boolean | null;
975
+ elementsChainAsString: boolean;
976
+ captureCopiedText: boolean;
977
+ scrollDepthMilestones: boolean;
978
+ scrollDepthPageleave: boolean;
979
+ scrollDepthListenerAttached: boolean;
980
+ };
913
981
  }
914
-
915
- /**
916
- * Autocapture
917
- *
918
- * Automatic DOM event capture for clicks, form submissions, and input changes.
919
- * Privacy-first approach with element chain tracking and sensitive data filtering.
920
- */
921
-
922
982
  /**
923
983
  * Autocapture class for automatic DOM event tracking.
924
984
  * Implements the Feature interface for consistent lifecycle management.
@@ -928,531 +988,287 @@ declare class Autocapture implements Feature {
928
988
  private _instance;
929
989
  private _initialized;
930
990
  private _isDisabledServerSide;
991
+ /** Explicit user override via vt.startAutocapture() / vt.stopAutocapture(). */
992
+ private _userOverride;
931
993
  private _elementSelectors;
932
994
  private _rageclicks;
995
+ /**
996
+ * Compact-payload mode for `$autocapture`. When true (the default), the SDK
997
+ * only sends the `$elements_chain` string and omits the verbose `$elements`
998
+ * array — the array is duplicate information for the ingestion side and is
999
+ * what pushes payloads past the client size cap on apps with deep DOM
1000
+ * trees / Tailwind / Material class soup. Integrators that still need
1001
+ * `$elements` (legacy filters / external pipelines) can opt out with
1002
+ * `vt.init({ elementsChainAsString: false })` or via `/decide`.
1003
+ */
933
1004
  private _elementsChainAsString;
934
- constructor(instance: VTilt);
1005
+ private _cachedConfig;
1006
+ private _cachedConfigSource;
1007
+ /**
1008
+ * Bound DOM handlers. Stored so stop() can detach them — passing the same
1009
+ * function reference is required by removeEventListener().
1010
+ */
1011
+ private _domHandler;
1012
+ private _copyHandler;
1013
+ private _copyHandlerAttached;
1014
+ private _scrollDepthTracker;
1015
+ private _pageviewUnsubscribe;
1016
+ static extractConfig(config: VTiltConfig): {
1017
+ enabled: boolean;
1018
+ };
1019
+ constructor(instance: VTilt, _config?: {
1020
+ enabled: boolean;
1021
+ });
935
1022
  private get _config();
936
1023
  get isEnabled(): boolean;
937
1024
  get isStarted(): boolean;
938
1025
  startIfEnabled(): void;
939
1026
  stop(): void;
940
- onConfigUpdate(_config: VTiltConfig): void;
941
- onRemoteConfig(response: RemoteConfig): void;
942
1027
  /**
943
- * Update autocapture configuration (for programmatic control)
1028
+ * Max scroll depth % for the current page when pageleave mode is enabled.
1029
+ * Used to enrich `$pageleave` payloads.
1030
+ */
1031
+ getMaxScrollDepthPctForPageleave(): number | null;
1032
+ onConfigUpdate(config: VTiltConfig): void;
1033
+ /**
1034
+ * Update autocapture configuration (for programmatic control).
1035
+ * Called from vt.startAutocapture() / vt.stopAutocapture().
944
1036
  */
945
1037
  updateConfig(config: Partial<{
946
1038
  enabled: boolean;
947
1039
  }>): void;
1040
+ getDiagnostics(): AutocaptureDiagnostics;
948
1041
  setElementSelectors(selectors: Set<string>): void;
949
1042
  getElementSelectors(element: Element | null): string[] | null;
1043
+ /**
1044
+ * Single-source-of-truth for whether autocapture should be active.
1045
+ * Returns the disabled reason so diagnostics and logging can be precise.
1046
+ */
1047
+ private _evaluateEnabled;
950
1048
  private _addDomEventHandlers;
951
- private _captureEvent;
952
- private _isBrowserSupported;
953
- }
954
-
955
- /**
956
- * Session Recording Types
957
- *
958
- * Type definitions for rrweb session recording.
959
- * Based on PostHog's implementation.
960
- */
961
-
962
- /** Mask options for input elements */
963
- type MaskInputOptions = Partial<{
964
- color: boolean;
965
- date: boolean;
966
- "datetime-local": boolean;
967
- email: boolean;
968
- month: boolean;
969
- number: boolean;
970
- range: boolean;
971
- search: boolean;
972
- tel: boolean;
973
- text: boolean;
974
- time: boolean;
975
- url: boolean;
976
- week: boolean;
977
- textarea: boolean;
978
- select: boolean;
979
- password: boolean;
980
- }>;
981
- /** Masking configuration */
982
- interface MaskingConfig {
983
- maskAllInputs?: boolean;
984
- maskTextSelector?: string;
985
- blockSelector?: string;
986
- }
987
- /** Session recording configuration */
988
- interface SessionRecordingConfig {
989
- /** Enable session recording */
990
- enabled?: boolean;
991
- /** Sample rate (0-1, where 1 = 100%) */
992
- sampleRate?: number;
993
- /** Minimum session duration in ms before sending */
994
- minimumDurationMs?: number;
995
- /** Session idle threshold in ms */
996
- sessionIdleThresholdMs?: number;
997
- /** Full snapshot interval in ms */
998
- fullSnapshotIntervalMs?: number;
999
- /** Enable console log capture */
1000
- captureConsole?: boolean;
1001
- /** Enable network request capture */
1002
- captureNetwork?: boolean;
1003
- /** Canvas recording settings */
1004
- captureCanvas?: {
1005
- recordCanvas?: boolean;
1006
- canvasFps?: number;
1007
- canvasQuality?: number;
1008
- };
1009
- /** Masking settings */
1010
- masking?: MaskingConfig;
1011
- /** Block class for elements to hide */
1012
- blockClass?: string;
1013
- /** Block selector for elements to hide */
1014
- blockSelector?: string;
1015
- /** Ignore class for input masking */
1016
- ignoreClass?: string;
1017
- /** Mask text class */
1018
- maskTextClass?: string;
1019
- /** Mask text selector */
1020
- maskTextSelector?: string;
1021
- /** Mask all inputs */
1022
- maskAllInputs?: boolean;
1023
- /** Mask input options */
1024
- maskInputOptions?: MaskInputOptions;
1025
- /** Record headers in network requests */
1026
- recordHeaders?: boolean;
1027
- /** Record body in network requests */
1028
- recordBody?: boolean;
1029
- /** Compress events before sending */
1030
- compressEvents?: boolean;
1031
- /** Internal: Mutation throttler refill rate */
1032
- __mutationThrottlerRefillRate?: number;
1033
- /** Internal: Mutation throttler bucket size */
1034
- __mutationThrottlerBucketSize?: number;
1035
- }
1036
- /** Recording start reason */
1037
- type SessionStartReason = "recording_initialized" | "session_id_changed" | "linked_flag_matched" | "linked_flag_overridden" | "sampling_overridden" | "url_trigger_matched" | "event_trigger_matched" | "sampled" | "config_updated" | "remote_config";
1038
-
1039
- /**
1040
- * Chat message structure (matches PostgreSQL chat_messages table)
1041
- * Note: Read status is determined by cursor comparison, not per-message
1042
- */
1043
- interface ChatMessage {
1044
- id: string;
1045
- channel_id: string;
1046
- sender_type: "user" | "agent" | "ai" | "system";
1047
- sender_id: string | null;
1048
- sender_name: string | null;
1049
- sender_avatar_url: string | null;
1050
- content: string;
1051
- content_type: "text" | "html" | "attachment";
1052
- metadata: Record<string, unknown>;
1053
- created_at: string;
1054
- }
1055
- /**
1056
- * Chat channel structure (maps 1:1 to Ably channel)
1057
- */
1058
- interface ChatChannel {
1059
- id: string;
1060
- project_id: string;
1061
- person_id: string;
1062
- distinct_id: string;
1063
- status: "open" | "closed" | "snoozed";
1064
- ai_mode: boolean;
1065
- unread_count: number;
1066
- last_message_at: string | null;
1067
- last_message_preview: string | null;
1068
- last_message_sender: "user" | "agent" | "ai" | "system" | null;
1069
- user_last_read_at: string | null;
1070
- agent_last_read_at: string | null;
1071
- created_at: string;
1072
- }
1073
- /**
1074
- * Lightweight channel summary for channel list view
1075
- * Used to avoid loading full channel data until user selects one
1076
- */
1077
- interface ChatChannelSummary {
1078
- id: string;
1079
- status: "open" | "closed" | "snoozed";
1080
- ai_mode: boolean;
1081
- last_message_at: string | null;
1082
- last_message_preview: string | null;
1083
- last_message_sender: "user" | "agent" | "ai" | "system" | null;
1084
- unread_count: number;
1085
- user_last_read_at: string | null;
1086
- created_at: string;
1087
- }
1088
- /**
1089
- * Widget view state - determines what UI to show
1090
- */
1091
- type ChatWidgetView = "list" | "conversation";
1092
- /**
1093
- * Chat widget configuration
1094
- *
1095
- * Settings can come from two sources:
1096
- * 1. Dashboard (fetched from /api/chat/settings) - "snippet-only" mode
1097
- * 2. Code config (passed to vt.init) - overrides dashboard settings
1098
- *
1099
- * This enables Intercom-like flexibility: just add snippet OR customize with code.
1100
- */
1101
- interface ChatConfig {
1049
+ private _removeDomEventHandlers;
1102
1050
  /**
1103
- * Enable/disable chat widget.
1104
- * - undefined: Use dashboard setting (auto-fetch)
1105
- * - true: Enable (override dashboard)
1106
- * - false: Disable entirely
1051
+ * Scroll depth listener — opt-in via `autocapture.scroll_depth` milestones and/or pageleave.
1107
1052
  */
1108
- enabled?: boolean;
1053
+ private _syncScrollDepthHandler;
1054
+ private _teardownScrollDepth;
1109
1055
  /**
1110
- * Auto-fetch settings from dashboard (default: true)
1111
- * When true, SDK fetches /api/chat/settings and uses those as base config.
1112
- * Code config always overrides fetched settings.
1056
+ * The copy/cut listener is opt-in via `autocapture.capture_copied_text`.
1057
+ * Callable from both startIfEnabled and onConfigUpdate so toggling the flag
1058
+ * mid-session correctly attaches or detaches the listener.
1113
1059
  */
1114
- autoConfig?: boolean;
1115
- /** Widget position (default: 'bottom-right') */
1116
- position?: "bottom-right" | "bottom-left";
1117
- /** Widget header/greeting message */
1118
- greeting?: string;
1119
- /** Widget primary color */
1120
- color?: string;
1121
- /** Start in AI mode (default: true) */
1122
- aiMode?: boolean;
1123
- /** AI greeting message (first message from AI) */
1124
- aiGreeting?: string;
1125
- /** Preload widget script on idle vs on-demand */
1126
- preload?: boolean;
1127
- /** Custom theme */
1128
- theme?: ChatTheme;
1129
- /** Offline message shown when business is unavailable */
1130
- offlineMessage?: string;
1131
- /** Collect email when offline */
1132
- collectEmailOffline?: boolean;
1133
- /** Called when widget is opened */
1134
- onWidgetOpen?: () => void;
1135
- /** Called when widget is closed */
1136
- onWidgetClose?: (data: {
1137
- timeOpenSeconds: number;
1138
- messagesSent: number;
1139
- }) => void;
1140
- /** Called when a new conversation is started */
1141
- onConversationStart?: (data: {
1142
- channelId: string;
1143
- aiMode: boolean;
1144
- }) => void;
1145
- /** Called when user sends a message */
1146
- onMessageSent?: (data: {
1147
- channelId: string;
1148
- messageId: string;
1149
- }) => void;
1150
- /** Called when a message is received (from AI or agent) */
1151
- onMessageReceived?: (data: {
1152
- channelId: string;
1153
- messageId: string;
1154
- senderType: "ai" | "agent";
1155
- }) => void;
1156
- }
1157
- /**
1158
- * Chat theme customization
1159
- */
1160
- interface ChatTheme {
1161
- primaryColor?: string;
1162
- fontFamily?: string;
1163
- borderRadius?: string;
1164
- headerBgColor?: string;
1165
- userBubbleColor?: string;
1166
- agentBubbleColor?: string;
1060
+ private _syncCopyHandler;
1061
+ private _captureEvent;
1062
+ private _isBrowserSupported;
1167
1063
  }
1168
1064
 
1169
1065
  /**
1170
- * Session Recording Wrapper
1066
+ * Consent Manager
1171
1067
  *
1172
- * Lightweight wrapper that handles the decision of WHEN to load recording.
1173
- * The actual recording logic is in LazyLoadedSessionRecording which is
1174
- * loaded on demand.
1068
+ * Manages user consent state for analytics, marketing, and advertising.
1069
+ * Stores consent in a first-party cookie (_vtilt_consent) and provides
1070
+ * consent properties to attach to every event.
1175
1071
  *
1176
- * Implements the ToggleableFeature interface for consistent lifecycle management.
1177
- * Based on PostHog's sessionrecording-wrapper.ts
1072
+ * When `requireConsent` is true in config, the CaptureManager gates
1073
+ * non-essential events until setConsent() is called.
1178
1074
  */
1179
-
1180
- /** Status when lazy loading is in progress */
1181
- declare const LAZY_LOADING: "lazy_loading";
1182
- /** Session recording status */
1183
- type SessionRecordingStatus = "disabled" | "buffering" | "active" | "paused" | "sampled" | "trigger_pending" | typeof LAZY_LOADING;
1184
-
1185
- /**
1186
- * Session Recording Wrapper
1187
- *
1188
- * This is the lightweight class that lives in the main bundle.
1189
- * Implements ToggleableFeature for consistent lifecycle management.
1190
- *
1191
- * It handles:
1192
- * - Deciding if recording should be enabled
1193
- * - Lazy loading the actual recording code
1194
- * - Delegating to LazyLoadedSessionRecording
1195
- */
1196
- declare class SessionRecordingWrapper implements ToggleableFeature {
1197
- private readonly _instance;
1198
- readonly name = "SessionRecording";
1199
- private _lazyLoadedRecording;
1200
- private _config;
1201
- private _isStarted;
1202
- constructor(_instance: VTilt, config?: SessionRecordingConfig);
1203
- /**
1204
- * Extract session recording config from VTiltConfig.
1205
- * Self-contained - vtilt.ts doesn't need to know config details.
1206
- */
1207
- static extractConfig(config: VTiltConfig): SessionRecordingConfig;
1208
- get isEnabled(): boolean;
1209
- get isStarted(): boolean;
1210
- /**
1211
- * Start if enabled (Feature interface).
1212
- * Use startIfEnabledOrStop for recording as it needs to actively stop.
1213
- */
1214
- startIfEnabled(): void;
1215
- /**
1216
- * Start if enabled, or stop if disabled (ToggleableFeature interface).
1217
- * This is the primary method for session recording.
1218
- */
1219
- startIfEnabledOrStop(trigger?: SessionStartReason): void;
1220
- /**
1221
- * Stop recording (Feature interface).
1222
- */
1223
- stop(): void;
1224
- /**
1225
- * Handle config updates (Feature interface).
1226
- */
1227
- onConfigUpdate(config: VTiltConfig): void;
1075
+ interface ConsentState {
1076
+ analytics?: boolean;
1077
+ marketing?: boolean;
1078
+ advertising?: boolean;
1079
+ }
1080
+ interface ConsentHost {
1081
+ _emitter?: {
1082
+ emit(event: string, payload?: unknown): void;
1083
+ };
1084
+ getConfig(): {
1085
+ require_consent?: boolean;
1086
+ };
1087
+ }
1088
+ declare class ConsentManager {
1089
+ private _host;
1090
+ private _state;
1091
+ private _hasBeenSet;
1092
+ constructor(host: ConsentHost);
1228
1093
  /**
1229
- * Handle remote config updates (Feature interface).
1094
+ * Set consent state. Merges with existing state.
1095
+ * Persists to cookie and emits consent:updated.
1230
1096
  */
1231
- onRemoteConfig(remoteConfig: RemoteConfig): void;
1232
- get started(): boolean;
1233
- get status(): SessionRecordingStatus;
1234
- get sessionId(): string;
1097
+ setConsent(consent: ConsentState): void;
1235
1098
  /**
1236
- * Alias for stop() for backwards compatibility.
1099
+ * Get current consent state.
1100
+ * Returns undefined fields for categories not yet set.
1237
1101
  */
1238
- stopRecording(): void;
1102
+ getConsent(): ConsentState;
1239
1103
  /**
1240
- * Log a message to the recording.
1104
+ * Whether setConsent() has been called (or consent was loaded from cookie).
1105
+ * Used by CaptureManager to gate events when requireConsent is true.
1241
1106
  */
1242
- log(message: string, level?: "log" | "warn" | "error"): void;
1107
+ hasConsent(): boolean;
1243
1108
  /**
1244
- * Update configuration.
1109
+ * Apply default-all-granted state when no explicit consent exists.
1110
+ * Sets all categories to true in memory but does NOT persist to cookie
1111
+ * (implicit defaults don't need storage — only explicit user choices do).
1245
1112
  */
1246
- updateConfig(config: Partial<SessionRecordingConfig>): void;
1247
- private get _scriptName();
1113
+ setDefaultGranted(): void;
1248
1114
  /**
1249
- * Lazy load the recording script and start.
1115
+ * Returns event properties to attach to every captured event.
1116
+ * Only includes fields that have been explicitly set.
1250
1117
  */
1251
- private _lazyLoadAndStart;
1118
+ getConsentProperties(): Record<string, boolean>;
1252
1119
  /**
1253
- * Called after the recording script is loaded.
1120
+ * Reset consent state (e.g. on user logout).
1254
1121
  */
1255
- private _onScriptLoaded;
1122
+ reset(): void;
1123
+ private _readFromCookie;
1124
+ private _writeToCookie;
1125
+ private _removeCookie;
1256
1126
  }
1257
1127
 
1258
1128
  /**
1259
- * Message callback type
1260
- */
1261
- type MessageCallback = (message: ChatMessage) => void;
1262
- /**
1263
- * Typing callback type
1129
+ * Consent Mode v2 bridge.
1130
+ *
1131
+ * Maps vTilt's 3-boolean consent (analytics / marketing / advertising) to
1132
+ * Google's 4-key Consent Mode v2 payload (ad_storage, ad_user_data,
1133
+ * ad_personalization, analytics_storage) and pushes it through gtag.
1134
+ *
1135
+ * Mapping (default):
1136
+ * advertising -> ad_storage + ad_user_data + ad_personalization
1137
+ * analytics -> analytics_storage
1138
+ * marketing is not sent to Google by default (it covers email/SMS, not ads).
1139
+ *
1140
+ * Admin UI may override individual keys (`consentOverride`) to hard-wire a
1141
+ * specific value regardless of user consent — useful for jurisdictions or
1142
+ * projects with fixed policies.
1264
1143
  */
1265
- type TypingCallback = (isTyping: boolean, senderType: string) => void;
1144
+
1145
+ type GtagFn = (...args: unknown[]) => void;
1146
+
1266
1147
  /**
1267
- * Connection callback type
1148
+ * `vt.gtag(...)` escape hatch.
1149
+ *
1150
+ * Power users may need to call `gtag` directly (e.g. to fire a custom
1151
+ * conversion from a third-party widget or to set a debug param). We expose
1152
+ * a flat passthrough that:
1153
+ *
1154
+ * - queues calls made before the SDK has booted (keeps them in FIFO order),
1155
+ * - queues calls made before `gtag.js` has loaded (forwarded via the
1156
+ * `dataLayer` shim, which naturally buffers until the real script runs),
1157
+ * - records every call so tests + the delivery log can see them.
1268
1158
  */
1269
- type ConnectionCallback = (connected: boolean) => void;
1159
+
1160
+ type GtagCall = unknown[];
1161
+
1270
1162
  /**
1271
- * Unsubscribe function type
1163
+ * Enhanced Conversions helper.
1164
+ *
1165
+ * Hashes user-provided PII (email/phone/name/address) with SHA-256 before
1166
+ * it leaves the browser, matching Google Ads' Enhanced Conversions format:
1167
+ * https://support.google.com/google-ads/answer/13258081
1168
+ *
1169
+ * Returns an object suitable for `gtag('set', 'user_data', { ... })`.
1272
1170
  */
1273
- type Unsubscribe$1 = () => void;
1171
+ interface RawUserData {
1172
+ email?: string | null;
1173
+ phone?: string | null;
1174
+ first_name?: string | null;
1175
+ last_name?: string | null;
1176
+ street?: string | null;
1177
+ city?: string | null;
1178
+ region?: string | null;
1179
+ postal_code?: string | null;
1180
+ country?: string | null;
1181
+ }
1274
1182
 
1275
1183
  /**
1276
- * Chat Wrapper
1184
+ * GoogleTagGateway feature.
1185
+ *
1186
+ * Orchestrates the client-side half of the Google Tag Gateway destination:
1277
1187
  *
1278
- * This is the lightweight class that lives in the main bundle.
1279
- * Implements Feature for consistent lifecycle management.
1188
+ * 1. Maintains `window.dataLayer` / `window.gtag`.
1189
+ * 2. Pushes Consent Mode v2 defaults BEFORE gtag.js loads, and re-pushes
1190
+ * on `consent:updated` events.
1191
+ * 3. Injects `gtag/js?id=<primary>` from the vTilt gateway (`/gt`) so
1192
+ * every measurement request flows through the first-party origin.
1193
+ * 4. Subscribes to EVENT_CAPTURED and forwards mapped events to Ads
1194
+ * (`gtag('event', 'conversion', ...)`).
1195
+ * 5. Exposes a flat `vt.gtag(...)` passthrough for power users.
1280
1196
  *
1281
- * It handles:
1282
- * - Auto-fetching settings from dashboard (Intercom-like)
1283
- * - Merging server settings with code config
1284
- * - Deciding if chat should be enabled
1285
- * - Lazy loading the actual chat widget code
1286
- * - Delegating to LazyLoadedChat
1287
- * - Queuing messages/callbacks before widget loads
1197
+ * Forwards only after `__remote_config_loaded` is true (same signal as
1198
+ * EventBuffer: fresh `/decide` applied, bootstrap, or fetch failure).
1199
+ * Captures before that are queued so we never drop events just because
1200
+ * `/decide` has not committed yet. After remote is ready, we install the
1201
+ * official `dataLayer` + `gtag` shim (`ensureDataLayer`) and forward
1202
+ * immediately the shim queues until `gtag.js` loads; we do not maintain a
1203
+ * second SDK queue for that window.
1204
+ *
1205
+ * Feature lifecycle matches the rest of the SDK — registered with
1206
+ * FeatureManager via a descriptor that pulls the admin-configured shape
1207
+ * out of `/decide` under the `googleTag` key.
1288
1208
  */
1289
- declare class ChatWrapper implements Feature {
1290
- private readonly _instance;
1291
- readonly name = "Chat";
1292
- private _lazyLoadedChat;
1209
+
1210
+ interface GoogleTagGatewayFeatureConfig extends FeatureConfig {
1211
+ remote?: GoogleTagClientConfig;
1212
+ }
1213
+ interface DeliveryLogEntry {
1214
+ ts: number;
1215
+ tag_ids: string[];
1216
+ event_name: string;
1217
+ send_to: string;
1218
+ status: "fired" | "dropped";
1219
+ reason?: string;
1220
+ }
1221
+ declare class GoogleTagGateway implements Feature {
1222
+ readonly name = "GoogleTagGateway";
1223
+ private _instance;
1293
1224
  private _config;
1294
- private _serverConfig;
1295
- private _configFetched;
1296
- private _isLoading;
1297
1225
  private _isStarted;
1298
- private _loadError;
1299
- private _pendingMessages;
1300
- private _pendingCallbacks;
1301
- private _messageCallbacks;
1302
- private _typingCallbacks;
1303
- private _connectionCallbacks;
1304
- constructor(_instance: VTilt, config?: ChatConfig);
1305
- /**
1306
- * Extract chat config from VTiltConfig.
1307
- * Self-contained - vtilt.ts doesn't need to know config details.
1308
- */
1309
- static extractConfig(config: VTiltConfig): ChatConfig;
1226
+ private _scriptInjected;
1227
+ private _scriptLoaded;
1228
+ private _consentDefaultsPushed;
1229
+ private _gtag;
1230
+ private _publicApi;
1231
+ private _loaderOptions;
1232
+ private _unsubscribeCaptured;
1233
+ private _unsubscribeConsent;
1234
+ private _deliveryLog;
1235
+ private readonly _maxDeliveryLog;
1236
+ /**
1237
+ * Captures observed before `__remote_config_loaded` only. Once remote
1238
+ * commits, `ensureDataLayer` + gtag forward — gtag's own queue handles
1239
+ * ordering until `gtag.js` is on the wire.
1240
+ */
1241
+ private readonly _pendingCaptures;
1242
+ constructor(instance: VTilt, config?: GoogleTagGatewayFeatureConfig);
1243
+ static extractConfig(config: VTiltConfig): GoogleTagGatewayFeatureConfig;
1310
1244
  get isEnabled(): boolean;
1311
1245
  get isStarted(): boolean;
1312
- /**
1313
- * Start chat if enabled (Feature interface).
1314
- * This is async because it may fetch server settings.
1315
- */
1246
+ /** Exposed for tests and the public `vt.gtag` binding. */
1247
+ get gtag(): GtagFn;
1248
+ get deliveryLog(): DeliveryLogEntry[];
1316
1249
  startIfEnabled(): void;
1317
- /**
1318
- * Stop the chat widget (Feature interface).
1319
- */
1320
1250
  stop(): void;
1321
- /**
1322
- * Handle config updates (Feature interface).
1323
- */
1324
1251
  onConfigUpdate(config: VTiltConfig): void;
1325
1252
  /**
1326
- * Handle remote config updates (Feature interface).
1327
- */
1328
- onRemoteConfig(remoteConfig: RemoteConfig): void;
1329
- /**
1330
- * Async start implementation
1331
- */
1332
- private _startAsync;
1333
- /**
1334
- * Whether the chat widget is open
1335
- */
1336
- get isOpen(): boolean;
1337
- /**
1338
- * Whether connected to realtime service
1339
- */
1340
- get isConnected(): boolean;
1341
- /**
1342
- * Whether the widget is loading
1343
- */
1344
- get isLoading(): boolean;
1345
- /**
1346
- * Number of unread messages
1347
- */
1348
- get unreadCount(): number;
1349
- /**
1350
- * Current channel (if any)
1351
- */
1352
- get channel(): ChatChannel | null;
1353
- /**
1354
- * List of user's channels (multi-channel support)
1355
- */
1356
- get channels(): ChatChannelSummary[];
1357
- /**
1358
- * Current view state ('list' or 'conversation')
1359
- */
1360
- get currentView(): ChatWidgetView;
1361
- /**
1362
- * Open the chat widget
1363
- */
1364
- open(): void;
1365
- /**
1366
- * Close the chat widget
1367
- */
1368
- close(): void;
1369
- /**
1370
- * Toggle the chat widget open/closed
1371
- */
1372
- toggle(): void;
1373
- /**
1374
- * Show the chat widget (make visible but not necessarily open)
1375
- */
1376
- show(): void;
1377
- /**
1378
- * Hide the chat widget
1379
- */
1380
- hide(): void;
1381
- /**
1382
- * Fetch/refresh the list of user's channels
1383
- */
1384
- getChannels(): void;
1385
- /**
1386
- * Select a channel and load its messages
1387
- */
1388
- selectChannel(channelId: string): void;
1389
- /**
1390
- * Create a new channel and enter it
1391
- */
1392
- createChannel(): void;
1393
- /**
1394
- * Go back to channel list from conversation view
1395
- */
1396
- goToChannelList(): void;
1397
- /**
1398
- * Send a message
1399
- */
1400
- sendMessage(content: string): void;
1401
- /**
1402
- * Mark messages as read
1403
- */
1404
- markAsRead(): void;
1405
- /**
1406
- * Subscribe to new messages
1407
- */
1408
- onMessage(callback: MessageCallback): Unsubscribe$1;
1409
- /**
1410
- * Subscribe to typing indicators
1411
- */
1412
- onTyping(callback: TypingCallback): Unsubscribe$1;
1413
- /**
1414
- * Subscribe to connection changes
1415
- */
1416
- onConnectionChange(callback: ConnectionCallback): Unsubscribe$1;
1417
- /**
1418
- * Update configuration
1419
- */
1420
- updateConfig(config: Partial<ChatConfig>): void;
1421
- /**
1422
- * Get the merged configuration (server + code, code takes precedence)
1423
- */
1424
- getMergedConfig(): ChatConfig;
1425
- /**
1426
- * Destroy the chat widget
1427
- */
1428
- destroy(): void;
1429
- /**
1430
- * Fetch chat settings from the server
1431
- * This enables "snippet-only" installation where widget configures from dashboard
1432
- */
1433
- private _fetchServerSettings;
1434
- /**
1435
- * Show the chat bubble (launcher button) without fully loading the widget
1436
- * This creates a lightweight bubble that loads the full widget on click
1253
+ * Set the user identifiers that power Enhanced Conversions. Values are
1254
+ * normalized + SHA-256 hashed client-side before being pushed to gtag,
1255
+ * so raw PII never leaves the browser.
1437
1256
  */
1438
- private _showBubble;
1439
- private get _scriptName();
1440
- /**
1441
- * Schedule preload on idle
1442
- */
1443
- private _schedulePreload;
1444
- /**
1445
- * Lazy load and then execute callback
1446
- */
1447
- private _lazyLoadAndThen;
1448
- /**
1449
- * Lazy load the chat script
1450
- */
1451
- private _lazyLoad;
1452
- /**
1453
- * Called after the chat script is loaded
1454
- */
1455
- private _onScriptLoaded;
1257
+ setUserData(raw: RawUserData): Promise<void>;
1258
+ getRecentPublicCalls(): GtagCall[];
1259
+ private _start;
1260
+ private _drainPending;
1261
+ private _pushConsentDefaultsOnce;
1262
+ private _subscribeCaptured;
1263
+ /**
1264
+ * Handle one captured event. Only `__remote_config_loaded === false` uses
1265
+ * `_pendingCaptures`. After remote commits, we boot the gtag pipeline on
1266
+ * demand (`startIfEnabled`) and forward — the dataLayer shim buffers until
1267
+ * `gtag.js` loads.
1268
+ */
1269
+ private _handleCaptured;
1270
+ private _subscribeConsent;
1271
+ private _record;
1456
1272
  }
1457
1273
 
1458
1274
  /**
@@ -1547,6 +1363,18 @@ interface QueuedRequest {
1547
1363
  transport?: "xhr" | "sendBeacon";
1548
1364
  }
1549
1365
 
1366
+ /**
1367
+ * Rate Limiter - Token Bucket Algorithm (PostHog-style)
1368
+ *
1369
+ * Prevents runaway loops from flooding the server with events.
1370
+ * Uses a token bucket algorithm with configurable rate and burst limits.
1371
+ *
1372
+ * Features:
1373
+ * - Configurable events per second (default: 10)
1374
+ * - Configurable burst limit (default: 100)
1375
+ * - Token replenishment over time
1376
+ * - Warning event when rate limited
1377
+ */
1550
1378
  interface RateLimitBucket {
1551
1379
  tokens: number;
1552
1380
  last: number;
@@ -1669,6 +1497,93 @@ declare class SimpleEventEmitter {
1669
1497
  hasListeners(event: string): boolean;
1670
1498
  }
1671
1499
 
1500
+ /**
1501
+ * Feature Manager
1502
+ *
1503
+ * Central registry for SDK features. Features self-describe via descriptors
1504
+ * that declare their config keys, remote config mappings, and class references.
1505
+ *
1506
+ * Responsibilities:
1507
+ * - Registration: features register descriptors before init
1508
+ * - Instance creation: createInstances() constructs instances during init() (pre-boot)
1509
+ * - Starting: initAll() starts created instances at boot via startIfEnabled
1510
+ * - Config propagation: notifyAll() calls onConfigUpdate on each instance
1511
+ * - Descriptor exposure: getDescriptors() lets RemoteConfigManager._apply iterate
1512
+ *
1513
+ * @see docs/patterns/tracker-feature-lifecycle.md
1514
+ */
1515
+
1516
+ /**
1517
+ * Self-describing metadata for a feature.
1518
+ * Declared once at registration time; used by FeatureManager and RemoteConfigManager.
1519
+ */
1520
+ interface FeatureDescriptor {
1521
+ /** Unique name (used as map key and for get()) */
1522
+ name: string;
1523
+ /** VTiltConfig key that holds this feature's config (e.g. "session_recording") */
1524
+ configKey?: keyof VTiltConfig;
1525
+ /** VTiltConfig boolean key that hard-disables the feature (e.g. "disable_session_recording") */
1526
+ disableKey?: keyof VTiltConfig;
1527
+ /** Remote config mapping — tells _apply how to extract config from RemoteConfig */
1528
+ remoteConfig?: {
1529
+ /** Key on the RemoteConfig object (e.g. "sessionRecording") */
1530
+ key: string;
1531
+ /** Transform remote section into the shape written to VTiltConfig[configKey] */
1532
+ map: (remote: Record<string, unknown>) => Record<string, unknown>;
1533
+ };
1534
+ /** Feature class — must have a static extractConfig and a constructor */
1535
+ FeatureClass: {
1536
+ new (instance: any, config?: any): Feature;
1537
+ extractConfig(config: VTiltConfig): any;
1538
+ };
1539
+ }
1540
+ /**
1541
+ * Minimal host interface to avoid circular dependency with VTilt.
1542
+ */
1543
+ interface FeatureHost {
1544
+ getConfig(): VTiltConfig;
1545
+ }
1546
+ declare class FeatureManager {
1547
+ private _host;
1548
+ private _descriptors;
1549
+ private _instances;
1550
+ constructor(host: FeatureHost);
1551
+ /** Register a feature descriptor. Call before initAll(). */
1552
+ register(desc: FeatureDescriptor): void;
1553
+ /**
1554
+ * Create instances for all registered features without starting them.
1555
+ * Safe to call before DOM boot — constructors are lightweight.
1556
+ * Idempotent: skips features that already have an instance.
1557
+ *
1558
+ * Hard-disabled features are skipped (instance not created):
1559
+ * - `disableKey` is true (e.g. `disable_chat: true`)
1560
+ * - `configKey` section has `enabled: false` (e.g. `chat: { enabled: false }`)
1561
+ */
1562
+ createInstances(): void;
1563
+ /**
1564
+ * Create and start all registered features.
1565
+ * Calls `createInstances()` first (idempotent), then `startIfEnabled()` on each.
1566
+ */
1567
+ initAll(): void;
1568
+ /** Notify all initialized features of a config change. */
1569
+ notifyAll(config: VTiltConfig): void;
1570
+ /** Get a feature instance by name. */
1571
+ get<T extends Feature>(name: string): T | undefined;
1572
+ /** Register a late-created feature instance (e.g. from a public start*() call). */
1573
+ set(name: string, instance: Feature): void;
1574
+ /** Expose descriptors so RemoteConfigManager._apply can iterate for remote config mapping. */
1575
+ getDescriptors(): Map<string, FeatureDescriptor>;
1576
+ /**
1577
+ * True when the integrator explicitly hard-disabled this feature in code config.
1578
+ * Two signals: `disableKey` flag (e.g. `disable_chat: true`) or
1579
+ * `configKey` section with `enabled: false` (e.g. `chat: { enabled: false }`).
1580
+ *
1581
+ * When `enabled` is omitted (undefined), the feature may still auto-configure
1582
+ * from dashboard settings, so we must construct the instance.
1583
+ */
1584
+ private _isHardDisabled;
1585
+ }
1586
+
1672
1587
  /**
1673
1588
  * VTilt SDK - Main Entry Point
1674
1589
  *
@@ -1681,57 +1596,111 @@ declare class SimpleEventEmitter {
1681
1596
  * - CaptureManager: Event capture and payload enrichment
1682
1597
  * - IdentityManager: High-level identity operations
1683
1598
  * - RemoteConfigManager: Remote configuration from /decide
1684
- * - FeatureManager: Feature lifecycle management
1599
+ * - FeatureManager: Feature lifecycle management (descriptors, initAll, notifyAll)
1685
1600
  *
1686
- * @see docs/tracker/README.md for full documentation
1601
+ * @see docs/patterns/tracker-feature-lifecycle.md
1687
1602
  */
1688
1603
 
1689
1604
  declare class VTilt {
1690
1605
  readonly version: string;
1691
1606
  __loaded: boolean;
1692
- __request_queue: QueuedRequest[];
1693
- historyAutocapture?: HistoryAutocapture;
1694
- autocapture?: Autocapture;
1695
- sessionRecording?: SessionRecordingWrapper;
1696
- chat?: ChatWrapper;
1697
- webVitals?: WebVitalsManager;
1698
- vtdOverlay?: VtdOverlay;
1699
1607
  private configManager;
1700
1608
  sessionManager: SessionManager;
1701
1609
  userManager: UserManager;
1702
1610
  private _captureManager;
1703
1611
  private _identityManager;
1704
1612
  private _remoteConfigManager;
1705
- private _featureManager;
1613
+ consentManager: ConsentManager;
1614
+ _featureManager: FeatureManager;
1615
+ vtdOverlay?: VtdOverlay;
1616
+ private _eventBuffer;
1706
1617
  private requestQueue;
1707
1618
  private retryQueue;
1708
1619
  rateLimiter: RateLimiter;
1709
1620
  _emitter: SimpleEventEmitter;
1710
1621
  private _has_warned_about_config;
1622
+ private static readonly BOOT_GATED_METHODS;
1623
+ private _booted;
1624
+ private _pendingCalls;
1625
+ private _postBootInitDone;
1711
1626
  constructor(config?: Partial<VTiltConfig>);
1627
+ /**
1628
+ * Register all features with their descriptors.
1629
+ * Descriptors tell FeatureManager and RemoteConfigManager how each feature works.
1630
+ */
1631
+ private _registerFeatures;
1632
+ private _installBootGate;
1633
+ _boot(): void;
1712
1634
  init(token: string, config?: Partial<VTiltConfig>, name?: string): VTilt;
1713
1635
  private _init;
1714
- private _initFeatures;
1715
- private _initHistoryAutocapture;
1716
- private _initAutocapture;
1717
- private _initWebVitals;
1718
- _initSessionRecording(): void;
1719
- _initChat(): void;
1636
+ /**
1637
+ * Substantive init work that requires both init() config AND a booted
1638
+ * browser environment. Runs exactly once.
1639
+ *
1640
+ * Init order: hydrate storage -> initAll features -> load remote config
1641
+ * Features must exist before remote config loads so cached/fresh config
1642
+ * can trigger onConfigUpdate on already-initialized features.
1643
+ */
1644
+ private _runPostBootInit;
1720
1645
  startAutocapture(): void;
1721
1646
  stopAutocapture(): void;
1722
1647
  isAutocaptureActive(): boolean;
1648
+ /**
1649
+ * Returns a structured snapshot of the autocapture state — useful for
1650
+ * debugging "no `$autocapture` events" complaints. Returns `null` if the
1651
+ * Autocapture feature has not been registered (e.g. before init).
1652
+ */
1653
+ getAutocaptureDiagnostics(): ReturnType<Autocapture["getDiagnostics"]> | null;
1723
1654
  startSessionRecording(): void;
1724
1655
  stopSessionRecording(): void;
1725
1656
  isRecordingActive(): boolean;
1726
1657
  getSessionRecordingId(): string | null;
1658
+ openChat(): void;
1659
+ closeChat(): void;
1660
+ toggleChat(): void;
1661
+ showChat(): void;
1662
+ hideChat(): void;
1663
+ /**
1664
+ * Send a chat message. By default uses the active conversation; pass options to
1665
+ * start a new conversation, target a channel, or open the widget panel.
1666
+ */
1667
+ sendChatMessage(content: SendChatMessageContent, options?: SendChatMessageOptions): void;
1668
+ /**
1669
+ * Raw `gtag(...)` passthrough. Queues calls made before the feature is
1670
+ * enabled or before `gtag.js` has loaded, flushed FIFO once ready. Safe
1671
+ * to call at any time, including before `vt.init()`.
1672
+ *
1673
+ * @example
1674
+ * vt.gtag('event', 'sign_up', { method: 'email' })
1675
+ */
1676
+ gtag(...args: unknown[]): void;
1677
+ /**
1678
+ * Set Enhanced Conversions user identifiers. Values are normalized and
1679
+ * SHA-256 hashed in the browser before being forwarded to gtag.
1680
+ */
1681
+ setGoogleUserData(data: Parameters<GoogleTagGateway["setUserData"]>[0]): Promise<void>;
1682
+ get featureManager(): FeatureManager;
1683
+ on<T = unknown>(event: string, listener: EventListener<T>): Unsubscribe;
1684
+ once<T = unknown>(event: string, listener: EventListener<T>): Unsubscribe;
1685
+ /** Removes all listeners for `event` (same semantics as internal emitter). */
1686
+ off(event: string): void;
1687
+ /**
1688
+ * Whether this browser session looks like a bot/crawler (PostHog parity).
1689
+ * When true and bot filtering is enabled, `capture()` is a no-op.
1690
+ */
1691
+ _is_bot(): boolean;
1727
1692
  capture(name: string, payload: EventPayload, options?: {
1728
1693
  skip_client_rate_limiting?: boolean;
1694
+ skip_engagement?: boolean;
1729
1695
  }): void;
1730
- trackEvent(name: string, payload?: EventPayload): void;
1696
+ /** Emit $pageleave for the current page when enabled (SPA transitions, lifecycle hooks). */
1697
+ tryCapturePageleave(reason: string): void;
1731
1698
  identify(newDistinctId?: string, userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
1732
1699
  setUserProperties(userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
1733
1700
  resetUser(reset_device_id?: boolean): void;
1734
- createAlias(alias: string, original?: string): void;
1701
+ alias(alias: string, original?: string): void;
1702
+ setConsent(consent: ConsentState): void;
1703
+ getConsent(): ConsentState;
1735
1704
  getUserIdentity(): Record<string, any>;
1736
1705
  getDeviceId(): string;
1737
1706
  getUserState(): "anonymous" | "identified";
@@ -1742,9 +1711,15 @@ declare class VTilt {
1742
1711
  getAnonymousId(): string;
1743
1712
  toString(): string;
1744
1713
  updateConfig(config: Partial<VTiltConfig>): void;
1745
- private _notifyFeaturesOfConfigUpdate;
1746
1714
  buildUrl(): string;
1747
- sendRequest(url: string, event: TrackingEvent, shouldEnqueue?: boolean): void;
1715
+ buildEndpointUrl(path: string): string;
1716
+ sendRequest(url: string, event: TrackingEvent): void;
1717
+ /**
1718
+ * Route a captured event through the EventBuffer.
1719
+ * $snapshot and $snapshot_items bypass the buffer (they have identity from
1720
+ * UserManager and don't need config filtering).
1721
+ */
1722
+ bufferEvent(name: string, url: string, event: TrackingEvent): void;
1748
1723
  private _is_configured;
1749
1724
  private _send_batched_request;
1750
1725
  private _send_http_request;
@@ -1761,4 +1736,4 @@ declare class VTilt {
1761
1736
  declare const vt: VTilt;
1762
1737
 
1763
1738
  export { ALL_WEB_VITALS_METRICS, DEFAULT_WEB_VITALS_METRICS, VTilt, vt as default, vt };
1764
- export type { AliasEvent, AutocaptureOptions, CaptureOptions, CapturePerformanceConfig, CaptureResult, ChatWidgetConfig, EventPayload, FeatureFlagsConfig, GeolocationData, GroupsConfig, PersistenceMethod, Properties, Property, PropertyOperations, RemoteConfig, RequestOptions, SessionData, SessionIdChangedCallback, SessionRecordingMaskInputOptions, SessionRecordingOptions, SupportedWebVitalsMetric, TrackingEvent, UserIdentity, UserProperties, VTiltConfig, WebVitalMetric };
1739
+ export type { AutocaptureOptions, CaptureOptions, CapturePerformanceConfig, CaptureResult, ChatWidgetConfig, EventPayload, FeatureFlagsConfig, GeolocationData, GoogleAdsConversionMapping, GoogleConsentOverride, GoogleConsentValue, GoogleTagClientConfig, GoogleTagEventMapping, GoogleTagParamMapping, GoogleTagProxyMode, GroupsConfig, IdentityUpdate, PersistenceMethod, Properties, Property, PropertyOperations, RemoteConfig, RequestOptions, SessionData, SessionIdChangedCallback, SessionRecordingMaskInputOptions, SessionRecordingOptions, SupportedWebVitalsMetric, TrackingEvent, UserIdentity, UserProperties, VTiltConfig, WebVitalMetric };