@v-tilt/browser 1.0.6 → 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.
- package/LICENSE +21 -0
- 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 +2 -0
- package/dist/extensions/history-autocapture.d.ts +19 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +187 -24
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +187 -24
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/session.d.ts +61 -4
- package/dist/tracking.d.ts +12 -7
- package/dist/types.d.ts +1 -1
- package/dist/utils/event-utils.d.ts +35 -0
- package/dist/utils/user-agent-utils.d.ts +18 -0
- package/dist/vtilt.d.ts +53 -26
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +3 -1
- package/lib/extensions/history-autocapture.d.ts +19 -0
- package/lib/extensions/history-autocapture.js +107 -0
- package/lib/session.d.ts +61 -4
- package/lib/session.js +197 -41
- package/lib/tracking.d.ts +12 -7
- package/lib/tracking.js +65 -79
- package/lib/types.d.ts +1 -1
- package/lib/utils/event-utils.d.ts +35 -0
- package/lib/utils/event-utils.js +178 -0
- package/lib/utils/user-agent-utils.d.ts +18 -0
- package/lib/utils/user-agent-utils.js +423 -0
- package/lib/vtilt.d.ts +53 -26
- package/lib/vtilt.js +145 -151
- package/package.json +60 -61
- package/dist/utils.d.ts +0 -21
- package/lib/utils.d.ts +0 -21
- package/lib/utils.js +0 -57
package/dist/vtilt.d.ts
CHANGED
|
@@ -1,18 +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
|
-
|
|
6
|
+
trackingManager: TrackingManager;
|
|
5
7
|
private webVitalsManager;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
historyAutocapture?: HistoryAutocapture;
|
|
9
|
+
__loaded: boolean;
|
|
8
10
|
private _initialPageviewCaptured;
|
|
9
11
|
private _visibilityStateListener;
|
|
10
|
-
private _popstateListener;
|
|
11
12
|
constructor(config?: Partial<VTiltConfig>);
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
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
|
|
14
42
|
*/
|
|
15
|
-
init():
|
|
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
|
|
55
|
+
*/
|
|
56
|
+
toString(): string;
|
|
16
57
|
/**
|
|
17
58
|
* Track a custom event
|
|
18
59
|
*/
|
|
@@ -103,25 +144,11 @@ export declare class VTilt {
|
|
|
103
144
|
* vtilt.createAlias('user_12345', 'anonymous_abc123')
|
|
104
145
|
*/
|
|
105
146
|
createAlias(alias: string, original?: string): void;
|
|
106
|
-
/**
|
|
107
|
-
* Setup page tracking with history API support
|
|
108
|
-
* Based on PostHog's setupHistoryEventTracking implementation
|
|
109
|
-
*/
|
|
110
|
-
private setupPageTracking;
|
|
111
147
|
/**
|
|
112
148
|
* Capture initial pageview with visibility check
|
|
113
149
|
* Based on PostHog's _captureInitialPageview implementation
|
|
114
150
|
*/
|
|
115
151
|
private _captureInitialPageview;
|
|
116
|
-
/**
|
|
117
|
-
* Capture pageview event for navigation changes
|
|
118
|
-
* Based on PostHog's captureNavigationEvent implementation
|
|
119
|
-
*/
|
|
120
|
-
private _capturePageview;
|
|
121
|
-
/**
|
|
122
|
-
* Setup popstate listener for browser back/forward navigation
|
|
123
|
-
*/
|
|
124
|
-
private _setupPopstateListener;
|
|
125
152
|
/**
|
|
126
153
|
* Get current configuration
|
|
127
154
|
*/
|
|
@@ -135,11 +162,11 @@ export declare class VTilt {
|
|
|
135
162
|
*/
|
|
136
163
|
updateConfig(config: Partial<VTiltConfig>): void;
|
|
137
164
|
/**
|
|
138
|
-
* _execute_array() deals with processing any
|
|
139
|
-
* calls that were called before the
|
|
165
|
+
* _execute_array() deals with processing any vTilt function
|
|
166
|
+
* calls that were called before the vTilt library was loaded
|
|
140
167
|
* (and are thus stored in an array so they can be called later)
|
|
141
168
|
*
|
|
142
|
-
* Note: we fire off all the
|
|
169
|
+
* Note: we fire off all the vTilt function calls BEFORE we fire off
|
|
143
170
|
* tracking calls. This is so identify/setUserProperties/updateConfig calls
|
|
144
171
|
* can properly modify early tracking calls.
|
|
145
172
|
*
|
|
@@ -152,12 +179,12 @@ export declare class VTilt {
|
|
|
152
179
|
_dom_loaded(): void;
|
|
153
180
|
}
|
|
154
181
|
/**
|
|
155
|
-
* Initialize
|
|
156
|
-
* Returns an uninitialized
|
|
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
|
|
157
184
|
*/
|
|
158
185
|
export declare function init_as_module(): VTilt;
|
|
159
186
|
/**
|
|
160
|
-
* Initialize
|
|
187
|
+
* Initialize vTilt from snippet (similar to PostHog's init_from_snippet)
|
|
161
188
|
* Processes queued calls from the snippet stub and replaces it with real instance
|
|
162
189
|
*
|
|
163
190
|
* The snippet uses some clever tricks to allow deferred loading of array.js (this code)
|
package/lib/constants.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
8
|
+
* Check if using web storage (localStorage or sessionStorage)
|
|
8
9
|
*/
|
|
9
|
-
private
|
|
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
|
|
24
|
+
* Get session ID from cookie
|
|
12
25
|
*/
|
|
13
|
-
|
|
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
|
-
*
|
|
66
|
+
* Set session ID in cookie
|
|
24
67
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
*
|
|
117
|
+
* Get session ID (PostHog-style: always returns a value, generates if needed)
|
|
56
118
|
*/
|
|
57
|
-
|
|
58
|
-
let
|
|
59
|
-
if (
|
|
60
|
-
|
|
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
|
-
|
|
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.
|
|
74
|
-
|
|
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
|
-
*
|
|
143
|
+
* Clear session ID from storage
|
|
94
144
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
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
|
-
|
|
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
|
|
155
|
+
document.cookie = `${constants_1.STORAGE_KEY}=; expires=${expires}; path=/; domain=${this.domain};`;
|
|
109
156
|
}
|
|
110
157
|
}
|
|
111
|
-
|
|
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,13 +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
|
-
* Based on PostHog's pageview tracking
|
|
44
|
-
*
|
|
45
|
-
* @param navigationType - Type of navigation that triggered the pageview
|
|
46
|
-
*/
|
|
47
|
-
trackPageHit(navigationType?: "initial_load" | "pushState" | "replaceState" | "popstate" | "hashchange"): void;
|
|
48
44
|
/**
|
|
49
45
|
* Get current session ID
|
|
50
46
|
*/
|
|
@@ -97,6 +93,15 @@ export declare class TrackingManager {
|
|
|
97
93
|
* Setup listener for identify, set, and alias events
|
|
98
94
|
*/
|
|
99
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;
|
|
100
105
|
/**
|
|
101
106
|
* Send identify event for session merging
|
|
102
107
|
*/
|