@v-tilt/browser 1.0.5 → 1.0.7

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/dist/array.js +1 -1
  3. package/dist/array.js.map +1 -1
  4. package/dist/array.no-external.js +1 -1
  5. package/dist/array.no-external.js.map +1 -1
  6. package/dist/constants.d.ts +2 -0
  7. package/dist/extensions/history-autocapture.d.ts +19 -0
  8. package/dist/main.js +1 -1
  9. package/dist/main.js.map +1 -1
  10. package/dist/module.d.ts +192 -18
  11. package/dist/module.js +1 -1
  12. package/dist/module.js.map +1 -1
  13. package/dist/module.no-external.d.ts +192 -18
  14. package/dist/module.no-external.js +1 -1
  15. package/dist/module.no-external.js.map +1 -1
  16. package/dist/session.d.ts +61 -4
  17. package/dist/tracking.d.ts +12 -4
  18. package/dist/types.d.ts +1 -1
  19. package/dist/utils/event-utils.d.ts +35 -0
  20. package/dist/utils/patch.d.ts +8 -0
  21. package/dist/utils/user-agent-utils.d.ts +18 -0
  22. package/dist/vtilt.d.ts +58 -20
  23. package/lib/constants.d.ts +2 -0
  24. package/lib/constants.js +3 -1
  25. package/lib/extensions/history-autocapture.d.ts +19 -0
  26. package/lib/extensions/history-autocapture.js +107 -0
  27. package/lib/session.d.ts +61 -4
  28. package/lib/session.js +197 -41
  29. package/lib/tracking.d.ts +12 -4
  30. package/lib/tracking.js +65 -70
  31. package/lib/types.d.ts +1 -1
  32. package/lib/utils/event-utils.d.ts +35 -0
  33. package/lib/utils/event-utils.js +178 -0
  34. package/lib/utils/patch.d.ts +8 -0
  35. package/lib/utils/patch.js +44 -0
  36. package/lib/utils/user-agent-utils.d.ts +18 -0
  37. package/lib/utils/user-agent-utils.js +423 -0
  38. package/lib/vtilt.d.ts +58 -20
  39. package/lib/vtilt.js +168 -142
  40. package/package.json +12 -13
  41. package/dist/utils.d.ts +0 -21
  42. package/lib/utils.d.ts +0 -21
  43. package/lib/utils.js +0 -57
package/dist/vtilt.d.ts CHANGED
@@ -1,14 +1,59 @@
1
1
  import { VTiltConfig, EventPayload } from "./types";
2
+ import { TrackingManager } from "./tracking";
3
+ import { HistoryAutocapture } from "./extensions/history-autocapture";
2
4
  export declare class VTilt {
3
5
  private configManager;
4
- private trackingManager;
6
+ trackingManager: TrackingManager;
5
7
  private webVitalsManager;
6
- private initialized;
8
+ historyAutocapture?: HistoryAutocapture;
9
+ __loaded: boolean;
10
+ private _initialPageviewCaptured;
11
+ private _visibilityStateListener;
7
12
  constructor(config?: Partial<VTiltConfig>);
8
13
  /**
9
- * Initialize VTilt tracking
14
+ * Initializes a new instance of the VTilt tracking object.
15
+ *
16
+ * @remarks
17
+ * All new instances are added to the main vt object as sub properties (such as
18
+ * `vt.library_name`) and also returned by this function.
19
+ *
20
+ * @example
21
+ * ```js
22
+ * // basic initialization
23
+ * vt.init('<project_id>', {
24
+ * api_host: '<client_api_host>'
25
+ * })
26
+ * ```
27
+ *
28
+ * @example
29
+ * ```js
30
+ * // multiple instances
31
+ * vt.init('<project_id>', {}, 'project1')
32
+ * vt.init('<project_id>', {}, 'project2')
33
+ * ```
34
+ *
35
+ * @public
36
+ *
37
+ * @param projectId - Your VTilt project ID
38
+ * @param config - A dictionary of config options to override
39
+ * @param name - The name for the new VTilt instance that you want created
40
+ *
41
+ * @returns The newly initialized VTilt instance
42
+ */
43
+ init(projectId: string, config?: Partial<VTiltConfig>, name?: string): VTilt;
44
+ /**
45
+ * Handles the actual initialization logic for a VTilt instance.
46
+ * This internal method should only be called by `init()`.
47
+ * Follows the PostHog convention of using a private `_init()` method for instance setup.
48
+ */
49
+ private _init;
50
+ /**
51
+ * Returns a string representation of the instance name (PostHog-style)
52
+ * Used for debugging and logging
53
+ *
54
+ * @internal
10
55
  */
11
- init(): void;
56
+ toString(): string;
12
57
  /**
13
58
  * Track a custom event
14
59
  */
@@ -100,17 +145,10 @@ export declare class VTilt {
100
145
  */
101
146
  createAlias(alias: string, original?: string): void;
102
147
  /**
103
- * Setup page tracking with history API support
104
- */
105
- private setupPageTracking;
106
- /**
107
- * Track page hit
108
- */
109
- private trackPageHit;
110
- /**
111
- * Setup visibility change tracking for prerendered pages
148
+ * Capture initial pageview with visibility check
149
+ * Based on PostHog's _captureInitialPageview implementation
112
150
  */
113
- private setupVisibilityTracking;
151
+ private _captureInitialPageview;
114
152
  /**
115
153
  * Get current configuration
116
154
  */
@@ -124,11 +162,11 @@ export declare class VTilt {
124
162
  */
125
163
  updateConfig(config: Partial<VTiltConfig>): void;
126
164
  /**
127
- * _execute_array() deals with processing any VTilt function
128
- * calls that were called before the VTilt library was loaded
165
+ * _execute_array() deals with processing any vTilt function
166
+ * calls that were called before the vTilt library was loaded
129
167
  * (and are thus stored in an array so they can be called later)
130
168
  *
131
- * Note: we fire off all the VTilt function calls BEFORE we fire off
169
+ * Note: we fire off all the vTilt function calls BEFORE we fire off
132
170
  * tracking calls. This is so identify/setUserProperties/updateConfig calls
133
171
  * can properly modify early tracking calls.
134
172
  *
@@ -141,12 +179,12 @@ export declare class VTilt {
141
179
  _dom_loaded(): void;
142
180
  }
143
181
  /**
144
- * Initialize VTilt as a module (similar to PostHog's init_as_module)
145
- * Returns an uninitialized VTilt instance that the user must call init() on
182
+ * Initialize vTilt as a module (similar to PostHog's init_as_module)
183
+ * Returns an uninitialized vTilt instance that the user must call init() on
146
184
  */
147
185
  export declare function init_as_module(): VTilt;
148
186
  /**
149
- * Initialize VTilt from snippet (similar to PostHog's init_from_snippet)
187
+ * Initialize vTilt from snippet (similar to PostHog's init_from_snippet)
150
188
  * Processes queued calls from the snippet stub and replaces it with real instance
151
189
  *
152
190
  * The snippet uses some clever tricks to allow deferred loading of array.js (this code)
@@ -1,5 +1,7 @@
1
1
  import { StorageMethods } from "./types";
2
2
  export declare const STORAGE_KEY = "vt_session_id";
3
+ export declare const WINDOW_ID_KEY = "vt_window_id";
4
+ export declare const PRIMARY_WINDOW_EXISTS_KEY = "vt_primary_window_exists";
3
5
  export declare const ANONYMOUS_ID_KEY = "vt_anonymous_id";
4
6
  export declare const DISTINCT_ID_KEY = "vt_distinct_id";
5
7
  export declare const DEVICE_ID_KEY = "vt_device_id";
package/lib/constants.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ATTRIBUTES_TO_MASK = exports.TIMEZONES = exports.STORAGE_METHODS = exports.USER_STATE_KEY = exports.USER_PROPERTIES_KEY = exports.DEVICE_ID_KEY = exports.DISTINCT_ID_KEY = exports.ANONYMOUS_ID_KEY = exports.STORAGE_KEY = void 0;
3
+ exports.ATTRIBUTES_TO_MASK = exports.TIMEZONES = exports.STORAGE_METHODS = exports.USER_STATE_KEY = exports.USER_PROPERTIES_KEY = exports.DEVICE_ID_KEY = exports.DISTINCT_ID_KEY = exports.ANONYMOUS_ID_KEY = exports.PRIMARY_WINDOW_EXISTS_KEY = exports.WINDOW_ID_KEY = exports.STORAGE_KEY = void 0;
4
4
  exports.STORAGE_KEY = "vt_session_id";
5
+ exports.WINDOW_ID_KEY = "vt_window_id";
6
+ exports.PRIMARY_WINDOW_EXISTS_KEY = "vt_primary_window_exists";
5
7
  exports.ANONYMOUS_ID_KEY = "vt_anonymous_id";
6
8
  exports.DISTINCT_ID_KEY = "vt_distinct_id";
7
9
  exports.DEVICE_ID_KEY = "vt_device_id";
@@ -0,0 +1,19 @@
1
+ import { VTilt } from "../vtilt";
2
+ /**
3
+ * This class is used to capture pageview events when the user navigates using the history API (pushState, replaceState)
4
+ * and when the user navigates using the browser's back/forward buttons.
5
+ *
6
+ * Based on PostHog's HistoryAutocapture implementation
7
+ */
8
+ export declare class HistoryAutocapture {
9
+ private _instance;
10
+ private _popstateListener;
11
+ private _lastPathname;
12
+ constructor(instance: VTilt);
13
+ get isEnabled(): boolean;
14
+ startIfEnabled(): void;
15
+ stop(): void;
16
+ monitorHistoryChanges(): void;
17
+ private _capturePageview;
18
+ private _setupPopstateListener;
19
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HistoryAutocapture = void 0;
4
+ const globals_1 = require("../utils/globals");
5
+ const utils_1 = require("../utils");
6
+ const patch_1 = require("../utils/patch");
7
+ /**
8
+ * This class is used to capture pageview events when the user navigates using the history API (pushState, replaceState)
9
+ * and when the user navigates using the browser's back/forward buttons.
10
+ *
11
+ * Based on PostHog's HistoryAutocapture implementation
12
+ */
13
+ class HistoryAutocapture {
14
+ constructor(instance) {
15
+ var _a;
16
+ this._instance = instance;
17
+ this._lastPathname = ((_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.location) === null || _a === void 0 ? void 0 : _a.pathname) || "";
18
+ }
19
+ get isEnabled() {
20
+ // For now, always enabled (vTilt always tracks pageviews)
21
+ // Can be made conditional based on config in the future
22
+ return true;
23
+ }
24
+ startIfEnabled() {
25
+ if (this.isEnabled) {
26
+ this.monitorHistoryChanges();
27
+ }
28
+ }
29
+ stop() {
30
+ if (this._popstateListener) {
31
+ this._popstateListener();
32
+ }
33
+ this._popstateListener = undefined;
34
+ }
35
+ monitorHistoryChanges() {
36
+ var _a, _b;
37
+ // Only setup page tracking in browser environment (not SSR)
38
+ if (!globals_1.window || !globals_1.location) {
39
+ return;
40
+ }
41
+ // Initialize last pathname
42
+ this._lastPathname = globals_1.location.pathname || "";
43
+ // Track history API changes using patch (PostHog-style)
44
+ if (globals_1.window.history) {
45
+ // Old fashioned, we could also use arrow functions but I think the closure for a patch is more reliable
46
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
47
+ const self = this;
48
+ // Only patch if not already wrapped
49
+ if (!((_a = globals_1.window.history.pushState) === null || _a === void 0 ? void 0 : _a.__vtilt_wrapped__)) {
50
+ (0, patch_1.patch)(globals_1.window.history, "pushState", (originalPushState) => {
51
+ return function patchedPushState(state, title, url) {
52
+ originalPushState.call(this, state, title, url);
53
+ self._capturePageview("pushState");
54
+ };
55
+ });
56
+ }
57
+ // Only patch if not already wrapped
58
+ if (!((_b = globals_1.window.history.replaceState) === null || _b === void 0 ? void 0 : _b.__vtilt_wrapped__)) {
59
+ (0, patch_1.patch)(globals_1.window.history, "replaceState", (originalReplaceState) => {
60
+ return function patchedReplaceState(state, title, url) {
61
+ originalReplaceState.call(this, state, title, url);
62
+ self._capturePageview("replaceState");
63
+ };
64
+ });
65
+ }
66
+ // For popstate we need to listen to the event instead of overriding a method
67
+ this._setupPopstateListener();
68
+ }
69
+ }
70
+ _capturePageview(navigationType) {
71
+ var _a;
72
+ try {
73
+ const currentPathname = (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.location) === null || _a === void 0 ? void 0 : _a.pathname;
74
+ if (!currentPathname || !globals_1.location || !globals_1.document) {
75
+ return;
76
+ }
77
+ // Only capture pageview if the pathname has changed and the feature is enabled
78
+ if (currentPathname !== this._lastPathname && this.isEnabled) {
79
+ // Note: $current_url, $host, $pathname, $referrer, $referring_domain, title
80
+ // are automatically added by sendEvent() for all events (title only for $pageview)
81
+ const payload = {
82
+ navigation_type: navigationType,
83
+ };
84
+ this._instance.trackingManager.sendEvent("$pageview", payload);
85
+ }
86
+ this._lastPathname = currentPathname;
87
+ }
88
+ catch (error) {
89
+ console.error(`Error capturing ${navigationType} pageview`, error);
90
+ }
91
+ }
92
+ _setupPopstateListener() {
93
+ if (this._popstateListener || !globals_1.window) {
94
+ return;
95
+ }
96
+ const handler = () => {
97
+ this._capturePageview("popstate");
98
+ };
99
+ (0, utils_1.addEventListener)(globals_1.window, "popstate", handler);
100
+ this._popstateListener = () => {
101
+ if (globals_1.window) {
102
+ globals_1.window.removeEventListener("popstate", handler);
103
+ }
104
+ };
105
+ }
106
+ }
107
+ exports.HistoryAutocapture = HistoryAutocapture;
package/lib/session.d.ts CHANGED
@@ -2,25 +2,82 @@ import { StorageMethod } from "./types";
2
2
  export declare class SessionManager {
3
3
  private storageMethod;
4
4
  private domain?;
5
+ private _windowId;
5
6
  constructor(storageMethod?: StorageMethod, domain?: string);
6
7
  /**
7
- * Get session ID from cookie
8
+ * Check if using web storage (localStorage or sessionStorage)
8
9
  */
9
- private getSessionIdFromCookie;
10
+ private _isWebStorage;
11
+ /**
12
+ * Get storage object (localStorage or sessionStorage)
13
+ */
14
+ private _getStorage;
15
+ /**
16
+ * Create session data object with expiry
17
+ */
18
+ private _createSessionData;
19
+ /**
20
+ * Store session ID in web storage
21
+ */
22
+ private _storeSessionIdInWebStorage;
10
23
  /**
11
- * Get session ID from storage
24
+ * Get session ID from cookie
12
25
  */
13
- getSessionId(): string | null;
26
+ private getSessionIdFromCookie;
14
27
  /**
15
28
  * Set session ID in cookie
16
29
  */
17
30
  private setSessionIdFromCookie;
31
+ /**
32
+ * Store session ID (in web storage or cookie)
33
+ */
34
+ private _storeSessionId;
35
+ /**
36
+ * Get session ID from storage (raw, can return null)
37
+ * Private method used internally
38
+ */
39
+ private _getSessionIdRaw;
40
+ /**
41
+ * Get session ID (PostHog-style: always returns a value, generates if needed)
42
+ */
43
+ getSessionId(): string;
18
44
  /**
19
45
  * Set session ID in storage
46
+ * Extends TTL if session_id exists, generates new one if not
20
47
  */
21
48
  setSessionId(): string;
49
+ /**
50
+ * Clear session ID from storage
51
+ */
52
+ private _clearSessionId;
22
53
  /**
23
54
  * Reset session ID (PostHog behavior: generates new session on reset)
24
55
  */
25
56
  resetSessionId(): void;
57
+ /**
58
+ * Get window ID (PostHog-style)
59
+ * Window ID is unique per browser tab/window and persists across page reloads
60
+ * Always returns a window_id (generates one if not set)
61
+ */
62
+ getWindowId(): string;
63
+ /**
64
+ * Set window ID (PostHog-style)
65
+ * Stores in sessionStorage which is unique per tab
66
+ */
67
+ private _setWindowId;
68
+ /**
69
+ * Check if we can use sessionStorage for window_id
70
+ * sessionStorage is unique per tab, perfect for window_id
71
+ */
72
+ private _canUseSessionStorage;
73
+ /**
74
+ * Initialize window ID (PostHog-style)
75
+ * Detects tab duplication and handles window_id persistence
76
+ */
77
+ private _initializeWindowId;
78
+ /**
79
+ * Listen to window unload to clear primary window flag
80
+ * This helps distinguish between page reloads and tab duplication
81
+ */
82
+ private _listenToUnload;
26
83
  }
package/lib/session.js CHANGED
@@ -3,10 +3,53 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SessionManager = void 0;
4
4
  const constants_1 = require("./constants");
5
5
  const utils_1 = require("./utils");
6
+ const globals_1 = require("./utils/globals");
6
7
  class SessionManager {
7
8
  constructor(storageMethod = "cookie", domain) {
8
9
  this.storageMethod = storageMethod;
9
10
  this.domain = domain;
11
+ this._windowId = undefined;
12
+ // Initialize window_id (PostHog-style)
13
+ this._initializeWindowId();
14
+ }
15
+ /**
16
+ * Check if using web storage (localStorage or sessionStorage)
17
+ */
18
+ _isWebStorage() {
19
+ return (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
20
+ this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage);
21
+ }
22
+ /**
23
+ * Get storage object (localStorage or sessionStorage)
24
+ */
25
+ _getStorage() {
26
+ if (!this._isWebStorage()) {
27
+ return null;
28
+ }
29
+ return this.storageMethod === constants_1.STORAGE_METHODS.localStorage
30
+ ? localStorage
31
+ : sessionStorage;
32
+ }
33
+ /**
34
+ * Create session data object with expiry
35
+ */
36
+ _createSessionData(sessionId) {
37
+ const now = new Date();
38
+ return {
39
+ value: sessionId,
40
+ expiry: now.getTime() + 1800 * 1000, // 30 minutes
41
+ };
42
+ }
43
+ /**
44
+ * Store session ID in web storage
45
+ */
46
+ _storeSessionIdInWebStorage(sessionId) {
47
+ const storage = this._getStorage();
48
+ if (!storage) {
49
+ return;
50
+ }
51
+ const item = this._createSessionData(sessionId);
52
+ storage.setItem(constants_1.STORAGE_KEY, JSON.stringify(item));
10
53
  }
11
54
  /**
12
55
  * Get session ID from cookie
@@ -20,14 +63,33 @@ class SessionManager {
20
63
  return cookie[constants_1.STORAGE_KEY] || null;
21
64
  }
22
65
  /**
23
- * Get session ID from storage
66
+ * Set session ID in cookie
24
67
  */
25
- getSessionId() {
26
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
27
- this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
28
- const storage = this.storageMethod === constants_1.STORAGE_METHODS.localStorage
29
- ? localStorage
30
- : sessionStorage;
68
+ setSessionIdFromCookie(sessionId) {
69
+ let cookieValue = `${constants_1.STORAGE_KEY}=${sessionId}; Max-Age=1800; path=/; secure`;
70
+ if (this.domain) {
71
+ cookieValue += `; domain=${this.domain}`;
72
+ }
73
+ document.cookie = cookieValue;
74
+ }
75
+ /**
76
+ * Store session ID (in web storage or cookie)
77
+ */
78
+ _storeSessionId(sessionId) {
79
+ if (this._isWebStorage()) {
80
+ this._storeSessionIdInWebStorage(sessionId);
81
+ }
82
+ else {
83
+ this.setSessionIdFromCookie(sessionId);
84
+ }
85
+ }
86
+ /**
87
+ * Get session ID from storage (raw, can return null)
88
+ * Private method used internally
89
+ */
90
+ _getSessionIdRaw() {
91
+ const storage = this._getStorage();
92
+ if (storage) {
31
93
  const serializedItem = storage.getItem(constants_1.STORAGE_KEY);
32
94
  if (!serializedItem) {
33
95
  return null;
@@ -52,17 +114,20 @@ class SessionManager {
52
114
  return this.getSessionIdFromCookie();
53
115
  }
54
116
  /**
55
- * Set session ID in cookie
117
+ * Get session ID (PostHog-style: always returns a value, generates if needed)
56
118
  */
57
- setSessionIdFromCookie(sessionId) {
58
- let cookieValue = `${constants_1.STORAGE_KEY}=${sessionId}; Max-Age=1800; path=/; secure`;
59
- if (this.domain) {
60
- cookieValue += `; domain=${this.domain}`;
119
+ getSessionId() {
120
+ let sessionId = this._getSessionIdRaw();
121
+ // Generate and store session_id if not found (PostHog-style: always ensure session_id exists)
122
+ if (!sessionId) {
123
+ sessionId = (0, utils_1.uuidv4)();
124
+ this._storeSessionId(sessionId);
61
125
  }
62
- document.cookie = cookieValue;
126
+ return sessionId;
63
127
  }
64
128
  /**
65
129
  * Set session ID in storage
130
+ * Extends TTL if session_id exists, generates new one if not
66
131
  */
67
132
  setSessionId() {
68
133
  /**
@@ -70,46 +135,137 @@ class SessionManager {
70
135
  * - First request in a session will generate a new session id
71
136
  * - The next request will keep the same session id and extend the TTL for 30 more minutes
72
137
  */
73
- const sessionId = this.getSessionId() || (0, utils_1.uuidv4)();
74
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
75
- this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
76
- const now = new Date();
77
- const item = {
78
- value: sessionId,
79
- expiry: now.getTime() + 1800 * 1000,
80
- };
81
- const value = JSON.stringify(item);
82
- const storage = this.storageMethod === constants_1.STORAGE_METHODS.localStorage
83
- ? localStorage
84
- : sessionStorage;
85
- storage.setItem(constants_1.STORAGE_KEY, value);
86
- }
87
- else {
88
- this.setSessionIdFromCookie(sessionId);
89
- }
138
+ const sessionId = this._getSessionIdRaw() || (0, utils_1.uuidv4)();
139
+ this._storeSessionId(sessionId);
90
140
  return sessionId;
91
141
  }
92
142
  /**
93
- * Reset session ID (PostHog behavior: generates new session on reset)
143
+ * Clear session ID from storage
94
144
  */
95
- resetSessionId() {
96
- // Clear existing session
97
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
98
- this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
99
- const storage = this.storageMethod === constants_1.STORAGE_METHODS.localStorage
100
- ? localStorage
101
- : sessionStorage;
145
+ _clearSessionId() {
146
+ const storage = this._getStorage();
147
+ if (storage) {
102
148
  storage.removeItem(constants_1.STORAGE_KEY);
103
149
  }
104
150
  else {
105
151
  // Clear cookie
106
- document.cookie = `${constants_1.STORAGE_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
152
+ const expires = "Thu, 01 Jan 1970 00:00:00 UTC";
153
+ document.cookie = `${constants_1.STORAGE_KEY}=; expires=${expires}; path=/;`;
107
154
  if (this.domain) {
108
- document.cookie = `${constants_1.STORAGE_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${this.domain};`;
155
+ document.cookie = `${constants_1.STORAGE_KEY}=; expires=${expires}; path=/; domain=${this.domain};`;
109
156
  }
110
157
  }
111
- // Generate new session ID
158
+ }
159
+ /**
160
+ * Reset session ID (PostHog behavior: generates new session on reset)
161
+ */
162
+ resetSessionId() {
163
+ this._clearSessionId();
164
+ // Generate new session ID and window ID
112
165
  this.setSessionId();
166
+ this._setWindowId((0, utils_1.uuidv4)());
167
+ }
168
+ /**
169
+ * Get window ID (PostHog-style)
170
+ * Window ID is unique per browser tab/window and persists across page reloads
171
+ * Always returns a window_id (generates one if not set)
172
+ */
173
+ getWindowId() {
174
+ if (this._windowId) {
175
+ return this._windowId;
176
+ }
177
+ // Try to get from sessionStorage (unique per tab)
178
+ if (this._canUseSessionStorage() && globals_1.window) {
179
+ const windowId = globals_1.window.sessionStorage.getItem(constants_1.WINDOW_ID_KEY);
180
+ if (windowId) {
181
+ this._windowId = windowId;
182
+ return windowId;
183
+ }
184
+ }
185
+ // Generate and store window_id if not found (PostHog-style: always ensure window_id exists)
186
+ const newWindowId = (0, utils_1.uuidv4)();
187
+ this._setWindowId(newWindowId);
188
+ return newWindowId;
189
+ }
190
+ /**
191
+ * Set window ID (PostHog-style)
192
+ * Stores in sessionStorage which is unique per tab
193
+ */
194
+ _setWindowId(windowId) {
195
+ if (windowId !== this._windowId) {
196
+ this._windowId = windowId;
197
+ if (this._canUseSessionStorage() && globals_1.window) {
198
+ globals_1.window.sessionStorage.setItem(constants_1.WINDOW_ID_KEY, windowId);
199
+ }
200
+ }
201
+ }
202
+ /**
203
+ * Check if we can use sessionStorage for window_id
204
+ * sessionStorage is unique per tab, perfect for window_id
205
+ */
206
+ _canUseSessionStorage() {
207
+ if (typeof globals_1.window === "undefined" || !globals_1.window.sessionStorage) {
208
+ return false;
209
+ }
210
+ try {
211
+ const testKey = "__vt_session_storage_test__";
212
+ globals_1.window.sessionStorage.setItem(testKey, "test");
213
+ globals_1.window.sessionStorage.removeItem(testKey);
214
+ return true;
215
+ }
216
+ catch (_a) {
217
+ return false;
218
+ }
219
+ }
220
+ /**
221
+ * Initialize window ID (PostHog-style)
222
+ * Detects tab duplication and handles window_id persistence
223
+ */
224
+ _initializeWindowId() {
225
+ if (!this._canUseSessionStorage() || !globals_1.window) {
226
+ // Fallback: generate window_id in memory (won't persist across reloads)
227
+ this._windowId = (0, utils_1.uuidv4)();
228
+ return;
229
+ }
230
+ // Check if primary window exists flag is set
231
+ // If it exists, this means the tab was duplicated/cloned (window.open, tab duplication, etc.)
232
+ // If it doesn't exist, this is a fresh/reloaded tab
233
+ const primaryWindowExists = globals_1.window.sessionStorage.getItem(constants_1.PRIMARY_WINDOW_EXISTS_KEY);
234
+ const lastWindowId = globals_1.window.sessionStorage.getItem(constants_1.WINDOW_ID_KEY);
235
+ if (lastWindowId && !primaryWindowExists) {
236
+ // Tab was reloaded - reuse the window_id from sessionStorage
237
+ this._windowId = lastWindowId;
238
+ }
239
+ else {
240
+ // New tab or duplicated tab - generate new window_id
241
+ if (lastWindowId) {
242
+ // Clear old window_id (this is a duplicated tab)
243
+ globals_1.window.sessionStorage.removeItem(constants_1.WINDOW_ID_KEY);
244
+ }
245
+ // Generate new window_id
246
+ this._setWindowId((0, utils_1.uuidv4)());
247
+ }
248
+ // Flag this session as having a primary window
249
+ globals_1.window.sessionStorage.setItem(constants_1.PRIMARY_WINDOW_EXISTS_KEY, "true");
250
+ // Listen for page unload to clear the primary window flag
251
+ // This allows us to detect tab duplication vs page reload
252
+ this._listenToUnload();
253
+ }
254
+ /**
255
+ * Listen to window unload to clear primary window flag
256
+ * This helps distinguish between page reloads and tab duplication
257
+ */
258
+ _listenToUnload() {
259
+ if (!globals_1.window || !this._canUseSessionStorage()) {
260
+ return;
261
+ }
262
+ (0, utils_1.addEventListener)(globals_1.window, "beforeunload", () => {
263
+ // Clear the primary window flag on unload
264
+ // Reloaded tabs won't have this flag, duplicated tabs will
265
+ if (globals_1.window && globals_1.window.sessionStorage) {
266
+ globals_1.window.sessionStorage.removeItem(constants_1.PRIMARY_WINDOW_EXISTS_KEY);
267
+ }
268
+ }, { capture: false });
113
269
  }
114
270
  }
115
271
  exports.SessionManager = SessionManager;
package/lib/tracking.d.ts CHANGED
@@ -23,6 +23,9 @@ export declare class TrackingManager {
23
23
  private _isConfigured;
24
24
  /**
25
25
  * Send event to endpoint
26
+ * PostHog-style: Automatically adds common properties to all events
27
+ * ($current_url, $host, $pathname, $referrer, $referring_domain, $browser_language, etc.)
28
+ * Also adds title property for $pageview events only
26
29
  */
27
30
  sendEvent(name: string, payload: EventPayload): Promise<void>;
28
31
  /**
@@ -38,10 +41,6 @@ export declare class TrackingManager {
38
41
  * Send a queued request (called after DOM is loaded)
39
42
  */
40
43
  _send_retriable_request(item: QueuedRequest): void;
41
- /**
42
- * Track page hit
43
- */
44
- trackPageHit(): void;
45
44
  /**
46
45
  * Get current session ID
47
46
  */
@@ -94,6 +93,15 @@ export declare class TrackingManager {
94
93
  * Setup listener for identify, set, and alias events
95
94
  */
96
95
  private setupIdentifyListener;
96
+ /**
97
+ * Get session and window IDs (PostHog-style)
98
+ * Both methods ensure IDs always exist (generate if needed)
99
+ */
100
+ private _getSessionAndWindowIds;
101
+ /**
102
+ * Create base tracking event structure
103
+ */
104
+ private _createTrackingEvent;
97
105
  /**
98
106
  * Send identify event for session merging
99
107
  */