@v-tilt/browser 1.6.0 → 1.7.2

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 (107) hide show
  1. package/dist/array.full.js +1 -1
  2. package/dist/array.full.js.map +1 -1
  3. package/dist/array.js +1 -1
  4. package/dist/array.js.map +1 -1
  5. package/dist/array.no-external.js +1 -1
  6. package/dist/array.no-external.js.map +1 -1
  7. package/dist/chat.js +1 -1
  8. package/dist/chat.js.map +1 -1
  9. package/dist/constants.d.ts +1 -1
  10. package/dist/entrypoints/server.es.d.ts +12 -0
  11. package/dist/external-scripts-loader.js +1 -1
  12. package/dist/main.js +1 -1
  13. package/dist/main.js.map +1 -1
  14. package/dist/module.d.ts +6 -3
  15. package/dist/module.js +1 -1
  16. package/dist/module.js.map +1 -1
  17. package/dist/module.no-external.d.ts +6 -3
  18. package/dist/module.no-external.js +1 -1
  19. package/dist/module.no-external.js.map +1 -1
  20. package/dist/server.d.ts +105 -0
  21. package/dist/server.js +2 -0
  22. package/dist/server.js.map +1 -0
  23. package/dist/types.d.ts +3 -1
  24. package/dist/vtilt.d.ts +3 -2
  25. package/package.json +1 -2
  26. package/lib/config.d.ts +0 -17
  27. package/lib/config.js +0 -76
  28. package/lib/constants.d.ts +0 -178
  29. package/lib/constants.js +0 -656
  30. package/lib/entrypoints/all-external-dependencies.d.ts +0 -8
  31. package/lib/entrypoints/all-external-dependencies.js +0 -10
  32. package/lib/entrypoints/array.d.ts +0 -2
  33. package/lib/entrypoints/array.full.d.ts +0 -17
  34. package/lib/entrypoints/array.full.js +0 -19
  35. package/lib/entrypoints/array.js +0 -4
  36. package/lib/entrypoints/array.no-external.d.ts +0 -1
  37. package/lib/entrypoints/array.no-external.js +0 -4
  38. package/lib/entrypoints/chat.d.ts +0 -22
  39. package/lib/entrypoints/chat.js +0 -32
  40. package/lib/entrypoints/external-scripts-loader.d.ts +0 -24
  41. package/lib/entrypoints/external-scripts-loader.js +0 -104
  42. package/lib/entrypoints/main.cjs.d.ts +0 -4
  43. package/lib/entrypoints/main.cjs.js +0 -29
  44. package/lib/entrypoints/module.es.d.ts +0 -4
  45. package/lib/entrypoints/module.es.js +0 -23
  46. package/lib/entrypoints/module.no-external.es.d.ts +0 -4
  47. package/lib/entrypoints/module.no-external.es.js +0 -23
  48. package/lib/entrypoints/recorder.d.ts +0 -23
  49. package/lib/entrypoints/recorder.js +0 -42
  50. package/lib/entrypoints/web-vitals.d.ts +0 -14
  51. package/lib/entrypoints/web-vitals.js +0 -29
  52. package/lib/extensions/chat/chat-wrapper.d.ts +0 -196
  53. package/lib/extensions/chat/chat-wrapper.js +0 -545
  54. package/lib/extensions/chat/chat.d.ts +0 -99
  55. package/lib/extensions/chat/chat.js +0 -1891
  56. package/lib/extensions/chat/index.d.ts +0 -10
  57. package/lib/extensions/chat/index.js +0 -27
  58. package/lib/extensions/chat/types.d.ts +0 -159
  59. package/lib/extensions/chat/types.js +0 -22
  60. package/lib/extensions/history-autocapture.d.ts +0 -17
  61. package/lib/extensions/history-autocapture.js +0 -105
  62. package/lib/extensions/replay/index.d.ts +0 -13
  63. package/lib/extensions/replay/index.js +0 -31
  64. package/lib/extensions/replay/session-recording-utils.d.ts +0 -92
  65. package/lib/extensions/replay/session-recording-utils.js +0 -212
  66. package/lib/extensions/replay/session-recording-wrapper.d.ts +0 -61
  67. package/lib/extensions/replay/session-recording-wrapper.js +0 -149
  68. package/lib/extensions/replay/session-recording.d.ts +0 -95
  69. package/lib/extensions/replay/session-recording.js +0 -700
  70. package/lib/extensions/replay/types.d.ts +0 -211
  71. package/lib/extensions/replay/types.js +0 -8
  72. package/lib/geolocation.d.ts +0 -5
  73. package/lib/geolocation.js +0 -31
  74. package/lib/rate-limiter.d.ts +0 -52
  75. package/lib/rate-limiter.js +0 -80
  76. package/lib/request-queue.d.ts +0 -78
  77. package/lib/request-queue.js +0 -156
  78. package/lib/request.d.ts +0 -54
  79. package/lib/request.js +0 -265
  80. package/lib/retry-queue.d.ts +0 -64
  81. package/lib/retry-queue.js +0 -182
  82. package/lib/session.d.ts +0 -66
  83. package/lib/session.js +0 -191
  84. package/lib/storage.d.ts +0 -117
  85. package/lib/storage.js +0 -438
  86. package/lib/types.d.ts +0 -350
  87. package/lib/types.js +0 -24
  88. package/lib/user-manager.d.ts +0 -154
  89. package/lib/user-manager.js +0 -589
  90. package/lib/utils/event-utils.d.ts +0 -52
  91. package/lib/utils/event-utils.js +0 -305
  92. package/lib/utils/globals.d.ts +0 -235
  93. package/lib/utils/globals.js +0 -30
  94. package/lib/utils/index.d.ts +0 -46
  95. package/lib/utils/index.js +0 -134
  96. package/lib/utils/patch.d.ts +0 -6
  97. package/lib/utils/patch.js +0 -39
  98. package/lib/utils/request-utils.d.ts +0 -17
  99. package/lib/utils/request-utils.js +0 -80
  100. package/lib/utils/type-utils.d.ts +0 -4
  101. package/lib/utils/type-utils.js +0 -9
  102. package/lib/utils/user-agent-utils.d.ts +0 -18
  103. package/lib/utils/user-agent-utils.js +0 -411
  104. package/lib/vtilt.d.ts +0 -359
  105. package/lib/vtilt.js +0 -1188
  106. package/lib/web-vitals.d.ts +0 -95
  107. package/lib/web-vitals.js +0 -380
package/lib/vtilt.js DELETED
@@ -1,1188 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VTilt = void 0;
4
- exports.init_as_module = init_as_module;
5
- exports.init_from_snippet = init_from_snippet;
6
- const constants_1 = require("./constants");
7
- const config_1 = require("./config");
8
- const session_1 = require("./session");
9
- const user_manager_1 = require("./user-manager");
10
- const web_vitals_1 = require("./web-vitals");
11
- const history_autocapture_1 = require("./extensions/history-autocapture");
12
- const replay_1 = require("./extensions/replay");
13
- const chat_wrapper_1 = require("./extensions/chat/chat-wrapper");
14
- const request_1 = require("./request");
15
- const request_queue_1 = require("./request-queue");
16
- const retry_queue_1 = require("./retry-queue");
17
- const rate_limiter_1 = require("./rate-limiter");
18
- const utils_1 = require("./utils");
19
- const event_utils_1 = require("./utils/event-utils");
20
- const globals_1 = require("./utils/globals");
21
- /*
22
- STYLE GUIDE:
23
-
24
- Naming conventions:
25
- - snake_case: Config options and public API methods (e.g., capture_pageview, get_distinct_id)
26
- - camelCase: Internal variables and class properties (e.g., sessionManager, requestQueue)
27
- - _snake_case: Private methods that can be mangled/minified (e.g., _init, _dom_loaded)
28
- - __snake_case: Internal but not minified, signals internal use (e.g., __loaded, __request_queue)
29
- - UPPER_CASE: Constants and globals (e.g., PRIMARY_INSTANCE_NAME, ENQUEUE_REQUESTS)
30
-
31
- Use TypeScript accessibility modifiers (private/protected) for non-public members.
32
- */
33
- // Helper to check if value is an array
34
- const isArray = Array.isArray;
35
- class VTilt {
36
- constructor(config = {}) {
37
- // SDK version from constants (single source of truth)
38
- this.version = constants_1.SDK_VERSION;
39
- this.__loaded = false; // Matches snippet's window.vt.__loaded check
40
- this._initial_pageview_captured = false;
41
- this._visibility_state_listener = null;
42
- this.__request_queue = []; // Legacy queue for DOM loaded handler (pre-init)
43
- this._has_warned_about_config = false; // Track if we've already warned about missing config
44
- this._set_once_properties_sent = false; // Track if $set_once with initial props has been sent (only send once per page load)
45
- this._remoteConfig = null; // Cached remote config from /decide
46
- this.configManager = new config_1.ConfigManager(config);
47
- const fullConfig = this.configManager.getConfig();
48
- this.sessionManager = new session_1.SessionManager(fullConfig.storage || "cookie", fullConfig.cross_subdomain_cookie);
49
- // Default to localStorage+cookie for better SSR support (following PostHog)
50
- // Critical properties (anonymous_id, device_id, etc.) are stored in cookies
51
- // This ensures identity persists across page navigations in traditional SSR websites
52
- this.userManager = new user_manager_1.UserManager(fullConfig.persistence || "localStorage+cookie", fullConfig.cross_subdomain_cookie);
53
- this.webVitalsManager = new web_vitals_1.WebVitalsManager(fullConfig, this);
54
- // Initialize rate limiter to prevent flooding
55
- // Default: 10 events/second with burst of 100
56
- this.rateLimiter = new rate_limiter_1.RateLimiter({
57
- eventsPerSecond: 10,
58
- eventsBurstLimit: 100,
59
- captureWarning: (message) => {
60
- // Send rate limit warning event (bypasses rate limiting)
61
- this._capture_internal(rate_limiter_1.RATE_LIMIT_WARNING_EVENT, {
62
- $$client_ingestion_warning_message: message,
63
- });
64
- },
65
- });
66
- // Initialize retry queue for failed requests
67
- // Retries with exponential backoff: 3s, 6s, 12s... up to 30 minutes
68
- this.retryQueue = new retry_queue_1.RetryQueue({
69
- sendRequest: (req) => this._send_http_request(req),
70
- sendBeacon: (req) => this._send_beacon_request(req),
71
- });
72
- // Initialize request queue for event batching
73
- // Events are batched and sent every 3 seconds (configurable)
74
- this.requestQueue = new request_queue_1.RequestQueue((req) => this._send_batched_request(req), { flush_interval_ms: 3000 });
75
- }
76
- /**
77
- * Initializes a new instance of the VTilt tracking object.
78
- *
79
- * @remarks
80
- * All new instances are added to the main vt object as sub properties (such as
81
- * `vt.library_name`) and also returned by this function.
82
- *
83
- * @example
84
- * ```js
85
- * // basic initialization
86
- * vt.init('<project_id>', {
87
- * api_host: '<client_api_host>'
88
- * })
89
- * ```
90
- *
91
- * @example
92
- * ```js
93
- * // multiple instances
94
- * vt.init('<project_id>', {}, 'project1')
95
- * vt.init('<project_id>', {}, 'project2')
96
- * ```
97
- *
98
- * @public
99
- *
100
- * @param projectId - Your VTilt project ID
101
- * @param config - A dictionary of config options to override
102
- * @param name - The name for the new VTilt instance that you want created
103
- *
104
- * @returns The newly initialized VTilt instance
105
- */
106
- init(projectId, config, name) {
107
- var _a;
108
- if (!name || name === PRIMARY_INSTANCE_NAME) {
109
- // This means we are initializing the primary instance (i.e. this)
110
- return this._init(projectId, config, name);
111
- }
112
- else {
113
- const namedVTilt = (_a = instances[name]) !== null && _a !== void 0 ? _a : new VTilt();
114
- namedVTilt._init(projectId, config, name);
115
- instances[name] = namedVTilt;
116
- // Add as a property to the primary instance (this isn't type-safe but its how it was always done)
117
- instances[PRIMARY_INSTANCE_NAME][name] = namedVTilt;
118
- return namedVTilt;
119
- }
120
- }
121
- /**
122
- * Handles the actual initialization logic for a VTilt instance.
123
- * This internal method should only be called by `init()`.
124
- */
125
- _init(projectId, config = {}, name) {
126
- // Guard: prevent re-initialization (matches snippet's __loaded check)
127
- if (this.__loaded) {
128
- console.warn("vTilt: You have already initialized vTilt! Re-initializing is a no-op");
129
- return this;
130
- }
131
- // Update config with projectId, token, and name
132
- this.updateConfig({
133
- ...config,
134
- projectId: projectId || config.projectId,
135
- name: name,
136
- });
137
- this.__loaded = true;
138
- // Load cached remote config (sync) and apply to config
139
- // Fresh fetch happens async in background for next session
140
- this._loadRemoteConfig();
141
- // Set initial person info: stores referrer and URL on first visit
142
- const fullConfig = this.configManager.getConfig();
143
- this.userManager.set_initial_person_info(fullConfig.mask_personal_data_properties, fullConfig.custom_personal_data_properties);
144
- // Initialize history autocapture
145
- this.historyAutocapture = new history_autocapture_1.HistoryAutocapture(this);
146
- this.historyAutocapture.startIfEnabled();
147
- // Initialize session recording if enabled
148
- this._initSessionRecording();
149
- // Initialize chat widget
150
- this._initChat();
151
- // Set up page unload handler to flush queued events
152
- this._setup_unload_handler();
153
- // Enable the request queue for batched sending (PostHog pattern)
154
- this._start_queue_if_opted_in();
155
- // Capture initial pageview (with visibility check)
156
- // Only if capture_pageview is enabled (default: true)
157
- if (fullConfig.capture_pageview !== false) {
158
- this._capture_initial_pageview();
159
- }
160
- return this;
161
- }
162
- /**
163
- * Start the request queue if user hasn't opted out
164
- * Following PostHog's pattern - called from both _init() and _dom_loaded()
165
- * Safe to call multiple times as enable() is idempotent
166
- */
167
- _start_queue_if_opted_in() {
168
- // TODO: Add opt-out check when consent management is implemented
169
- // if (!this.is_capturing()) return;
170
- // Enable batched sending
171
- this.requestQueue.enable();
172
- }
173
- /**
174
- * Set up handler to flush event queue on page unload
175
- * Uses both beforeunload and pagehide for maximum compatibility
176
- */
177
- _setup_unload_handler() {
178
- if (!globals_1.window) {
179
- return;
180
- }
181
- const unloadHandler = () => {
182
- // Flush all queued events using sendBeacon for reliable delivery
183
- this.requestQueue.unload();
184
- // Also flush any pending retries
185
- this.retryQueue.unload();
186
- };
187
- // beforeunload is more reliable but pagehide works better on mobile
188
- (0, utils_1.addEventListener)(globals_1.window, "beforeunload", unloadHandler);
189
- (0, utils_1.addEventListener)(globals_1.window, "pagehide", unloadHandler);
190
- }
191
- /**
192
- * Load remote config from cache and fetch fresh in background.
193
- * Uses sessionStorage for per-session caching.
194
- * Local config always overrides remote config.
195
- */
196
- _loadRemoteConfig() {
197
- const config = this.configManager.getConfig();
198
- if (!config.projectId)
199
- return;
200
- // Try to load cached config (sync)
201
- try {
202
- const cached = sessionStorage.getItem(`${constants_1.REMOTE_CONFIG_KEY}_${config.projectId}`);
203
- if (cached) {
204
- this._remoteConfig = JSON.parse(cached);
205
- this._applyRemoteConfig(this._remoteConfig);
206
- }
207
- }
208
- catch (_a) {
209
- // sessionStorage not available or parse error
210
- }
211
- // Fetch fresh config async (for next page load / feature updates)
212
- this._fetchRemoteConfig();
213
- }
214
- /**
215
- * Fetch remote config from /decide endpoint.
216
- * Caches response in sessionStorage.
217
- */
218
- _fetchRemoteConfig() {
219
- const config = this.configManager.getConfig();
220
- if (!config.projectId || !config.api_host)
221
- return;
222
- const url = `${config.api_host}/api/projects/${config.projectId}/decide`;
223
- // Use fetch with timeout for non-blocking request
224
- const controller = new AbortController();
225
- const timeout = setTimeout(() => controller.abort(), 3000);
226
- (globals_1.fetch || fetch)(url, {
227
- method: "GET",
228
- signal: controller.signal,
229
- headers: { Accept: "application/json" },
230
- })
231
- .then((res) => res.json())
232
- .then((data) => {
233
- clearTimeout(timeout);
234
- this._remoteConfig = data;
235
- // Cache for next page load
236
- try {
237
- sessionStorage.setItem(`${constants_1.REMOTE_CONFIG_KEY}_${config.projectId}`, JSON.stringify(data));
238
- }
239
- catch (_a) {
240
- // sessionStorage quota exceeded or not available
241
- }
242
- // Apply to config (for features not yet initialized)
243
- this._applyRemoteConfig(data);
244
- })
245
- .catch(() => {
246
- clearTimeout(timeout);
247
- // Silently fail - use cached or default config
248
- });
249
- }
250
- /**
251
- * Apply remote config to current config.
252
- * Local config takes precedence over remote.
253
- */
254
- _applyRemoteConfig(remote) {
255
- var _a;
256
- const config = this.configManager.getConfig();
257
- const updates = {};
258
- // Session Recording (only if not explicitly set locally)
259
- if (remote.sessionRecording && config.session_recording === undefined) {
260
- updates.session_recording = {
261
- enabled: remote.sessionRecording.enabled,
262
- sampleRate: remote.sessionRecording.sampleRate,
263
- minimumDurationMs: remote.sessionRecording.minimumDurationMs,
264
- maskAllInputs: remote.sessionRecording.maskAllInputs,
265
- captureConsole: remote.sessionRecording.captureConsole,
266
- captureCanvas: remote.sessionRecording.captureCanvas,
267
- };
268
- }
269
- // Analytics
270
- if (remote.analytics) {
271
- if (remote.analytics.capturePageview !== undefined &&
272
- config.capture_pageview === undefined) {
273
- updates.capture_pageview = remote.analytics.capturePageview;
274
- }
275
- if (remote.analytics.capturePageleave !== undefined &&
276
- config.capture_pageleave === undefined) {
277
- updates.capture_pageleave = remote.analytics.capturePageleave
278
- ? true
279
- : false;
280
- }
281
- if (remote.analytics.capturePerformance !== undefined &&
282
- config.capture_performance === undefined) {
283
- updates.capture_performance = remote.analytics.capturePerformance;
284
- }
285
- if (remote.analytics.autocapture !== undefined &&
286
- config.autocapture === undefined) {
287
- updates.autocapture = remote.analytics.autocapture;
288
- }
289
- }
290
- // Privacy
291
- if (((_a = remote.privacy) === null || _a === void 0 ? void 0 : _a.respectDnt) !== undefined &&
292
- config.respect_dnt === undefined) {
293
- updates.respect_dnt = remote.privacy.respectDnt;
294
- }
295
- // Chat widget position/color (handled by ChatWrapper separately)
296
- // Apply updates
297
- if (Object.keys(updates).length > 0) {
298
- this.updateConfig(updates);
299
- }
300
- }
301
- /**
302
- * Returns a string representation of the instance name
303
- * Used for debugging and logging
304
- *
305
- * @internal
306
- */
307
- toString() {
308
- var _a;
309
- const config = this.configManager.getConfig();
310
- let name = (_a = config.name) !== null && _a !== void 0 ? _a : PRIMARY_INSTANCE_NAME;
311
- if (name !== PRIMARY_INSTANCE_NAME) {
312
- name = PRIMARY_INSTANCE_NAME + "." + name;
313
- }
314
- return name;
315
- }
316
- /**
317
- * Get current domain from location
318
- * Returns full URL format for consistency with dashboard
319
- */
320
- getCurrentDomain() {
321
- if (!globals_1.location) {
322
- return "";
323
- }
324
- const protocol = globals_1.location.protocol;
325
- const hostname = globals_1.location.hostname;
326
- const port = globals_1.location.port;
327
- const portSuffix = port ? `:${port}` : "";
328
- return `${protocol}//${hostname}${portSuffix}`;
329
- }
330
- /**
331
- * Check if tracking is properly configured
332
- * Returns true if projectId and token are present, false otherwise
333
- * Logs a warning only once per instance if not configured
334
- */
335
- _is_configured() {
336
- const config = this.configManager.getConfig();
337
- if (config.projectId && config.token) {
338
- return true;
339
- }
340
- // Only warn once to avoid console spam
341
- if (!this._has_warned_about_config) {
342
- console.warn("VTilt: projectId and token are required for tracking. " +
343
- "Events will be skipped until init() or updateConfig() is called with these fields.");
344
- this._has_warned_about_config = true;
345
- }
346
- return false;
347
- }
348
- /**
349
- * Build the tracking URL with token in query parameters
350
- */
351
- buildUrl() {
352
- const config = this.configManager.getConfig();
353
- const { proxyUrl, proxy, api_host, token } = config;
354
- // Use proxy endpoint to handle backend authentication
355
- if (proxyUrl) {
356
- // Use the full proxy URL as provided
357
- return proxyUrl;
358
- }
359
- else if (proxy) {
360
- // Construct the proxy URL from the proxy domain with token
361
- return `${proxy}/api/tracking?token=${token}`;
362
- }
363
- else if (api_host) {
364
- const cleanHost = api_host.replace(/\/+$/gm, "");
365
- return `${cleanHost}/api/tracking?token=${token}`;
366
- }
367
- else {
368
- // Use relative path to our tracking proxy endpoint
369
- return `/api/tracking?token=${token}`;
370
- }
371
- }
372
- /**
373
- * Send HTTP request
374
- * This is the central entry point for all tracking requests
375
- * Events are batched and sent every 3 seconds for better performance
376
- */
377
- sendRequest(url, event, shouldEnqueue) {
378
- // Validate configuration (only warns once per instance)
379
- // This is the single place where validation happens for all sending methods
380
- if (!this._is_configured()) {
381
- return;
382
- }
383
- // Check if we should queue requests (for older browsers before DOM is loaded)
384
- if (shouldEnqueue && typeof globals_1.window !== "undefined") {
385
- // ENQUEUE_REQUESTS is defined in vtilt.ts as a module-level variable
386
- const ENQUEUE_REQUESTS = globals_1.window.__VTILT_ENQUEUE_REQUESTS;
387
- if (ENQUEUE_REQUESTS) {
388
- this.__request_queue.push({ url, event });
389
- return;
390
- }
391
- }
392
- // Enqueue event for batched sending
393
- this.requestQueue.enqueue({ url, event });
394
- }
395
- /**
396
- * Send a batched request with multiple events
397
- * Called by RequestQueue when flushing
398
- * Uses RetryQueue for automatic retry on failure
399
- */
400
- _send_batched_request(req) {
401
- const { transport } = req;
402
- // Use sendBeacon for reliable delivery on page unload
403
- if (transport === "sendBeacon") {
404
- this._send_beacon_request(req);
405
- return;
406
- }
407
- // Use retry queue for normal requests (handles failures automatically)
408
- this.retryQueue.retriableRequest(req);
409
- }
410
- /**
411
- * Send HTTP request and return status code
412
- * Uses GZip compression for payloads > 1KB
413
- * Used by RetryQueue for retryable requests
414
- */
415
- _send_http_request(req) {
416
- return new Promise((resolve) => {
417
- const { url, events } = req;
418
- // Prepare payload (single event or batch)
419
- const payload = events.length === 1 ? events[0] : { events };
420
- // Determine compression
421
- const compression = this.configManager.getConfig().disable_compression
422
- ? request_1.Compression.None
423
- : request_1.Compression.GZipJS;
424
- (0, request_1.request)({
425
- url,
426
- data: payload,
427
- method: "POST",
428
- transport: "XHR",
429
- compression,
430
- callback: (response) => {
431
- resolve({ statusCode: response.statusCode });
432
- },
433
- });
434
- });
435
- }
436
- /**
437
- * Send request using sendBeacon for reliable delivery on page unload
438
- * Uses GZip compression for payloads > 1KB
439
- */
440
- _send_beacon_request(req) {
441
- const { url, events } = req;
442
- // Prepare payload (single event or batch)
443
- const payload = events.length === 1 ? events[0] : { events };
444
- // Determine compression
445
- const compression = this.configManager.getConfig().disable_compression
446
- ? request_1.Compression.None
447
- : request_1.Compression.GZipJS;
448
- (0, request_1.request)({
449
- url,
450
- data: payload,
451
- method: "POST",
452
- transport: "sendBeacon",
453
- compression,
454
- });
455
- }
456
- /**
457
- * Send a queued request (called after DOM is loaded)
458
- */
459
- _send_retriable_request(item) {
460
- this.sendRequest(item.url, item.event, false);
461
- }
462
- /**
463
- * Capture an event
464
- * Automatically adds common properties to all events
465
- * ($current_url, $host, $pathname, $referrer, $referring_domain, $browser, $os, $device, $timezone, etc.)
466
- * Only properties in EVENT_TO_PERSON_PROPERTIES are copied to person properties
467
- * Also adds title property for $pageview events only
468
- *
469
- * @param name - Event name
470
- * @param payload - Event payload
471
- * @param options - Optional capture options
472
- */
473
- capture(name, payload, options) {
474
- this.sessionManager.setSessionId();
475
- // Only send events in browser environment (not SSR)
476
- if (!globals_1.navigator || !globals_1.navigator.userAgent) {
477
- return;
478
- }
479
- if (!(0, utils_1.isValidUserAgent)(globals_1.navigator.userAgent)) {
480
- return;
481
- }
482
- // Check rate limiting (unless explicitly skipped)
483
- if (!(options === null || options === void 0 ? void 0 : options.skip_client_rate_limiting) &&
484
- !this.rateLimiter.shouldAllowEvent()) {
485
- // Event is rate limited - drop it silently
486
- return;
487
- }
488
- const url = this.buildUrl();
489
- // Add properties to all events
490
- // This includes: $current_url, $host, $pathname, $referrer, $referring_domain, $browser, $os, $device, $timezone, etc.
491
- // (Only properties in EVENT_TO_PERSON_PROPERTIES are copied to person properties)
492
- const eventProperties = (0, event_utils_1.getEventProperties)();
493
- // Get person properties (includes $device_id and other user properties)
494
- // These are automatically included in all events
495
- const personProperties = this.userManager.getUserProperties();
496
- // Get initial props: $initial_* properties from stored initial person info
497
- const initialProps = this.userManager.get_initial_props();
498
- // Get session and window IDs
499
- // Both methods ensure IDs always exist (generate if needed)
500
- const session_id = this.sessionManager.getSessionId();
501
- const window_id = this.sessionManager.getWindowId();
502
- // Always include anonymous_id in properties for identity linking
503
- // This allows linking events with different distinct_ids that share the same anonymous_id
504
- // This is especially important for handling race conditions when $identify events arrive
505
- const anonymousId = this.userManager.getAnonymousId();
506
- // Build $set_once from initial props (only on first event per page load)
507
- // This optimization follows PostHog's pattern: initial props only need to be sent once
508
- // since $set_once preserves first values. Sending them on every event would be redundant
509
- // and cause unnecessary server processing for identity creation/updates.
510
- const setOnce = {};
511
- // Only include initial props if we haven't sent them yet this page load
512
- if (!this._set_once_properties_sent &&
513
- Object.keys(initialProps).length > 0) {
514
- Object.assign(setOnce, initialProps);
515
- }
516
- // Always merge with user-provided $set_once if present
517
- if (payload.$set_once) {
518
- Object.assign(setOnce, payload.$set_once);
519
- }
520
- const enrichedPayload = {
521
- ...eventProperties, // Base properties for all events
522
- ...personProperties, // Person properties (includes $device_id)
523
- $session_id: session_id, // Session ID in properties
524
- $window_id: window_id, // Window ID in properties
525
- // Always include $anon_distinct_id for identity linking (even for identified users)
526
- // This allows the server to merge identities proactively when events arrive out of order
527
- ...(anonymousId ? { $anon_distinct_id: anonymousId } : {}),
528
- // Add $set_once with initial props (only if there are properties to set)
529
- ...(Object.keys(setOnce).length > 0 ? { $set_once: setOnce } : {}),
530
- // Merge user-provided $set if present
531
- ...(payload.$set ? { $set: payload.$set } : {}),
532
- ...payload, // User-provided payload (can override base and person properties)
533
- };
534
- // Mark that $set_once with initial props has been sent (only do this once per page load)
535
- if (!this._set_once_properties_sent &&
536
- Object.keys(initialProps).length > 0) {
537
- this._set_once_properties_sent = true;
538
- }
539
- // Add title only to $pageview events
540
- if (name === "$pageview" && globals_1.document) {
541
- enrichedPayload.title = globals_1.document.title;
542
- }
543
- const config = this.configManager.getConfig();
544
- let processedPayload;
545
- if (config.stringifyPayload !== false) {
546
- processedPayload = Object.assign({}, enrichedPayload, config.globalAttributes);
547
- processedPayload = JSON.stringify(processedPayload);
548
- if (!(0, utils_1.isValidPayload)(processedPayload)) {
549
- return;
550
- }
551
- }
552
- else {
553
- processedPayload = Object.assign({}, enrichedPayload, config.globalAttributes);
554
- const payloadStr = JSON.stringify(processedPayload);
555
- if (!(0, utils_1.isValidPayload)(payloadStr)) {
556
- return;
557
- }
558
- }
559
- // Use current distinct_id (or anonymous_id) for event's distinct_id
560
- // For $identify events, use anonymous_id as the event's distinct_id (identifying FROM anonymous TO new ID)
561
- // New distinct_id should be in payload properties (e.g., for $identify events)
562
- let distinct_id;
563
- if (name === "$identify") {
564
- // For $identify events, always use anonymous_id as the event's distinct_id
565
- // The new distinct_id is in the payload
566
- distinct_id = this.userManager.getAnonymousId();
567
- }
568
- else {
569
- // For other events, use current distinct_id or fall back to anonymous_id
570
- distinct_id =
571
- this.userManager.getDistinctId() || this.userManager.getAnonymousId();
572
- }
573
- const trackingEvent = {
574
- timestamp: new Date().toISOString(),
575
- event: name,
576
- project_id: config.projectId || "",
577
- payload: processedPayload,
578
- distinct_id: distinct_id,
579
- // Always include anonymous_id in the event for identity linking
580
- // This allows the server to merge identities proactively
581
- anonymous_id: anonymousId,
582
- };
583
- this.sendRequest(url, trackingEvent, true);
584
- }
585
- /**
586
- * Internal capture method that bypasses rate limiting
587
- * Used for system events like rate limit warnings
588
- */
589
- _capture_internal(name, payload) {
590
- this.capture(name, payload, { skip_client_rate_limiting: true });
591
- }
592
- /**
593
- * Track a custom event (alias for capture)
594
- */
595
- trackEvent(name, payload = {}) {
596
- this.capture(name, payload);
597
- }
598
- /**
599
- * Identify a user with property operations
600
- * Sends $identify event when transitioning from anonymous to identified
601
- * Event's distinct_id is the previous/anonymous ID, new distinct_id is in payload
602
- *
603
- * @example
604
- * ```js
605
- * // Basic identify with properties
606
- * vTilt.identify('user_123',
607
- * { name: 'John Doe', email: 'john@example.com' }, // $set properties
608
- * { first_login: new Date().toISOString() } // $set_once properties
609
- * )
610
- * ```
611
- *
612
- * @example
613
- * ```js
614
- * // If no $set is provided, all properties are treated as $set
615
- * vTilt.identify('user_123', { name: 'John Doe', email: 'john@example.com' })
616
- * ```
617
- */
618
- identify(newDistinctId, userPropertiesToSet, userPropertiesToSetOnce) {
619
- // Validation: Convert number to string
620
- if (typeof newDistinctId === "number") {
621
- newDistinctId = String(newDistinctId);
622
- console.warn("The first argument to vTilt.identify was a number, but it should be a string. It has been converted to a string.");
623
- }
624
- // Validation: Check if distinct_id is provided
625
- if (!newDistinctId) {
626
- console.error("Unique user id has not been set in vTilt.identify");
627
- return;
628
- }
629
- // Validation: Check for hardcoded strings
630
- if (this.userManager.isDistinctIdStringLikePublic(newDistinctId)) {
631
- console.error(`The string "${newDistinctId}" was set in vTilt.identify which indicates an error. This ID should be unique to the user and not a hardcoded string.`);
632
- return;
633
- }
634
- // Validation: Check for cookieless sentinel value
635
- if (newDistinctId === "COOKIELESS_SENTINEL_VALUE") {
636
- console.error(`The string "${newDistinctId}" was set in vTilt.identify which indicates an error. This ID is only used as a sentinel value.`);
637
- return;
638
- }
639
- const previousDistinctId = this.userManager.getDistinctId();
640
- const anonymousId = this.userManager.getAnonymousId();
641
- const deviceId = this.userManager.getDeviceId();
642
- const isKnownAnonymous = this.userManager.getUserState() === "anonymous";
643
- // Handle device ID if not already set
644
- if (!deviceId) {
645
- const device_id = previousDistinctId || anonymousId;
646
- this.userManager.ensureDeviceId(device_id);
647
- }
648
- // Send $identify event when distinct_id is changing AND user was anonymous
649
- // Event's distinct_id is the previous/anonymous ID, new distinct_id goes in payload
650
- if (newDistinctId !== previousDistinctId && isKnownAnonymous) {
651
- // Update user state and properties BEFORE sending event
652
- // (But don't update distinct_id yet - sendEvent needs to use previous ID)
653
- this.userManager.setUserState("identified");
654
- this.userManager.updateUserProperties(userPropertiesToSet, userPropertiesToSetOnce);
655
- // Send $identify event with previous/anonymous ID as event's distinct_id
656
- // New distinct_id and properties go in payload
657
- this.capture("$identify", {
658
- distinct_id: newDistinctId, // New distinct_id in payload
659
- $anon_distinct_id: anonymousId, // Previous ID in payload
660
- $set: userPropertiesToSet || {},
661
- $set_once: userPropertiesToSetOnce || {},
662
- });
663
- // Now update distinct_id after sending the event
664
- this.userManager.setDistinctId(newDistinctId);
665
- }
666
- else if (userPropertiesToSet || userPropertiesToSetOnce) {
667
- // If distinct_id is not changing but we have properties to set
668
- // Update user state if not already identified
669
- if (isKnownAnonymous) {
670
- this.userManager.setUserState("identified");
671
- }
672
- // Update properties
673
- this.userManager.updateUserProperties(userPropertiesToSet, userPropertiesToSetOnce);
674
- // Send $set event to notify server of property updates
675
- this.capture("$set", {
676
- $set: userPropertiesToSet || {},
677
- $set_once: userPropertiesToSetOnce || {},
678
- });
679
- }
680
- else if (newDistinctId !== previousDistinctId) {
681
- // If distinct_id is changing but user was already identified, just update the distinct_id
682
- this.userManager.setUserState("identified");
683
- this.userManager.setDistinctId(newDistinctId);
684
- }
685
- }
686
- /**
687
- * Set user properties
688
- * Sets properties on the person profile associated with the current distinct_id
689
- *
690
- * @example
691
- * ```js
692
- * // Set properties that can be updated
693
- * vTilt.setUserProperties({ name: 'John Doe', email: 'john@example.com' })
694
- * ```
695
- *
696
- * @example
697
- * ```js
698
- * // Set properties with $set and $set_once operations
699
- * vTilt.setUserProperties(
700
- * { name: 'John Doe', last_login: new Date().toISOString() }, // $set properties
701
- * { first_login: new Date().toISOString() } // $set_once properties
702
- * )
703
- * ```
704
- *
705
- * @param userPropertiesToSet Optional: Properties to set (can be updated)
706
- * @param userPropertiesToSetOnce Optional: Properties to set once (preserves first value)
707
- */
708
- setUserProperties(userPropertiesToSet, userPropertiesToSetOnce) {
709
- // Update user properties in UserManager
710
- // Returns true if properties were updated (not a duplicate), false otherwise
711
- const shouldSendEvent = this.userManager.setUserProperties(userPropertiesToSet, userPropertiesToSetOnce);
712
- // Only send $set event if properties were actually updated (not a duplicate)
713
- if (shouldSendEvent) {
714
- this.capture("$set", {
715
- $set: userPropertiesToSet || {},
716
- $set_once: userPropertiesToSetOnce || {},
717
- });
718
- }
719
- }
720
- /**
721
- * Reset user identity (logout)
722
- * Clears all user data, generates new anonymous ID, resets session
723
- *
724
- * @param reset_device_id - If true, also resets device_id. Default: false
725
- *
726
- * @example
727
- * // Reset on user logout
728
- * vtilt.resetUser()
729
- *
730
- * @example
731
- * // Reset and generate new device ID
732
- * vtilt.resetUser(true)
733
- */
734
- resetUser(reset_device_id) {
735
- // Reset session ID
736
- this.sessionManager.resetSessionId();
737
- this.userManager.reset(reset_device_id);
738
- }
739
- /**
740
- * Get current user identity
741
- */
742
- getUserIdentity() {
743
- return this.userManager.getUserIdentity();
744
- }
745
- /**
746
- * Get current device ID
747
- */
748
- getDeviceId() {
749
- return this.userManager.getDeviceId();
750
- }
751
- /**
752
- * Get current user state
753
- */
754
- getUserState() {
755
- return this.userManager.getUserState();
756
- }
757
- /**
758
- * Create an alias to link two distinct IDs
759
- * Links anonymous session to account on signup
760
- *
761
- * @param alias - A unique identifier that you want to use for this user in the future
762
- * @param original - The current identifier being used for this user (optional, defaults to current distinct_id)
763
- *
764
- * @example
765
- * // Link anonymous user to account on signup
766
- * vtilt.createAlias('user_12345')
767
- *
768
- * @example
769
- * // Explicit alias with original ID
770
- * vtilt.createAlias('user_12345', 'anonymous_abc123')
771
- */
772
- createAlias(alias, original) {
773
- // Get alias information from UserManager
774
- const aliasInfo = this.userManager.createAlias(alias, original);
775
- // If alias was created, send $alias event
776
- if (aliasInfo) {
777
- this.capture("$alias", {
778
- $original_id: aliasInfo.original,
779
- $alias_id: aliasInfo.distinct_id,
780
- });
781
- }
782
- else {
783
- // If alias matches original, use identify instead
784
- const distinctId = original ||
785
- this.userManager.getDistinctId() ||
786
- this.userManager.getAnonymousId();
787
- if (alias === distinctId) {
788
- this.identify(alias);
789
- }
790
- }
791
- }
792
- /**
793
- * Capture initial pageview with visibility check
794
- * Note: The capture_pageview config check happens at the call site (in _init)
795
- */
796
- _capture_initial_pageview() {
797
- if (!globals_1.document) {
798
- return;
799
- }
800
- // If page is not visible, add a listener to detect when the page becomes visible
801
- // and trigger the pageview only then
802
- // This is useful to avoid `prerender` calls from Chrome/Wordpress/SPAs
803
- // that are not visible to the user
804
- if (globals_1.document.visibilityState !== "visible") {
805
- if (!this._visibility_state_listener) {
806
- this._visibility_state_listener = () => {
807
- this._capture_initial_pageview();
808
- };
809
- (0, utils_1.addEventListener)(globals_1.document, "visibilitychange", this._visibility_state_listener);
810
- }
811
- return;
812
- }
813
- // Extra check here to guarantee we only ever trigger a single initial pageview event
814
- if (!this._initial_pageview_captured) {
815
- this._initial_pageview_captured = true;
816
- // Wait a bit for SPA routers
817
- setTimeout(() => {
818
- // Double-check we're still in browser environment (defensive check)
819
- if (!globals_1.document || !globals_1.location) {
820
- return;
821
- }
822
- // Note: $current_url, $host, $pathname, $referrer, $referring_domain, title
823
- // are automatically added by capture() for all events (title only for $pageview)
824
- const payload = {
825
- navigation_type: "initial_load",
826
- };
827
- this.capture("$pageview", payload);
828
- }, 300);
829
- // After we've captured the initial pageview, we can remove the listener
830
- if (this._visibility_state_listener) {
831
- globals_1.document.removeEventListener("visibilitychange", this._visibility_state_listener);
832
- this._visibility_state_listener = null;
833
- }
834
- }
835
- }
836
- /**
837
- * Get current configuration
838
- */
839
- getConfig() {
840
- return this.configManager.getConfig();
841
- }
842
- /**
843
- * Get current session ID
844
- */
845
- getSessionId() {
846
- return this.sessionManager.getSessionId();
847
- }
848
- /**
849
- * Get current distinct ID
850
- */
851
- getDistinctId() {
852
- return (this.userManager.getDistinctId() || this.userManager.getAnonymousId());
853
- }
854
- /**
855
- * Get anonymous ID
856
- */
857
- getAnonymousId() {
858
- return this.userManager.getAnonymousId();
859
- }
860
- /**
861
- * Update configuration
862
- */
863
- updateConfig(config) {
864
- this.configManager.updateConfig(config);
865
- const fullConfig = this.configManager.getConfig();
866
- // Recreate managers with new config
867
- this.sessionManager = new session_1.SessionManager(fullConfig.storage || "cookie", fullConfig.cross_subdomain_cookie);
868
- // Default to localStorage+cookie for better SSR support (following PostHog)
869
- this.userManager = new user_manager_1.UserManager(fullConfig.persistence || "localStorage+cookie", fullConfig.cross_subdomain_cookie);
870
- this.webVitalsManager = new web_vitals_1.WebVitalsManager(fullConfig, this);
871
- // Update session recording config if it exists
872
- if (this.sessionRecording && fullConfig.session_recording) {
873
- this.sessionRecording.updateConfig(this._buildSessionRecordingConfig(fullConfig));
874
- }
875
- }
876
- // ============================================================================
877
- // Session Recording
878
- // ============================================================================
879
- /**
880
- * Initialize session recording
881
- */
882
- _initSessionRecording() {
883
- const config = this.configManager.getConfig();
884
- // Don't initialize if explicitly disabled
885
- if (config.disable_session_recording) {
886
- return;
887
- }
888
- // Build session recording config
889
- const sessionRecordingConfig = this._buildSessionRecordingConfig(config);
890
- // Create session recording wrapper (lightweight - actual recorder is lazy-loaded)
891
- this.sessionRecording = new replay_1.SessionRecordingWrapper(this, sessionRecordingConfig);
892
- // Start recording if enabled (this will lazy-load the recorder script)
893
- if (sessionRecordingConfig.enabled) {
894
- this.sessionRecording.startIfEnabledOrStop("recording_initialized");
895
- }
896
- }
897
- /**
898
- * Build session recording config from VTiltConfig
899
- */
900
- _buildSessionRecordingConfig(config) {
901
- var _a, _b, _c;
902
- const sessionRecording = config.session_recording || {};
903
- return {
904
- enabled: (_a = sessionRecording.enabled) !== null && _a !== void 0 ? _a : false,
905
- sampleRate: sessionRecording.sampleRate,
906
- minimumDurationMs: sessionRecording.minimumDurationMs,
907
- sessionIdleThresholdMs: sessionRecording.sessionIdleThresholdMs,
908
- fullSnapshotIntervalMs: sessionRecording.fullSnapshotIntervalMs,
909
- captureConsole: (_b = sessionRecording.captureConsole) !== null && _b !== void 0 ? _b : false,
910
- captureNetwork: (_c = sessionRecording.captureNetwork) !== null && _c !== void 0 ? _c : false,
911
- captureCanvas: sessionRecording.captureCanvas,
912
- blockClass: sessionRecording.blockClass,
913
- blockSelector: sessionRecording.blockSelector,
914
- ignoreClass: sessionRecording.ignoreClass,
915
- maskTextClass: sessionRecording.maskTextClass,
916
- maskTextSelector: sessionRecording.maskTextSelector,
917
- maskAllInputs: sessionRecording.maskAllInputs,
918
- maskInputOptions: sessionRecording.maskInputOptions,
919
- masking: sessionRecording.masking,
920
- recordHeaders: sessionRecording.recordHeaders,
921
- recordBody: sessionRecording.recordBody,
922
- compressEvents: sessionRecording.compressEvents,
923
- __mutationThrottlerRefillRate: sessionRecording.__mutationThrottlerRefillRate,
924
- __mutationThrottlerBucketSize: sessionRecording.__mutationThrottlerBucketSize,
925
- };
926
- }
927
- /**
928
- * Start session recording
929
- * Call this to manually start recording if it wasn't enabled initially
930
- */
931
- startSessionRecording() {
932
- if (!this.sessionRecording) {
933
- const config = this.configManager.getConfig();
934
- const sessionRecordingConfig = this._buildSessionRecordingConfig(config);
935
- sessionRecordingConfig.enabled = true;
936
- this.sessionRecording = new replay_1.SessionRecordingWrapper(this, sessionRecordingConfig);
937
- }
938
- this.sessionRecording.startIfEnabledOrStop("recording_initialized");
939
- }
940
- /**
941
- * Stop session recording
942
- */
943
- stopSessionRecording() {
944
- var _a;
945
- (_a = this.sessionRecording) === null || _a === void 0 ? void 0 : _a.stopRecording();
946
- }
947
- /**
948
- * Check if session recording is active
949
- */
950
- isRecordingActive() {
951
- var _a;
952
- return ((_a = this.sessionRecording) === null || _a === void 0 ? void 0 : _a.status) === "active";
953
- }
954
- /**
955
- * Get session recording ID
956
- */
957
- getSessionRecordingId() {
958
- var _a;
959
- return ((_a = this.sessionRecording) === null || _a === void 0 ? void 0 : _a.sessionId) || null;
960
- }
961
- // ============================================================================
962
- // Chat
963
- // ============================================================================
964
- /**
965
- * Initialize chat widget
966
- */
967
- _initChat() {
968
- const config = this.configManager.getConfig();
969
- // Don't initialize if explicitly disabled
970
- if (config.disable_chat) {
971
- return;
972
- }
973
- // Build chat config from VTiltConfig
974
- const chatConfig = this._buildChatConfig(config);
975
- // Create chat wrapper (lightweight - actual chat is lazy-loaded)
976
- this.chat = new chat_wrapper_1.ChatWrapper(this, chatConfig);
977
- // Start chat if enabled (this will fetch server settings and show bubble)
978
- this.chat.startIfEnabled();
979
- }
980
- /**
981
- * Build chat config from VTiltConfig
982
- */
983
- _buildChatConfig(config) {
984
- var _a;
985
- const chat = config.chat || {};
986
- return {
987
- enabled: chat.enabled,
988
- autoConfig: (_a = chat.autoConfig) !== null && _a !== void 0 ? _a : true, // Default to auto-config from dashboard
989
- position: chat.position,
990
- greeting: chat.greeting,
991
- color: chat.color,
992
- aiMode: chat.aiMode,
993
- aiGreeting: chat.aiGreeting,
994
- preload: chat.preload,
995
- offlineMessage: chat.offlineMessage,
996
- collectEmailOffline: chat.collectEmailOffline,
997
- };
998
- }
999
- /**
1000
- * _execute_array() deals with processing any vTilt function
1001
- * calls that were called before the vTilt library was loaded
1002
- * (and are thus stored in an array so they can be called later)
1003
- *
1004
- * Note: we fire off all the vTilt function calls BEFORE we fire off
1005
- * tracking calls. This is so identify/setUserProperties/updateConfig calls
1006
- * can properly modify early tracking calls.
1007
- *
1008
- * @param {Array} array Array of queued calls in format [methodName, ...args]
1009
- */
1010
- _execute_array(array) {
1011
- if (!Array.isArray(array)) {
1012
- return;
1013
- }
1014
- array.forEach((item) => {
1015
- if (item && Array.isArray(item) && item.length > 0) {
1016
- const methodName = item[0];
1017
- const args = item.slice(1);
1018
- if (typeof this[methodName] === "function") {
1019
- try {
1020
- this[methodName](...args);
1021
- }
1022
- catch (error) {
1023
- console.error(`vTilt: Error executing queued call ${methodName}:`, error);
1024
- }
1025
- }
1026
- }
1027
- });
1028
- }
1029
- /**
1030
- * Called when DOM is loaded - processes queued requests and enables batching
1031
- * Following PostHog's pattern in _dom_loaded()
1032
- */
1033
- _dom_loaded() {
1034
- // Process all pre-init queued requests
1035
- this.__request_queue.forEach((item) => {
1036
- this._send_retriable_request(item);
1037
- });
1038
- // Clear the legacy queue
1039
- this.__request_queue = [];
1040
- // Enable the request queue for batched sending (PostHog pattern)
1041
- this._start_queue_if_opted_in();
1042
- }
1043
- }
1044
- exports.VTilt = VTilt;
1045
- const instances = {};
1046
- const PRIMARY_INSTANCE_NAME = "vt";
1047
- // Browser support detection for request queuing
1048
- // IE<10 does not support cross-origin XHR's but script tags
1049
- // with defer won't block window.onload; ENQUEUE_REQUESTS
1050
- // should only be true for Opera<12
1051
- const SUPPORTS_REQUEST = typeof globals_1.XMLHttpRequest !== "undefined" || typeof globals_1.fetch !== "undefined";
1052
- let ENQUEUE_REQUESTS = !SUPPORTS_REQUEST &&
1053
- (globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf("MSIE")) === -1 &&
1054
- (globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf("Mozilla")) === -1;
1055
- // Expose ENQUEUE_REQUESTS to window for request queuing
1056
- // Use typeof check to safely access global window object
1057
- if (typeof globals_1.window !== "undefined" && globals_1.window !== null) {
1058
- globals_1.window.__VTILT_ENQUEUE_REQUESTS = ENQUEUE_REQUESTS;
1059
- }
1060
- /**
1061
- * Add DOM loaded handler to process queued requests
1062
- */
1063
- const add_dom_loaded_handler = function () {
1064
- // Cross browser DOM Loaded support
1065
- function dom_loaded_handler() {
1066
- // function flag since we only want to execute this once
1067
- if (dom_loaded_handler.done) {
1068
- return;
1069
- }
1070
- dom_loaded_handler.done = true;
1071
- // Disable request queuing now that DOM is loaded
1072
- ENQUEUE_REQUESTS = false;
1073
- if (typeof globals_1.window !== "undefined" && globals_1.window !== null) {
1074
- globals_1.window.__VTILT_ENQUEUE_REQUESTS = false;
1075
- }
1076
- // Process queued requests for all instances
1077
- (0, utils_1.each)(instances, function (inst) {
1078
- inst._dom_loaded();
1079
- });
1080
- }
1081
- if (globals_1.document && typeof globals_1.document.addEventListener === "function") {
1082
- if (globals_1.document.readyState === "complete") {
1083
- // Safari 4 can fire the DOMContentLoaded event before loading all
1084
- // external JS (including this file). you will see some copypasta
1085
- // on the internet that checks for 'complete' and 'loaded', but
1086
- // 'loaded' is an IE thing
1087
- dom_loaded_handler();
1088
- }
1089
- else {
1090
- (0, utils_1.addEventListener)(globals_1.document, "DOMContentLoaded", dom_loaded_handler, {
1091
- capture: false,
1092
- });
1093
- }
1094
- return;
1095
- }
1096
- // Only IE6-8 don't support `document.addEventListener` and we don't support them
1097
- // so let's simply log an error stating vTilt couldn't be initialized
1098
- // We're checking for `window` to avoid erroring out on a SSR context
1099
- if (globals_1.window) {
1100
- console.error("Browser doesn't support `document.addEventListener` so vTilt couldn't be initialized");
1101
- }
1102
- };
1103
- /**
1104
- * Initialize vTilt as a module
1105
- * Returns an uninitialized vTilt instance that the user must call init() on
1106
- */
1107
- function init_as_module() {
1108
- const vTiltMain = (instances[PRIMARY_INSTANCE_NAME] = new VTilt());
1109
- add_dom_loaded_handler();
1110
- return vTiltMain;
1111
- }
1112
- /**
1113
- * Initialize vTilt from snippet
1114
- * Processes queued calls from the snippet stub and replaces it with real instance
1115
- *
1116
- * The snippet uses some clever tricks to allow deferred loading of array.js (this code)
1117
- *
1118
- * window.vt is an array which the queue of calls made before the lib is loaded
1119
- * It has methods attached to it to simulate the vt object so for instance
1120
- *
1121
- * window.vt.init("projectId", {token: "token", host: "host"})
1122
- * window.vt.trackEvent("my-event", {foo: "bar"})
1123
- *
1124
- * ... will mean that window.vt will look like this:
1125
- * window.vt == [
1126
- * ["trackEvent", "my-event", {foo: "bar"}]
1127
- * ]
1128
- *
1129
- * window.vt._i == [
1130
- * ["projectId", {token: "token", host: "host"}, "vt"]
1131
- * ]
1132
- *
1133
- * If a name is given to the init function then the same as above is true but as a sub-property on the object:
1134
- *
1135
- * window.vt.init("projectId", {token: "token"}, "vt2")
1136
- * window.vt.vt2.trackEvent("my-event", {foo: "bar"})
1137
- *
1138
- * window.vt.vt2 == [
1139
- * ["trackEvent", "my-event", {foo: "bar"}]
1140
- * ]
1141
- */
1142
- function init_from_snippet() {
1143
- const vTiltMain = (instances[PRIMARY_INSTANCE_NAME] = new VTilt());
1144
- const snippetVT = globals_1.assignableWindow["vt"];
1145
- if (snippetVT) {
1146
- /**
1147
- * The snippet uses some clever tricks to allow deferred loading of array.js (this code)
1148
- *
1149
- * window.vt is an array which the queue of calls made before the lib is loaded
1150
- * It has methods attached to it to simulate the vt object so for instance
1151
- *
1152
- * window.vt.init("projectId", {token: "token", host: "host"})
1153
- * window.vt.trackEvent("my-event", {foo: "bar"})
1154
- *
1155
- * ... will mean that window.vt will look like this:
1156
- * window.vt == [
1157
- * ["trackEvent", "my-event", {foo: "bar"}]
1158
- * ]
1159
- *
1160
- * window.vt._i == [
1161
- * ["projectId", {token: "token", host: "host"}, "vt"]
1162
- * ]
1163
- *
1164
- * If a name is given to the init function then the same as above is true but as a sub-property on the object:
1165
- *
1166
- * window.vt.init("projectId", {token: "token"}, "vt2")
1167
- * window.vt.vt2.trackEvent("my-event", {foo: "bar"})
1168
- *
1169
- * window.vt.vt2 == [
1170
- * ["trackEvent", "my-event", {foo: "bar"}]
1171
- * ]
1172
- *
1173
- */
1174
- // Call all pre-loaded init calls properly
1175
- (0, utils_1.each)(snippetVT["_i"], function (item) {
1176
- if (item && isArray(item)) {
1177
- const instance = vTiltMain.init(item[0], item[1], item[2]);
1178
- const instanceSnippet = snippetVT[item[2] || "vt"] || snippetVT;
1179
- if (instance) {
1180
- // Execute all queued calls for this instance
1181
- instance._execute_array(instanceSnippet);
1182
- }
1183
- }
1184
- });
1185
- }
1186
- globals_1.assignableWindow["vt"] = vTiltMain;
1187
- add_dom_loaded_handler();
1188
- }