@v-tilt/browser 1.0.8 → 1.0.10

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