@v-tilt/browser 1.4.3 → 1.4.4

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/storage.js CHANGED
@@ -1,194 +1,369 @@
1
1
  "use strict";
2
2
  /**
3
- * Unified Storage Manager - Following PostHog's PostHogPersistence pattern
3
+ * Unified Storage Manager
4
4
  *
5
- * Single class handles all storage operations for both sessions and users.
6
- * Reduces code duplication and ensures consistent cookie handling.
5
+ * Handles all storage operations for sessions and users with consistent
6
+ * cookie handling across different persistence methods.
7
7
  *
8
8
  * Storage methods:
9
- * - cookie: Browser cookies (cross-subdomain support)
10
- * - localStorage: Persistent local storage
11
- * - sessionStorage: Tab-specific storage (cleared on tab close)
12
- * - localStorage+cookie: Both for redundancy
13
- * - memory: In-memory only (no persistence)
9
+ * - `cookie`: Browser cookies with cross-subdomain support
10
+ * - `localStorage`: Persistent local storage
11
+ * - `sessionStorage`: Tab-specific storage (cleared on tab close)
12
+ * - `localStorage+cookie`: Hybrid mode (default) - full data in localStorage,
13
+ * critical identity properties also in cookies for SSR support
14
+ * - `memory`: In-memory only (no persistence)
15
+ *
16
+ * The `localStorage+cookie` mode is designed for traditional server-side
17
+ * rendered websites where each page navigation reloads JavaScript:
18
+ * - Critical properties (anonymous_id, device_id, etc.) are stored in cookies
19
+ * - Cookies ensure identity persists across full page reloads
20
+ * - localStorage provides fast access for SPA-style navigation
21
+ * - Falls back to cookie-only if localStorage is unavailable
14
22
  */
15
23
  Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.StorageManager = exports.USER_COOKIE_MAX_AGE = exports.SESSION_COOKIE_MAX_AGE = void 0;
24
+ exports.StorageManager = exports.USER_COOKIE_MAX_AGE = exports.SESSION_COOKIE_MAX_AGE = exports.COOKIE_PERSISTED_PROPERTIES = void 0;
17
25
  exports.shouldUseCrossSubdomainCookie = shouldUseCrossSubdomainCookie;
18
26
  exports.createStorageManager = createStorageManager;
19
27
  const constants_1 = require("./constants");
20
28
  const globals_1 = require("./utils/globals");
21
- // Default cookie TTLs
22
- exports.SESSION_COOKIE_MAX_AGE = 1800; // 30 minutes
23
- exports.USER_COOKIE_MAX_AGE = 31536000; // 1 year
24
- // Platforms excluded from cross-subdomain cookies (following PostHog)
29
+ /**
30
+ * Critical properties persisted to cookies in `localStorage+cookie` mode.
31
+ * These ensure identity survives full page reloads in traditional SSR websites.
32
+ */
33
+ exports.COOKIE_PERSISTED_PROPERTIES = [
34
+ constants_1.ANONYMOUS_ID_KEY,
35
+ constants_1.DEVICE_ID_KEY,
36
+ constants_1.DISTINCT_ID_KEY,
37
+ constants_1.USER_STATE_KEY,
38
+ ];
39
+ /** Session cookie TTL: 30 minutes */
40
+ exports.SESSION_COOKIE_MAX_AGE = 1800;
41
+ /** User cookie TTL: 1 year */
42
+ exports.USER_COOKIE_MAX_AGE = 31536000;
43
+ /** Platforms where cross-subdomain cookies are disabled */
25
44
  const EXCLUDED_FROM_CROSS_SUBDOMAIN = [
26
45
  "herokuapp.com",
27
46
  "vercel.app",
28
47
  "netlify.app",
29
48
  ];
30
49
  /**
31
- * Auto-detect if cross-subdomain cookies should be enabled
32
- * Returns false for platforms like herokuapp.com, vercel.app, netlify.app
50
+ * Check if cross-subdomain cookies should be enabled.
51
+ * Returns false for shared hosting platforms to prevent cookie conflicts.
33
52
  */
34
53
  function shouldUseCrossSubdomainCookie() {
35
54
  var _a;
36
- if (typeof document === "undefined") {
55
+ if (typeof document === "undefined")
37
56
  return false;
38
- }
39
57
  const hostname = (_a = document.location) === null || _a === void 0 ? void 0 : _a.hostname;
40
- if (!hostname) {
58
+ if (!hostname)
41
59
  return false;
42
- }
43
60
  const lastTwoParts = hostname.split(".").slice(-2).join(".");
44
- for (const excluded of EXCLUDED_FROM_CROSS_SUBDOMAIN) {
45
- if (lastTwoParts === excluded) {
46
- return false;
47
- }
48
- }
49
- return true;
61
+ return !EXCLUDED_FROM_CROSS_SUBDOMAIN.includes(lastTwoParts);
50
62
  }
51
63
  /**
52
- * Get the cookie domain for cross-subdomain cookies
53
- * Returns domain like ".example.com" or empty string for same-origin
64
+ * Get cookie domain for cross-subdomain cookies.
65
+ * @returns Domain like ".example.com" or empty string for same-origin
54
66
  */
55
67
  function getCookieDomain(cross_subdomain) {
56
68
  var _a;
57
- if (!cross_subdomain) {
69
+ if (!cross_subdomain || typeof document === "undefined")
58
70
  return "";
59
- }
60
- if (typeof document === "undefined") {
61
- return "";
62
- }
63
71
  const hostname = (_a = document.location) === null || _a === void 0 ? void 0 : _a.hostname;
64
- if (!hostname) {
72
+ if (!hostname)
65
73
  return "";
66
- }
67
- // Match domain pattern like "example.com" from "sub.example.com"
68
74
  const matches = hostname.match(/[a-z0-9][a-z0-9-]+\.[a-z]{2,}$/i);
69
75
  return matches ? `.${matches[0]}` : "";
70
76
  }
71
77
  /**
72
78
  * Unified Storage Manager
79
+ *
73
80
  * Provides consistent storage operations across all persistence methods
81
+ * with automatic fallbacks and SSR support.
74
82
  */
75
83
  class StorageManager {
76
84
  constructor(options) {
77
- var _a, _b;
85
+ var _a, _b, _c;
78
86
  this.memoryStorage = new Map();
87
+ this._localStorageSupported = null;
79
88
  this.method = options.method;
80
89
  this.cross_subdomain =
81
90
  (_a = options.cross_subdomain) !== null && _a !== void 0 ? _a : shouldUseCrossSubdomainCookie();
82
- this.sameSite = options.sameSite || "Lax";
83
- // Auto-detect secure flag from protocol
91
+ this.sameSite = (_b = options.sameSite) !== null && _b !== void 0 ? _b : "Lax";
84
92
  this.secure =
85
- (_b = options.secure) !== null && _b !== void 0 ? _b : (typeof location !== "undefined" && location.protocol === "https:");
93
+ (_c = options.secure) !== null && _c !== void 0 ? _c : (typeof location !== "undefined" && location.protocol === "https:");
86
94
  }
87
- // ============================================================================
88
- // Public API - Simple key-value operations
89
- // ============================================================================
95
+ // ---------------------------------------------------------------------------
96
+ // Storage availability checks
97
+ // ---------------------------------------------------------------------------
98
+ /** Check if localStorage is available (result is cached) */
99
+ isLocalStorageSupported() {
100
+ if (this._localStorageSupported !== null) {
101
+ return this._localStorageSupported;
102
+ }
103
+ if (typeof globals_1.window === "undefined" || !globals_1.window.localStorage) {
104
+ this._localStorageSupported = false;
105
+ return false;
106
+ }
107
+ try {
108
+ const testKey = "__vt_ls_test__";
109
+ globals_1.window.localStorage.setItem(testKey, "1");
110
+ const ok = globals_1.window.localStorage.getItem(testKey) === "1";
111
+ globals_1.window.localStorage.removeItem(testKey);
112
+ this._localStorageSupported = ok;
113
+ if (!ok) {
114
+ console.warn("[vTilt] localStorage unavailable, using cookies");
115
+ }
116
+ return ok;
117
+ }
118
+ catch (_a) {
119
+ this._localStorageSupported = false;
120
+ console.warn("[vTilt] localStorage unavailable, using cookies");
121
+ return false;
122
+ }
123
+ }
124
+ /** Check if a key should be persisted to cookies */
125
+ isCriticalProperty(key) {
126
+ return exports.COOKIE_PERSISTED_PROPERTIES.includes(key);
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // Public API
130
+ // ---------------------------------------------------------------------------
90
131
  /**
91
- * Get a value from storage
132
+ * Get a value from storage.
133
+ *
134
+ * For `localStorage+cookie` mode:
135
+ * - Critical properties: read cookie first (for SSR), fall back to localStorage
136
+ * - Non-critical properties: read from localStorage only
92
137
  */
93
138
  get(key) {
94
139
  var _a;
95
140
  try {
96
- if (this.method === constants_1.PERSISTENCE_METHODS.memory) {
97
- return (_a = this.memoryStorage.get(key)) !== null && _a !== void 0 ? _a : null;
98
- }
99
- if (this.usesLocalStorage()) {
100
- const value = localStorage.getItem(key);
101
- if (value !== null) {
102
- return value;
103
- }
104
- // Fall back to cookie for localStorage+cookie mode
105
- if (this.method === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
141
+ switch (this.method) {
142
+ case constants_1.PERSISTENCE_METHODS.memory:
143
+ return (_a = this.memoryStorage.get(key)) !== null && _a !== void 0 ? _a : null;
144
+ case constants_1.PERSISTENCE_METHODS.localStoragePlusCookie:
145
+ return this.getLocalStoragePlusCookie(key);
146
+ case constants_1.PERSISTENCE_METHODS.localStorage:
147
+ return this.getLocalStorage(key);
148
+ case constants_1.PERSISTENCE_METHODS.sessionStorage:
149
+ return this.readFromSessionStorage(key);
150
+ case constants_1.PERSISTENCE_METHODS.cookie:
151
+ default:
106
152
  return this.getCookie(key);
107
- }
108
- return null;
109
- }
110
- if (this.usesSessionStorage()) {
111
- return sessionStorage.getItem(key);
112
153
  }
113
- // Cookie-only mode
114
- return this.getCookie(key);
115
154
  }
116
- catch (error) {
117
- console.warn(`[StorageManager] Failed to get "${key}":`, error);
155
+ catch (err) {
156
+ console.warn(`[vTilt] Storage get error for "${key}":`, err);
118
157
  return null;
119
158
  }
120
159
  }
121
160
  /**
122
- * Set a value in storage
123
- * @param maxAge - Cookie max age in seconds (ignored for localStorage/sessionStorage)
161
+ * Set a value in storage.
162
+ * @param maxAge Cookie max age in seconds (only for cookie-based storage)
163
+ *
164
+ * For `localStorage+cookie` mode:
165
+ * - All values stored in localStorage (if available)
166
+ * - Critical properties ALWAYS stored in cookies for SSR support
124
167
  */
125
168
  set(key, value, maxAge) {
126
169
  try {
127
- if (this.method === constants_1.PERSISTENCE_METHODS.memory) {
128
- this.memoryStorage.set(key, value);
129
- return;
130
- }
131
- if (this.usesLocalStorage()) {
132
- localStorage.setItem(key, value);
133
- // Also set cookie for localStorage+cookie mode
134
- if (this.method === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
135
- this.setCookie(key, value, maxAge || exports.USER_COOKIE_MAX_AGE);
136
- }
137
- return;
170
+ switch (this.method) {
171
+ case constants_1.PERSISTENCE_METHODS.memory:
172
+ this.memoryStorage.set(key, value);
173
+ break;
174
+ case constants_1.PERSISTENCE_METHODS.localStoragePlusCookie:
175
+ this.setLocalStoragePlusCookie(key, value, maxAge);
176
+ break;
177
+ case constants_1.PERSISTENCE_METHODS.localStorage:
178
+ this.setLocalStorage(key, value);
179
+ break;
180
+ case constants_1.PERSISTENCE_METHODS.sessionStorage:
181
+ this.writeToSessionStorage(key, value);
182
+ break;
183
+ case constants_1.PERSISTENCE_METHODS.cookie:
184
+ default:
185
+ this.setCookie(key, value, maxAge !== null && maxAge !== void 0 ? maxAge : exports.USER_COOKIE_MAX_AGE);
138
186
  }
139
- if (this.usesSessionStorage()) {
140
- sessionStorage.setItem(key, value);
141
- return;
142
- }
143
- // Cookie-only mode
144
- this.setCookie(key, value, maxAge || exports.USER_COOKIE_MAX_AGE);
145
187
  }
146
- catch (error) {
147
- console.warn(`[StorageManager] Failed to set "${key}":`, error);
188
+ catch (err) {
189
+ console.warn(`[vTilt] Storage set error for "${key}":`, err);
148
190
  }
149
191
  }
150
- /**
151
- * Remove a value from storage
152
- */
192
+ /** Remove a value from storage */
153
193
  remove(key) {
154
194
  try {
155
- if (this.method === constants_1.PERSISTENCE_METHODS.memory) {
156
- this.memoryStorage.delete(key);
157
- return;
158
- }
159
- if (this.usesLocalStorage()) {
160
- localStorage.removeItem(key);
161
- if (this.method === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
195
+ switch (this.method) {
196
+ case constants_1.PERSISTENCE_METHODS.memory:
197
+ this.memoryStorage.delete(key);
198
+ break;
199
+ case constants_1.PERSISTENCE_METHODS.localStoragePlusCookie:
200
+ this.removeLocalStoragePlusCookie(key);
201
+ break;
202
+ case constants_1.PERSISTENCE_METHODS.localStorage:
203
+ this.removeLocalStorage(key);
204
+ break;
205
+ case constants_1.PERSISTENCE_METHODS.sessionStorage:
206
+ this.removeFromSessionStorage(key);
207
+ break;
208
+ case constants_1.PERSISTENCE_METHODS.cookie:
209
+ default:
162
210
  this.removeCookie(key);
163
- }
164
- return;
165
211
  }
166
- if (this.usesSessionStorage()) {
167
- sessionStorage.removeItem(key);
168
- return;
212
+ }
213
+ catch (err) {
214
+ console.warn(`[vTilt] Storage remove error for "${key}":`, err);
215
+ }
216
+ }
217
+ // ---------------------------------------------------------------------------
218
+ // localStorage+cookie mode (hybrid)
219
+ // ---------------------------------------------------------------------------
220
+ getLocalStoragePlusCookie(key) {
221
+ if (this.isCriticalProperty(key)) {
222
+ // Critical: cookie takes precedence (for SSR support)
223
+ const cookieValue = this.getCookie(key);
224
+ const lsValue = this.getLocalStorage(key);
225
+ if (cookieValue !== null) {
226
+ // Sync to localStorage for faster future reads
227
+ if (lsValue !== cookieValue && this.isLocalStorageSupported()) {
228
+ try {
229
+ globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage.setItem(key, cookieValue);
230
+ }
231
+ catch (_a) {
232
+ /* ignore */
233
+ }
234
+ }
235
+ return cookieValue;
169
236
  }
170
- // Cookie-only mode
237
+ return lsValue;
238
+ }
239
+ // Non-critical: localStorage only
240
+ return this.getLocalStorage(key);
241
+ }
242
+ setLocalStoragePlusCookie(key, value, maxAge) {
243
+ // Always try localStorage first
244
+ this.setLocalStorage(key, value);
245
+ // Critical properties MUST go to cookies (SSR support)
246
+ if (this.isCriticalProperty(key)) {
247
+ this.setCookie(key, value, maxAge !== null && maxAge !== void 0 ? maxAge : exports.USER_COOKIE_MAX_AGE);
248
+ }
249
+ }
250
+ removeLocalStoragePlusCookie(key) {
251
+ this.removeLocalStorage(key);
252
+ if (this.isCriticalProperty(key)) {
171
253
  this.removeCookie(key);
172
254
  }
173
- catch (error) {
174
- console.warn(`[StorageManager] Failed to remove "${key}":`, error);
255
+ }
256
+ // ---------------------------------------------------------------------------
257
+ // localStorage operations
258
+ // ---------------------------------------------------------------------------
259
+ getLocalStorage(key) {
260
+ var _a;
261
+ if (!this.isLocalStorageSupported())
262
+ return null;
263
+ try {
264
+ return (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage.getItem(key)) !== null && _a !== void 0 ? _a : null;
265
+ }
266
+ catch (_b) {
267
+ return null;
175
268
  }
176
269
  }
177
- // ============================================================================
178
- // JSON operations with optional expiry (for session data)
179
- // ============================================================================
180
- /**
181
- * Get JSON value with expiry check
182
- * Returns null if expired or not found
183
- */
270
+ setLocalStorage(key, value) {
271
+ if (!this.isLocalStorageSupported())
272
+ return;
273
+ try {
274
+ globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage.setItem(key, value);
275
+ }
276
+ catch (err) {
277
+ console.warn("[vTilt] localStorage write error:", err);
278
+ }
279
+ }
280
+ removeLocalStorage(key) {
281
+ if (!this.isLocalStorageSupported())
282
+ return;
283
+ try {
284
+ globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage.removeItem(key);
285
+ }
286
+ catch (_a) {
287
+ /* ignore */
288
+ }
289
+ }
290
+ // ---------------------------------------------------------------------------
291
+ // sessionStorage operations (internal)
292
+ // ---------------------------------------------------------------------------
293
+ readFromSessionStorage(key) {
294
+ var _a;
295
+ try {
296
+ return (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage.getItem(key)) !== null && _a !== void 0 ? _a : null;
297
+ }
298
+ catch (_b) {
299
+ return null;
300
+ }
301
+ }
302
+ writeToSessionStorage(key, value) {
303
+ try {
304
+ globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage.setItem(key, value);
305
+ }
306
+ catch (err) {
307
+ console.warn("[vTilt] sessionStorage write error:", err);
308
+ }
309
+ }
310
+ removeFromSessionStorage(key) {
311
+ try {
312
+ globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage.removeItem(key);
313
+ }
314
+ catch (_a) {
315
+ /* ignore */
316
+ }
317
+ }
318
+ // ---------------------------------------------------------------------------
319
+ // Cookie operations
320
+ // ---------------------------------------------------------------------------
321
+ getCookie(name) {
322
+ if (typeof document === "undefined")
323
+ return null;
324
+ const nameEq = name + "=";
325
+ for (const part of document.cookie.split(";")) {
326
+ const trimmed = part.trim();
327
+ if (trimmed.startsWith(nameEq)) {
328
+ try {
329
+ return decodeURIComponent(trimmed.slice(nameEq.length));
330
+ }
331
+ catch (_a) {
332
+ return trimmed.slice(nameEq.length);
333
+ }
334
+ }
335
+ }
336
+ return null;
337
+ }
338
+ setCookie(name, value, maxAge) {
339
+ if (typeof document === "undefined")
340
+ return;
341
+ const domain = getCookieDomain(this.cross_subdomain);
342
+ let cookie = `${name}=${encodeURIComponent(value)}; Max-Age=${maxAge}; path=/; SameSite=${this.sameSite}`;
343
+ if (this.secure)
344
+ cookie += "; Secure";
345
+ if (domain)
346
+ cookie += `; domain=${domain}`;
347
+ document.cookie = cookie;
348
+ }
349
+ removeCookie(name) {
350
+ if (typeof document === "undefined")
351
+ return;
352
+ const domain = getCookieDomain(this.cross_subdomain);
353
+ document.cookie = `${name}=; Max-Age=0; path=/${domain ? `; domain=${domain}` : ""}`;
354
+ // Also try without domain
355
+ document.cookie = `${name}=; Max-Age=0; path=/`;
356
+ }
357
+ // ---------------------------------------------------------------------------
358
+ // JSON helpers
359
+ // ---------------------------------------------------------------------------
360
+ /** Get JSON value with expiry check */
184
361
  getWithExpiry(key) {
185
362
  const raw = this.get(key);
186
- if (!raw) {
363
+ if (!raw)
187
364
  return null;
188
- }
189
365
  try {
190
366
  const item = JSON.parse(raw);
191
- // Check expiry if set
192
367
  if (item.expiry && Date.now() > item.expiry) {
193
368
  this.remove(key);
194
369
  return null;
@@ -196,14 +371,10 @@ class StorageManager {
196
371
  return item.value;
197
372
  }
198
373
  catch (_a) {
199
- // Not JSON or invalid format - return raw value if compatible
200
374
  return null;
201
375
  }
202
376
  }
203
- /**
204
- * Set JSON value with optional expiry
205
- * @param ttlMs - Time to live in milliseconds
206
- */
377
+ /** Set JSON value with optional TTL */
207
378
  setWithExpiry(key, value, ttlMs) {
208
379
  const item = {
209
380
  value,
@@ -211,14 +382,11 @@ class StorageManager {
211
382
  };
212
383
  this.set(key, JSON.stringify(item));
213
384
  }
214
- /**
215
- * Get JSON value (no expiry check)
216
- */
385
+ /** Get parsed JSON value */
217
386
  getJSON(key) {
218
387
  const raw = this.get(key);
219
- if (!raw) {
388
+ if (!raw)
220
389
  return null;
221
- }
222
390
  try {
223
391
  return JSON.parse(raw);
224
392
  }
@@ -226,126 +394,45 @@ class StorageManager {
226
394
  return null;
227
395
  }
228
396
  }
229
- /**
230
- * Set JSON value
231
- */
397
+ /** Set JSON value */
232
398
  setJSON(key, value, maxAge) {
233
399
  this.set(key, JSON.stringify(value), maxAge);
234
400
  }
235
- // ============================================================================
236
- // Cookie operations (internal)
237
- // ============================================================================
238
- getCookie(name) {
239
- if (typeof document === "undefined") {
240
- return null;
241
- }
242
- const cookies = document.cookie.split(";");
243
- for (const cookie of cookies) {
244
- const [key, ...valueParts] = cookie.trim().split("=");
245
- if (key === name) {
246
- const value = valueParts.join("="); // Handle values with = in them
247
- try {
248
- return decodeURIComponent(value);
249
- }
250
- catch (_a) {
251
- return value;
252
- }
253
- }
254
- }
255
- return null;
256
- }
257
- setCookie(name, value, maxAge) {
258
- if (typeof document === "undefined") {
259
- return;
260
- }
261
- let cookieString = `${name}=${encodeURIComponent(value)}`;
262
- cookieString += `; Max-Age=${maxAge}`;
263
- cookieString += `; path=/`;
264
- cookieString += `; SameSite=${this.sameSite}`;
265
- if (this.secure) {
266
- cookieString += `; Secure`;
267
- }
268
- // Auto-detect domain for cross-subdomain cookies
269
- const domain = getCookieDomain(this.cross_subdomain);
270
- if (domain) {
271
- cookieString += `; domain=${domain}`;
272
- }
273
- document.cookie = cookieString;
274
- }
275
- removeCookie(name) {
276
- if (typeof document === "undefined") {
277
- return;
278
- }
279
- // Auto-detect domain for cross-subdomain cookies
280
- const domain = getCookieDomain(this.cross_subdomain);
281
- // Set cookie with expired date
282
- let cookieString = `${name}=; Max-Age=0; path=/`;
283
- if (domain) {
284
- cookieString += `; domain=${domain}`;
285
- }
286
- document.cookie = cookieString;
287
- // Also try without domain (in case it was set without)
288
- document.cookie = `${name}=; Max-Age=0; path=/`;
289
- }
290
- // ============================================================================
291
- // Helper methods
292
- // ============================================================================
293
- usesLocalStorage() {
294
- return (this.method === constants_1.PERSISTENCE_METHODS.localStorage ||
295
- this.method === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie);
296
- }
297
- usesSessionStorage() {
298
- return this.method === constants_1.PERSISTENCE_METHODS.sessionStorage;
299
- }
300
- /**
301
- * Check if sessionStorage is available
302
- */
401
+ // ---------------------------------------------------------------------------
402
+ // Utility methods
403
+ // ---------------------------------------------------------------------------
404
+ /** Check if sessionStorage is available */
303
405
  canUseSessionStorage() {
304
- if (typeof globals_1.window === "undefined" || !(globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage)) {
406
+ if (typeof globals_1.window === "undefined" || !(globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage))
305
407
  return false;
306
- }
307
408
  try {
308
- const testKey = "__vt_storage_test__";
309
- globals_1.window.sessionStorage.setItem(testKey, "test");
310
- globals_1.window.sessionStorage.removeItem(testKey);
409
+ const k = "__vt_ss_test__";
410
+ globals_1.window.sessionStorage.setItem(k, "1");
411
+ globals_1.window.sessionStorage.removeItem(k);
311
412
  return true;
312
413
  }
313
414
  catch (_a) {
314
415
  return false;
315
416
  }
316
417
  }
317
- /**
318
- * Get direct access to sessionStorage (for window_id which always uses sessionStorage)
319
- */
418
+ /** Get sessionStorage instance (for window_id which always uses sessionStorage) */
320
419
  getSessionStorage() {
321
420
  var _a;
322
- if (!this.canUseSessionStorage()) {
323
- return null;
324
- }
325
- return (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage) !== null && _a !== void 0 ? _a : null;
421
+ return this.canUseSessionStorage()
422
+ ? ((_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage) !== null && _a !== void 0 ? _a : null)
423
+ : null;
326
424
  }
327
- /**
328
- * Update storage method at runtime
329
- */
425
+ /** Update storage method at runtime */
330
426
  setMethod(method) {
331
427
  this.method = method;
332
428
  }
333
- /**
334
- * Get current storage method
335
- */
429
+ /** Get current storage method */
336
430
  getMethod() {
337
431
  return this.method;
338
432
  }
339
433
  }
340
434
  exports.StorageManager = StorageManager;
341
- /**
342
- * Create a shared storage instance
343
- * Use this for creating storage managers with consistent settings
344
- */
435
+ /** Factory function to create a StorageManager with default settings */
345
436
  function createStorageManager(method, cross_subdomain) {
346
- return new StorageManager({
347
- method,
348
- cross_subdomain,
349
- sameSite: "Lax",
350
- });
437
+ return new StorageManager({ method, cross_subdomain, sameSite: "Lax" });
351
438
  }
@@ -94,7 +94,21 @@ export declare class UserManager {
94
94
  */
95
95
  update_referrer_info(): void;
96
96
  /**
97
- * Load user identity from storage
97
+ * Load user identity from storage.
98
+ *
99
+ * For traditional SSR websites where each page navigation reloads JavaScript,
100
+ * identity MUST be persisted immediately when generated to ensure the same
101
+ * anonymous_id is used across all page loads.
102
+ *
103
+ * Flow:
104
+ * 1. Load from storage (reads cookies first for critical properties in SSR mode)
105
+ * 2. Generate new IDs if not found
106
+ * 3. Immediately persist to storage (saved to both localStorage and cookies)
107
+ *
108
+ * With `localStorage+cookie` persistence (default):
109
+ * - Critical properties are stored in cookies for SSR compatibility
110
+ * - Full data is stored in localStorage for fast SPA-style access
111
+ * - Cookies ensure identity persists across full page reloads
98
112
  */
99
113
  private loadUserIdentity;
100
114
  /**