@tracelog/lib 0.4.0 → 0.5.0

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 (98) hide show
  1. package/dist/browser/tracelog.js +620 -658
  2. package/dist/cjs/api.d.ts +1 -53
  3. package/dist/cjs/api.js +0 -59
  4. package/dist/cjs/app.constants.d.ts +1 -1
  5. package/dist/cjs/app.d.ts +1 -5
  6. package/dist/cjs/app.js +4 -12
  7. package/dist/cjs/constants/api.constants.d.ts +5 -2
  8. package/dist/cjs/constants/api.constants.js +5 -14
  9. package/dist/cjs/constants/config.constants.d.ts +3 -3
  10. package/dist/cjs/constants/config.constants.js +3 -3
  11. package/dist/cjs/constants/error.constants.d.ts +7 -2
  12. package/dist/cjs/constants/error.constants.js +13 -2
  13. package/dist/cjs/handlers/click.handler.js +0 -6
  14. package/dist/cjs/handlers/error.handler.js +9 -0
  15. package/dist/cjs/handlers/scroll.handler.js +0 -5
  16. package/dist/cjs/handlers/session.handler.js +5 -2
  17. package/dist/cjs/integrations/google-analytics.integration.d.ts +1 -1
  18. package/dist/cjs/integrations/google-analytics.integration.js +2 -1
  19. package/dist/cjs/managers/api.manager.d.ts +1 -1
  20. package/dist/cjs/managers/api.manager.js +3 -3
  21. package/dist/cjs/managers/config.builder.d.ts +33 -0
  22. package/dist/cjs/managers/config.builder.js +116 -0
  23. package/dist/cjs/managers/config.manager.d.ts +13 -14
  24. package/dist/cjs/managers/config.manager.js +52 -58
  25. package/dist/cjs/managers/event.manager.d.ts +1 -46
  26. package/dist/cjs/managers/event.manager.js +15 -70
  27. package/dist/cjs/managers/sender.manager.d.ts +1 -28
  28. package/dist/cjs/managers/sender.manager.js +43 -73
  29. package/dist/cjs/managers/session.manager.d.ts +2 -49
  30. package/dist/cjs/managers/session.manager.js +42 -83
  31. package/dist/cjs/managers/state.manager.d.ts +1 -28
  32. package/dist/cjs/managers/state.manager.js +5 -33
  33. package/dist/cjs/managers/storage.manager.d.ts +6 -0
  34. package/dist/cjs/managers/storage.manager.js +18 -1
  35. package/dist/cjs/public-api.d.ts +1 -1
  36. package/dist/cjs/test-bridge.d.ts +3 -2
  37. package/dist/cjs/test-bridge.js +34 -7
  38. package/dist/cjs/types/api.types.d.ts +24 -8
  39. package/dist/cjs/types/api.types.js +24 -8
  40. package/dist/cjs/types/event.types.d.ts +2 -4
  41. package/dist/cjs/types/event.types.js +0 -1
  42. package/dist/cjs/types/test-bridge.types.d.ts +2 -1
  43. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +1 -2
  44. package/dist/cjs/utils/logging/debug-logger.utils.js +2 -3
  45. package/dist/cjs/utils/validations/config-validations.utils.d.ts +1 -26
  46. package/dist/cjs/utils/validations/config-validations.utils.js +5 -117
  47. package/dist/cjs/utils/validations/event-validations.utils.d.ts +2 -2
  48. package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +3 -3
  49. package/dist/cjs/utils/validations/metadata-validations.utils.js +41 -3
  50. package/dist/esm/api.d.ts +1 -53
  51. package/dist/esm/api.js +0 -59
  52. package/dist/esm/app.constants.d.ts +1 -1
  53. package/dist/esm/app.d.ts +1 -5
  54. package/dist/esm/app.js +5 -13
  55. package/dist/esm/constants/api.constants.d.ts +5 -2
  56. package/dist/esm/constants/api.constants.js +5 -13
  57. package/dist/esm/constants/config.constants.d.ts +3 -3
  58. package/dist/esm/constants/config.constants.js +3 -3
  59. package/dist/esm/constants/error.constants.d.ts +7 -2
  60. package/dist/esm/constants/error.constants.js +12 -1
  61. package/dist/esm/handlers/click.handler.js +0 -6
  62. package/dist/esm/handlers/error.handler.js +10 -1
  63. package/dist/esm/handlers/scroll.handler.js +0 -5
  64. package/dist/esm/handlers/session.handler.js +5 -2
  65. package/dist/esm/integrations/google-analytics.integration.d.ts +1 -1
  66. package/dist/esm/integrations/google-analytics.integration.js +2 -1
  67. package/dist/esm/managers/api.manager.d.ts +1 -1
  68. package/dist/esm/managers/api.manager.js +3 -3
  69. package/dist/esm/managers/config.builder.d.ts +33 -0
  70. package/dist/esm/managers/config.builder.js +112 -0
  71. package/dist/esm/managers/config.manager.d.ts +13 -14
  72. package/dist/esm/managers/config.manager.js +54 -60
  73. package/dist/esm/managers/event.manager.d.ts +1 -46
  74. package/dist/esm/managers/event.manager.js +15 -70
  75. package/dist/esm/managers/sender.manager.d.ts +1 -28
  76. package/dist/esm/managers/sender.manager.js +44 -74
  77. package/dist/esm/managers/session.manager.d.ts +2 -49
  78. package/dist/esm/managers/session.manager.js +42 -83
  79. package/dist/esm/managers/state.manager.d.ts +1 -28
  80. package/dist/esm/managers/state.manager.js +4 -33
  81. package/dist/esm/managers/storage.manager.d.ts +6 -0
  82. package/dist/esm/managers/storage.manager.js +18 -1
  83. package/dist/esm/public-api.d.ts +1 -1
  84. package/dist/esm/test-bridge.d.ts +3 -2
  85. package/dist/esm/test-bridge.js +34 -7
  86. package/dist/esm/types/api.types.d.ts +24 -8
  87. package/dist/esm/types/api.types.js +24 -8
  88. package/dist/esm/types/event.types.d.ts +2 -4
  89. package/dist/esm/types/event.types.js +0 -1
  90. package/dist/esm/types/test-bridge.types.d.ts +2 -1
  91. package/dist/esm/utils/logging/debug-logger.utils.d.ts +1 -2
  92. package/dist/esm/utils/logging/debug-logger.utils.js +3 -4
  93. package/dist/esm/utils/validations/config-validations.utils.d.ts +1 -26
  94. package/dist/esm/utils/validations/config-validations.utils.js +5 -114
  95. package/dist/esm/utils/validations/event-validations.utils.d.ts +2 -2
  96. package/dist/esm/utils/validations/metadata-validations.utils.d.ts +3 -3
  97. package/dist/esm/utils/validations/metadata-validations.utils.js +41 -3
  98. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { QUEUE_KEY, EVENT_EXPIRY_HOURS, SYNC_XHR_TIMEOUT_MS, MAX_RETRIES, RETRY_DELAY_MS, REQUEST_TIMEOUT_MS, } from '../constants';
1
+ import { QUEUE_KEY, EVENT_EXPIRY_HOURS, MAX_RETRIES, RETRY_DELAY_MS, REQUEST_TIMEOUT_MS } from '../constants';
2
2
  import { SpecialProjectId } from '../types';
3
3
  import { debugLog } from '../utils';
4
4
  import { StateManager } from './state.manager';
@@ -15,40 +15,34 @@ export class SenderManager extends StateManager {
15
15
  const userId = this.get('userId') || 'anonymous';
16
16
  return `${QUEUE_KEY(projectId)}:${userId}`;
17
17
  }
18
- /**
19
- * Send events synchronously using sendBeacon or XHR fallback
20
- * Used primarily for page unload scenarios
21
- */
22
18
  sendEventsQueueSync(body) {
23
- // For skip mode, simulate success immediately (sync version)
24
19
  if (this.shouldSkipSend()) {
25
- this.clearPersistedEvents();
26
20
  this.resetRetryState();
27
21
  return true;
28
22
  }
23
+ const config = this.get('config');
24
+ if (config?.id === SpecialProjectId.Fail) {
25
+ debugLog.warn('SenderManager', 'Fail mode: simulating network failure (sync)', {
26
+ events: body.events.length,
27
+ });
28
+ return false;
29
+ }
29
30
  const success = this.sendQueueSyncInternal(body);
30
31
  if (success) {
31
- this.clearPersistedEvents();
32
32
  this.resetRetryState();
33
33
  }
34
34
  return success;
35
35
  }
36
- /**
37
- * Send events asynchronously with persistence and retry logic
38
- * Main method for sending events during normal operation
39
- */
40
36
  async sendEventsQueue(body, callbacks) {
41
- // First, try to persist events for recovery (even in skip mode for consistency)
42
37
  const persisted = this.persistEvents(body);
43
38
  if (!persisted && !this.shouldSkipSend()) {
44
39
  debugLog.warn('SenderManager', 'Failed to persist events, attempting immediate send');
45
40
  }
46
- // Attempt to send events (or simulate in skip mode)
47
41
  const success = await this.send(body);
48
42
  if (success) {
49
43
  this.clearPersistedEvents();
50
44
  this.resetRetryState();
51
- callbacks?.onSuccess?.(body.events.length);
45
+ callbacks?.onSuccess?.(body.events.length, body.events, body);
52
46
  }
53
47
  else {
54
48
  this.scheduleRetry(body, callbacks);
@@ -56,10 +50,6 @@ export class SenderManager extends StateManager {
56
50
  }
57
51
  return success;
58
52
  }
59
- /**
60
- * Recover and send previously persisted events
61
- * Called during initialization to handle events from previous session
62
- */
63
53
  async recoverPersistedEvents(callbacks) {
64
54
  try {
65
55
  const persistedData = this.getPersistedData();
@@ -72,7 +62,7 @@ export class SenderManager extends StateManager {
72
62
  if (success) {
73
63
  this.clearPersistedEvents();
74
64
  this.resetRetryState();
75
- callbacks?.onSuccess?.(persistedData.events.length);
65
+ callbacks?.onSuccess?.(persistedData.events.length, persistedData.events, body);
76
66
  }
77
67
  else {
78
68
  this.scheduleRetry(body, callbacks);
@@ -81,35 +71,30 @@ export class SenderManager extends StateManager {
81
71
  }
82
72
  catch (error) {
83
73
  debugLog.error('SenderManager', 'Failed to recover persisted events', { error });
84
- this.clearPersistedEvents(); // Clean up corrupted data
74
+ this.clearPersistedEvents();
85
75
  }
86
76
  }
87
- /**
88
- * Persist events for recovery in case of failure
89
- */
90
77
  persistEventsForRecovery(body) {
91
78
  return this.persistEvents(body);
92
79
  }
93
- /**
94
- * Legacy method for backward compatibility
95
- * @deprecated Use sendEventsQueue instead
96
- */
97
80
  async sendEventsQueueAsync(body) {
98
81
  return this.sendEventsQueue(body);
99
82
  }
100
- /**
101
- * Stop the sender manager and clean up resources
102
- */
103
83
  stop() {
104
84
  this.clearRetryTimeout();
105
85
  this.resetRetryState();
106
- // Clear any persisted events on shutdown to prevent stale data
107
- this.clearPersistedEvents();
108
86
  }
109
87
  async send(body) {
110
88
  if (this.shouldSkipSend()) {
111
89
  return this.simulateSuccessfulSend();
112
90
  }
91
+ const config = this.get('config');
92
+ if (config?.id === SpecialProjectId.Fail) {
93
+ debugLog.warn('SenderManager', 'Fail mode: simulating network failure', {
94
+ events: body.events.length,
95
+ });
96
+ return false;
97
+ }
113
98
  const { url, payload } = this.prepareRequest(body);
114
99
  try {
115
100
  const response = await this.sendWithTimeout(url, payload);
@@ -120,7 +105,7 @@ export class SenderManager extends StateManager {
120
105
  debugLog.error('SenderManager', 'Send request failed', {
121
106
  error: errorMessage,
122
107
  events: body.events.length,
123
- url: url.replace(/\/\/[^/]+/, '//[DOMAIN]'), // Hide domain for privacy
108
+ url: url.replace(/\/\/[^/]+/, '//[DOMAIN]'),
124
109
  });
125
110
  return false;
126
111
  }
@@ -128,11 +113,13 @@ export class SenderManager extends StateManager {
128
113
  async sendWithTimeout(url, payload) {
129
114
  const controller = new AbortController();
130
115
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
116
+ const config = this.get('config');
131
117
  try {
132
118
  const response = await fetch(url, {
133
119
  method: 'POST',
134
120
  headers: {
135
121
  'Content-Type': 'application/json',
122
+ 'X-TraceLog-Project': config?.id || 'unknown',
136
123
  },
137
124
  body: payload,
138
125
  keepalive: true,
@@ -151,42 +138,33 @@ export class SenderManager extends StateManager {
151
138
  sendQueueSyncInternal(body) {
152
139
  const { url, payload } = this.prepareRequest(body);
153
140
  const blob = new Blob([payload], { type: 'application/json' });
154
- if (this.isSendBeaconAvailable() && navigator.sendBeacon(url, blob)) {
155
- return true;
156
- }
157
- return this.sendSyncXHR(url, payload);
158
- }
159
- sendSyncXHR(url, payload) {
160
- const xhr = new XMLHttpRequest();
161
- try {
162
- xhr.open('POST', url, false);
163
- xhr.setRequestHeader('Content-Type', 'application/json');
164
- xhr.withCredentials = true;
165
- xhr.timeout = SYNC_XHR_TIMEOUT_MS;
166
- xhr.send(payload);
167
- const success = xhr.status >= 200 && xhr.status < 300;
168
- if (!success) {
169
- debugLog.warn('SenderManager', 'Sync XHR failed', {
170
- status: xhr.status,
171
- statusText: xhr.statusText || 'Unknown error',
172
- });
141
+ if (this.isSendBeaconAvailable()) {
142
+ const success = navigator.sendBeacon(url, blob);
143
+ if (success) {
144
+ return true;
173
145
  }
174
- return success;
146
+ debugLog.warn('SenderManager', 'sendBeacon failed, persisting events for recovery');
175
147
  }
176
- catch (error) {
177
- const errorMessage = error instanceof Error ? error.message : String(error);
178
- debugLog.warn('SenderManager', 'Sync XHR error', {
179
- error: errorMessage,
180
- status: xhr.status || 'unknown',
181
- });
182
- return false;
148
+ else {
149
+ debugLog.warn('SenderManager', 'sendBeacon not available, persisting events for recovery');
183
150
  }
151
+ this.persistEventsForRecovery(body);
152
+ return false;
184
153
  }
185
154
  prepareRequest(body) {
186
155
  const url = `${this.get('apiUrl')}/collect`;
156
+ // Enrich payload with metadata for sendBeacon() fallback
157
+ // sendBeacon() doesn't send custom headers, so we include referer in payload
158
+ const enrichedBody = {
159
+ ...body,
160
+ _metadata: {
161
+ referer: typeof window !== 'undefined' ? window.location.href : undefined,
162
+ timestamp: Date.now(),
163
+ },
164
+ };
187
165
  return {
188
166
  url,
189
- payload: JSON.stringify(body),
167
+ payload: JSON.stringify(enrichedBody),
190
168
  };
191
169
  }
192
170
  getPersistedData() {
@@ -199,7 +177,6 @@ export class SenderManager extends StateManager {
199
177
  }
200
178
  catch (error) {
201
179
  debugLog.warn('SenderManager', 'Failed to parse persisted data', { error });
202
- // Clean up corrupted data
203
180
  this.clearPersistedEvents();
204
181
  }
205
182
  return null;
@@ -242,7 +219,8 @@ export class SenderManager extends StateManager {
242
219
  }
243
220
  clearPersistedEvents() {
244
221
  try {
245
- this.storeManager.removeItem(this.getQueueStorageKey());
222
+ const key = this.getQueueStorageKey();
223
+ this.storeManager.removeItem(key);
246
224
  }
247
225
  catch (error) {
248
226
  debugLog.warn('SenderManager', 'Failed to clear persisted events', { error });
@@ -265,13 +243,10 @@ export class SenderManager extends StateManager {
265
243
  return;
266
244
  }
267
245
  const retryDelay = RETRY_DELAY_MS * Math.pow(2, this.retryCount); // Exponential backoff
246
+ this.isRetrying = true;
268
247
  this.retryTimeoutId = window.setTimeout(async () => {
269
248
  this.retryTimeoutId = null;
270
- if (this.isRetrying) {
271
- return;
272
- }
273
249
  this.retryCount++;
274
- this.isRetrying = true;
275
250
  try {
276
251
  const success = await this.send(body);
277
252
  if (success) {
@@ -303,15 +278,10 @@ export class SenderManager extends StateManager {
303
278
  const { id } = config || {};
304
279
  return id === SpecialProjectId.Skip;
305
280
  }
306
- /**
307
- * Simulate a successful send operation for skip mode
308
- * Provides realistic timing and behavior without making HTTP requests
309
- */
310
281
  async simulateSuccessfulSend() {
311
- // Simulate realistic network delay (100-500ms)
312
282
  const delay = Math.random() * 400 + 100;
313
283
  await new Promise((resolve) => setTimeout(resolve, delay));
314
- return true; // Always successful in skip mode
284
+ return true;
315
285
  }
316
286
  isSendBeaconAvailable() {
317
287
  return typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function';
@@ -4,83 +4,36 @@ import { EventManager } from './event.manager';
4
4
  export declare class SessionManager extends StateManager {
5
5
  private readonly storageManager;
6
6
  private readonly eventManager;
7
+ private readonly projectId;
7
8
  private sessionTimeoutId;
8
9
  private broadcastChannel;
9
10
  private activityHandler;
10
11
  private visibilityChangeHandler;
11
12
  private beforeUnloadHandler;
12
13
  private isTracking;
13
- constructor(storageManager: StorageManager, eventManager: EventManager);
14
- /**
15
- * Initialize cross-tab synchronization
16
- */
14
+ constructor(storageManager: StorageManager, eventManager: EventManager, projectId: string);
17
15
  private initCrossTabSync;
18
- /**
19
- * Share session with other tabs
20
- */
21
16
  private shareSession;
22
17
  private broadcastSessionEnd;
23
- /**
24
- * Cleanup cross-tab sync
25
- */
26
18
  private cleanupCrossTabSync;
27
- /**
28
- * Recover session from localStorage if it exists and hasn't expired
29
- */
30
19
  private recoverSession;
31
- /**
32
- * Persist session data to localStorage
33
- */
34
20
  private persistSession;
35
21
  private clearStoredSession;
36
22
  private loadStoredSession;
37
23
  private saveStoredSession;
38
24
  private getSessionStorageKey;
39
25
  private getProjectId;
40
- /**
41
- * Start session tracking
42
- */
43
26
  startTracking(): Promise<void>;
44
- /**
45
- * Generate unique session ID
46
- */
47
27
  private generateSessionId;
48
- /**
49
- * Setup session timeout
50
- */
51
28
  private setupSessionTimeout;
52
- /**
53
- * Reset session timeout and update activity
54
- */
55
29
  private resetSessionTimeout;
56
- /**
57
- * Clear session timeout
58
- */
59
30
  private clearSessionTimeout;
60
- /**
61
- * Setup activity listeners to track user engagement
62
- */
63
31
  private setupActivityListeners;
64
- /**
65
- * Clean up activity listeners
66
- */
67
32
  private cleanupActivityListeners;
68
- /**
69
- * Setup page lifecycle listeners (visibility and unload)
70
- */
71
33
  private setupLifecycleListeners;
72
34
  private cleanupLifecycleListeners;
73
- /**
74
- * End current session
75
- */
76
35
  private endSession;
77
36
  private resetSessionState;
78
- /**
79
- * Stop session tracking
80
- */
81
37
  stopTracking(): Promise<void>;
82
- /**
83
- * Clean up all resources
84
- */
85
38
  destroy(): void;
86
39
  }
@@ -3,7 +3,7 @@ import { EventType } from '../types';
3
3
  import { debugLog } from '../utils/logging';
4
4
  import { StateManager } from './state.manager';
5
5
  export class SessionManager extends StateManager {
6
- constructor(storageManager, eventManager) {
6
+ constructor(storageManager, eventManager, projectId) {
7
7
  super();
8
8
  this.sessionTimeoutId = null;
9
9
  this.broadcastChannel = null;
@@ -13,10 +13,8 @@ export class SessionManager extends StateManager {
13
13
  this.isTracking = false;
14
14
  this.storageManager = storageManager;
15
15
  this.eventManager = eventManager;
16
+ this.projectId = projectId;
16
17
  }
17
- /**
18
- * Initialize cross-tab synchronization
19
- */
20
18
  initCrossTabSync() {
21
19
  if (typeof BroadcastChannel === 'undefined') {
22
20
  debugLog.warn('SessionManager', 'BroadcastChannel not supported');
@@ -45,41 +43,38 @@ export class SessionManager extends StateManager {
45
43
  }
46
44
  };
47
45
  }
48
- /**
49
- * Share session with other tabs
50
- */
51
46
  shareSession(sessionId) {
52
- this.broadcastChannel?.postMessage({
53
- action: 'session_start',
54
- projectId: this.getProjectId(),
55
- sessionId,
56
- timestamp: Date.now(),
57
- });
47
+ if (this.broadcastChannel && typeof this.broadcastChannel.postMessage === 'function') {
48
+ this.broadcastChannel.postMessage({
49
+ action: 'session_start',
50
+ projectId: this.getProjectId(),
51
+ sessionId,
52
+ timestamp: Date.now(),
53
+ });
54
+ }
58
55
  }
59
56
  broadcastSessionEnd(sessionId, reason) {
60
57
  if (!sessionId) {
61
58
  return;
62
59
  }
63
- this.broadcastChannel?.postMessage({
64
- action: 'session_end',
65
- projectId: this.getProjectId(),
66
- sessionId,
67
- reason,
68
- timestamp: Date.now(),
69
- });
60
+ if (this.broadcastChannel && typeof this.broadcastChannel.postMessage === 'function') {
61
+ this.broadcastChannel.postMessage({
62
+ action: 'session_end',
63
+ projectId: this.getProjectId(),
64
+ sessionId,
65
+ reason,
66
+ timestamp: Date.now(),
67
+ });
68
+ }
70
69
  }
71
- /**
72
- * Cleanup cross-tab sync
73
- */
74
70
  cleanupCrossTabSync() {
75
71
  if (this.broadcastChannel) {
76
- this.broadcastChannel.close();
72
+ if (typeof this.broadcastChannel.close === 'function') {
73
+ this.broadcastChannel.close();
74
+ }
77
75
  this.broadcastChannel = null;
78
76
  }
79
77
  }
80
- /**
81
- * Recover session from localStorage if it exists and hasn't expired
82
- */
83
78
  recoverSession() {
84
79
  const storedSession = this.loadStoredSession();
85
80
  if (!storedSession) {
@@ -94,9 +89,6 @@ export class SessionManager extends StateManager {
94
89
  debugLog.info('SessionManager', 'Session recovered from storage', { sessionId: storedSession.id });
95
90
  return storedSession.id;
96
91
  }
97
- /**
98
- * Persist session data to localStorage
99
- */
100
92
  persistSession(sessionId, lastActivity = Date.now()) {
101
93
  this.saveStoredSession({
102
94
  id: sessionId,
@@ -133,11 +125,8 @@ export class SessionManager extends StateManager {
133
125
  return SESSION_STORAGE_KEY(this.getProjectId());
134
126
  }
135
127
  getProjectId() {
136
- return this.get('config')?.id ?? '';
128
+ return this.projectId;
137
129
  }
138
- /**
139
- * Start session tracking
140
- */
141
130
  async startTracking() {
142
131
  if (this.isTracking) {
143
132
  debugLog.warn('SessionManager', 'Session tracking already active');
@@ -150,12 +139,11 @@ export class SessionManager extends StateManager {
150
139
  try {
151
140
  this.set('sessionId', sessionId);
152
141
  this.persistSession(sessionId);
153
- // Track session start event
154
- this.eventManager.track({
155
- type: EventType.SESSION_START,
156
- ...(isRecovered && { session_start_recovered: true }),
157
- });
158
- // Initialize components
142
+ if (!isRecovered) {
143
+ this.eventManager.track({
144
+ type: EventType.SESSION_START,
145
+ });
146
+ }
159
147
  this.initCrossTabSync();
160
148
  this.shareSession(sessionId);
161
149
  this.setupSessionTimeout();
@@ -173,15 +161,9 @@ export class SessionManager extends StateManager {
173
161
  throw error;
174
162
  }
175
163
  }
176
- /**
177
- * Generate unique session ID
178
- */
179
164
  generateSessionId() {
180
165
  return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
181
166
  }
182
- /**
183
- * Setup session timeout
184
- */
185
167
  setupSessionTimeout() {
186
168
  this.clearSessionTimeout();
187
169
  const sessionTimeout = this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;
@@ -189,9 +171,6 @@ export class SessionManager extends StateManager {
189
171
  this.endSession('inactivity');
190
172
  }, sessionTimeout);
191
173
  }
192
- /**
193
- * Reset session timeout and update activity
194
- */
195
174
  resetSessionTimeout() {
196
175
  this.setupSessionTimeout();
197
176
  const sessionId = this.get('sessionId');
@@ -199,27 +178,18 @@ export class SessionManager extends StateManager {
199
178
  this.persistSession(sessionId);
200
179
  }
201
180
  }
202
- /**
203
- * Clear session timeout
204
- */
205
181
  clearSessionTimeout() {
206
182
  if (this.sessionTimeoutId) {
207
183
  clearTimeout(this.sessionTimeoutId);
208
184
  this.sessionTimeoutId = null;
209
185
  }
210
186
  }
211
- /**
212
- * Setup activity listeners to track user engagement
213
- */
214
187
  setupActivityListeners() {
215
188
  this.activityHandler = () => this.resetSessionTimeout();
216
189
  document.addEventListener('click', this.activityHandler, { passive: true });
217
190
  document.addEventListener('keydown', this.activityHandler, { passive: true });
218
191
  document.addEventListener('scroll', this.activityHandler, { passive: true });
219
192
  }
220
- /**
221
- * Clean up activity listeners
222
- */
223
193
  cleanupActivityListeners() {
224
194
  if (this.activityHandler) {
225
195
  document.removeEventListener('click', this.activityHandler);
@@ -228,9 +198,6 @@ export class SessionManager extends StateManager {
228
198
  this.activityHandler = null;
229
199
  }
230
200
  }
231
- /**
232
- * Setup page lifecycle listeners (visibility and unload)
233
- */
234
201
  setupLifecycleListeners() {
235
202
  if (this.visibilityChangeHandler || this.beforeUnloadHandler) {
236
203
  return;
@@ -249,9 +216,7 @@ export class SessionManager extends StateManager {
249
216
  this.beforeUnloadHandler = () => {
250
217
  this.endSession('page_unload');
251
218
  };
252
- // Handle tab visibility changes
253
219
  document.addEventListener('visibilitychange', this.visibilityChangeHandler);
254
- // Handle page unload
255
220
  window.addEventListener('beforeunload', this.beforeUnloadHandler);
256
221
  }
257
222
  cleanupLifecycleListeners() {
@@ -264,14 +229,11 @@ export class SessionManager extends StateManager {
264
229
  this.beforeUnloadHandler = null;
265
230
  }
266
231
  }
267
- /**
268
- * End current session
269
- */
270
- endSession(reason) {
232
+ async endSession(reason) {
271
233
  const sessionId = this.get('sessionId');
272
234
  if (!sessionId) {
273
235
  debugLog.warn('SessionManager', 'endSession called without active session', { reason });
274
- this.resetSessionState();
236
+ this.resetSessionState(reason);
275
237
  return;
276
238
  }
277
239
  debugLog.info('SessionManager', 'Ending session', { sessionId, reason });
@@ -281,42 +243,39 @@ export class SessionManager extends StateManager {
281
243
  });
282
244
  const finalize = () => {
283
245
  this.broadcastSessionEnd(sessionId, reason);
284
- this.resetSessionState();
246
+ this.resetSessionState(reason);
285
247
  };
286
248
  const flushResult = this.eventManager.flushImmediatelySync();
287
249
  if (flushResult) {
288
250
  finalize();
289
251
  return;
290
252
  }
291
- this.eventManager
292
- .flushImmediately()
293
- .then(finalize)
294
- .catch((error) => {
253
+ try {
254
+ await this.eventManager.flushImmediately();
255
+ finalize();
256
+ }
257
+ catch (error) {
295
258
  debugLog.warn('SessionManager', 'Async flush failed during session end', {
296
259
  error: error instanceof Error ? error.message : 'Unknown error',
297
260
  });
298
261
  finalize();
299
- });
262
+ }
300
263
  }
301
- resetSessionState() {
264
+ resetSessionState(reason) {
302
265
  this.clearSessionTimeout();
303
266
  this.cleanupActivityListeners();
304
267
  this.cleanupLifecycleListeners();
305
268
  this.cleanupCrossTabSync();
306
- this.clearStoredSession();
269
+ if (reason !== 'page_unload') {
270
+ this.clearStoredSession();
271
+ }
307
272
  this.set('sessionId', null);
308
273
  this.set('hasStartSession', false);
309
274
  this.isTracking = false;
310
275
  }
311
- /**
312
- * Stop session tracking
313
- */
314
276
  async stopTracking() {
315
- this.endSession('manual_stop');
277
+ await this.endSession('manual_stop');
316
278
  }
317
- /**
318
- * Clean up all resources
319
- */
320
279
  destroy() {
321
280
  this.clearSessionTimeout();
322
281
  this.cleanupActivityListeners();
@@ -1,38 +1,11 @@
1
1
  import { State } from '../types';
2
- /**
3
- * Resets the global state to its initial empty state.
4
- * Used primarily for testing and cleanup scenarios.
5
- */
2
+ export declare function getGlobalState(): Readonly<State>;
6
3
  export declare function resetGlobalState(): void;
7
- /**
8
- * Abstract base class providing state management capabilities to TraceLog components.
9
- *
10
- * All managers and handlers extend this class to access and modify the shared global state.
11
- * State operations are synchronous and thread-safe within the single-threaded browser environment.
12
- */
13
4
  export declare abstract class StateManager {
14
- /**
15
- * Gets a value from the global state
16
- */
17
5
  protected get<T extends keyof State>(key: T): State[T];
18
- /**
19
- * Sets a value in the global state
20
- */
21
6
  protected set<T extends keyof State>(key: T, value: State[T]): void;
22
- /**
23
- * Gets the entire state object (for debugging purposes)
24
- */
25
7
  protected getState(): Readonly<State>;
26
- /**
27
- * Checks if a state key is considered critical for logging
28
- */
29
8
  private isCriticalStateKey;
30
- /**
31
- * Determines if a state change should be logged
32
- */
33
9
  private shouldLog;
34
- /**
35
- * Formats values for logging (avoiding large object dumps)
36
- */
37
10
  private formatLogValue;
38
11
  }