@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.
@@ -1,16 +1,35 @@
1
1
  "use strict";
2
+ /**
3
+ * User Manager - Handles user identity and properties
4
+ *
5
+ * Uses shared StorageManager for consistent storage operations.
6
+ *
7
+ * Manages:
8
+ * - anonymous_id: Generated ID for anonymous users
9
+ * - distinct_id: User-provided ID after identification
10
+ * - device_id: Persistent device identifier
11
+ * - user_properties: Custom properties set via identify/setUserProperties
12
+ * - user_state: "anonymous" or "identified"
13
+ */
2
14
  Object.defineProperty(exports, "__esModule", { value: true });
3
15
  exports.UserManager = void 0;
4
16
  const constants_1 = require("./constants");
5
17
  const utils_1 = require("./utils");
6
18
  const event_utils_1 = require("./utils/event-utils");
19
+ const storage_1 = require("./storage");
7
20
  class UserManager {
8
21
  constructor(storageMethod = "localStorage", domain) {
9
22
  this._cachedPersonProperties = null; // Cache for deduplication
10
- this.storageMethod = storageMethod;
11
- this.domain = domain;
23
+ this.storage = new storage_1.StorageManager({
24
+ method: storageMethod,
25
+ domain,
26
+ sameSite: "Lax",
27
+ });
12
28
  this.userIdentity = this.loadUserIdentity();
13
29
  }
30
+ // ============================================================================
31
+ // Public Getters
32
+ // ============================================================================
14
33
  /**
15
34
  * Get current user identity
16
35
  */
@@ -30,8 +49,7 @@ class UserManager {
30
49
  if (!this.userIdentity.anonymous_id) {
31
50
  // Regenerate if somehow undefined
32
51
  this.userIdentity.anonymous_id = this.generateAnonymousId();
33
- // Save to storage
34
- this.setStoredValue(constants_1.ANONYMOUS_ID_KEY, this.userIdentity.anonymous_id);
52
+ this.storage.set(constants_1.ANONYMOUS_ID_KEY, this.userIdentity.anonymous_id, storage_1.USER_COOKIE_MAX_AGE);
35
53
  }
36
54
  return this.userIdentity.anonymous_id;
37
55
  }
@@ -41,6 +59,27 @@ class UserManager {
41
59
  getUserProperties() {
42
60
  return { ...this.userIdentity.properties };
43
61
  }
62
+ /**
63
+ * Get the effective ID for event tracking
64
+ */
65
+ getEffectiveId() {
66
+ return this.userIdentity.distinct_id || this.getAnonymousId();
67
+ }
68
+ /**
69
+ * Get current device ID
70
+ */
71
+ getDeviceId() {
72
+ return this.userIdentity.device_id;
73
+ }
74
+ /**
75
+ * Get current user state
76
+ */
77
+ getUserState() {
78
+ return this.userIdentity.user_state;
79
+ }
80
+ // ============================================================================
81
+ // Identity Operations
82
+ // ============================================================================
44
83
  /**
45
84
  * Identify a user with distinct ID and properties
46
85
  */
@@ -148,25 +187,6 @@ class UserManager {
148
187
  }
149
188
  /**
150
189
  * Set user properties without changing distinct ID
151
- * Sets properties on the person profile associated with the current distinct_id
152
- *
153
- * @example
154
- * ```js
155
- * // Set properties that can be updated
156
- * vt.setUserProperties({ name: 'John Doe', email: 'john@example.com' })
157
- * ```
158
- *
159
- * @example
160
- * ```js
161
- * // Set properties with $set and $set_once operations
162
- * vt.setUserProperties(
163
- * { name: 'John Doe', last_login: new Date().toISOString() }, // $set properties
164
- * { first_login: new Date().toISOString() } // $set_once properties
165
- * )
166
- * ```
167
- *
168
- * @param userPropertiesToSet Optional: Properties to set (can be updated)
169
- * @param userPropertiesToSetOnce Optional: Properties to set once (preserves first value)
170
190
  */
171
191
  setUserProperties(userPropertiesToSet, userPropertiesToSetOnce) {
172
192
  // Return early if no properties provided
@@ -203,23 +223,9 @@ class UserManager {
203
223
  // Note: Event sending is now handled by VTilt.setUserProperties()
204
224
  return true;
205
225
  }
206
- /**
207
- * Get hash for person properties
208
- * Used for deduplication of identical setUserProperties calls
209
- */
210
- getPersonPropertiesHash(distinctId, userPropertiesToSet, userPropertiesToSetOnce) {
211
- // Create deterministic hash from distinct_id and properties
212
- return JSON.stringify({
213
- distinct_id: distinctId,
214
- userPropertiesToSet,
215
- userPropertiesToSetOnce,
216
- });
217
- }
218
226
  /**
219
227
  * Reset user identity (logout)
220
228
  * Generates new anonymous ID, clears user data, optionally resets device ID
221
- *
222
- * @param reset_device_id - If true, also resets device_id. Default: false (preserves device_id)
223
229
  */
224
230
  reset(reset_device_id) {
225
231
  // Generate completely new anonymous ID (don't revert to original)
@@ -245,24 +251,9 @@ class UserManager {
245
251
  };
246
252
  this.saveUserIdentity();
247
253
  }
248
- /**
249
- * Get the effective ID for event tracking
250
- */
251
- getEffectiveId() {
252
- return this.userIdentity.distinct_id || this.getAnonymousId();
253
- }
254
- /**
255
- * Get current device ID
256
- */
257
- getDeviceId() {
258
- return this.userIdentity.device_id;
259
- }
260
- /**
261
- * Get current user state
262
- */
263
- getUserState() {
264
- return this.userIdentity.user_state;
265
- }
254
+ // ============================================================================
255
+ // Internal Setters (for VTilt class)
256
+ // ============================================================================
266
257
  /**
267
258
  * Update distinct ID (internal use - for VTilt)
268
259
  */
@@ -312,21 +303,11 @@ class UserManager {
312
303
  this.saveUserIdentity();
313
304
  }
314
305
  }
315
- /**
316
- * Check if distinct ID is string-like (hardcoded string) - public for validation
317
- * This is a wrapper to expose the private method
318
- */
319
- isDistinctIdStringLikePublic(distinctId) {
320
- return this.isDistinctIdStringLike(distinctId);
321
- }
306
+ // ============================================================================
307
+ // Alias Operations
308
+ // ============================================================================
322
309
  /**
323
310
  * Create an alias to link two distinct IDs
324
- * If original is not provided, uses current distinct_id
325
- * If alias matches original, returns null (caller should use identify instead)
326
- *
327
- * @param alias - A unique identifier that you want to use for this user in the future
328
- * @param original - The current identifier being used for this user (optional, defaults to current distinct_id)
329
- * @returns AliasEvent if alias was created, null if alias matches original or invalid
330
311
  */
331
312
  createAlias(alias, original) {
332
313
  if (!this.isValidDistinctId(alias)) {
@@ -355,75 +336,58 @@ class UserManager {
355
336
  return aliasEvent;
356
337
  }
357
338
  /**
358
- * Validate distinct ID to prevent hardcoded strings
339
+ * Check if distinct ID is string-like (hardcoded string) - public for validation
359
340
  */
360
- isValidDistinctId(distinctId) {
361
- if (!distinctId || typeof distinctId !== "string") {
362
- return false;
363
- }
364
- // Validation patterns
365
- const invalidPatterns = [
366
- "null",
367
- "undefined",
368
- "false",
369
- "true",
370
- "anonymous",
371
- "anon",
372
- "user",
373
- "test",
374
- "guest",
375
- "visitor",
376
- "unknown",
377
- "none",
378
- ];
379
- const lower = distinctId.toLowerCase().trim();
380
- if (invalidPatterns.includes(lower)) {
381
- return false;
382
- }
383
- // Check for empty or whitespace-only strings
384
- if (distinctId.trim().length === 0) {
385
- return false;
341
+ isDistinctIdStringLikePublic(distinctId) {
342
+ return this.isDistinctIdStringLike(distinctId);
343
+ }
344
+ // ============================================================================
345
+ // Initial Person Info
346
+ // ============================================================================
347
+ /**
348
+ * Set initial person info
349
+ */
350
+ set_initial_person_info(maskPersonalDataProperties, customPersonalDataProperties) {
351
+ const stored = this.getStoredUserProperties();
352
+ // Check if already set (backwards compatibility check)
353
+ if (stored[constants_1.INITIAL_PERSON_INFO]) {
354
+ return;
386
355
  }
387
- return true;
356
+ const personInfo = (0, event_utils_1.getPersonInfo)(maskPersonalDataProperties, customPersonalDataProperties);
357
+ this.register_once({
358
+ [constants_1.INITIAL_PERSON_INFO]: personInfo,
359
+ }, undefined);
388
360
  }
389
361
  /**
390
- * Check if distinct ID is string-like (hardcoded string)
362
+ * Get initial props
391
363
  */
392
- isDistinctIdStringLike(distinctId) {
393
- if (!distinctId || typeof distinctId !== "string") {
394
- return false;
364
+ get_initial_props() {
365
+ const stored = this.getStoredUserProperties();
366
+ const initialPersonInfo = stored[constants_1.INITIAL_PERSON_INFO];
367
+ if (!initialPersonInfo) {
368
+ return {};
395
369
  }
396
- // Hardcoded string patterns
397
- const hardcodedPatterns = [
398
- "null",
399
- "undefined",
400
- "false",
401
- "true",
402
- "anonymous",
403
- "anon",
404
- "user",
405
- "test",
406
- "guest",
407
- "visitor",
408
- "unknown",
409
- "none",
410
- "demo",
411
- "example",
412
- "sample",
413
- "placeholder",
414
- ];
415
- const lower = distinctId.toLowerCase().trim();
416
- return hardcodedPatterns.includes(lower);
370
+ return (0, event_utils_1.getInitialPersonPropsFromInfo)(initialPersonInfo);
417
371
  }
372
+ /**
373
+ * Update referrer info
374
+ */
375
+ update_referrer_info() {
376
+ const referrerInfo = (0, event_utils_1.getReferrerInfo)();
377
+ this.register_once(referrerInfo, undefined);
378
+ }
379
+ // ============================================================================
380
+ // Storage Operations (using StorageManager)
381
+ // ============================================================================
418
382
  /**
419
383
  * Load user identity from storage
420
384
  */
421
385
  loadUserIdentity() {
422
- const anonymousId = this.getStoredValue(constants_1.ANONYMOUS_ID_KEY) || this.generateAnonymousId();
423
- const distinctId = this.getStoredValue(constants_1.DISTINCT_ID_KEY) || null;
424
- const deviceId = this.getStoredValue(constants_1.DEVICE_ID_KEY) || this.generateDeviceId();
386
+ const anonymousId = this.storage.get(constants_1.ANONYMOUS_ID_KEY) || this.generateAnonymousId();
387
+ const distinctId = this.storage.get(constants_1.DISTINCT_ID_KEY) || null;
388
+ const deviceId = this.storage.get(constants_1.DEVICE_ID_KEY) || this.generateDeviceId();
425
389
  const properties = this.getStoredUserProperties();
426
- const userState = this.getStoredValue(constants_1.USER_STATE_KEY) ||
390
+ const userState = this.storage.get(constants_1.USER_STATE_KEY) ||
427
391
  "anonymous";
428
392
  // Ensure anonymous_id is never undefined
429
393
  const safeAnonymousId = anonymousId || this.generateAnonymousId();
@@ -440,167 +404,31 @@ class UserManager {
440
404
  * Save user identity to storage
441
405
  */
442
406
  saveUserIdentity() {
443
- this.setStoredValue(constants_1.ANONYMOUS_ID_KEY, this.userIdentity.anonymous_id);
444
- this.setStoredValue(constants_1.DEVICE_ID_KEY, this.userIdentity.device_id);
445
- this.setStoredValue(constants_1.USER_STATE_KEY, this.userIdentity.user_state);
407
+ this.storage.set(constants_1.ANONYMOUS_ID_KEY, this.userIdentity.anonymous_id, storage_1.USER_COOKIE_MAX_AGE);
408
+ this.storage.set(constants_1.DEVICE_ID_KEY, this.userIdentity.device_id, storage_1.USER_COOKIE_MAX_AGE);
409
+ this.storage.set(constants_1.USER_STATE_KEY, this.userIdentity.user_state, storage_1.USER_COOKIE_MAX_AGE);
446
410
  if (this.userIdentity.distinct_id) {
447
- this.setStoredValue(constants_1.DISTINCT_ID_KEY, this.userIdentity.distinct_id);
411
+ this.storage.set(constants_1.DISTINCT_ID_KEY, this.userIdentity.distinct_id, storage_1.USER_COOKIE_MAX_AGE);
448
412
  }
449
413
  else {
450
- this.removeStoredValue(constants_1.DISTINCT_ID_KEY);
414
+ this.storage.remove(constants_1.DISTINCT_ID_KEY);
451
415
  }
452
416
  this.setStoredUserProperties(this.userIdentity.properties);
453
417
  }
454
- /**
455
- * Generate a new anonymous ID
456
- */
457
- generateAnonymousId() {
458
- return `anon_${(0, utils_1.uuidv4)()}`;
459
- }
460
- /**
461
- * Generate a new device ID
462
- */
463
- generateDeviceId() {
464
- return `device_${(0, utils_1.uuidv4)()}`;
465
- }
466
- /**
467
- * Get stored value from storage
468
- */
469
- getStoredValue(key) {
470
- try {
471
- // Memory mode doesn't persist - return null (will use in-memory userIdentity)
472
- if (this.storageMethod === constants_1.PERSISTENCE_METHODS.memory) {
473
- return null;
474
- }
475
- if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage ||
476
- this.storageMethod === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
477
- // Try localStorage first for localStorage and localStorage+cookie modes
478
- const value = localStorage.getItem(key);
479
- if (value !== null) {
480
- return value;
481
- }
482
- // Fall back to cookie for localStorage+cookie mode
483
- if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
484
- return this.getCookieValue(key);
485
- }
486
- return null;
487
- }
488
- else {
489
- // Cookie-only mode
490
- return this.getCookieValue(key);
491
- }
492
- }
493
- catch (error) {
494
- // If storage access fails, return null
495
- console.warn("Failed to access storage:", error);
496
- return null;
497
- }
498
- }
499
- /**
500
- * Set stored value in storage
501
- */
502
- setStoredValue(key, value) {
503
- try {
504
- // Memory mode doesn't persist
505
- if (this.storageMethod === constants_1.PERSISTENCE_METHODS.memory) {
506
- return;
507
- }
508
- if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage) {
509
- localStorage.setItem(key, value);
510
- }
511
- else if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
512
- // Store in both localStorage and cookie
513
- localStorage.setItem(key, value);
514
- this.setCookieValue(key, value);
515
- }
516
- else {
517
- // Cookie-only mode
518
- this.setCookieValue(key, value);
519
- }
520
- }
521
- catch (error) {
522
- // If storage access fails, log warning but don't throw
523
- console.warn("Failed to save to storage:", error);
524
- }
525
- }
526
- /**
527
- * Remove stored value from storage
528
- */
529
- removeStoredValue(key) {
530
- // Memory mode doesn't persist
531
- if (this.storageMethod === constants_1.PERSISTENCE_METHODS.memory) {
532
- return;
533
- }
534
- if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage) {
535
- localStorage.removeItem(key);
536
- }
537
- else if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
538
- // Remove from both localStorage and cookie
539
- localStorage.removeItem(key);
540
- this.removeCookieValue(key);
541
- }
542
- else {
543
- // Cookie-only mode
544
- this.removeCookieValue(key);
545
- }
546
- }
547
418
  /**
548
419
  * Get user properties from storage
549
420
  */
550
421
  getStoredUserProperties() {
551
- const stored = this.getStoredValue(constants_1.USER_PROPERTIES_KEY);
552
- if (!stored) {
553
- return {};
554
- }
555
- try {
556
- return JSON.parse(stored);
557
- }
558
- catch (_a) {
559
- return {};
560
- }
422
+ return this.storage.getJSON(constants_1.USER_PROPERTIES_KEY) || {};
561
423
  }
562
424
  /**
563
425
  * Set user properties in storage
564
426
  */
565
427
  setStoredUserProperties(properties) {
566
- this.setStoredValue(constants_1.USER_PROPERTIES_KEY, JSON.stringify(properties));
567
- }
568
- /**
569
- * Get cookie value
570
- */
571
- getCookieValue(name) {
572
- const cookies = document.cookie.split(";");
573
- for (const cookie of cookies) {
574
- const [key, value] = cookie.trim().split("=");
575
- if (key === name) {
576
- return decodeURIComponent(value);
577
- }
578
- }
579
- return null;
580
- }
581
- /**
582
- * Set cookie value
583
- */
584
- setCookieValue(name, value) {
585
- let cookieValue = `${name}=${encodeURIComponent(value)}; Max-Age=31536000; path=/; secure; SameSite=Lax`;
586
- if (this.domain) {
587
- cookieValue += `; domain=${this.domain}`;
588
- }
589
- document.cookie = cookieValue;
590
- }
591
- /**
592
- * Remove cookie value
593
- */
594
- removeCookieValue(name) {
595
- let cookieValue = `${name}=; Max-Age=0; path=/`;
596
- if (this.domain) {
597
- cookieValue += `; domain=${this.domain}`;
598
- }
599
- document.cookie = cookieValue;
428
+ this.storage.setJSON(constants_1.USER_PROPERTIES_KEY, properties, storage_1.USER_COOKIE_MAX_AGE);
600
429
  }
601
430
  /**
602
431
  * Register a value once (only if not already set)
603
- * Stores properties in localStorage only if they don't already exist
604
432
  */
605
433
  register_once(props, defaultValues) {
606
434
  const stored = this.getStoredUserProperties();
@@ -627,41 +455,103 @@ class UserManager {
627
455
  this.setStoredUserProperties(stored);
628
456
  }
629
457
  }
458
+ // ============================================================================
459
+ // Utility Methods
460
+ // ============================================================================
630
461
  /**
631
- * Set initial person info
632
- * Stores referrer and URL info on first visit for generating $initial_* properties
462
+ * Generate a new anonymous ID
633
463
  */
634
- set_initial_person_info(maskPersonalDataProperties, customPersonalDataProperties) {
635
- const stored = this.getStoredUserProperties();
636
- // Check if already set (backwards compatibility check)
637
- if (stored[constants_1.INITIAL_PERSON_INFO]) {
638
- return;
464
+ generateAnonymousId() {
465
+ return `anon_${(0, utils_1.uuidv4)()}`;
466
+ }
467
+ /**
468
+ * Generate a new device ID
469
+ */
470
+ generateDeviceId() {
471
+ return `device_${(0, utils_1.uuidv4)()}`;
472
+ }
473
+ /**
474
+ * Get hash for person properties (for deduplication)
475
+ */
476
+ getPersonPropertiesHash(distinctId, userPropertiesToSet, userPropertiesToSetOnce) {
477
+ return JSON.stringify({
478
+ distinct_id: distinctId,
479
+ userPropertiesToSet,
480
+ userPropertiesToSetOnce,
481
+ });
482
+ }
483
+ /**
484
+ * Validate distinct ID
485
+ */
486
+ isValidDistinctId(distinctId) {
487
+ if (!distinctId || typeof distinctId !== "string") {
488
+ return false;
639
489
  }
640
- const personInfo = (0, event_utils_1.getPersonInfo)(maskPersonalDataProperties, customPersonalDataProperties);
641
- this.register_once({
642
- [constants_1.INITIAL_PERSON_INFO]: personInfo,
643
- }, undefined);
490
+ const invalidPatterns = [
491
+ "null",
492
+ "undefined",
493
+ "false",
494
+ "true",
495
+ "anonymous",
496
+ "anon",
497
+ "user",
498
+ "test",
499
+ "guest",
500
+ "visitor",
501
+ "unknown",
502
+ "none",
503
+ ];
504
+ const lower = distinctId.toLowerCase().trim();
505
+ if (invalidPatterns.includes(lower)) {
506
+ return false;
507
+ }
508
+ if (distinctId.trim().length === 0) {
509
+ return false;
510
+ }
511
+ return true;
644
512
  }
645
513
  /**
646
- * Get initial props
647
- * Generates $initial_* properties from stored initial person info
648
- * These are sent with events as $set_once to preserve first values
514
+ * Check if distinct ID is string-like (hardcoded string)
649
515
  */
650
- get_initial_props() {
651
- const stored = this.getStoredUserProperties();
652
- const initialPersonInfo = stored[constants_1.INITIAL_PERSON_INFO];
653
- if (!initialPersonInfo) {
654
- return {};
516
+ isDistinctIdStringLike(distinctId) {
517
+ if (!distinctId || typeof distinctId !== "string") {
518
+ return false;
655
519
  }
656
- return (0, event_utils_1.getInitialPersonPropsFromInfo)(initialPersonInfo);
520
+ const hardcodedPatterns = [
521
+ "null",
522
+ "undefined",
523
+ "false",
524
+ "true",
525
+ "anonymous",
526
+ "anon",
527
+ "user",
528
+ "test",
529
+ "guest",
530
+ "visitor",
531
+ "unknown",
532
+ "none",
533
+ "demo",
534
+ "example",
535
+ "sample",
536
+ "placeholder",
537
+ ];
538
+ const lower = distinctId.toLowerCase().trim();
539
+ return hardcodedPatterns.includes(lower);
657
540
  }
541
+ // ============================================================================
542
+ // Storage Management
543
+ // ============================================================================
658
544
  /**
659
- * Update referrer info
660
- * Stores current referrer information if not already stored
545
+ * Update storage method at runtime
661
546
  */
662
- update_referrer_info() {
663
- const referrerInfo = (0, event_utils_1.getReferrerInfo)();
664
- this.register_once(referrerInfo, undefined);
547
+ updateStorageMethod(method, domain) {
548
+ this.storage = new storage_1.StorageManager({
549
+ method,
550
+ domain,
551
+ sameSite: "Lax",
552
+ });
553
+ // Reload identity from new storage
554
+ this.userIdentity = this.loadUserIdentity();
665
555
  }
666
556
  }
667
557
  exports.UserManager = UserManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v-tilt/browser",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "vTilt browser tracking library",
5
5
  "main": "dist/main.js",
6
6
  "module": "dist/module.js",