@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
@@ -10,12 +10,26 @@
10
10
  * - user_properties: Custom properties set via identify/setUserProperties
11
11
  * - user_state: "anonymous" or "identified"
12
12
  */
13
- import { UserIdentity, AliasEvent, PersistenceMethod } from "./types";
13
+ import { UserIdentity, IdentityUpdate, PersistenceMethod } from "./types";
14
14
  export declare class UserManager {
15
15
  private storage;
16
16
  private userIdentity;
17
- private _cachedPersonProperties;
17
+ private _isFirstVisit;
18
18
  constructor(storageMethod?: PersistenceMethod, cross_subdomain?: boolean);
19
+ /**
20
+ * Hydrate identity from persistent storage.
21
+ *
22
+ * Called by VTilt._boot() once the browser environment is guaranteed ready
23
+ * (DOMContentLoaded). This is the only code path that reads from
24
+ * localStorage / cookies. If storage is empty (first visit), the generated
25
+ * IDs are persisted so they survive the next page load.
26
+ */
27
+ hydrateFromStorage(): void;
28
+ /**
29
+ * Generate an ephemeral in-memory identity with no storage access.
30
+ * Used by the constructor so the SDK is safe to instantiate in SSR.
31
+ */
32
+ private generateEphemeralIdentity;
19
33
  /**
20
34
  * Get current user identity
21
35
  */
@@ -45,42 +59,20 @@ export declare class UserManager {
45
59
  */
46
60
  getUserState(): "anonymous" | "identified";
47
61
  /**
48
- * Identify a user with distinct ID and properties
49
- */
50
- identify(newDistinctId?: string, userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
51
- /**
52
- * Set user properties without changing distinct ID
62
+ * Returns true if this is the user's first visit (no prior anonymous_id
63
+ * in storage when the SDK booted). Resets after first call.
64
+ * Matches GA4's _fv=1 semantics.
53
65
  */
54
- setUserProperties(userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): boolean;
66
+ consumeFirstVisit(): boolean;
55
67
  /**
56
- * Reset user identity (logout)
57
- * Generates new anonymous ID, clears user data, optionally resets device ID
68
+ * Apply batched identity changes with a single save.
69
+ * Replaces individual setters to avoid multiple storage writes per operation.
58
70
  */
59
- reset(reset_device_id?: boolean): void;
71
+ applyUpdate(update: IdentityUpdate): void;
60
72
  /**
61
- * Update distinct ID (internal use - for VTilt)
73
+ * Reset identity to anonymous state. Generates new IDs and clears user data.
62
74
  */
63
- setDistinctId(distinctId: string): void;
64
- /**
65
- * Update user state (internal use - for VTilt)
66
- */
67
- setUserState(state: "anonymous" | "identified"): void;
68
- /**
69
- * Update user properties (internal use - for VTilt)
70
- */
71
- updateUserProperties(userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
72
- /**
73
- * Set device ID if not already set (internal use - for VTilt)
74
- */
75
- ensureDeviceId(deviceId: string): void;
76
- /**
77
- * Create an alias to link two distinct IDs
78
- */
79
- createAlias(alias: string, original?: string): AliasEvent | null;
80
- /**
81
- * Check if distinct ID is string-like (hardcoded string) - public for validation
82
- */
83
- isDistinctIdStringLikePublic(distinctId: string): boolean;
75
+ reset(resetDeviceId?: boolean): void;
84
76
  /**
85
77
  * Set initial person info
86
78
  */
@@ -127,28 +119,10 @@ export declare class UserManager {
127
119
  * Register a value once (only if not already set)
128
120
  */
129
121
  private register_once;
130
- /**
131
- * Generate a new anonymous ID
132
- */
133
122
  private generateAnonymousId;
134
- /**
135
- * Generate a new device ID
136
- */
137
123
  private generateDeviceId;
138
124
  /**
139
- * Get hash for person properties (for deduplication)
140
- */
141
- private getPersonPropertiesHash;
142
- /**
143
- * Validate distinct ID
144
- */
145
- private isValidDistinctId;
146
- /**
147
- * Check if distinct ID is string-like (hardcoded string)
148
- */
149
- private isDistinctIdStringLike;
150
- /**
151
- * Update storage method at runtime
125
+ * Update storage method at runtime.
152
126
  */
153
127
  updateStorageMethod(method: PersistenceMethod, cross_subdomain?: boolean): void;
154
128
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Base64 encoding for binary data.
3
+ *
4
+ * Used by the request layer (`request.ts`) and session recording
5
+ * (`extensions/replay/session-recording.ts`) to send gzip-compressed bodies
6
+ * over `navigator.sendBeacon` as **text** rather than as a binary `Blob`.
7
+ *
8
+ * Why this exists:
9
+ *
10
+ * `sendBeacon(url, blob)` with a binary `Blob` (e.g. a gzipped Uint8Array)
11
+ * is racy. Browsers queue the beacon synchronously, then serialize the
12
+ * body to the network *after* the JS context has been torn down on
13
+ * `pagehide` / tab discard. If the underlying `ArrayBuffer` is detached
14
+ * or GC'd before that flush, the wire body ends up empty or truncated —
15
+ * while the URL we already committed still carries `?compression=gzip-js`.
16
+ * The server then tries `gunzipSync` against an empty buffer and throws
17
+ * `Z_BUF_ERROR: unexpected end of file`.
18
+ *
19
+ * Sending the gzip bytes as a base64 *string* sidesteps the problem
20
+ * entirely: `Blob([string])` owns its own UTF-8 encoded copy, and
21
+ * `sendBeacon` is reliable for text bodies. The server already accepts
22
+ * `?compression=base64` for this exact path.
23
+ */
24
+ /**
25
+ * Encode a `Uint8Array` to a base64 string using `btoa`.
26
+ *
27
+ * Chunks the bytes through `String.fromCharCode` so we don't blow the
28
+ * argument-count limit on `apply()` for large buffers.
29
+ */
30
+ export declare function uint8ArrayToBase64(bytes: Uint8Array): string;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Bot / crawler detection for the browser SDK.
3
+ *
4
+ * User-agent patterns are aligned with PostHog's `@posthog/core` bot list
5
+ * (https://github.com/PostHog/posthog-js — `utils/bot-detection.ts`) so
6
+ * behaviour matches what PostHog integrators expect.
7
+ *
8
+ * Keep in sync with `app/src/lib/tracking/bot-detection.ts`.
9
+ */
10
+ /** Substrings matched case-insensitively against `User-Agent` (and SDK `navigator.userAgent`). */
11
+ export declare const DEFAULT_BLOCKED_UA_STRS: readonly string[];
12
+ /**
13
+ * Returns true when the user agent matches a known bot/crawler pattern.
14
+ */
15
+ export declare function isBlockedUA(ua: string | undefined | null, customBlockedUserAgents?: string[]): boolean;
16
+ /** Subset of `Navigator.userAgentData` used for bot checks. */
17
+ export interface NavigatorUAData {
18
+ brands?: {
19
+ brand: string;
20
+ version: string;
21
+ }[];
22
+ platform?: string;
23
+ }
24
+ /**
25
+ * Browser-side bot check (PostHog `isLikelyBot` parity): UA string, CH brand
26
+ * hints, and `navigator.webdriver`.
27
+ */
28
+ export declare function isLikelyBot(nav: Navigator | undefined, customBlockedUserAgents?: string[]): boolean;
@@ -0,0 +1,36 @@
1
+ import type { VTiltConfig } from "../types";
2
+ /** Header that carries the project token on every authenticated SDK call.
3
+ * Matches the server-side `withPublicProjectAccess` wrapper, which reads
4
+ * the same name (`request.headers.get('x-api-key')`) before falling back
5
+ * to the `?token=` query parameter. */
6
+ export declare const PROJECT_TOKEN_HEADER = "x-api-key";
7
+ export interface BuildEndpointUrlOptions {
8
+ /**
9
+ * Append `?token=<token>` to the URL. Off by default so the URL stays
10
+ * clean — privacy/ad blockers commonly match on `?token=` in known
11
+ * analytics origins, and the cleaner the URL is the less likely a
12
+ * legitimate request gets cancelled with `ERR_BLOCKED_BY_CLIENT`.
13
+ *
14
+ * Pass `true` only for transports that cannot carry a custom header
15
+ * (i.e. `navigator.sendBeacon`), where the query parameter is still
16
+ * the only practical way to authenticate the request.
17
+ */
18
+ includeTokenQuery?: boolean;
19
+ }
20
+ /**
21
+ * Resolves a path + project token to a full request URL.
22
+ * Used by the main client and by lazy chunks (e.g. session recording) that
23
+ * only have access to `getConfig()` and must not depend on `VTilt` prototype methods.
24
+ *
25
+ * By default the returned URL does NOT carry the project token. Callers
26
+ * are expected to attach it via the `x-api-key` header (see
27
+ * `getProjectTokenHeader`). For `sendBeacon` paths where headers aren't
28
+ * supported, pass `{ includeTokenQuery: true }`.
29
+ */
30
+ export declare function buildEndpointUrlFromConfig(config: Pick<VTiltConfig, "api_host" | "token">, path: string, options?: BuildEndpointUrlOptions): string;
31
+ /**
32
+ * Returns the auth header dict to merge into a `fetch` / `XMLHttpRequest`
33
+ * call. Returns an empty object when no token is configured so callers
34
+ * can spread it unconditionally.
35
+ */
36
+ export declare function getProjectTokenHeader(config: Pick<VTiltConfig, "token">): Record<string, string>;
@@ -96,6 +96,7 @@ export declare const VTiltEvents: {
96
96
  readonly SESSION_ROTATED: "session:rotated";
97
97
  readonly FEATURE_STARTED: "feature:started";
98
98
  readonly FEATURE_STOPPED: "feature:stopped";
99
+ readonly CONSENT_UPDATED: "consent:updated";
99
100
  readonly EVENT_CAPTURED: "event:captured";
100
101
  readonly EVENT_SENT: "event:sent";
101
102
  readonly EVENT_FAILED: "event:failed";
@@ -13,7 +13,6 @@ export interface LazyLoadedSessionRecordingInterface {
13
13
  sessionId: string;
14
14
  status: string;
15
15
  isStarted: boolean;
16
- onRemoteConfig?: (response: any) => void;
17
16
  log: (message: string, level: "log" | "warn" | "error") => void;
18
17
  updateConfig: (config: any) => void;
19
18
  }
@@ -136,6 +135,8 @@ export interface ChatConfig {
136
135
  offlineMessage?: string;
137
136
  /** Collect email when offline */
138
137
  collectEmailOffline?: boolean;
138
+ /** Bubble appearance and behavior */
139
+ bubble?: BubbleConfig;
139
140
  /** Called when widget is opened */
140
141
  onWidgetOpen?: () => void;
141
142
  /** Called when widget is closed */
@@ -171,6 +172,47 @@ export interface ChatTheme {
171
172
  userBubbleColor?: string;
172
173
  agentBubbleColor?: string;
173
174
  }
175
+ /**
176
+ * First argument to `vt.sendChatMessage()` / {@link LazyLoadedChatInterface.sendMessage}.
177
+ *
178
+ * Plain strings stay plain text for backward compatibility. Use `{ markdown: '...' }`,
179
+ * `{ html: '...' }`, or `options.format` for rich content.
180
+ */
181
+ export type SendChatMessageContent = string | {
182
+ text: string;
183
+ } | {
184
+ markdown: string;
185
+ } | {
186
+ html: string;
187
+ };
188
+ /**
189
+ * Options for `vt.sendChatMessage()` / {@link LazyLoadedChatInterface.sendMessage}.
190
+ */
191
+ export interface SendChatMessageOptions {
192
+ /**
193
+ * Target conversation: `'new'` creates a channel, a UUID selects an existing one,
194
+ * omit to use the active conversation (must already be in conversation view).
195
+ */
196
+ channel?: "new" | string;
197
+ /**
198
+ * Open the widget panel before sending. Default: true. Pass `open: false` to send without opening.
199
+ */
200
+ open?: boolean;
201
+ /**
202
+ * Applies when the first argument is a plain string. Default: `text`.
203
+ * `markdown` and `html` use the same sanitized rendering as AI/agent messages.
204
+ */
205
+ format?: "text" | "markdown" | "html";
206
+ }
207
+ /**
208
+ * Chat bubble appearance and behavior configuration
209
+ */
210
+ export interface BubbleConfig {
211
+ /** Allow user to drag the bubble to reposition (default: false) */
212
+ draggable?: boolean;
213
+ /** Show the bubble on load (default: true). Set to false to control via vt.chat.show() */
214
+ visible?: boolean;
215
+ }
174
216
  /**
175
217
  * Interface for lazy-loaded chat widget (set by chat.ts entrypoint)
176
218
  */
@@ -195,12 +237,39 @@ export interface LazyLoadedChatInterface {
195
237
  createChannel(): Promise<void>;
196
238
  /** Go back to channel list from conversation view */
197
239
  goToChannelList(): void;
198
- sendMessage(content: string): Promise<void>;
240
+ sendMessage(content: SendChatMessageContent, options?: SendChatMessageOptions): Promise<void>;
199
241
  markAsRead(): void;
200
242
  onMessage(callback: (message: ChatMessage) => void): () => void;
201
243
  onTyping(callback: (isTyping: boolean, senderType: string) => void): () => void;
202
244
  onConnectionChange(callback: (connected: boolean) => void): () => void;
203
245
  updateConfig?(config: ChatConfig): void;
246
+ registerWidget?(definition: {
247
+ type: string;
248
+ toolDescription: string;
249
+ parameters: Record<string, {
250
+ type: string;
251
+ description: string;
252
+ }>;
253
+ render: (params: Record<string, unknown>, context: {
254
+ channelId: string;
255
+ distinctId: string;
256
+ primaryColor: string;
257
+ apiBase: string;
258
+ token: string;
259
+ messageId: string;
260
+ }) => string;
261
+ onAction: (action: string, data: Record<string, unknown>, context: {
262
+ channelId: string;
263
+ distinctId: string;
264
+ primaryColor: string;
265
+ apiBase: string;
266
+ token: string;
267
+ messageId: string;
268
+ }) => Promise<{
269
+ replaceHTML?: string;
270
+ sendMessage?: string;
271
+ }>;
272
+ }): void;
204
273
  destroy(): void;
205
274
  }
206
275
  /**
@@ -1,6 +1,9 @@
1
1
  export * from "./type-guards";
2
2
  export * from "./safewrap";
3
+ export * from "./endpoint-url";
3
4
  export * from "./event-emitter";
5
+ export * from "./transport-health";
6
+ export { getLogger, resolveLogLevel, setLevelFromConfig, type LogLevel, type Logger, } from "./logger";
4
7
  /**
5
8
  * Generate a short, URL-safe random ID (nanoid-compatible).
6
9
  * Uses crypto.getRandomValues for entropy — same approach as nanoid.
@@ -12,7 +15,23 @@ export declare function generateId(size?: number): string;
12
15
  */
13
16
  export declare function isValidUserAgent(userAgent: string): boolean;
14
17
  /**
15
- * Validate payload string
18
+ * Maximum serialized event payload size, in bytes (1 MB).
19
+ *
20
+ * The transport layer gzips bodies before sending and the ingestion API
21
+ * applies its own server-side limits, so this cap exists only to defeat
22
+ * runaway client-side bugs (infinite property growth, accidental DOM dumps).
23
+ *
24
+ * Previously 10 KB, which silently dropped legitimate `$autocapture` events
25
+ * on real apps where 15–25 ancestor elements with framework class soup put
26
+ * the JSON above 10 KB before any user-defined properties were added.
27
+ *
28
+ * Aligned with PostHog's analogous client cap (1 MB).
29
+ */
30
+ export declare const MAX_PAYLOAD_BYTES = 1048576;
31
+ /**
32
+ * Validate payload string. Used to defend against pathological / runaway
33
+ * payloads only; routine "this event got too big" cases (deep DOM chains)
34
+ * must NOT be silently dropped here. See {@link MAX_PAYLOAD_BYTES}.
16
35
  */
17
36
  export declare function isValidPayload(payloadStr: string): boolean;
18
37
  /**
@@ -45,7 +64,3 @@ export declare function stripEmptyProperties<T extends Record<string, any>>(obj:
45
64
  * Strip leading dollar sign from string
46
65
  */
47
66
  export declare function stripLeadingDollar(str: string): string;
48
- /**
49
- * Check if value is null
50
- */
51
- export declare function isNull(value: any): boolean;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Configurable logger for SDK development tracing.
3
+ *
4
+ * Level taxonomy (single source of truth — every SDK file follows it):
5
+ * - error: SDK or integrator-supplied input is broken (failed network with no
6
+ * retry left, invalid identify input, storage write rejected, feature
7
+ * crashed inside safewrap).
8
+ * - warn: degraded but recovering (deprecated API, falling back from
9
+ * localStorage to memory, retrying a failed request, ignoring an
10
+ * unsupported config option, browser feature missing).
11
+ * - info: one-time lifecycle events the integrator wants in the console
12
+ * while wiring the SDK up (SDK initialised, autocapture started/stopped,
13
+ * replay started/stopped, chat connected, person identified).
14
+ * - debug: per-event trace useful when filing a bug (every captured event
15
+ * name, every autocapture skip with reason, every queue flush, request
16
+ * URLs, decide-endpoint payload).
17
+ *
18
+ * Default floor is 'warn' — so SDK errors and warnings always reach the
19
+ * integrator without opt-in (matches Amplitude/PostHog/Sentry). Use 'none'
20
+ * to silence everything (e.g. shared kiosk consoles).
21
+ *
22
+ * Resolution precedence for the active level:
23
+ * 1. log_level / debug passed to vt.init() — locks the level.
24
+ * 2. Remote config from /decide (diagnostics.defaultLogLevel) — applied only
25
+ * if init did not lock the level.
26
+ * 3. Module default 'warn'.
27
+ */
28
+ import type { VTiltConfig } from "../types";
29
+ export type LogLevel = "none" | "error" | "warn" | "info" | "debug";
30
+ export interface Logger {
31
+ /**
32
+ * Set the active level. When `lockedByInit` is true the level is pinned
33
+ * and subsequent calls to `setLevelFromRemote` are ignored. Use this
34
+ * variant only from the init-config resolver in vtilt.ts.
35
+ */
36
+ setLevel(level: LogLevel, lockedByInit?: boolean): void;
37
+ /**
38
+ * Apply a level coming from remote config. No-op when the integrator
39
+ * pinned a level at init time, so a hard-coded snippet always wins.
40
+ */
41
+ setLevelFromRemote(level: LogLevel | null | undefined): void;
42
+ getLevel(): LogLevel;
43
+ isLockedByInit(): boolean;
44
+ debug(...args: unknown[]): void;
45
+ info(...args: unknown[]): void;
46
+ warn(...args: unknown[]): void;
47
+ error(...args: unknown[]): void;
48
+ }
49
+ /**
50
+ * Get the global SDK logger. Level is set by VTilt from config (log_level or
51
+ * debug: true) and may later be overridden by remote config via
52
+ * setLevelFromRemote — unless init pinned the level.
53
+ */
54
+ export declare function getLogger(): Logger;
55
+ /**
56
+ * Resolve a log level from VTiltConfig. Centralises the
57
+ * `log_level ?? (debug ? 'debug' : 'warn')` rule so call sites cannot drift.
58
+ */
59
+ export declare function resolveLogLevel(config: Pick<VTiltConfig, "log_level" | "debug">): LogLevel;
60
+ /**
61
+ * Apply a config-derived level to the global logger. Pass `lockedByInit`
62
+ * (default true) when the call originates from vt.init() so remote config
63
+ * cannot later override the integrator's choice.
64
+ */
65
+ export declare function setLevelFromConfig(config: Pick<VTiltConfig, "log_level" | "debug">, lockedByInit?: boolean): LogLevel;
66
+ export declare function __resetLoggerForTests(): void;
@@ -7,6 +7,11 @@
7
7
  * IE11 doesn't support `new URL`, so we use anchor element
8
8
  */
9
9
  export declare function convertToURL(url: string): HTMLAnchorElement | null;
10
+ /**
11
+ * Parse all query parameters from a URL into a key/value map.
12
+ * When a key appears more than once, the last value wins.
13
+ */
14
+ export declare function getAllQueryParams(url: string): Record<string, string>;
10
15
  /**
11
16
  * Get query parameter from URL
12
17
  */
@@ -3,9 +3,14 @@
3
3
  *
4
4
  * Error isolation utilities to prevent feature errors from crashing the SDK.
5
5
  * Following PostHog's safewrap pattern.
6
+ *
7
+ * All errors are routed through the central level-gated logger
8
+ * (`getLogger()`) so they respect the integrator's `log_level` choice.
9
+ * `error`-level messages always surface in the default `warn` floor.
6
10
  */
7
11
  /**
8
- * Logger interface for error reporting
12
+ * Logger interface for error reporting. Kept narrow so callers can inject a
13
+ * custom logger in tests without depending on the full `Logger` shape.
9
14
  */
10
15
  interface Logger {
11
16
  error: (...args: unknown[]) => void;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Transport health tracking
3
+ *
4
+ * Chromium and Firefox surface `ERR_BLOCKED_BY_CLIENT` (or the equivalent
5
+ * "NetworkError when attempting to fetch") to the JS layer as a generic
6
+ * `TypeError: Failed to fetch` — there is no programmatic way to tell a
7
+ * cancelled-by-extension request apart from an offline or DNS failure.
8
+ *
9
+ * What we can do is notice the pattern. Once a handful of consecutive
10
+ * top-level transport failures happen in a row we treat the transport as
11
+ * "likely blocked" for the rest of the page session and:
12
+ *
13
+ * 1. Stop scheduling new requests (no more retries, no new session
14
+ * recording POSTs). The browser will keep logging
15
+ * `ERR_BLOCKED_BY_CLIENT` for any request the SDK *does* fire, so
16
+ * we minimise that noise by firing fewer of them.
17
+ * 2. Log a single explanatory warning that points integrators at the
18
+ * reverse-proxy escape hatch so they aren't left guessing.
19
+ *
20
+ * A subsequent successful response resets the counter — transient
21
+ * connectivity glitches don't latch the SDK into "blocked" mode.
22
+ *
23
+ * The state is *page-session scoped*. We deliberately do not persist it
24
+ * to storage: ad-blocker filter lists change, the user might disable a
25
+ * blocker for our domain, and we don't want a single bad page load to
26
+ * cripple every future visit.
27
+ */
28
+ /**
29
+ * Record a top-level transport failure (network error / blocked / aborted).
30
+ *
31
+ * `scope` is the SDK module reporting the failure (e.g. `"replay"`,
32
+ * `"capture"`); it's used purely for the warning log message.
33
+ */
34
+ export declare function markTransportFailure(scope: string): void;
35
+ /**
36
+ * Record a successful response. Resets the failure counter; a future run
37
+ * of failures must accumulate from zero before flipping the flag again.
38
+ *
39
+ * Does NOT clear the `blocked` flag: once the SDK has decided to back
40
+ * off in a session it stays backed off for the lifetime of that page.
41
+ * Without this rule a single intermittent success between failures would
42
+ * silently re-enable the retry storm we are trying to suppress.
43
+ */
44
+ export declare function markTransportSuccess(): void;
45
+ /**
46
+ * Once the SDK believes the transport is blocked, this returns `true`
47
+ * for the rest of the page session. Callers should skip scheduling work
48
+ * that would otherwise spam `ERR_BLOCKED_BY_CLIENT` into the console.
49
+ */
50
+ export declare function isTransportLikelyBlocked(): boolean;
51
+ /**
52
+ * Test-only reset. Not exported from the package barrel — only
53
+ * `__tests__` should use it.
54
+ */
55
+ export declare function __resetTransportHealthForTests(): void;