@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
@@ -6,16 +6,6 @@ const types_1 = require("../types");
6
6
  const utils_1 = require("../utils");
7
7
  const sender_manager_1 = require("./sender.manager");
8
8
  const state_manager_1 = require("./state.manager");
9
- /**
10
- * EventManager - Core event tracking and queue management
11
- *
12
- * Responsibilities:
13
- * - Track user events (clicks, scrolls, page views, custom events)
14
- * - Queue events and batch send them to the analytics API
15
- * - Handle deduplication of similar events
16
- * - Manage event sending intervals and retry logic
17
- * - Integrate with Google Analytics when configured
18
- */
19
9
  class EventManager extends state_manager_1.StateManager {
20
10
  constructor(storeManager, googleAnalytics = null, emitter = null) {
21
11
  super();
@@ -27,16 +17,15 @@ class EventManager extends state_manager_1.StateManager {
27
17
  this.dataSender = new sender_manager_1.SenderManager(storeManager);
28
18
  this.emitter = emitter;
29
19
  }
30
- /**
31
- * Recovers persisted events from localStorage
32
- * Should be called after initialization to recover any events that failed to send
33
- */
34
20
  async recoverPersistedEvents() {
35
21
  await this.dataSender.recoverPersistedEvents({
36
- onSuccess: (_eventCount, recoveredEvents) => {
22
+ onSuccess: (_eventCount, recoveredEvents, body) => {
37
23
  if (recoveredEvents && recoveredEvents.length > 0) {
38
24
  const eventIds = recoveredEvents.map((e) => e.timestamp + '_' + e.type);
39
25
  this.removeProcessedEvents(eventIds);
26
+ if (body) {
27
+ this.emitEventsQueue(body);
28
+ }
40
29
  }
41
30
  },
42
31
  onFailure: async () => {
@@ -44,10 +33,7 @@ class EventManager extends state_manager_1.StateManager {
44
33
  },
45
34
  });
46
35
  }
47
- /**
48
- * Track user events with automatic deduplication and queueing
49
- */
50
- track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, error_data, session_end_reason, session_start_recovered, }) {
36
+ track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, error_data, session_end_reason, }) {
51
37
  if (!type) {
52
38
  utils_1.debugLog.warn('EventManager', 'Event type is required');
53
39
  return;
@@ -56,7 +42,6 @@ class EventManager extends state_manager_1.StateManager {
56
42
  const isSessionStart = eventType === types_1.EventType.SESSION_START;
57
43
  const isSessionEnd = eventType === types_1.EventType.SESSION_END;
58
44
  const isCriticalEvent = isSessionStart || isSessionEnd;
59
- // Build event payload
60
45
  const currentPageUrl = page_url || this.get('pageUrl');
61
46
  const payload = this.buildEventPayload({
62
47
  type: eventType,
@@ -68,13 +53,10 @@ class EventManager extends state_manager_1.StateManager {
68
53
  web_vitals,
69
54
  error_data,
70
55
  session_end_reason,
71
- session_start_recovered,
72
56
  });
73
- // Check URL exclusions
74
57
  if (this.isEventExcluded(payload)) {
75
58
  return;
76
59
  }
77
- // Skip sampling for critical events, apply for the rest
78
60
  if (!isCriticalEvent && !this.shouldSample()) {
79
61
  return;
80
62
  }
@@ -92,41 +74,27 @@ class EventManager extends state_manager_1.StateManager {
92
74
  }
93
75
  this.set('hasStartSession', true);
94
76
  }
95
- // Check for duplicates
96
77
  if (this.isDuplicateEvent(payload)) {
97
78
  return;
98
79
  }
99
- // Add to queue and schedule sending
100
80
  this.addToQueue(payload);
101
81
  }
102
82
  stop() {
103
- // Clear interval
104
83
  if (this.sendIntervalId) {
105
84
  clearInterval(this.sendIntervalId);
106
85
  this.sendIntervalId = null;
107
86
  }
108
- // Reset state
109
87
  this.eventsQueue = [];
110
88
  this.lastEventFingerprint = null;
111
89
  this.lastEventTime = 0;
112
- // Stop sender
113
90
  this.dataSender.stop();
114
91
  }
115
- /**
116
- * Flush all queued events immediately (async)
117
- */
118
92
  async flushImmediately() {
119
93
  return this.flushEvents(false);
120
94
  }
121
- /**
122
- * Flush all queued events immediately (sync)
123
- */
124
95
  flushImmediatelySync() {
125
96
  return this.flushEvents(true);
126
97
  }
127
- /**
128
- * Queue management and sending intervals
129
- */
130
98
  getQueueLength() {
131
99
  return this.eventsQueue.length;
132
100
  }
@@ -136,9 +104,6 @@ class EventManager extends state_manager_1.StateManager {
136
104
  this.sendIntervalId = null;
137
105
  }
138
106
  }
139
- /**
140
- * Shared flush implementation for both sync and async modes
141
- */
142
107
  flushEvents(isSync) {
143
108
  if (this.eventsQueue.length === 0) {
144
109
  return isSync ? true : Promise.resolve(true);
@@ -151,6 +116,7 @@ class EventManager extends state_manager_1.StateManager {
151
116
  if (success) {
152
117
  this.removeProcessedEvents(eventIds);
153
118
  this.clearSendInterval();
119
+ this.emitEventsQueue(body);
154
120
  }
155
121
  return success;
156
122
  }
@@ -169,9 +135,6 @@ class EventManager extends state_manager_1.StateManager {
169
135
  });
170
136
  }
171
137
  }
172
- /**
173
- * Send queued events to the API
174
- */
175
138
  async sendEventsQueue() {
176
139
  if (!this.get('sessionId') || this.eventsQueue.length === 0) {
177
140
  return;
@@ -191,10 +154,6 @@ class EventManager extends state_manager_1.StateManager {
191
154
  },
192
155
  });
193
156
  }
194
- /**
195
- * Build the payload for sending events to the API
196
- * Includes basic deduplication and sorting
197
- */
198
157
  buildEventsPayload() {
199
158
  const eventMap = new Map();
200
159
  const order = [];
@@ -217,9 +176,6 @@ class EventManager extends state_manager_1.StateManager {
217
176
  ...(this.get('config')?.globalMetadata && { global_metadata: this.get('config')?.globalMetadata }),
218
177
  };
219
178
  }
220
- /**
221
- * Helper methods for event processing
222
- */
223
179
  buildEventPayload(data) {
224
180
  const isSessionStart = data.type === types_1.EventType.SESSION_START;
225
181
  const currentPageUrl = data.page_url ?? this.get('pageUrl');
@@ -235,10 +191,8 @@ class EventManager extends state_manager_1.StateManager {
235
191
  ...(data.web_vitals && { web_vitals: data.web_vitals }),
236
192
  ...(data.error_data && { error_data: data.error_data }),
237
193
  ...(data.session_end_reason && { session_end_reason: data.session_end_reason }),
238
- ...(data.session_start_recovered && { session_start_recovered: data.session_start_recovered }),
239
194
  ...(isSessionStart && (0, utils_1.getUTMParameters)() && { utm: (0, utils_1.getUTMParameters)() }),
240
195
  };
241
- // Add project tags
242
196
  const projectTags = this.get('config')?.tags;
243
197
  if (projectTags?.length) {
244
198
  payload.tags = projectTags;
@@ -259,11 +213,9 @@ class EventManager extends state_manager_1.StateManager {
259
213
  isDuplicateEvent(event) {
260
214
  const now = Date.now();
261
215
  const fingerprint = this.createEventFingerprint(event);
262
- // Check if this is a duplicate within the threshold
263
216
  if (this.lastEventFingerprint === fingerprint && now - this.lastEventTime < config_constants_1.DUPLICATE_EVENT_THRESHOLD_MS) {
264
217
  return true;
265
218
  }
266
- // Update tracking
267
219
  this.lastEventFingerprint = fingerprint;
268
220
  this.lastEventTime = now;
269
221
  return false;
@@ -271,7 +223,6 @@ class EventManager extends state_manager_1.StateManager {
271
223
  createEventFingerprint(event) {
272
224
  let fingerprint = `${event.type}_${event.page_url}`;
273
225
  if (event.click_data) {
274
- // Round coordinates to reduce false duplicates
275
226
  const x = Math.round((event.click_data.x || 0) / 10) * 10;
276
227
  const y = Math.round((event.click_data.y || 0) / 10) * 10;
277
228
  fingerprint += `_click_${x}_${y}`;
@@ -285,6 +236,9 @@ class EventManager extends state_manager_1.StateManager {
285
236
  if (event.web_vitals) {
286
237
  fingerprint += `_vitals_${event.web_vitals.type}`;
287
238
  }
239
+ if (event.error_data) {
240
+ fingerprint += `_error_${event.error_data.type}_${event.error_data.message}`;
241
+ }
288
242
  return fingerprint;
289
243
  }
290
244
  createEventSignature(event) {
@@ -292,21 +246,20 @@ class EventManager extends state_manager_1.StateManager {
292
246
  }
293
247
  addToQueue(event) {
294
248
  this.eventsQueue.push(event);
295
- utils_1.debugLog.info('EventManager', 'Event added to queue', event);
296
249
  this.emitEvent(event);
297
- // Prevent queue overflow
298
250
  if (this.eventsQueue.length > config_constants_1.MAX_EVENTS_QUEUE_LENGTH) {
299
- const removedEvent = this.eventsQueue.shift();
300
- utils_1.debugLog.warn('EventManager', 'Event queue overflow, oldest event removed', {
251
+ const nonCriticalIndex = this.eventsQueue.findIndex((e) => e.type !== types_1.EventType.SESSION_START && e.type !== types_1.EventType.SESSION_END);
252
+ const removedEvent = nonCriticalIndex >= 0 ? this.eventsQueue.splice(nonCriticalIndex, 1)[0] : this.eventsQueue.shift();
253
+ utils_1.debugLog.warn('EventManager', 'Event queue overflow, oldest non-critical event removed', {
301
254
  maxLength: config_constants_1.MAX_EVENTS_QUEUE_LENGTH,
302
255
  currentLength: this.eventsQueue.length,
303
256
  removedEventType: removedEvent?.type,
257
+ wasCritical: removedEvent?.type === types_1.EventType.SESSION_START || removedEvent?.type === types_1.EventType.SESSION_END,
304
258
  });
305
259
  }
306
260
  if (!this.sendIntervalId) {
307
261
  this.startSendInterval();
308
262
  }
309
- // Google Analytics integration
310
263
  this.handleGoogleAnalyticsIntegration(event);
311
264
  }
312
265
  startSendInterval() {
@@ -319,11 +272,9 @@ class EventManager extends state_manager_1.StateManager {
319
272
  handleGoogleAnalyticsIntegration(event) {
320
273
  if (this.googleAnalytics && event.type === types_1.EventType.CUSTOM && event.custom_event) {
321
274
  if (this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug') {
322
- // Skip GA tracking in QA/debug modes
323
- }
324
- else {
325
- this.googleAnalytics.trackEvent(event.custom_event.name, event.custom_event.metadata ?? {});
275
+ return;
326
276
  }
277
+ this.googleAnalytics.trackEvent(event.custom_event.name, event.custom_event.metadata ?? {});
327
278
  }
328
279
  }
329
280
  shouldSample() {
@@ -337,17 +288,11 @@ class EventManager extends state_manager_1.StateManager {
337
288
  return !eventIdSet.has(eventId);
338
289
  });
339
290
  }
340
- /**
341
- * Emit event for external listeners
342
- */
343
291
  emitEvent(eventData) {
344
292
  if (this.emitter) {
345
293
  this.emitter.emit(types_1.EmitterEvent.EVENT, eventData);
346
294
  }
347
295
  }
348
- /**
349
- * Emit events queue for external listeners
350
- */
351
296
  emitEventsQueue(queue) {
352
297
  if (this.emitter) {
353
298
  this.emitter.emit(types_1.EmitterEvent.QUEUE, queue);
@@ -2,7 +2,7 @@ import { BaseEventsQueueDto } from '../types';
2
2
  import { StorageManager } from './storage.manager';
3
3
  import { StateManager } from './state.manager';
4
4
  interface SendCallbacks {
5
- onSuccess?: (eventCount?: number, events?: any[]) => void;
5
+ onSuccess?: (eventCount?: number, events?: any[], body?: BaseEventsQueueDto) => void;
6
6
  onFailure?: () => void;
7
7
  }
8
8
  export declare class SenderManager extends StateManager {
@@ -12,38 +12,15 @@ export declare class SenderManager extends StateManager {
12
12
  private isRetrying;
13
13
  constructor(storeManager: StorageManager);
14
14
  private getQueueStorageKey;
15
- /**
16
- * Send events synchronously using sendBeacon or XHR fallback
17
- * Used primarily for page unload scenarios
18
- */
19
15
  sendEventsQueueSync(body: BaseEventsQueueDto): boolean;
20
- /**
21
- * Send events asynchronously with persistence and retry logic
22
- * Main method for sending events during normal operation
23
- */
24
16
  sendEventsQueue(body: BaseEventsQueueDto, callbacks?: SendCallbacks): Promise<boolean>;
25
- /**
26
- * Recover and send previously persisted events
27
- * Called during initialization to handle events from previous session
28
- */
29
17
  recoverPersistedEvents(callbacks?: SendCallbacks): Promise<void>;
30
- /**
31
- * Persist events for recovery in case of failure
32
- */
33
18
  persistEventsForRecovery(body: BaseEventsQueueDto): boolean;
34
- /**
35
- * Legacy method for backward compatibility
36
- * @deprecated Use sendEventsQueue instead
37
- */
38
19
  sendEventsQueueAsync(body: BaseEventsQueueDto): Promise<boolean>;
39
- /**
40
- * Stop the sender manager and clean up resources
41
- */
42
20
  stop(): void;
43
21
  private send;
44
22
  private sendWithTimeout;
45
23
  private sendQueueSyncInternal;
46
- private sendSyncXHR;
47
24
  private prepareRequest;
48
25
  private getPersistedData;
49
26
  private isDataRecent;
@@ -53,10 +30,6 @@ export declare class SenderManager extends StateManager {
53
30
  private resetRetryState;
54
31
  private scheduleRetry;
55
32
  private shouldSkipSend;
56
- /**
57
- * Simulate a successful send operation for skip mode
58
- * Provides realistic timing and behavior without making HTTP requests
59
- */
60
33
  private simulateSuccessfulSend;
61
34
  private isSendBeaconAvailable;
62
35
  private clearRetryTimeout;
@@ -18,40 +18,34 @@ class SenderManager extends state_manager_1.StateManager {
18
18
  const userId = this.get('userId') || 'anonymous';
19
19
  return `${(0, constants_1.QUEUE_KEY)(projectId)}:${userId}`;
20
20
  }
21
- /**
22
- * Send events synchronously using sendBeacon or XHR fallback
23
- * Used primarily for page unload scenarios
24
- */
25
21
  sendEventsQueueSync(body) {
26
- // For skip mode, simulate success immediately (sync version)
27
22
  if (this.shouldSkipSend()) {
28
- this.clearPersistedEvents();
29
23
  this.resetRetryState();
30
24
  return true;
31
25
  }
26
+ const config = this.get('config');
27
+ if (config?.id === types_1.SpecialProjectId.Fail) {
28
+ utils_1.debugLog.warn('SenderManager', 'Fail mode: simulating network failure (sync)', {
29
+ events: body.events.length,
30
+ });
31
+ return false;
32
+ }
32
33
  const success = this.sendQueueSyncInternal(body);
33
34
  if (success) {
34
- this.clearPersistedEvents();
35
35
  this.resetRetryState();
36
36
  }
37
37
  return success;
38
38
  }
39
- /**
40
- * Send events asynchronously with persistence and retry logic
41
- * Main method for sending events during normal operation
42
- */
43
39
  async sendEventsQueue(body, callbacks) {
44
- // First, try to persist events for recovery (even in skip mode for consistency)
45
40
  const persisted = this.persistEvents(body);
46
41
  if (!persisted && !this.shouldSkipSend()) {
47
42
  utils_1.debugLog.warn('SenderManager', 'Failed to persist events, attempting immediate send');
48
43
  }
49
- // Attempt to send events (or simulate in skip mode)
50
44
  const success = await this.send(body);
51
45
  if (success) {
52
46
  this.clearPersistedEvents();
53
47
  this.resetRetryState();
54
- callbacks?.onSuccess?.(body.events.length);
48
+ callbacks?.onSuccess?.(body.events.length, body.events, body);
55
49
  }
56
50
  else {
57
51
  this.scheduleRetry(body, callbacks);
@@ -59,10 +53,6 @@ class SenderManager extends state_manager_1.StateManager {
59
53
  }
60
54
  return success;
61
55
  }
62
- /**
63
- * Recover and send previously persisted events
64
- * Called during initialization to handle events from previous session
65
- */
66
56
  async recoverPersistedEvents(callbacks) {
67
57
  try {
68
58
  const persistedData = this.getPersistedData();
@@ -75,7 +65,7 @@ class SenderManager extends state_manager_1.StateManager {
75
65
  if (success) {
76
66
  this.clearPersistedEvents();
77
67
  this.resetRetryState();
78
- callbacks?.onSuccess?.(persistedData.events.length);
68
+ callbacks?.onSuccess?.(persistedData.events.length, persistedData.events, body);
79
69
  }
80
70
  else {
81
71
  this.scheduleRetry(body, callbacks);
@@ -84,35 +74,30 @@ class SenderManager extends state_manager_1.StateManager {
84
74
  }
85
75
  catch (error) {
86
76
  utils_1.debugLog.error('SenderManager', 'Failed to recover persisted events', { error });
87
- this.clearPersistedEvents(); // Clean up corrupted data
77
+ this.clearPersistedEvents();
88
78
  }
89
79
  }
90
- /**
91
- * Persist events for recovery in case of failure
92
- */
93
80
  persistEventsForRecovery(body) {
94
81
  return this.persistEvents(body);
95
82
  }
96
- /**
97
- * Legacy method for backward compatibility
98
- * @deprecated Use sendEventsQueue instead
99
- */
100
83
  async sendEventsQueueAsync(body) {
101
84
  return this.sendEventsQueue(body);
102
85
  }
103
- /**
104
- * Stop the sender manager and clean up resources
105
- */
106
86
  stop() {
107
87
  this.clearRetryTimeout();
108
88
  this.resetRetryState();
109
- // Clear any persisted events on shutdown to prevent stale data
110
- this.clearPersistedEvents();
111
89
  }
112
90
  async send(body) {
113
91
  if (this.shouldSkipSend()) {
114
92
  return this.simulateSuccessfulSend();
115
93
  }
94
+ const config = this.get('config');
95
+ if (config?.id === types_1.SpecialProjectId.Fail) {
96
+ utils_1.debugLog.warn('SenderManager', 'Fail mode: simulating network failure', {
97
+ events: body.events.length,
98
+ });
99
+ return false;
100
+ }
116
101
  const { url, payload } = this.prepareRequest(body);
117
102
  try {
118
103
  const response = await this.sendWithTimeout(url, payload);
@@ -123,7 +108,7 @@ class SenderManager extends state_manager_1.StateManager {
123
108
  utils_1.debugLog.error('SenderManager', 'Send request failed', {
124
109
  error: errorMessage,
125
110
  events: body.events.length,
126
- url: url.replace(/\/\/[^/]+/, '//[DOMAIN]'), // Hide domain for privacy
111
+ url: url.replace(/\/\/[^/]+/, '//[DOMAIN]'),
127
112
  });
128
113
  return false;
129
114
  }
@@ -131,11 +116,13 @@ class SenderManager extends state_manager_1.StateManager {
131
116
  async sendWithTimeout(url, payload) {
132
117
  const controller = new AbortController();
133
118
  const timeoutId = setTimeout(() => controller.abort(), constants_1.REQUEST_TIMEOUT_MS);
119
+ const config = this.get('config');
134
120
  try {
135
121
  const response = await fetch(url, {
136
122
  method: 'POST',
137
123
  headers: {
138
124
  'Content-Type': 'application/json',
125
+ 'X-TraceLog-Project': config?.id || 'unknown',
139
126
  },
140
127
  body: payload,
141
128
  keepalive: true,
@@ -154,42 +141,33 @@ class SenderManager extends state_manager_1.StateManager {
154
141
  sendQueueSyncInternal(body) {
155
142
  const { url, payload } = this.prepareRequest(body);
156
143
  const blob = new Blob([payload], { type: 'application/json' });
157
- if (this.isSendBeaconAvailable() && navigator.sendBeacon(url, blob)) {
158
- return true;
159
- }
160
- return this.sendSyncXHR(url, payload);
161
- }
162
- sendSyncXHR(url, payload) {
163
- const xhr = new XMLHttpRequest();
164
- try {
165
- xhr.open('POST', url, false);
166
- xhr.setRequestHeader('Content-Type', 'application/json');
167
- xhr.withCredentials = true;
168
- xhr.timeout = constants_1.SYNC_XHR_TIMEOUT_MS;
169
- xhr.send(payload);
170
- const success = xhr.status >= 200 && xhr.status < 300;
171
- if (!success) {
172
- utils_1.debugLog.warn('SenderManager', 'Sync XHR failed', {
173
- status: xhr.status,
174
- statusText: xhr.statusText || 'Unknown error',
175
- });
144
+ if (this.isSendBeaconAvailable()) {
145
+ const success = navigator.sendBeacon(url, blob);
146
+ if (success) {
147
+ return true;
176
148
  }
177
- return success;
149
+ utils_1.debugLog.warn('SenderManager', 'sendBeacon failed, persisting events for recovery');
178
150
  }
179
- catch (error) {
180
- const errorMessage = error instanceof Error ? error.message : String(error);
181
- utils_1.debugLog.warn('SenderManager', 'Sync XHR error', {
182
- error: errorMessage,
183
- status: xhr.status || 'unknown',
184
- });
185
- return false;
151
+ else {
152
+ utils_1.debugLog.warn('SenderManager', 'sendBeacon not available, persisting events for recovery');
186
153
  }
154
+ this.persistEventsForRecovery(body);
155
+ return false;
187
156
  }
188
157
  prepareRequest(body) {
189
158
  const url = `${this.get('apiUrl')}/collect`;
159
+ // Enrich payload with metadata for sendBeacon() fallback
160
+ // sendBeacon() doesn't send custom headers, so we include referer in payload
161
+ const enrichedBody = {
162
+ ...body,
163
+ _metadata: {
164
+ referer: typeof window !== 'undefined' ? window.location.href : undefined,
165
+ timestamp: Date.now(),
166
+ },
167
+ };
190
168
  return {
191
169
  url,
192
- payload: JSON.stringify(body),
170
+ payload: JSON.stringify(enrichedBody),
193
171
  };
194
172
  }
195
173
  getPersistedData() {
@@ -202,7 +180,6 @@ class SenderManager extends state_manager_1.StateManager {
202
180
  }
203
181
  catch (error) {
204
182
  utils_1.debugLog.warn('SenderManager', 'Failed to parse persisted data', { error });
205
- // Clean up corrupted data
206
183
  this.clearPersistedEvents();
207
184
  }
208
185
  return null;
@@ -245,7 +222,8 @@ class SenderManager extends state_manager_1.StateManager {
245
222
  }
246
223
  clearPersistedEvents() {
247
224
  try {
248
- this.storeManager.removeItem(this.getQueueStorageKey());
225
+ const key = this.getQueueStorageKey();
226
+ this.storeManager.removeItem(key);
249
227
  }
250
228
  catch (error) {
251
229
  utils_1.debugLog.warn('SenderManager', 'Failed to clear persisted events', { error });
@@ -268,13 +246,10 @@ class SenderManager extends state_manager_1.StateManager {
268
246
  return;
269
247
  }
270
248
  const retryDelay = constants_1.RETRY_DELAY_MS * Math.pow(2, this.retryCount); // Exponential backoff
249
+ this.isRetrying = true;
271
250
  this.retryTimeoutId = window.setTimeout(async () => {
272
251
  this.retryTimeoutId = null;
273
- if (this.isRetrying) {
274
- return;
275
- }
276
252
  this.retryCount++;
277
- this.isRetrying = true;
278
253
  try {
279
254
  const success = await this.send(body);
280
255
  if (success) {
@@ -306,15 +281,10 @@ class SenderManager extends state_manager_1.StateManager {
306
281
  const { id } = config || {};
307
282
  return id === types_1.SpecialProjectId.Skip;
308
283
  }
309
- /**
310
- * Simulate a successful send operation for skip mode
311
- * Provides realistic timing and behavior without making HTTP requests
312
- */
313
284
  async simulateSuccessfulSend() {
314
- // Simulate realistic network delay (100-500ms)
315
285
  const delay = Math.random() * 400 + 100;
316
286
  await new Promise((resolve) => setTimeout(resolve, delay));
317
- return true; // Always successful in skip mode
287
+ return true;
318
288
  }
319
289
  isSendBeaconAvailable() {
320
290
  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
  }