@v-tilt/browser 1.1.2 → 1.1.3

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/lib/session.js CHANGED
@@ -1,118 +1,34 @@
1
1
  "use strict";
2
+ /**
3
+ * Session Manager - Handles session_id and window_id
4
+ *
5
+ * Uses shared StorageManager for consistent storage operations.
6
+ *
7
+ * Session ID: Unique per user session, expires after 30 minutes of inactivity
8
+ * Window ID: Unique per browser tab, persists across page reloads
9
+ */
2
10
  Object.defineProperty(exports, "__esModule", { value: true });
3
11
  exports.SessionManager = void 0;
4
12
  const constants_1 = require("./constants");
5
13
  const utils_1 = require("./utils");
6
14
  const globals_1 = require("./utils/globals");
15
+ const storage_1 = require("./storage");
16
+ // Session TTL in milliseconds (30 minutes)
17
+ const SESSION_TTL_MS = 30 * 60 * 1000;
7
18
  class SessionManager {
8
19
  constructor(storageMethod = "cookie", domain) {
9
- this.storageMethod = storageMethod;
10
- this.domain = domain;
20
+ this.storage = new storage_1.StorageManager({
21
+ method: storageMethod,
22
+ domain,
23
+ sameSite: "Lax",
24
+ });
11
25
  this._windowId = undefined;
12
26
  // Initialize window_id
13
27
  this._initializeWindowId();
14
28
  }
15
- /**
16
- * Check if using web storage (localStorage or sessionStorage)
17
- */
18
- _isWebStorage() {
19
- return (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage ||
20
- this.storageMethod === constants_1.PERSISTENCE_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.PERSISTENCE_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));
53
- }
54
- /**
55
- * Get session ID from cookie
56
- */
57
- getSessionIdFromCookie() {
58
- const cookie = {};
59
- document.cookie.split(";").forEach(function (el) {
60
- const [key, value] = el.split("=");
61
- cookie[key.trim()] = value;
62
- });
63
- return cookie[constants_1.STORAGE_KEY] || null;
64
- }
65
- /**
66
- * Set session ID in cookie
67
- */
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) {
93
- const serializedItem = storage.getItem(constants_1.STORAGE_KEY);
94
- if (!serializedItem) {
95
- return null;
96
- }
97
- let item = null;
98
- try {
99
- item = JSON.parse(serializedItem);
100
- }
101
- catch (_a) {
102
- return null;
103
- }
104
- if (typeof item !== "object" || item === null) {
105
- return null;
106
- }
107
- const now = new Date();
108
- if (now.getTime() > item.expiry) {
109
- storage.removeItem(constants_1.STORAGE_KEY);
110
- return null;
111
- }
112
- return item.value;
113
- }
114
- return this.getSessionIdFromCookie();
115
- }
29
+ // ============================================================================
30
+ // Session ID Operations
31
+ // ============================================================================
116
32
  /**
117
33
  * Get session ID (always returns a value, generates if needed)
118
34
  */
@@ -139,23 +55,6 @@ class SessionManager {
139
55
  this._storeSessionId(sessionId);
140
56
  return sessionId;
141
57
  }
142
- /**
143
- * Clear session ID from storage
144
- */
145
- _clearSessionId() {
146
- const storage = this._getStorage();
147
- if (storage) {
148
- storage.removeItem(constants_1.STORAGE_KEY);
149
- }
150
- else {
151
- // Clear cookie
152
- const expires = "Thu, 01 Jan 1970 00:00:00 UTC";
153
- document.cookie = `${constants_1.STORAGE_KEY}=; expires=${expires}; path=/;`;
154
- if (this.domain) {
155
- document.cookie = `${constants_1.STORAGE_KEY}=; expires=${expires}; path=/; domain=${this.domain};`;
156
- }
157
- }
158
- }
159
58
  /**
160
59
  * Reset session ID (generates new session on reset)
161
60
  */
@@ -165,6 +64,61 @@ class SessionManager {
165
64
  this.setSessionId();
166
65
  this._setWindowId((0, utils_1.uuidv4)());
167
66
  }
67
+ /**
68
+ * Get session ID from storage (raw, can return null)
69
+ */
70
+ _getSessionIdRaw() {
71
+ // Use getWithExpiry for localStorage/sessionStorage (handles JSON + expiry)
72
+ const sessionData = this.storage.getWithExpiry(constants_1.STORAGE_KEY);
73
+ if (sessionData) {
74
+ return sessionData;
75
+ }
76
+ // For cookie mode, the cookie itself handles expiry via Max-Age
77
+ // Just get the raw value
78
+ const rawValue = this.storage.get(constants_1.STORAGE_KEY);
79
+ if (rawValue) {
80
+ // Check if it's JSON format (from web storage) or plain string (from cookie)
81
+ try {
82
+ const parsed = JSON.parse(rawValue);
83
+ // If it's a StorageItem, extract value and check expiry
84
+ if (typeof parsed === "object" &&
85
+ parsed !== null &&
86
+ "value" in parsed) {
87
+ if (parsed.expiry && Date.now() > parsed.expiry) {
88
+ this.storage.remove(constants_1.STORAGE_KEY);
89
+ return null;
90
+ }
91
+ return parsed.value;
92
+ }
93
+ return rawValue;
94
+ }
95
+ catch (_a) {
96
+ // Plain string value (from cookie)
97
+ return rawValue;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ /**
103
+ * Store session ID
104
+ */
105
+ _storeSessionId(sessionId) {
106
+ // Use setWithExpiry for localStorage/sessionStorage
107
+ // For cookies, the StorageManager handles Max-Age
108
+ this.storage.setWithExpiry(sessionId, sessionId, SESSION_TTL_MS);
109
+ // Also set as plain cookie for cookie mode (overwrites JSON format)
110
+ // This ensures cookies work properly with Max-Age
111
+ this.storage.set(constants_1.STORAGE_KEY, sessionId, storage_1.SESSION_COOKIE_MAX_AGE);
112
+ }
113
+ /**
114
+ * Clear session ID from storage
115
+ */
116
+ _clearSessionId() {
117
+ this.storage.remove(constants_1.STORAGE_KEY);
118
+ }
119
+ // ============================================================================
120
+ // Window ID Operations
121
+ // ============================================================================
168
122
  /**
169
123
  * Get window ID
170
124
  * Window ID is unique per browser tab/window and persists across page reloads
@@ -175,8 +129,9 @@ class SessionManager {
175
129
  return this._windowId;
176
130
  }
177
131
  // 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);
132
+ const sessionStorage = this.storage.getSessionStorage();
133
+ if (sessionStorage) {
134
+ const windowId = sessionStorage.getItem(constants_1.WINDOW_ID_KEY);
180
135
  if (windowId) {
181
136
  this._windowId = windowId;
182
137
  return windowId;
@@ -194,35 +149,19 @@ class SessionManager {
194
149
  _setWindowId(windowId) {
195
150
  if (windowId !== this._windowId) {
196
151
  this._windowId = windowId;
197
- if (this._canUseSessionStorage() && globals_1.window) {
198
- globals_1.window.sessionStorage.setItem(constants_1.WINDOW_ID_KEY, windowId);
152
+ const sessionStorage = this.storage.getSessionStorage();
153
+ if (sessionStorage) {
154
+ sessionStorage.setItem(constants_1.WINDOW_ID_KEY, windowId);
199
155
  }
200
156
  }
201
157
  }
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
158
  /**
221
159
  * Initialize window ID
222
160
  * Detects tab duplication and handles window_id persistence
223
161
  */
224
162
  _initializeWindowId() {
225
- if (!this._canUseSessionStorage() || !globals_1.window) {
163
+ const sessionStorage = this.storage.getSessionStorage();
164
+ if (!sessionStorage) {
226
165
  // Fallback: generate window_id in memory (won't persist across reloads)
227
166
  this._windowId = (0, utils_1.uuidv4)();
228
167
  return;
@@ -230,8 +169,8 @@ class SessionManager {
230
169
  // Check if primary window exists flag is set
231
170
  // If it exists, this means the tab was duplicated/cloned (window.open, tab duplication, etc.)
232
171
  // 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);
172
+ const primaryWindowExists = sessionStorage.getItem(constants_1.PRIMARY_WINDOW_EXISTS_KEY);
173
+ const lastWindowId = sessionStorage.getItem(constants_1.WINDOW_ID_KEY);
235
174
  if (lastWindowId && !primaryWindowExists) {
236
175
  // Tab was reloaded - reuse the window_id from sessionStorage
237
176
  this._windowId = lastWindowId;
@@ -240,13 +179,13 @@ class SessionManager {
240
179
  // New tab or duplicated tab - generate new window_id
241
180
  if (lastWindowId) {
242
181
  // Clear old window_id (this is a duplicated tab)
243
- globals_1.window.sessionStorage.removeItem(constants_1.WINDOW_ID_KEY);
182
+ sessionStorage.removeItem(constants_1.WINDOW_ID_KEY);
244
183
  }
245
184
  // Generate new window_id
246
185
  this._setWindowId((0, utils_1.uuidv4)());
247
186
  }
248
187
  // Flag this session as having a primary window
249
- globals_1.window.sessionStorage.setItem(constants_1.PRIMARY_WINDOW_EXISTS_KEY, "true");
188
+ sessionStorage.setItem(constants_1.PRIMARY_WINDOW_EXISTS_KEY, "true");
250
189
  // Listen for page unload to clear the primary window flag
251
190
  // This allows us to detect tab duplication vs page reload
252
191
  this._listenToUnload();
@@ -256,16 +195,31 @@ class SessionManager {
256
195
  * This helps distinguish between page reloads and tab duplication
257
196
  */
258
197
  _listenToUnload() {
259
- if (!globals_1.window || !this._canUseSessionStorage()) {
198
+ const sessionStorage = this.storage.getSessionStorage();
199
+ if (!globals_1.window || !sessionStorage) {
260
200
  return;
261
201
  }
262
202
  (0, utils_1.addEventListener)(globals_1.window, "beforeunload", () => {
263
203
  // Clear the primary window flag on unload
264
204
  // 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);
205
+ const ss = this.storage.getSessionStorage();
206
+ if (ss) {
207
+ ss.removeItem(constants_1.PRIMARY_WINDOW_EXISTS_KEY);
267
208
  }
268
209
  }, { capture: false });
269
210
  }
211
+ // ============================================================================
212
+ // Storage Management
213
+ // ============================================================================
214
+ /**
215
+ * Update storage method at runtime
216
+ */
217
+ updateStorageMethod(method, domain) {
218
+ this.storage = new storage_1.StorageManager({
219
+ method,
220
+ domain,
221
+ sameSite: "Lax",
222
+ });
223
+ }
270
224
  }
271
225
  exports.SessionManager = SessionManager;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Unified Storage Manager - Following PostHog's PostHogPersistence pattern
3
+ *
4
+ * Single class handles all storage operations for both sessions and users.
5
+ * Reduces code duplication and ensures consistent cookie handling.
6
+ *
7
+ * Storage methods:
8
+ * - cookie: Browser cookies (cross-subdomain support)
9
+ * - localStorage: Persistent local storage
10
+ * - sessionStorage: Tab-specific storage (cleared on tab close)
11
+ * - localStorage+cookie: Both for redundancy
12
+ * - memory: In-memory only (no persistence)
13
+ */
14
+ import { PersistenceMethod } from "./types";
15
+ export declare const SESSION_COOKIE_MAX_AGE = 1800;
16
+ export declare const USER_COOKIE_MAX_AGE = 31536000;
17
+ export interface StorageOptions {
18
+ method: PersistenceMethod;
19
+ domain?: string;
20
+ secure?: boolean;
21
+ sameSite?: "Strict" | "Lax" | "None";
22
+ }
23
+ export interface StorageItem<T = string> {
24
+ value: T;
25
+ expiry?: number;
26
+ }
27
+ /**
28
+ * Unified Storage Manager
29
+ * Provides consistent storage operations across all persistence methods
30
+ */
31
+ export declare class StorageManager {
32
+ private method;
33
+ private domain?;
34
+ private secure;
35
+ private sameSite;
36
+ private memoryStorage;
37
+ constructor(options: StorageOptions);
38
+ /**
39
+ * Get a value from storage
40
+ */
41
+ get(key: string): string | null;
42
+ /**
43
+ * Set a value in storage
44
+ * @param maxAge - Cookie max age in seconds (ignored for localStorage/sessionStorage)
45
+ */
46
+ set(key: string, value: string, maxAge?: number): void;
47
+ /**
48
+ * Remove a value from storage
49
+ */
50
+ remove(key: string): void;
51
+ /**
52
+ * Get JSON value with expiry check
53
+ * Returns null if expired or not found
54
+ */
55
+ getWithExpiry<T>(key: string): T | null;
56
+ /**
57
+ * Set JSON value with optional expiry
58
+ * @param ttlMs - Time to live in milliseconds
59
+ */
60
+ setWithExpiry<T>(key: string, value: T, ttlMs?: number): void;
61
+ /**
62
+ * Get JSON value (no expiry check)
63
+ */
64
+ getJSON<T>(key: string): T | null;
65
+ /**
66
+ * Set JSON value
67
+ */
68
+ setJSON<T>(key: string, value: T, maxAge?: number): void;
69
+ private getCookie;
70
+ private setCookie;
71
+ private removeCookie;
72
+ private usesLocalStorage;
73
+ private usesSessionStorage;
74
+ /**
75
+ * Check if sessionStorage is available
76
+ */
77
+ canUseSessionStorage(): boolean;
78
+ /**
79
+ * Get direct access to sessionStorage (for window_id which always uses sessionStorage)
80
+ */
81
+ getSessionStorage(): Storage | null;
82
+ /**
83
+ * Update storage method at runtime
84
+ */
85
+ setMethod(method: PersistenceMethod): void;
86
+ /**
87
+ * Get current storage method
88
+ */
89
+ getMethod(): PersistenceMethod;
90
+ }
91
+ /**
92
+ * Create a shared storage instance
93
+ * Use this for creating storage managers with consistent settings
94
+ */
95
+ export declare function createStorageManager(method: PersistenceMethod, domain?: string): StorageManager;