@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.
- package/dist/array.full.js +1 -1
- package/dist/array.full.js.map +1 -1
- package/dist/array.js +1 -1
- package/dist/array.js.map +1 -1
- package/dist/array.no-external.js +1 -1
- package/dist/array.no-external.js.map +1 -1
- package/dist/chat.js +1 -1
- package/dist/chat.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/entrypoints/server.es.d.ts +12 -0
- package/dist/external-scripts-loader.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +6 -3
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +6 -3
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/server.d.ts +105 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +3 -1
- package/dist/vtilt.d.ts +3 -2
- package/package.json +1 -2
- package/lib/config.d.ts +0 -17
- package/lib/config.js +0 -76
- package/lib/constants.d.ts +0 -178
- package/lib/constants.js +0 -656
- package/lib/entrypoints/all-external-dependencies.d.ts +0 -8
- package/lib/entrypoints/all-external-dependencies.js +0 -10
- package/lib/entrypoints/array.d.ts +0 -2
- package/lib/entrypoints/array.full.d.ts +0 -17
- package/lib/entrypoints/array.full.js +0 -19
- package/lib/entrypoints/array.js +0 -4
- package/lib/entrypoints/array.no-external.d.ts +0 -1
- package/lib/entrypoints/array.no-external.js +0 -4
- package/lib/entrypoints/chat.d.ts +0 -22
- package/lib/entrypoints/chat.js +0 -32
- package/lib/entrypoints/external-scripts-loader.d.ts +0 -24
- package/lib/entrypoints/external-scripts-loader.js +0 -104
- package/lib/entrypoints/main.cjs.d.ts +0 -4
- package/lib/entrypoints/main.cjs.js +0 -29
- package/lib/entrypoints/module.es.d.ts +0 -4
- package/lib/entrypoints/module.es.js +0 -23
- package/lib/entrypoints/module.no-external.es.d.ts +0 -4
- package/lib/entrypoints/module.no-external.es.js +0 -23
- package/lib/entrypoints/recorder.d.ts +0 -23
- package/lib/entrypoints/recorder.js +0 -42
- package/lib/entrypoints/web-vitals.d.ts +0 -14
- package/lib/entrypoints/web-vitals.js +0 -29
- package/lib/extensions/chat/chat-wrapper.d.ts +0 -196
- package/lib/extensions/chat/chat-wrapper.js +0 -545
- package/lib/extensions/chat/chat.d.ts +0 -99
- package/lib/extensions/chat/chat.js +0 -1891
- package/lib/extensions/chat/index.d.ts +0 -10
- package/lib/extensions/chat/index.js +0 -27
- package/lib/extensions/chat/types.d.ts +0 -159
- package/lib/extensions/chat/types.js +0 -22
- package/lib/extensions/history-autocapture.d.ts +0 -17
- package/lib/extensions/history-autocapture.js +0 -105
- package/lib/extensions/replay/index.d.ts +0 -13
- package/lib/extensions/replay/index.js +0 -31
- package/lib/extensions/replay/session-recording-utils.d.ts +0 -92
- package/lib/extensions/replay/session-recording-utils.js +0 -212
- package/lib/extensions/replay/session-recording-wrapper.d.ts +0 -61
- package/lib/extensions/replay/session-recording-wrapper.js +0 -149
- package/lib/extensions/replay/session-recording.d.ts +0 -95
- package/lib/extensions/replay/session-recording.js +0 -700
- package/lib/extensions/replay/types.d.ts +0 -211
- package/lib/extensions/replay/types.js +0 -8
- package/lib/geolocation.d.ts +0 -5
- package/lib/geolocation.js +0 -31
- package/lib/rate-limiter.d.ts +0 -52
- package/lib/rate-limiter.js +0 -80
- package/lib/request-queue.d.ts +0 -78
- package/lib/request-queue.js +0 -156
- package/lib/request.d.ts +0 -54
- package/lib/request.js +0 -265
- package/lib/retry-queue.d.ts +0 -64
- package/lib/retry-queue.js +0 -182
- package/lib/session.d.ts +0 -66
- package/lib/session.js +0 -191
- package/lib/storage.d.ts +0 -117
- package/lib/storage.js +0 -438
- package/lib/types.d.ts +0 -350
- package/lib/types.js +0 -24
- package/lib/user-manager.d.ts +0 -154
- package/lib/user-manager.js +0 -589
- package/lib/utils/event-utils.d.ts +0 -52
- package/lib/utils/event-utils.js +0 -305
- package/lib/utils/globals.d.ts +0 -235
- package/lib/utils/globals.js +0 -30
- package/lib/utils/index.d.ts +0 -46
- package/lib/utils/index.js +0 -134
- package/lib/utils/patch.d.ts +0 -6
- package/lib/utils/patch.js +0 -39
- package/lib/utils/request-utils.d.ts +0 -17
- package/lib/utils/request-utils.js +0 -80
- package/lib/utils/type-utils.d.ts +0 -4
- package/lib/utils/type-utils.js +0 -9
- package/lib/utils/user-agent-utils.d.ts +0 -18
- package/lib/utils/user-agent-utils.js +0 -411
- package/lib/vtilt.d.ts +0 -359
- package/lib/vtilt.js +0 -1188
- package/lib/web-vitals.d.ts +0 -95
- 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
|
-
}
|