@v-tilt/browser 1.1.2 → 1.1.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 ADDED
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ /**
3
+ * Unified Storage Manager - Following PostHog's PostHogPersistence pattern
4
+ *
5
+ * Single class handles all storage operations for both sessions and users.
6
+ * Reduces code duplication and ensures consistent cookie handling.
7
+ *
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)
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.StorageManager = exports.USER_COOKIE_MAX_AGE = exports.SESSION_COOKIE_MAX_AGE = void 0;
17
+ exports.createStorageManager = createStorageManager;
18
+ const constants_1 = require("./constants");
19
+ const globals_1 = require("./utils/globals");
20
+ // Default cookie TTLs
21
+ exports.SESSION_COOKIE_MAX_AGE = 1800; // 30 minutes
22
+ exports.USER_COOKIE_MAX_AGE = 31536000; // 1 year
23
+ /**
24
+ * Unified Storage Manager
25
+ * Provides consistent storage operations across all persistence methods
26
+ */
27
+ class StorageManager {
28
+ constructor(options) {
29
+ var _a;
30
+ this.memoryStorage = new Map();
31
+ this.method = options.method;
32
+ this.domain = options.domain;
33
+ this.sameSite = options.sameSite || "Lax";
34
+ // Auto-detect secure flag from protocol
35
+ this.secure =
36
+ (_a = options.secure) !== null && _a !== void 0 ? _a : (typeof location !== "undefined" && location.protocol === "https:");
37
+ }
38
+ // ============================================================================
39
+ // Public API - Simple key-value operations
40
+ // ============================================================================
41
+ /**
42
+ * Get a value from storage
43
+ */
44
+ get(key) {
45
+ var _a;
46
+ try {
47
+ if (this.method === constants_1.PERSISTENCE_METHODS.memory) {
48
+ return (_a = this.memoryStorage.get(key)) !== null && _a !== void 0 ? _a : null;
49
+ }
50
+ if (this.usesLocalStorage()) {
51
+ const value = localStorage.getItem(key);
52
+ if (value !== null) {
53
+ return value;
54
+ }
55
+ // Fall back to cookie for localStorage+cookie mode
56
+ if (this.method === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
57
+ return this.getCookie(key);
58
+ }
59
+ return null;
60
+ }
61
+ if (this.usesSessionStorage()) {
62
+ return sessionStorage.getItem(key);
63
+ }
64
+ // Cookie-only mode
65
+ return this.getCookie(key);
66
+ }
67
+ catch (error) {
68
+ console.warn(`[StorageManager] Failed to get "${key}":`, error);
69
+ return null;
70
+ }
71
+ }
72
+ /**
73
+ * Set a value in storage
74
+ * @param maxAge - Cookie max age in seconds (ignored for localStorage/sessionStorage)
75
+ */
76
+ set(key, value, maxAge) {
77
+ try {
78
+ if (this.method === constants_1.PERSISTENCE_METHODS.memory) {
79
+ this.memoryStorage.set(key, value);
80
+ return;
81
+ }
82
+ if (this.usesLocalStorage()) {
83
+ localStorage.setItem(key, value);
84
+ // Also set cookie for localStorage+cookie mode
85
+ if (this.method === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
86
+ this.setCookie(key, value, maxAge || exports.USER_COOKIE_MAX_AGE);
87
+ }
88
+ return;
89
+ }
90
+ if (this.usesSessionStorage()) {
91
+ sessionStorage.setItem(key, value);
92
+ return;
93
+ }
94
+ // Cookie-only mode
95
+ this.setCookie(key, value, maxAge || exports.USER_COOKIE_MAX_AGE);
96
+ }
97
+ catch (error) {
98
+ console.warn(`[StorageManager] Failed to set "${key}":`, error);
99
+ }
100
+ }
101
+ /**
102
+ * Remove a value from storage
103
+ */
104
+ remove(key) {
105
+ try {
106
+ if (this.method === constants_1.PERSISTENCE_METHODS.memory) {
107
+ this.memoryStorage.delete(key);
108
+ return;
109
+ }
110
+ if (this.usesLocalStorage()) {
111
+ localStorage.removeItem(key);
112
+ if (this.method === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
113
+ this.removeCookie(key);
114
+ }
115
+ return;
116
+ }
117
+ if (this.usesSessionStorage()) {
118
+ sessionStorage.removeItem(key);
119
+ return;
120
+ }
121
+ // Cookie-only mode
122
+ this.removeCookie(key);
123
+ }
124
+ catch (error) {
125
+ console.warn(`[StorageManager] Failed to remove "${key}":`, error);
126
+ }
127
+ }
128
+ // ============================================================================
129
+ // JSON operations with optional expiry (for session data)
130
+ // ============================================================================
131
+ /**
132
+ * Get JSON value with expiry check
133
+ * Returns null if expired or not found
134
+ */
135
+ getWithExpiry(key) {
136
+ const raw = this.get(key);
137
+ if (!raw) {
138
+ return null;
139
+ }
140
+ try {
141
+ const item = JSON.parse(raw);
142
+ // Check expiry if set
143
+ if (item.expiry && Date.now() > item.expiry) {
144
+ this.remove(key);
145
+ return null;
146
+ }
147
+ return item.value;
148
+ }
149
+ catch (_a) {
150
+ // Not JSON or invalid format - return raw value if compatible
151
+ return null;
152
+ }
153
+ }
154
+ /**
155
+ * Set JSON value with optional expiry
156
+ * @param ttlMs - Time to live in milliseconds
157
+ */
158
+ setWithExpiry(key, value, ttlMs) {
159
+ const item = {
160
+ value,
161
+ ...(ttlMs ? { expiry: Date.now() + ttlMs } : {}),
162
+ };
163
+ this.set(key, JSON.stringify(item));
164
+ }
165
+ /**
166
+ * Get JSON value (no expiry check)
167
+ */
168
+ getJSON(key) {
169
+ const raw = this.get(key);
170
+ if (!raw) {
171
+ return null;
172
+ }
173
+ try {
174
+ return JSON.parse(raw);
175
+ }
176
+ catch (_a) {
177
+ return null;
178
+ }
179
+ }
180
+ /**
181
+ * Set JSON value
182
+ */
183
+ setJSON(key, value, maxAge) {
184
+ this.set(key, JSON.stringify(value), maxAge);
185
+ }
186
+ // ============================================================================
187
+ // Cookie operations (internal)
188
+ // ============================================================================
189
+ getCookie(name) {
190
+ if (typeof document === "undefined") {
191
+ return null;
192
+ }
193
+ const cookies = document.cookie.split(";");
194
+ for (const cookie of cookies) {
195
+ const [key, ...valueParts] = cookie.trim().split("=");
196
+ if (key === name) {
197
+ const value = valueParts.join("="); // Handle values with = in them
198
+ try {
199
+ return decodeURIComponent(value);
200
+ }
201
+ catch (_a) {
202
+ return value;
203
+ }
204
+ }
205
+ }
206
+ return null;
207
+ }
208
+ setCookie(name, value, maxAge) {
209
+ if (typeof document === "undefined") {
210
+ return;
211
+ }
212
+ let cookieString = `${name}=${encodeURIComponent(value)}`;
213
+ cookieString += `; Max-Age=${maxAge}`;
214
+ cookieString += `; path=/`;
215
+ cookieString += `; SameSite=${this.sameSite}`;
216
+ if (this.secure) {
217
+ cookieString += `; Secure`;
218
+ }
219
+ if (this.domain) {
220
+ cookieString += `; domain=${this.domain}`;
221
+ }
222
+ document.cookie = cookieString;
223
+ }
224
+ removeCookie(name) {
225
+ if (typeof document === "undefined") {
226
+ return;
227
+ }
228
+ // Set cookie with expired date
229
+ let cookieString = `${name}=; Max-Age=0; path=/`;
230
+ if (this.domain) {
231
+ cookieString += `; domain=${this.domain}`;
232
+ }
233
+ document.cookie = cookieString;
234
+ // Also try without domain (in case it was set without)
235
+ document.cookie = `${name}=; Max-Age=0; path=/`;
236
+ }
237
+ // ============================================================================
238
+ // Helper methods
239
+ // ============================================================================
240
+ usesLocalStorage() {
241
+ return (this.method === constants_1.PERSISTENCE_METHODS.localStorage ||
242
+ this.method === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie);
243
+ }
244
+ usesSessionStorage() {
245
+ return this.method === constants_1.PERSISTENCE_METHODS.sessionStorage;
246
+ }
247
+ /**
248
+ * Check if sessionStorage is available
249
+ */
250
+ canUseSessionStorage() {
251
+ if (typeof globals_1.window === "undefined" || !(globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage)) {
252
+ return false;
253
+ }
254
+ try {
255
+ const testKey = "__vt_storage_test__";
256
+ globals_1.window.sessionStorage.setItem(testKey, "test");
257
+ globals_1.window.sessionStorage.removeItem(testKey);
258
+ return true;
259
+ }
260
+ catch (_a) {
261
+ return false;
262
+ }
263
+ }
264
+ /**
265
+ * Get direct access to sessionStorage (for window_id which always uses sessionStorage)
266
+ */
267
+ getSessionStorage() {
268
+ var _a;
269
+ if (!this.canUseSessionStorage()) {
270
+ return null;
271
+ }
272
+ return (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.sessionStorage) !== null && _a !== void 0 ? _a : null;
273
+ }
274
+ /**
275
+ * Update storage method at runtime
276
+ */
277
+ setMethod(method) {
278
+ this.method = method;
279
+ }
280
+ /**
281
+ * Get current storage method
282
+ */
283
+ getMethod() {
284
+ return this.method;
285
+ }
286
+ }
287
+ exports.StorageManager = StorageManager;
288
+ /**
289
+ * Create a shared storage instance
290
+ * Use this for creating storage managers with consistent settings
291
+ */
292
+ function createStorageManager(method, domain) {
293
+ return new StorageManager({
294
+ method,
295
+ domain,
296
+ sameSite: "Lax",
297
+ });
298
+ }
@@ -1,7 +1,18 @@
1
+ /**
2
+ * User Manager - Handles user identity and properties
3
+ *
4
+ * Uses shared StorageManager for consistent storage operations.
5
+ *
6
+ * Manages:
7
+ * - anonymous_id: Generated ID for anonymous users
8
+ * - distinct_id: User-provided ID after identification
9
+ * - device_id: Persistent device identifier
10
+ * - user_properties: Custom properties set via identify/setUserProperties
11
+ * - user_state: "anonymous" or "identified"
12
+ */
1
13
  import { UserIdentity, AliasEvent, PersistenceMethod } from "./types";
2
14
  export declare class UserManager {
3
- private storageMethod;
4
- private domain?;
15
+ private storage;
5
16
  private userIdentity;
6
17
  private _cachedPersonProperties;
7
18
  constructor(storageMethod?: PersistenceMethod, domain?: string);
@@ -21,57 +32,31 @@ export declare class UserManager {
21
32
  * Get current user properties
22
33
  */
23
34
  getUserProperties(): Record<string, any>;
35
+ /**
36
+ * Get the effective ID for event tracking
37
+ */
38
+ getEffectiveId(): string;
39
+ /**
40
+ * Get current device ID
41
+ */
42
+ getDeviceId(): string;
43
+ /**
44
+ * Get current user state
45
+ */
46
+ getUserState(): "anonymous" | "identified";
24
47
  /**
25
48
  * Identify a user with distinct ID and properties
26
49
  */
27
50
  identify(newDistinctId?: string, userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
28
51
  /**
29
52
  * Set user properties without changing distinct ID
30
- * Sets properties on the person profile associated with the current distinct_id
31
- *
32
- * @example
33
- * ```js
34
- * // Set properties that can be updated
35
- * vt.setUserProperties({ name: 'John Doe', email: 'john@example.com' })
36
- * ```
37
- *
38
- * @example
39
- * ```js
40
- * // Set properties with $set and $set_once operations
41
- * vt.setUserProperties(
42
- * { name: 'John Doe', last_login: new Date().toISOString() }, // $set properties
43
- * { first_login: new Date().toISOString() } // $set_once properties
44
- * )
45
- * ```
46
- *
47
- * @param userPropertiesToSet Optional: Properties to set (can be updated)
48
- * @param userPropertiesToSetOnce Optional: Properties to set once (preserves first value)
49
53
  */
50
54
  setUserProperties(userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): boolean;
51
- /**
52
- * Get hash for person properties
53
- * Used for deduplication of identical setUserProperties calls
54
- */
55
- private getPersonPropertiesHash;
56
55
  /**
57
56
  * Reset user identity (logout)
58
57
  * Generates new anonymous ID, clears user data, optionally resets device ID
59
- *
60
- * @param reset_device_id - If true, also resets device_id. Default: false (preserves device_id)
61
58
  */
62
59
  reset(reset_device_id?: boolean): void;
63
- /**
64
- * Get the effective ID for event tracking
65
- */
66
- getEffectiveId(): string;
67
- /**
68
- * Get current device ID
69
- */
70
- getDeviceId(): string;
71
- /**
72
- * Get current user state
73
- */
74
- getUserState(): "anonymous" | "identified";
75
60
  /**
76
61
  * Update distinct ID (internal use - for VTilt)
77
62
  */
@@ -88,29 +73,26 @@ export declare class UserManager {
88
73
  * Set device ID if not already set (internal use - for VTilt)
89
74
  */
90
75
  ensureDeviceId(deviceId: string): void;
76
+ /**
77
+ * Create an alias to link two distinct IDs
78
+ */
79
+ createAlias(alias: string, original?: string): AliasEvent | null;
91
80
  /**
92
81
  * Check if distinct ID is string-like (hardcoded string) - public for validation
93
- * This is a wrapper to expose the private method
94
82
  */
95
83
  isDistinctIdStringLikePublic(distinctId: string): boolean;
96
84
  /**
97
- * Create an alias to link two distinct IDs
98
- * If original is not provided, uses current distinct_id
99
- * If alias matches original, returns null (caller should use identify instead)
100
- *
101
- * @param alias - A unique identifier that you want to use for this user in the future
102
- * @param original - The current identifier being used for this user (optional, defaults to current distinct_id)
103
- * @returns AliasEvent if alias was created, null if alias matches original or invalid
85
+ * Set initial person info
104
86
  */
105
- createAlias(alias: string, original?: string): AliasEvent | null;
87
+ set_initial_person_info(maskPersonalDataProperties?: boolean, customPersonalDataProperties?: string[]): void;
106
88
  /**
107
- * Validate distinct ID to prevent hardcoded strings
89
+ * Get initial props
108
90
  */
109
- private isValidDistinctId;
91
+ get_initial_props(): Record<string, any>;
110
92
  /**
111
- * Check if distinct ID is string-like (hardcoded string)
93
+ * Update referrer info
112
94
  */
113
- private isDistinctIdStringLike;
95
+ update_referrer_info(): void;
114
96
  /**
115
97
  * Load user identity from storage
116
98
  */
@@ -119,26 +101,6 @@ export declare class UserManager {
119
101
  * Save user identity to storage
120
102
  */
121
103
  private saveUserIdentity;
122
- /**
123
- * Generate a new anonymous ID
124
- */
125
- private generateAnonymousId;
126
- /**
127
- * Generate a new device ID
128
- */
129
- private generateDeviceId;
130
- /**
131
- * Get stored value from storage
132
- */
133
- private getStoredValue;
134
- /**
135
- * Set stored value in storage
136
- */
137
- private setStoredValue;
138
- /**
139
- * Remove stored value from storage
140
- */
141
- private removeStoredValue;
142
104
  /**
143
105
  * Get user properties from storage
144
106
  */
@@ -148,36 +110,31 @@ export declare class UserManager {
148
110
  */
149
111
  private setStoredUserProperties;
150
112
  /**
151
- * Get cookie value
113
+ * Register a value once (only if not already set)
152
114
  */
153
- private getCookieValue;
115
+ private register_once;
154
116
  /**
155
- * Set cookie value
117
+ * Generate a new anonymous ID
156
118
  */
157
- private setCookieValue;
119
+ private generateAnonymousId;
158
120
  /**
159
- * Remove cookie value
121
+ * Generate a new device ID
160
122
  */
161
- private removeCookieValue;
123
+ private generateDeviceId;
162
124
  /**
163
- * Register a value once (only if not already set)
164
- * Stores properties in localStorage only if they don't already exist
125
+ * Get hash for person properties (for deduplication)
165
126
  */
166
- private register_once;
127
+ private getPersonPropertiesHash;
167
128
  /**
168
- * Set initial person info
169
- * Stores referrer and URL info on first visit for generating $initial_* properties
129
+ * Validate distinct ID
170
130
  */
171
- set_initial_person_info(maskPersonalDataProperties?: boolean, customPersonalDataProperties?: string[]): void;
131
+ private isValidDistinctId;
172
132
  /**
173
- * Get initial props
174
- * Generates $initial_* properties from stored initial person info
175
- * These are sent with events as $set_once to preserve first values
133
+ * Check if distinct ID is string-like (hardcoded string)
176
134
  */
177
- get_initial_props(): Record<string, any>;
135
+ private isDistinctIdStringLike;
178
136
  /**
179
- * Update referrer info
180
- * Stores current referrer information if not already stored
137
+ * Update storage method at runtime
181
138
  */
182
- update_referrer_info(): void;
139
+ updateStorageMethod(method: PersistenceMethod, domain?: string): void;
183
140
  }