@v-tilt/browser 1.0.11 → 1.1.1

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.
Files changed (45) hide show
  1. package/dist/array.js +1 -1
  2. package/dist/array.js.map +1 -1
  3. package/dist/array.no-external.js +1 -1
  4. package/dist/array.no-external.js.map +1 -1
  5. package/dist/constants.d.ts +172 -10
  6. package/dist/main.js +1 -1
  7. package/dist/main.js.map +1 -1
  8. package/dist/module.d.ts +230 -46
  9. package/dist/module.js +1 -1
  10. package/dist/module.js.map +1 -1
  11. package/dist/module.no-external.d.ts +230 -46
  12. package/dist/module.no-external.js +1 -1
  13. package/dist/module.no-external.js.map +1 -1
  14. package/dist/rate-limiter.d.ts +52 -0
  15. package/dist/request-queue.d.ts +78 -0
  16. package/dist/request.d.ts +54 -0
  17. package/dist/retry-queue.d.ts +64 -0
  18. package/dist/session.d.ts +2 -2
  19. package/dist/types.d.ts +154 -37
  20. package/dist/user-manager.d.ts +2 -2
  21. package/dist/vtilt.d.ts +51 -12
  22. package/lib/config.js +6 -13
  23. package/lib/constants.d.ts +172 -10
  24. package/lib/constants.js +644 -439
  25. package/lib/rate-limiter.d.ts +52 -0
  26. package/lib/rate-limiter.js +80 -0
  27. package/lib/request-queue.d.ts +78 -0
  28. package/lib/request-queue.js +156 -0
  29. package/lib/request.d.ts +54 -0
  30. package/lib/request.js +265 -0
  31. package/lib/retry-queue.d.ts +64 -0
  32. package/lib/retry-queue.js +182 -0
  33. package/lib/session.d.ts +2 -2
  34. package/lib/session.js +3 -3
  35. package/lib/types.d.ts +154 -37
  36. package/lib/types.js +6 -0
  37. package/lib/user-manager.d.ts +2 -2
  38. package/lib/user-manager.js +38 -11
  39. package/lib/utils/event-utils.js +88 -82
  40. package/lib/utils/index.js +2 -2
  41. package/lib/utils/request-utils.js +21 -19
  42. package/lib/vtilt.d.ts +51 -12
  43. package/lib/vtilt.js +199 -40
  44. package/lib/web-vitals.js +1 -1
  45. package/package.json +2 -1
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Retry Queue - Exponential Backoff (PostHog-style)
3
+ *
4
+ * Retries failed requests with jittered exponential backoff.
5
+ * Detects online/offline status and pauses retries when offline.
6
+ *
7
+ * Features:
8
+ * - Exponential backoff: 3s, 6s, 12s, 24s... up to 30 minutes
9
+ * - Jitter: +/- 50% to prevent thundering herd
10
+ * - Online/offline detection
11
+ * - Max 10 retries before giving up
12
+ * - Uses sendBeacon on page unload for final attempt
13
+ */
14
+ import type { BatchedRequest } from "./request-queue";
15
+ /**
16
+ * Generates a jittered exponential backoff delay in milliseconds
17
+ *
18
+ * Base value is 3 seconds, doubled with each retry up to 30 minutes max.
19
+ * Each value has +/- 50% jitter.
20
+ *
21
+ * @param retriesPerformedSoFar - Number of retries already attempted
22
+ * @returns Delay in milliseconds
23
+ */
24
+ export declare function pickNextRetryDelay(retriesPerformedSoFar: number): number;
25
+ export interface RetryQueueConfig {
26
+ sendRequest: (req: BatchedRequest) => Promise<{
27
+ statusCode: number;
28
+ }>;
29
+ sendBeacon: (req: BatchedRequest) => void;
30
+ }
31
+ export declare class RetryQueue {
32
+ private _isPolling;
33
+ private _poller?;
34
+ private _pollIntervalMs;
35
+ private _queue;
36
+ private _areWeOnline;
37
+ private _sendRequest;
38
+ private _sendBeacon;
39
+ constructor(config: RetryQueueConfig);
40
+ /**
41
+ * Get current queue length
42
+ */
43
+ get length(): number;
44
+ /**
45
+ * Enqueue a failed request for retry
46
+ */
47
+ enqueue(request: BatchedRequest, retriesPerformedSoFar?: number): void;
48
+ /**
49
+ * Attempt to send a request with retry on failure
50
+ */
51
+ retriableRequest(request: BatchedRequest): Promise<void>;
52
+ /**
53
+ * Start polling for retries
54
+ */
55
+ private _poll;
56
+ /**
57
+ * Flush ready items from the queue
58
+ */
59
+ private _flush;
60
+ /**
61
+ * Flush all queued requests using sendBeacon on page unload
62
+ */
63
+ unload(): void;
64
+ }
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ /**
3
+ * Retry Queue - Exponential Backoff (PostHog-style)
4
+ *
5
+ * Retries failed requests with jittered exponential backoff.
6
+ * Detects online/offline status and pauses retries when offline.
7
+ *
8
+ * Features:
9
+ * - Exponential backoff: 3s, 6s, 12s, 24s... up to 30 minutes
10
+ * - Jitter: +/- 50% to prevent thundering herd
11
+ * - Online/offline detection
12
+ * - Max 10 retries before giving up
13
+ * - Uses sendBeacon on page unload for final attempt
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.RetryQueue = void 0;
17
+ exports.pickNextRetryDelay = pickNextRetryDelay;
18
+ const utils_1 = require("./utils");
19
+ const globals_1 = require("./utils/globals");
20
+ const THIRTY_MINUTES = 30 * 60 * 1000;
21
+ const MAX_RETRIES = 10;
22
+ /**
23
+ * Generates a jittered exponential backoff delay in milliseconds
24
+ *
25
+ * Base value is 3 seconds, doubled with each retry up to 30 minutes max.
26
+ * Each value has +/- 50% jitter.
27
+ *
28
+ * @param retriesPerformedSoFar - Number of retries already attempted
29
+ * @returns Delay in milliseconds
30
+ */
31
+ function pickNextRetryDelay(retriesPerformedSoFar) {
32
+ const rawBackoffTime = 3000 * 2 ** retriesPerformedSoFar;
33
+ const minBackoff = rawBackoffTime / 2;
34
+ const cappedBackoffTime = Math.min(THIRTY_MINUTES, rawBackoffTime);
35
+ const jitterFraction = Math.random() - 0.5; // Random between -0.5 and 0.5
36
+ const jitter = jitterFraction * (cappedBackoffTime - minBackoff);
37
+ return Math.ceil(cappedBackoffTime + jitter);
38
+ }
39
+ class RetryQueue {
40
+ constructor(config) {
41
+ this._isPolling = false;
42
+ this._pollIntervalMs = 3000;
43
+ this._queue = [];
44
+ this._areWeOnline = true;
45
+ this._sendRequest = config.sendRequest;
46
+ this._sendBeacon = config.sendBeacon;
47
+ // Set up online/offline detection
48
+ if (globals_1.window && typeof globals_1.navigator !== "undefined" && "onLine" in globals_1.navigator) {
49
+ this._areWeOnline = globals_1.navigator.onLine;
50
+ (0, utils_1.addEventListener)(globals_1.window, "online", () => {
51
+ this._areWeOnline = true;
52
+ this._flush();
53
+ });
54
+ (0, utils_1.addEventListener)(globals_1.window, "offline", () => {
55
+ this._areWeOnline = false;
56
+ });
57
+ }
58
+ }
59
+ /**
60
+ * Get current queue length
61
+ */
62
+ get length() {
63
+ return this._queue.length;
64
+ }
65
+ /**
66
+ * Enqueue a failed request for retry
67
+ */
68
+ enqueue(request, retriesPerformedSoFar = 0) {
69
+ // Don't retry if we've exceeded max retries
70
+ if (retriesPerformedSoFar >= MAX_RETRIES) {
71
+ console.warn(`VTilt: Request failed after ${MAX_RETRIES} retries, giving up`);
72
+ return;
73
+ }
74
+ const msToNextRetry = pickNextRetryDelay(retriesPerformedSoFar);
75
+ const retryAt = Date.now() + msToNextRetry;
76
+ this._queue.push({
77
+ retryAt,
78
+ request,
79
+ retriesPerformedSoFar: retriesPerformedSoFar + 1,
80
+ });
81
+ let logMessage = `VTilt: Enqueued failed request for retry in ${Math.round(msToNextRetry / 1000)}s`;
82
+ if (!this._areWeOnline) {
83
+ logMessage += " (Browser is offline)";
84
+ }
85
+ console.warn(logMessage);
86
+ // Start polling if not already
87
+ if (!this._isPolling) {
88
+ this._isPolling = true;
89
+ this._poll();
90
+ }
91
+ }
92
+ /**
93
+ * Attempt to send a request with retry on failure
94
+ */
95
+ async retriableRequest(request) {
96
+ try {
97
+ const response = await this._sendRequest(request);
98
+ // Retry on server errors (5xx) or network errors (0)
99
+ // Don't retry on client errors (4xx)
100
+ if (response.statusCode !== 200 &&
101
+ (response.statusCode < 400 || response.statusCode >= 500)) {
102
+ this.enqueue(request, 0);
103
+ }
104
+ }
105
+ catch (_a) {
106
+ // Network error - enqueue for retry
107
+ this.enqueue(request, 0);
108
+ }
109
+ }
110
+ /**
111
+ * Start polling for retries
112
+ */
113
+ _poll() {
114
+ if (this._poller) {
115
+ clearTimeout(this._poller);
116
+ }
117
+ this._poller = setTimeout(() => {
118
+ if (this._areWeOnline && this._queue.length > 0) {
119
+ this._flush();
120
+ }
121
+ // Continue polling if there are items in queue
122
+ if (this._queue.length > 0) {
123
+ this._poll();
124
+ }
125
+ else {
126
+ this._isPolling = false;
127
+ }
128
+ }, this._pollIntervalMs);
129
+ }
130
+ /**
131
+ * Flush ready items from the queue
132
+ */
133
+ _flush() {
134
+ const now = Date.now();
135
+ const notReady = [];
136
+ const ready = [];
137
+ this._queue.forEach((item) => {
138
+ if (item.retryAt < now) {
139
+ ready.push(item);
140
+ }
141
+ else {
142
+ notReady.push(item);
143
+ }
144
+ });
145
+ this._queue = notReady;
146
+ // Retry ready items
147
+ ready.forEach(async ({ request, retriesPerformedSoFar }) => {
148
+ try {
149
+ const response = await this._sendRequest(request);
150
+ // If still failing, re-enqueue
151
+ if (response.statusCode !== 200 &&
152
+ (response.statusCode < 400 || response.statusCode >= 500)) {
153
+ this.enqueue(request, retriesPerformedSoFar);
154
+ }
155
+ }
156
+ catch (_a) {
157
+ // Network error - re-enqueue
158
+ this.enqueue(request, retriesPerformedSoFar);
159
+ }
160
+ });
161
+ }
162
+ /**
163
+ * Flush all queued requests using sendBeacon on page unload
164
+ */
165
+ unload() {
166
+ if (this._poller) {
167
+ clearTimeout(this._poller);
168
+ this._poller = undefined;
169
+ }
170
+ // Attempt final send of all queued requests
171
+ this._queue.forEach(({ request }) => {
172
+ try {
173
+ this._sendBeacon(request);
174
+ }
175
+ catch (e) {
176
+ console.error("VTilt: Failed to send beacon on unload", e);
177
+ }
178
+ });
179
+ this._queue = [];
180
+ }
181
+ }
182
+ exports.RetryQueue = RetryQueue;
package/lib/session.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { StorageMethod } from "./types";
1
+ import { PersistenceMethod } from "./types";
2
2
  export declare class SessionManager {
3
3
  private storageMethod;
4
4
  private domain?;
5
5
  private _windowId;
6
- constructor(storageMethod?: StorageMethod, domain?: string);
6
+ constructor(storageMethod?: PersistenceMethod, domain?: string);
7
7
  /**
8
8
  * Check if using web storage (localStorage or sessionStorage)
9
9
  */
package/lib/session.js CHANGED
@@ -16,8 +16,8 @@ class SessionManager {
16
16
  * Check if using web storage (localStorage or sessionStorage)
17
17
  */
18
18
  _isWebStorage() {
19
- return (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
20
- this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage);
19
+ return (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage ||
20
+ this.storageMethod === constants_1.PERSISTENCE_METHODS.sessionStorage);
21
21
  }
22
22
  /**
23
23
  * Get storage object (localStorage or sessionStorage)
@@ -26,7 +26,7 @@ class SessionManager {
26
26
  if (!this._isWebStorage()) {
27
27
  return null;
28
28
  }
29
- return this.storageMethod === constants_1.STORAGE_METHODS.localStorage
29
+ return this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage
30
30
  ? localStorage
31
31
  : sessionStorage;
32
32
  }
package/lib/types.d.ts CHANGED
@@ -1,68 +1,185 @@
1
+ /**
2
+ * VTilt Types
3
+ *
4
+ * Type definitions for the VTilt tracking SDK.
5
+ * Following PostHog's patterns where applicable.
6
+ */
7
+ import type { PersonProfilesMode } from './constants';
1
8
  export interface VTiltConfig {
2
- projectId: string;
9
+ /** Project identifier (required) */
3
10
  token: string;
4
- name?: string;
5
- host?: string;
6
- scriptHost?: string;
11
+ /** API host for tracking (default: https://api.vtilt.io) */
12
+ api_host?: string;
13
+ /** UI host for dashboard links */
14
+ ui_host?: string | null;
15
+ /** Proxy domain for tracking requests */
7
16
  proxy?: string;
17
+ /** Full proxy URL for tracking requests */
8
18
  proxyUrl?: string;
19
+ /** Instance name (for multiple instances) */
20
+ name?: string;
21
+ /** Project ID (set via init() first argument) */
22
+ projectId?: string;
23
+ /** Domain to track (auto-detected if not provided) */
9
24
  domain?: string;
10
- storage?: "cookie" | "localStorage" | "sessionStorage";
25
+ /** Storage method for session data */
26
+ storage?: PersistenceMethod;
27
+ /** Persistence method for user data */
28
+ persistence?: PersistenceMethod;
29
+ /** Persistence name prefix */
30
+ persistence_name?: string;
31
+ /** Enable cross-subdomain cookies */
32
+ cross_subdomain_cookie?: boolean;
33
+ /**
34
+ * Person profiles mode:
35
+ * - 'always': Always create person profiles (default)
36
+ * - 'identified_only': Only create when user is identified
37
+ * - 'never': Never create person profiles
38
+ */
39
+ person_profiles?: PersonProfilesMode;
40
+ /** Enable autocapture */
41
+ autocapture?: boolean;
42
+ /** Enable web vitals tracking */
43
+ capture_performance?: boolean;
44
+ /** Enable page view tracking */
45
+ capture_pageview?: boolean | 'auto';
46
+ /** Enable page leave tracking */
47
+ capture_pageleave?: boolean | 'if_capture_pageview';
48
+ /** Disable compression */
49
+ disable_compression?: boolean;
50
+ /** Whether to stringify payload before sending */
11
51
  stringifyPayload?: boolean;
12
- webVitals?: boolean;
52
+ /** Properties to exclude from events */
53
+ property_denylist?: string[];
54
+ /** Mask text in autocapture */
55
+ mask_all_text?: boolean;
56
+ /** Mask all element attributes */
57
+ mask_all_element_attributes?: boolean;
58
+ /** Respect Do Not Track browser setting */
59
+ respect_dnt?: boolean;
60
+ /** Opt users out by default */
61
+ opt_out_capturing_by_default?: boolean;
62
+ /** Global attributes added to all events */
13
63
  globalAttributes?: Record<string, string>;
14
- persistence?: "localStorage" | "cookie";
15
- crossSubdomainCookie?: boolean;
16
- }
17
- export interface SessionData {
18
- value: string;
19
- expiry: number;
20
- }
21
- export interface WebVitalMetric {
22
- name: string;
23
- value: number;
24
- delta: number;
25
- rating: string;
26
- id: string;
27
- navigationType: string;
28
- }
29
- export interface GeolocationData {
30
- country?: string;
31
- locale?: string;
64
+ /** Bootstrap data for initialization */
65
+ bootstrap?: {
66
+ distinctID?: string;
67
+ isIdentifiedID?: boolean;
68
+ featureFlags?: Record<string, boolean | string>;
69
+ };
70
+ /** Before send hook for modifying events */
71
+ before_send?: (event: CaptureResult) => CaptureResult | null;
72
+ /** Loaded callback */
73
+ loaded?: (vtilt: any) => void;
32
74
  }
33
75
  export interface EventPayload {
34
76
  [key: string]: any;
35
77
  }
78
+ export interface CaptureResult {
79
+ uuid: string;
80
+ event: string;
81
+ properties: Properties;
82
+ $set?: Properties;
83
+ $set_once?: Properties;
84
+ timestamp?: string;
85
+ }
86
+ export interface CaptureOptions {
87
+ /** Override timestamp */
88
+ timestamp?: Date;
89
+ /** Properties to $set on person */
90
+ $set?: Properties;
91
+ /** Properties to $set_once on person */
92
+ $set_once?: Properties;
93
+ /** Send immediately (skip batching) */
94
+ send_instantly?: boolean;
95
+ }
36
96
  export interface TrackingEvent {
37
97
  timestamp: string;
38
98
  event: string;
39
99
  project_id: string;
40
100
  domain: string;
41
- payload: EventPayload;
42
- distinct_id?: string;
101
+ distinct_id: string;
43
102
  anonymous_id?: string;
103
+ payload: EventPayload;
44
104
  }
45
- export type StorageMethod = "cookie" | "localStorage" | "sessionStorage";
46
- export interface StorageMethods {
47
- cookie: "cookie";
48
- localStorage: "localStorage";
49
- sessionStorage: "sessionStorage";
105
+ export type Property = string | number | boolean | null | undefined | Date | any[] | Record<string, any>;
106
+ export interface Properties {
107
+ [key: string]: Property;
50
108
  }
109
+ export interface PropertyOperations {
110
+ $set?: Properties;
111
+ $set_once?: Properties;
112
+ $unset?: string[];
113
+ }
114
+ export interface SessionData {
115
+ value: string;
116
+ expiry: number;
117
+ }
118
+ /**
119
+ * Persistence method for user/session data
120
+ * Following PostHog's approach:
121
+ * - 'localStorage+cookie': Stores limited data in cookies, rest in localStorage (default)
122
+ * - 'cookie': Stores all data in cookies
123
+ * - 'localStorage': Stores all data in localStorage
124
+ * - 'sessionStorage': Stores all data in sessionStorage
125
+ * - 'memory': Stores all data in memory only (no persistence)
126
+ */
127
+ export type PersistenceMethod = 'localStorage+cookie' | 'cookie' | 'localStorage' | 'sessionStorage' | 'memory';
128
+ /** User identity state */
51
129
  export interface UserIdentity {
130
+ /** Current distinct ID (null if anonymous) */
52
131
  distinct_id: string | null;
132
+ /** Anonymous ID (always present) */
53
133
  anonymous_id: string;
134
+ /** Device ID (persists across sessions) */
54
135
  device_id: string;
55
- properties: Record<string, any>;
56
- user_state: "anonymous" | "identified";
136
+ /** User properties */
137
+ properties: Properties;
138
+ /** Identity state */
139
+ user_state: 'anonymous' | 'identified';
57
140
  }
58
141
  export interface UserProperties {
59
142
  [key: string]: any;
60
143
  }
61
- export interface PropertyOperations {
62
- $set?: Record<string, any>;
63
- $set_once?: Record<string, any>;
64
- }
65
144
  export interface AliasEvent {
66
145
  distinct_id: string;
67
146
  original: string;
68
147
  }
148
+ export interface WebVitalMetric {
149
+ name: string;
150
+ value: number;
151
+ delta: number;
152
+ rating: 'good' | 'needs-improvement' | 'poor';
153
+ id: string;
154
+ navigationType: string;
155
+ }
156
+ export interface GeolocationData {
157
+ country?: string;
158
+ locale?: string;
159
+ }
160
+ export interface GroupsConfig {
161
+ [groupType: string]: string;
162
+ }
163
+ export interface FeatureFlagsConfig {
164
+ [flagKey: string]: boolean | string;
165
+ }
166
+ export type SessionIdChangedCallback = (newSessionId: string, previousSessionId: string | null, changeInfo: {
167
+ reason: 'timeout' | 'new_session' | 'reset';
168
+ }) => void;
169
+ export interface RequestOptions {
170
+ method?: 'POST' | 'GET';
171
+ headers?: Record<string, string>;
172
+ timeout?: number;
173
+ retry?: boolean;
174
+ }
175
+ export interface RemoteConfig {
176
+ /** Default to identified_only mode */
177
+ defaultIdentifiedOnly?: boolean;
178
+ /** Feature flags */
179
+ featureFlags?: FeatureFlagsConfig;
180
+ /** Session recording config */
181
+ sessionRecording?: {
182
+ enabled?: boolean;
183
+ sampleRate?: number;
184
+ };
185
+ }
package/lib/types.js CHANGED
@@ -1,2 +1,8 @@
1
1
  "use strict";
2
+ /**
3
+ * VTilt Types
4
+ *
5
+ * Type definitions for the VTilt tracking SDK.
6
+ * Following PostHog's patterns where applicable.
7
+ */
2
8
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,10 +1,10 @@
1
- import { UserIdentity, AliasEvent, StorageMethod } from "./types";
1
+ import { UserIdentity, AliasEvent, PersistenceMethod } from "./types";
2
2
  export declare class UserManager {
3
3
  private storageMethod;
4
4
  private domain?;
5
5
  private userIdentity;
6
6
  private _cachedPersonProperties;
7
- constructor(storageMethod?: StorageMethod, domain?: string);
7
+ constructor(storageMethod?: PersistenceMethod, domain?: string);
8
8
  /**
9
9
  * Get current user identity
10
10
  */
@@ -30,7 +30,8 @@ class UserManager {
30
30
  if (!this.userIdentity.anonymous_id) {
31
31
  // Regenerate if somehow undefined
32
32
  this.userIdentity.anonymous_id = this.generateAnonymousId();
33
- this.saveUserIdentity();
33
+ // Save to storage
34
+ this.setStoredValue(constants_1.ANONYMOUS_ID_KEY, this.userIdentity.anonymous_id);
34
35
  }
35
36
  return this.userIdentity.anonymous_id;
36
37
  }
@@ -467,13 +468,25 @@ class UserManager {
467
468
  */
468
469
  getStoredValue(key) {
469
470
  try {
470
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage) {
471
- return localStorage.getItem(key);
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;
472
474
  }
473
- else if (this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
474
- return sessionStorage.getItem(key);
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;
475
487
  }
476
488
  else {
489
+ // Cookie-only mode
477
490
  return this.getCookieValue(key);
478
491
  }
479
492
  }
@@ -488,13 +501,20 @@ class UserManager {
488
501
  */
489
502
  setStoredValue(key, value) {
490
503
  try {
491
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage) {
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) {
492
509
  localStorage.setItem(key, value);
493
510
  }
494
- else if (this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
495
- sessionStorage.setItem(key, value);
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);
496
515
  }
497
516
  else {
517
+ // Cookie-only mode
498
518
  this.setCookieValue(key, value);
499
519
  }
500
520
  }
@@ -507,13 +527,20 @@ class UserManager {
507
527
  * Remove stored value from storage
508
528
  */
509
529
  removeStoredValue(key) {
510
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage) {
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) {
511
535
  localStorage.removeItem(key);
512
536
  }
513
- else if (this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
514
- sessionStorage.removeItem(key);
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);
515
541
  }
516
542
  else {
543
+ // Cookie-only mode
517
544
  this.removeCookieValue(key);
518
545
  }
519
546
  }