@v-tilt/browser 1.0.7 → 1.0.9
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.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/constants.d.ts +0 -1
- package/dist/extensions/history-autocapture.d.ts +0 -2
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +52 -132
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +52 -132
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/session.d.ts +5 -5
- package/dist/types.d.ts +1 -0
- package/dist/user-manager.d.ts +30 -20
- package/dist/utils/event-utils.d.ts +7 -8
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/patch.d.ts +0 -2
- package/dist/utils/type-utils.d.ts +4 -0
- package/dist/vtilt.d.ts +54 -14
- package/dist/web-vitals.d.ts +3 -3
- package/lib/constants.d.ts +0 -1
- package/lib/constants.js +1 -23
- package/lib/extensions/history-autocapture.d.ts +0 -2
- package/lib/extensions/history-autocapture.js +3 -5
- package/lib/session.d.ts +5 -5
- package/lib/session.js +8 -8
- package/lib/types.d.ts +1 -0
- package/lib/user-manager.d.ts +30 -20
- package/lib/user-manager.js +103 -92
- package/lib/utils/event-utils.d.ts +7 -8
- package/lib/utils/event-utils.js +8 -9
- package/lib/utils/index.d.ts +0 -5
- package/lib/utils/index.js +0 -13
- package/lib/utils/patch.d.ts +0 -2
- package/lib/utils/patch.js +2 -7
- package/lib/utils/type-utils.d.ts +4 -0
- package/lib/utils/type-utils.js +9 -0
- package/lib/utils/user-agent-utils.js +5 -17
- package/lib/vtilt.d.ts +54 -14
- package/lib/vtilt.js +328 -45
- package/lib/web-vitals.d.ts +3 -3
- package/lib/web-vitals.js +3 -3
- package/package.json +13 -12
- package/LICENSE +0 -21
- package/dist/tracking.d.ts +0 -120
- package/lib/tracking.d.ts +0 -120
- package/lib/tracking.js +0 -338
package/lib/vtilt.js
CHANGED
|
@@ -4,10 +4,13 @@ exports.VTilt = void 0;
|
|
|
4
4
|
exports.init_as_module = init_as_module;
|
|
5
5
|
exports.init_from_snippet = init_from_snippet;
|
|
6
6
|
const config_1 = require("./config");
|
|
7
|
-
const
|
|
7
|
+
const session_1 = require("./session");
|
|
8
|
+
const user_manager_1 = require("./user-manager");
|
|
8
9
|
const web_vitals_1 = require("./web-vitals");
|
|
9
10
|
const history_autocapture_1 = require("./extensions/history-autocapture");
|
|
10
11
|
const utils_1 = require("./utils");
|
|
12
|
+
const event_utils_1 = require("./utils/event-utils");
|
|
13
|
+
const utils_2 = require("./utils");
|
|
11
14
|
const globals_1 = require("./utils/globals");
|
|
12
15
|
// Helper to check if value is an array
|
|
13
16
|
const isArray = Array.isArray;
|
|
@@ -16,9 +19,18 @@ class VTilt {
|
|
|
16
19
|
this.__loaded = false; // Matches snippet's window.vt.__loaded check
|
|
17
20
|
this._initialPageviewCaptured = false;
|
|
18
21
|
this._visibilityStateListener = null;
|
|
22
|
+
this.__request_queue = []; // Public for DOM loaded handler
|
|
23
|
+
this._hasWarnedAboutConfig = false; // Track if we've already warned about missing config
|
|
19
24
|
this.configManager = new config_1.ConfigManager(config);
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
const fullConfig = this.configManager.getConfig();
|
|
26
|
+
// Auto-detect domain from location if not provided
|
|
27
|
+
let domain = fullConfig.domain;
|
|
28
|
+
if (!domain && globals_1.location) {
|
|
29
|
+
domain = this.getCurrentDomain();
|
|
30
|
+
}
|
|
31
|
+
this.sessionManager = new session_1.SessionManager(fullConfig.storage || "cookie", domain);
|
|
32
|
+
this.userManager = new user_manager_1.UserManager(fullConfig.persistence || "localStorage", domain);
|
|
33
|
+
this.webVitalsManager = new web_vitals_1.WebVitalsManager(fullConfig, this);
|
|
22
34
|
}
|
|
23
35
|
/**
|
|
24
36
|
* Initializes a new instance of the VTilt tracking object.
|
|
@@ -68,7 +80,6 @@ class VTilt {
|
|
|
68
80
|
/**
|
|
69
81
|
* Handles the actual initialization logic for a VTilt instance.
|
|
70
82
|
* This internal method should only be called by `init()`.
|
|
71
|
-
* Follows the PostHog convention of using a private `_init()` method for instance setup.
|
|
72
83
|
*/
|
|
73
84
|
_init(projectId, config = {}, name) {
|
|
74
85
|
// Guard: prevent re-initialization (matches snippet's __loaded check)
|
|
@@ -76,14 +87,14 @@ class VTilt {
|
|
|
76
87
|
console.warn("vTilt: You have already initialized vTilt! Re-initializing is a no-op");
|
|
77
88
|
return this;
|
|
78
89
|
}
|
|
79
|
-
// Update config with projectId, token, and name
|
|
90
|
+
// Update config with projectId, token, and name
|
|
80
91
|
this.updateConfig({
|
|
81
92
|
...config,
|
|
82
93
|
projectId: projectId || config.projectId,
|
|
83
94
|
name: name,
|
|
84
95
|
});
|
|
85
96
|
this.__loaded = true;
|
|
86
|
-
// Initialize history autocapture
|
|
97
|
+
// Initialize history autocapture
|
|
87
98
|
this.historyAutocapture = new history_autocapture_1.HistoryAutocapture(this);
|
|
88
99
|
this.historyAutocapture.startIfEnabled();
|
|
89
100
|
// Capture initial pageview (with visibility check)
|
|
@@ -91,7 +102,7 @@ class VTilt {
|
|
|
91
102
|
return this;
|
|
92
103
|
}
|
|
93
104
|
/**
|
|
94
|
-
* Returns a string representation of the instance name
|
|
105
|
+
* Returns a string representation of the instance name
|
|
95
106
|
* Used for debugging and logging
|
|
96
107
|
*
|
|
97
108
|
* @internal
|
|
@@ -106,14 +117,191 @@ class VTilt {
|
|
|
106
117
|
return name;
|
|
107
118
|
}
|
|
108
119
|
/**
|
|
109
|
-
*
|
|
120
|
+
* Get current domain from location
|
|
121
|
+
* Returns full URL format for consistency with dashboard
|
|
122
|
+
*/
|
|
123
|
+
getCurrentDomain() {
|
|
124
|
+
if (!globals_1.location) {
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
const protocol = globals_1.location.protocol;
|
|
128
|
+
const hostname = globals_1.location.hostname;
|
|
129
|
+
const port = globals_1.location.port;
|
|
130
|
+
const portSuffix = port ? `:${port}` : "";
|
|
131
|
+
return `${protocol}//${hostname}${portSuffix}`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if tracking is properly configured
|
|
135
|
+
* Returns true if projectId and token are present, false otherwise
|
|
136
|
+
* Logs a warning only once per instance if not configured
|
|
137
|
+
*/
|
|
138
|
+
_isConfigured() {
|
|
139
|
+
const config = this.configManager.getConfig();
|
|
140
|
+
if (config.projectId && config.token) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
// Only warn once to avoid console spam
|
|
144
|
+
if (!this._hasWarnedAboutConfig) {
|
|
145
|
+
console.warn("VTilt: projectId and token are required for tracking. " +
|
|
146
|
+
"Events will be skipped until init() or updateConfig() is called with these fields.");
|
|
147
|
+
this._hasWarnedAboutConfig = true;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Build the tracking URL with token in query parameters
|
|
153
|
+
*/
|
|
154
|
+
buildUrl() {
|
|
155
|
+
const config = this.configManager.getConfig();
|
|
156
|
+
const { proxyUrl, proxy, host, token } = config;
|
|
157
|
+
// Use proxy endpoint to handle Tinybird authentication
|
|
158
|
+
if (proxyUrl) {
|
|
159
|
+
// Use the full proxy URL as provided
|
|
160
|
+
return proxyUrl;
|
|
161
|
+
}
|
|
162
|
+
else if (proxy) {
|
|
163
|
+
// Construct the proxy URL from the proxy domain with token
|
|
164
|
+
return `${proxy}/api/tracking?token=${token}`;
|
|
165
|
+
}
|
|
166
|
+
else if (host) {
|
|
167
|
+
const cleanHost = host.replace(/\/+$/gm, "");
|
|
168
|
+
return `${cleanHost}/api/tracking?token=${token}`;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Use relative path to our tracking proxy endpoint
|
|
172
|
+
return `/api/tracking?token=${token}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Send HTTP request
|
|
177
|
+
* This is the central entry point for all tracking requests
|
|
178
|
+
*/
|
|
179
|
+
sendRequest(url, event, shouldEnqueue) {
|
|
180
|
+
// Validate configuration (only warns once per instance)
|
|
181
|
+
// This is the single place where validation happens for all sending methods
|
|
182
|
+
if (!this._isConfigured()) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Check if we should queue requests (for older browsers before DOM is loaded)
|
|
186
|
+
if (shouldEnqueue && typeof globals_1.window !== "undefined") {
|
|
187
|
+
// ENQUEUE_REQUESTS is defined in vtilt.ts as a module-level variable
|
|
188
|
+
const ENQUEUE_REQUESTS = globals_1.window.__VTILT_ENQUEUE_REQUESTS;
|
|
189
|
+
if (ENQUEUE_REQUESTS) {
|
|
190
|
+
this.__request_queue.push({ url, event });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const request = new XMLHttpRequest();
|
|
195
|
+
request.open("POST", url, true);
|
|
196
|
+
request.setRequestHeader("Content-Type", "application/json");
|
|
197
|
+
request.send(JSON.stringify(event));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Send a queued request (called after DOM is loaded)
|
|
201
|
+
*/
|
|
202
|
+
_send_retriable_request(item) {
|
|
203
|
+
this.sendRequest(item.url, item.event, false);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Capture an event
|
|
207
|
+
* Automatically adds common properties to all events
|
|
208
|
+
* ($current_url, $host, $pathname, $referrer, $referring_domain, $browser_language, etc.)
|
|
209
|
+
* Also adds title property for $pageview events only
|
|
210
|
+
*
|
|
211
|
+
* @param name - Event name
|
|
212
|
+
* @param payload - Event payload
|
|
213
|
+
*/
|
|
214
|
+
capture(name, payload) {
|
|
215
|
+
this.sessionManager.setSessionId();
|
|
216
|
+
// Only send events in browser environment (not SSR)
|
|
217
|
+
if (!globals_1.navigator || !globals_1.navigator.userAgent) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!(0, utils_1.isValidUserAgent)(globals_1.navigator.userAgent)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const url = this.buildUrl();
|
|
224
|
+
// Add properties to all events
|
|
225
|
+
// This includes: $current_url, $host, $pathname, $referrer, $referring_domain, $browser_language, etc.
|
|
226
|
+
const eventProperties = (0, event_utils_1.getEventProperties)();
|
|
227
|
+
// Get person properties (includes $device_id and other user properties)
|
|
228
|
+
// These are automatically included in all events
|
|
229
|
+
const personProperties = this.userManager.getUserProperties();
|
|
230
|
+
// Get session and window IDs
|
|
231
|
+
// Both methods ensure IDs always exist (generate if needed)
|
|
232
|
+
const session_id = this.sessionManager.getSessionId();
|
|
233
|
+
const window_id = this.sessionManager.getWindowId();
|
|
234
|
+
// Always include anonymous_id in properties for identity linking
|
|
235
|
+
// This allows linking events with different distinct_ids that share the same anonymous_id
|
|
236
|
+
// This is especially important for handling race conditions when $identify events arrive
|
|
237
|
+
const anonymousId = this.userManager.getAnonymousId();
|
|
238
|
+
const enrichedPayload = {
|
|
239
|
+
...eventProperties, // Base properties for all events
|
|
240
|
+
...personProperties, // Person properties (includes $device_id)
|
|
241
|
+
$session_id: session_id, // Session ID in properties
|
|
242
|
+
$window_id: window_id, // Window ID in properties
|
|
243
|
+
// Always include $anon_distinct_id for identity linking (even for identified users)
|
|
244
|
+
// This allows the server to merge identities proactively when events arrive out of order
|
|
245
|
+
...(anonymousId ? { $anon_distinct_id: anonymousId } : {}),
|
|
246
|
+
...payload, // User-provided payload (can override base and person properties)
|
|
247
|
+
};
|
|
248
|
+
// Add title only to $pageview events
|
|
249
|
+
if (name === "$pageview" && globals_1.document) {
|
|
250
|
+
enrichedPayload.title = globals_1.document.title;
|
|
251
|
+
}
|
|
252
|
+
const config = this.configManager.getConfig();
|
|
253
|
+
let processedPayload;
|
|
254
|
+
if (config.stringifyPayload !== false) {
|
|
255
|
+
processedPayload = Object.assign({}, enrichedPayload, config.globalAttributes);
|
|
256
|
+
processedPayload = JSON.stringify(processedPayload);
|
|
257
|
+
if (!(0, utils_1.isValidPayload)(processedPayload)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
processedPayload = Object.assign({}, enrichedPayload, config.globalAttributes);
|
|
263
|
+
const payloadStr = JSON.stringify(processedPayload);
|
|
264
|
+
if (!(0, utils_1.isValidPayload)(payloadStr)) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Use current distinct_id (or anonymous_id) for event's distinct_id
|
|
269
|
+
// For $identify events, use anonymous_id as the event's distinct_id (identifying FROM anonymous TO new ID)
|
|
270
|
+
// New distinct_id should be in payload properties (e.g., for $identify events)
|
|
271
|
+
let distinct_id;
|
|
272
|
+
if (name === "$identify") {
|
|
273
|
+
// For $identify events, always use anonymous_id as the event's distinct_id
|
|
274
|
+
// The new distinct_id is in the payload
|
|
275
|
+
distinct_id = this.userManager.getAnonymousId();
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
// For other events, use current distinct_id or fall back to anonymous_id
|
|
279
|
+
distinct_id =
|
|
280
|
+
this.userManager.getDistinctId() || this.userManager.getAnonymousId();
|
|
281
|
+
}
|
|
282
|
+
const trackingEvent = {
|
|
283
|
+
timestamp: new Date().toISOString(),
|
|
284
|
+
event: name,
|
|
285
|
+
tenant_id: config.projectId || "",
|
|
286
|
+
domain: config.domain || this.getCurrentDomain(), // Use config domain or current domain
|
|
287
|
+
payload: processedPayload,
|
|
288
|
+
distinct_id: distinct_id,
|
|
289
|
+
// Always include anonymous_id in the event for identity linking
|
|
290
|
+
// This allows the server to merge identities proactively
|
|
291
|
+
anonymous_id: anonymousId,
|
|
292
|
+
};
|
|
293
|
+
this.sendRequest(url, trackingEvent, true);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Track a custom event (alias for capture)
|
|
110
297
|
*/
|
|
111
298
|
trackEvent(name, payload = {}) {
|
|
112
|
-
this.
|
|
299
|
+
this.capture(name, payload);
|
|
113
300
|
}
|
|
114
301
|
/**
|
|
115
|
-
* Identify a user with
|
|
116
|
-
*
|
|
302
|
+
* Identify a user with property operations
|
|
303
|
+
* Sends $identify event when transitioning from anonymous to identified
|
|
304
|
+
* Event's distinct_id is the previous/anonymous ID, new distinct_id is in payload
|
|
117
305
|
*
|
|
118
306
|
* @example
|
|
119
307
|
* ```js
|
|
@@ -130,11 +318,76 @@ class VTilt {
|
|
|
130
318
|
* vTilt.identify('user_123', { name: 'John Doe', email: 'john@example.com' })
|
|
131
319
|
* ```
|
|
132
320
|
*/
|
|
133
|
-
identify(
|
|
134
|
-
|
|
321
|
+
identify(newDistinctId, userPropertiesToSet, userPropertiesToSetOnce) {
|
|
322
|
+
// Validation: Convert number to string
|
|
323
|
+
if (typeof newDistinctId === "number") {
|
|
324
|
+
newDistinctId = String(newDistinctId);
|
|
325
|
+
console.warn("The first argument to vTilt.identify was a number, but it should be a string. It has been converted to a string.");
|
|
326
|
+
}
|
|
327
|
+
// Validation: Check if distinct_id is provided
|
|
328
|
+
if (!newDistinctId) {
|
|
329
|
+
console.error("Unique user id has not been set in vTilt.identify");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
// Validation: Check for hardcoded strings
|
|
333
|
+
if (this.userManager.isDistinctIdStringLikePublic(newDistinctId)) {
|
|
334
|
+
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.`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// Validation: Check for cookieless sentinel value
|
|
338
|
+
if (newDistinctId === "COOKIELESS_SENTINEL_VALUE") {
|
|
339
|
+
console.error(`The string "${newDistinctId}" was set in vTilt.identify which indicates an error. This ID is only used as a sentinel value.`);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const previousDistinctId = this.userManager.getDistinctId();
|
|
343
|
+
const anonymousId = this.userManager.getAnonymousId();
|
|
344
|
+
const deviceId = this.userManager.getDeviceId();
|
|
345
|
+
const isKnownAnonymous = this.userManager.getUserState() === "anonymous";
|
|
346
|
+
// Handle device ID if not already set
|
|
347
|
+
if (!deviceId) {
|
|
348
|
+
const device_id = previousDistinctId || anonymousId;
|
|
349
|
+
this.userManager.ensureDeviceId(device_id);
|
|
350
|
+
}
|
|
351
|
+
// Send $identify event when distinct_id is changing AND user was anonymous
|
|
352
|
+
// Event's distinct_id is the previous/anonymous ID, new distinct_id goes in payload
|
|
353
|
+
if (newDistinctId !== previousDistinctId && isKnownAnonymous) {
|
|
354
|
+
// Update user state and properties BEFORE sending event
|
|
355
|
+
// (But don't update distinct_id yet - sendEvent needs to use previous ID)
|
|
356
|
+
this.userManager.setUserState("identified");
|
|
357
|
+
this.userManager.updateUserProperties(userPropertiesToSet, userPropertiesToSetOnce);
|
|
358
|
+
// Send $identify event with previous/anonymous ID as event's distinct_id
|
|
359
|
+
// New distinct_id and properties go in payload
|
|
360
|
+
this.capture("$identify", {
|
|
361
|
+
distinct_id: newDistinctId, // New distinct_id in payload
|
|
362
|
+
$anon_distinct_id: previousDistinctId || anonymousId, // Previous ID in payload
|
|
363
|
+
$set: userPropertiesToSet || {},
|
|
364
|
+
$set_once: userPropertiesToSetOnce || {},
|
|
365
|
+
});
|
|
366
|
+
// Now update distinct_id after sending the event
|
|
367
|
+
this.userManager.setDistinctId(newDistinctId);
|
|
368
|
+
}
|
|
369
|
+
else if (userPropertiesToSet || userPropertiesToSetOnce) {
|
|
370
|
+
// If distinct_id is not changing but we have properties to set
|
|
371
|
+
// Update user state if not already identified
|
|
372
|
+
if (isKnownAnonymous) {
|
|
373
|
+
this.userManager.setUserState("identified");
|
|
374
|
+
}
|
|
375
|
+
// Update properties
|
|
376
|
+
this.userManager.updateUserProperties(userPropertiesToSet, userPropertiesToSetOnce);
|
|
377
|
+
// Send $set event to notify server of property updates
|
|
378
|
+
this.capture("$set", {
|
|
379
|
+
$set: userPropertiesToSet || {},
|
|
380
|
+
$set_once: userPropertiesToSetOnce || {},
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
else if (newDistinctId !== previousDistinctId) {
|
|
384
|
+
// If distinct_id is changing but user was already identified, just update the distinct_id
|
|
385
|
+
this.userManager.setUserState("identified");
|
|
386
|
+
this.userManager.setDistinctId(newDistinctId);
|
|
387
|
+
}
|
|
135
388
|
}
|
|
136
389
|
/**
|
|
137
|
-
* Set user properties
|
|
390
|
+
* Set user properties
|
|
138
391
|
* Sets properties on the person profile associated with the current distinct_id
|
|
139
392
|
*
|
|
140
393
|
* @example
|
|
@@ -156,11 +409,20 @@ class VTilt {
|
|
|
156
409
|
* @param userPropertiesToSetOnce Optional: Properties to set once (preserves first value)
|
|
157
410
|
*/
|
|
158
411
|
setUserProperties(userPropertiesToSet, userPropertiesToSetOnce) {
|
|
159
|
-
|
|
412
|
+
// Update user properties in UserManager
|
|
413
|
+
// Returns true if properties were updated (not a duplicate), false otherwise
|
|
414
|
+
const shouldSendEvent = this.userManager.setUserProperties(userPropertiesToSet, userPropertiesToSetOnce);
|
|
415
|
+
// Only send $set event if properties were actually updated (not a duplicate)
|
|
416
|
+
if (shouldSendEvent) {
|
|
417
|
+
this.capture("$set", {
|
|
418
|
+
$set: userPropertiesToSet || {},
|
|
419
|
+
$set_once: userPropertiesToSetOnce || {},
|
|
420
|
+
});
|
|
421
|
+
}
|
|
160
422
|
}
|
|
161
423
|
/**
|
|
162
424
|
* Reset user identity (logout)
|
|
163
|
-
*
|
|
425
|
+
* Clears all user data, generates new anonymous ID, resets session
|
|
164
426
|
*
|
|
165
427
|
* @param reset_device_id - If true, also resets device_id. Default: false
|
|
166
428
|
*
|
|
@@ -173,29 +435,31 @@ class VTilt {
|
|
|
173
435
|
* vtilt.resetUser(true)
|
|
174
436
|
*/
|
|
175
437
|
resetUser(reset_device_id) {
|
|
176
|
-
|
|
438
|
+
// Reset session ID
|
|
439
|
+
this.sessionManager.resetSessionId();
|
|
440
|
+
this.userManager.reset(reset_device_id);
|
|
177
441
|
}
|
|
178
442
|
/**
|
|
179
443
|
* Get current user identity
|
|
180
444
|
*/
|
|
181
445
|
getUserIdentity() {
|
|
182
|
-
return this.
|
|
446
|
+
return this.userManager.getUserIdentity();
|
|
183
447
|
}
|
|
184
448
|
/**
|
|
185
449
|
* Get current device ID
|
|
186
450
|
*/
|
|
187
451
|
getDeviceId() {
|
|
188
|
-
return this.
|
|
452
|
+
return this.userManager.getDeviceId();
|
|
189
453
|
}
|
|
190
454
|
/**
|
|
191
455
|
* Get current user state
|
|
192
456
|
*/
|
|
193
457
|
getUserState() {
|
|
194
|
-
return this.
|
|
458
|
+
return this.userManager.getUserState();
|
|
195
459
|
}
|
|
196
460
|
/**
|
|
197
461
|
* Create an alias to link two distinct IDs
|
|
198
|
-
*
|
|
462
|
+
* Links anonymous session to account on signup
|
|
199
463
|
*
|
|
200
464
|
* @param alias - A unique identifier that you want to use for this user in the future
|
|
201
465
|
* @param original - The current identifier being used for this user (optional, defaults to current distinct_id)
|
|
@@ -209,11 +473,27 @@ class VTilt {
|
|
|
209
473
|
* vtilt.createAlias('user_12345', 'anonymous_abc123')
|
|
210
474
|
*/
|
|
211
475
|
createAlias(alias, original) {
|
|
212
|
-
|
|
476
|
+
// Get alias information from UserManager
|
|
477
|
+
const aliasInfo = this.userManager.createAlias(alias, original);
|
|
478
|
+
// If alias was created, send $alias event
|
|
479
|
+
if (aliasInfo) {
|
|
480
|
+
this.capture("$alias", {
|
|
481
|
+
$original_id: aliasInfo.original,
|
|
482
|
+
$alias_id: aliasInfo.distinct_id,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
// If alias matches original, use identify instead
|
|
487
|
+
const distinctId = original ||
|
|
488
|
+
this.userManager.getDistinctId() ||
|
|
489
|
+
this.userManager.getAnonymousId();
|
|
490
|
+
if (alias === distinctId) {
|
|
491
|
+
this.identify(alias);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
213
494
|
}
|
|
214
495
|
/**
|
|
215
496
|
* Capture initial pageview with visibility check
|
|
216
|
-
* Based on PostHog's _captureInitialPageview implementation
|
|
217
497
|
*/
|
|
218
498
|
_captureInitialPageview() {
|
|
219
499
|
if (!globals_1.document) {
|
|
@@ -228,25 +508,25 @@ class VTilt {
|
|
|
228
508
|
this._visibilityStateListener = () => {
|
|
229
509
|
this._captureInitialPageview();
|
|
230
510
|
};
|
|
231
|
-
(0,
|
|
511
|
+
(0, utils_2.addEventListener)(globals_1.document, "visibilitychange", this._visibilityStateListener);
|
|
232
512
|
}
|
|
233
513
|
return;
|
|
234
514
|
}
|
|
235
515
|
// Extra check here to guarantee we only ever trigger a single initial pageview event
|
|
236
516
|
if (!this._initialPageviewCaptured) {
|
|
237
517
|
this._initialPageviewCaptured = true;
|
|
238
|
-
// Wait a bit for SPA routers
|
|
518
|
+
// Wait a bit for SPA routers
|
|
239
519
|
setTimeout(() => {
|
|
240
520
|
// Double-check we're still in browser environment (defensive check)
|
|
241
521
|
if (!globals_1.document || !globals_1.location) {
|
|
242
522
|
return;
|
|
243
523
|
}
|
|
244
524
|
// Note: $current_url, $host, $pathname, $referrer, $referring_domain, title
|
|
245
|
-
// are automatically added by
|
|
525
|
+
// are automatically added by capture() for all events (title only for $pageview)
|
|
246
526
|
const payload = {
|
|
247
527
|
navigation_type: "initial_load",
|
|
248
528
|
};
|
|
249
|
-
this.
|
|
529
|
+
this.capture("$pageview", payload);
|
|
250
530
|
}, 300);
|
|
251
531
|
// After we've captured the initial pageview, we can remove the listener
|
|
252
532
|
if (this._visibilityStateListener) {
|
|
@@ -265,16 +545,22 @@ class VTilt {
|
|
|
265
545
|
* Get current session ID
|
|
266
546
|
*/
|
|
267
547
|
getSessionId() {
|
|
268
|
-
return this.
|
|
548
|
+
return this.sessionManager.getSessionId();
|
|
269
549
|
}
|
|
270
550
|
/**
|
|
271
551
|
* Update configuration
|
|
272
552
|
*/
|
|
273
553
|
updateConfig(config) {
|
|
274
554
|
this.configManager.updateConfig(config);
|
|
555
|
+
const fullConfig = this.configManager.getConfig();
|
|
275
556
|
// Recreate managers with new config
|
|
276
|
-
|
|
277
|
-
|
|
557
|
+
let domain = fullConfig.domain;
|
|
558
|
+
if (!domain && globals_1.location) {
|
|
559
|
+
domain = this.getCurrentDomain();
|
|
560
|
+
}
|
|
561
|
+
this.sessionManager = new session_1.SessionManager(fullConfig.storage || "cookie", domain);
|
|
562
|
+
this.userManager = new user_manager_1.UserManager(fullConfig.persistence || "localStorage", domain);
|
|
563
|
+
this.webVitalsManager = new web_vitals_1.WebVitalsManager(fullConfig, this);
|
|
278
564
|
}
|
|
279
565
|
/**
|
|
280
566
|
* _execute_array() deals with processing any vTilt function
|
|
@@ -310,14 +596,12 @@ class VTilt {
|
|
|
310
596
|
* Called when DOM is loaded - processes queued requests
|
|
311
597
|
*/
|
|
312
598
|
_dom_loaded() {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
this.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
this.trackingManager.__request_queue = [];
|
|
320
|
-
}
|
|
599
|
+
// Process all queued requests
|
|
600
|
+
this.__request_queue.forEach((item) => {
|
|
601
|
+
this._send_retriable_request(item);
|
|
602
|
+
});
|
|
603
|
+
// Clear the queue
|
|
604
|
+
this.__request_queue = [];
|
|
321
605
|
}
|
|
322
606
|
}
|
|
323
607
|
exports.VTilt = VTilt;
|
|
@@ -331,13 +615,12 @@ const SUPPORTS_REQUEST = typeof globals_1.XMLHttpRequest !== "undefined" || type
|
|
|
331
615
|
let ENQUEUE_REQUESTS = !SUPPORTS_REQUEST &&
|
|
332
616
|
(globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf("MSIE")) === -1 &&
|
|
333
617
|
(globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf("Mozilla")) === -1;
|
|
334
|
-
// Expose ENQUEUE_REQUESTS to window for
|
|
618
|
+
// Expose ENQUEUE_REQUESTS to window for request queuing
|
|
335
619
|
if (globals_1.window) {
|
|
336
620
|
globals_1.window.__VTILT_ENQUEUE_REQUESTS = ENQUEUE_REQUESTS;
|
|
337
621
|
}
|
|
338
622
|
/**
|
|
339
623
|
* Add DOM loaded handler to process queued requests
|
|
340
|
-
* Similar to PostHog's add_dom_loaded_handler
|
|
341
624
|
*/
|
|
342
625
|
const add_dom_loaded_handler = function () {
|
|
343
626
|
// Cross browser DOM Loaded support
|
|
@@ -353,7 +636,7 @@ const add_dom_loaded_handler = function () {
|
|
|
353
636
|
globals_1.window.__VTILT_ENQUEUE_REQUESTS = false;
|
|
354
637
|
}
|
|
355
638
|
// Process queued requests for all instances
|
|
356
|
-
(0,
|
|
639
|
+
(0, utils_2.each)(instances, function (inst) {
|
|
357
640
|
inst._dom_loaded();
|
|
358
641
|
});
|
|
359
642
|
}
|
|
@@ -366,7 +649,7 @@ const add_dom_loaded_handler = function () {
|
|
|
366
649
|
dom_loaded_handler();
|
|
367
650
|
}
|
|
368
651
|
else {
|
|
369
|
-
(0,
|
|
652
|
+
(0, utils_2.addEventListener)(globals_1.document, "DOMContentLoaded", dom_loaded_handler, {
|
|
370
653
|
capture: false,
|
|
371
654
|
});
|
|
372
655
|
}
|
|
@@ -380,7 +663,7 @@ const add_dom_loaded_handler = function () {
|
|
|
380
663
|
}
|
|
381
664
|
};
|
|
382
665
|
/**
|
|
383
|
-
* Initialize vTilt as a module
|
|
666
|
+
* Initialize vTilt as a module
|
|
384
667
|
* Returns an uninitialized vTilt instance that the user must call init() on
|
|
385
668
|
*/
|
|
386
669
|
function init_as_module() {
|
|
@@ -389,7 +672,7 @@ function init_as_module() {
|
|
|
389
672
|
return vTiltMain;
|
|
390
673
|
}
|
|
391
674
|
/**
|
|
392
|
-
* Initialize vTilt from snippet
|
|
675
|
+
* Initialize vTilt from snippet
|
|
393
676
|
* Processes queued calls from the snippet stub and replaces it with real instance
|
|
394
677
|
*
|
|
395
678
|
* The snippet uses some clever tricks to allow deferred loading of array.js (this code)
|
|
@@ -451,7 +734,7 @@ function init_from_snippet() {
|
|
|
451
734
|
*
|
|
452
735
|
*/
|
|
453
736
|
// Call all pre-loaded init calls properly
|
|
454
|
-
(0,
|
|
737
|
+
(0, utils_2.each)(snippetVT["_i"], function (item) {
|
|
455
738
|
if (item && isArray(item)) {
|
|
456
739
|
const instance = vTiltMain.init(item[0], item[1], item[2]);
|
|
457
740
|
const instanceSnippet = snippetVT[item[2] || "vt"] || snippetVT;
|
package/lib/web-vitals.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { VTiltConfig } from "./types";
|
|
2
|
-
import {
|
|
2
|
+
import { VTilt } from "./vtilt";
|
|
3
3
|
export declare class WebVitalsManager {
|
|
4
|
-
private
|
|
4
|
+
private instance;
|
|
5
5
|
private webVitals;
|
|
6
|
-
constructor(config: VTiltConfig,
|
|
6
|
+
constructor(config: VTiltConfig, instance: VTilt);
|
|
7
7
|
/**
|
|
8
8
|
* Initialize web vitals tracking
|
|
9
9
|
*/
|
package/lib/web-vitals.js
CHANGED
|
@@ -4,8 +4,8 @@ exports.WebVitalsManager = void 0;
|
|
|
4
4
|
const geolocation_1 = require("./geolocation");
|
|
5
5
|
const globals_1 = require("./utils/globals");
|
|
6
6
|
class WebVitalsManager {
|
|
7
|
-
constructor(config,
|
|
8
|
-
this.
|
|
7
|
+
constructor(config, instance) {
|
|
8
|
+
this.instance = instance;
|
|
9
9
|
// Load web-vitals if enabled and in browser environment (not SSR)
|
|
10
10
|
if (config.webVitals && globals_1.window) {
|
|
11
11
|
try {
|
|
@@ -33,7 +33,7 @@ class WebVitalsManager {
|
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
const { country, locale } = (0, geolocation_1.getCountryAndLocale)();
|
|
36
|
-
this.
|
|
36
|
+
this.instance.capture("web_vital", {
|
|
37
37
|
name: metric.name,
|
|
38
38
|
value: metric.value,
|
|
39
39
|
delta: metric.delta,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v-tilt/browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "vTilt browser tracking library",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"module": "dist/module.js",
|
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -b && rollup -c",
|
|
17
|
+
"dev": "rollup -c -w",
|
|
18
|
+
"type-check": "tsc --noEmit",
|
|
19
|
+
"lint": "eslint src --ext .ts",
|
|
20
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
21
|
+
"clean": "rimraf lib dist",
|
|
22
|
+
"prepublishOnly": "pnpm run build"
|
|
23
|
+
},
|
|
15
24
|
"keywords": [
|
|
16
25
|
"analytics",
|
|
17
26
|
"tracking",
|
|
@@ -29,14 +38,14 @@
|
|
|
29
38
|
"@rollup/plugin-terser": "^0.4.4",
|
|
30
39
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
31
40
|
"@types/node": "^20.10.5",
|
|
41
|
+
"@v-tilt/eslint-config": "workspace:*",
|
|
32
42
|
"eslint": "^9.0.0",
|
|
33
43
|
"rimraf": "^5.0.5",
|
|
34
44
|
"rollup": "^4.9.1",
|
|
35
45
|
"rollup-plugin-dts": "^6.2.3",
|
|
36
46
|
"rollup-plugin-terser": "^7.0.2",
|
|
37
47
|
"rollup-plugin-visualizer": "^6.0.3",
|
|
38
|
-
"typescript": "^5.3.3"
|
|
39
|
-
"@v-tilt/eslint-config": "1.0.0"
|
|
48
|
+
"typescript": "^5.3.3"
|
|
40
49
|
},
|
|
41
50
|
"dependencies": {
|
|
42
51
|
"web-vitals": "^3.5.0"
|
|
@@ -48,13 +57,5 @@
|
|
|
48
57
|
"web-vitals": {
|
|
49
58
|
"optional": true
|
|
50
59
|
}
|
|
51
|
-
},
|
|
52
|
-
"scripts": {
|
|
53
|
-
"build": "tsc -b && rollup -c",
|
|
54
|
-
"dev": "rollup -c -w",
|
|
55
|
-
"type-check": "tsc --noEmit",
|
|
56
|
-
"lint": "eslint src --ext .ts",
|
|
57
|
-
"lint:fix": "eslint src --ext .ts --fix",
|
|
58
|
-
"clean": "rimraf lib dist"
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Tinybird.co
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|