@tracelog/lib 0.0.8 → 0.2.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 (228) hide show
  1. package/README.md +58 -24
  2. package/dist/browser/tracelog.js +1934 -3226
  3. package/dist/cjs/api.d.ts +33 -19
  4. package/dist/cjs/api.js +111 -156
  5. package/dist/cjs/app.constants.d.ts +80 -1
  6. package/dist/cjs/app.constants.js +90 -3
  7. package/dist/cjs/app.d.ts +29 -44
  8. package/dist/cjs/app.js +114 -212
  9. package/dist/cjs/app.types.d.ts +2 -7
  10. package/dist/cjs/app.types.js +10 -21
  11. package/dist/cjs/constants/api.constants.js +11 -5
  12. package/dist/cjs/constants/config.constants.d.ts +75 -0
  13. package/dist/cjs/constants/config.constants.js +178 -0
  14. package/dist/cjs/constants/error.constants.d.ts +29 -0
  15. package/dist/cjs/constants/error.constants.js +50 -0
  16. package/dist/cjs/constants/index.d.ts +3 -6
  17. package/dist/cjs/constants/index.js +3 -6
  18. package/dist/cjs/constants/performance.constants.d.ts +28 -0
  19. package/dist/cjs/constants/performance.constants.js +43 -0
  20. package/dist/cjs/handlers/click.handler.d.ts +1 -0
  21. package/dist/cjs/handlers/click.handler.js +30 -49
  22. package/dist/cjs/handlers/error.handler.d.ts +11 -6
  23. package/dist/cjs/handlers/error.handler.js +91 -51
  24. package/dist/cjs/handlers/page-view.handler.js +38 -29
  25. package/dist/cjs/handlers/performance.handler.d.ts +3 -0
  26. package/dist/cjs/handlers/performance.handler.js +76 -37
  27. package/dist/cjs/handlers/scroll.handler.d.ts +15 -0
  28. package/dist/cjs/handlers/scroll.handler.js +105 -31
  29. package/dist/cjs/handlers/session.handler.d.ts +6 -20
  30. package/dist/cjs/handlers/session.handler.js +38 -326
  31. package/dist/cjs/integrations/google-analytics.integration.d.ts +0 -1
  32. package/dist/cjs/integrations/google-analytics.integration.js +27 -98
  33. package/dist/cjs/listeners/input-listener-managers.d.ts +18 -9
  34. package/dist/cjs/listeners/input-listener-managers.js +24 -33
  35. package/dist/cjs/listeners/touch-listener-manager.d.ts +1 -3
  36. package/dist/cjs/listeners/touch-listener-manager.js +1 -23
  37. package/dist/cjs/listeners/visibility-listener-manager.d.ts +1 -4
  38. package/dist/cjs/listeners/visibility-listener-manager.js +6 -42
  39. package/dist/cjs/managers/api.manager.d.ts +13 -3
  40. package/dist/cjs/managers/api.manager.js +35 -5
  41. package/dist/cjs/managers/config.manager.d.ts +53 -3
  42. package/dist/cjs/managers/config.manager.js +131 -62
  43. package/dist/cjs/managers/event.manager.d.ts +57 -36
  44. package/dist/cjs/managers/event.manager.js +266 -417
  45. package/dist/cjs/managers/sender.manager.d.ts +40 -22
  46. package/dist/cjs/managers/sender.manager.js +200 -198
  47. package/dist/cjs/managers/session.manager.d.ts +80 -66
  48. package/dist/cjs/managers/session.manager.js +267 -522
  49. package/dist/cjs/managers/state.manager.d.ts +33 -0
  50. package/dist/cjs/managers/state.manager.js +79 -6
  51. package/dist/cjs/managers/storage.manager.d.ts +26 -2
  52. package/dist/cjs/managers/storage.manager.js +67 -34
  53. package/dist/cjs/managers/tags.manager.d.ts +31 -7
  54. package/dist/cjs/managers/tags.manager.js +123 -241
  55. package/dist/cjs/managers/user.manager.d.ts +14 -5
  56. package/dist/cjs/managers/user.manager.js +17 -9
  57. package/dist/cjs/public-api.d.ts +10 -1
  58. package/dist/cjs/public-api.js +18 -24
  59. package/dist/cjs/test-bridge.d.ts +48 -0
  60. package/dist/cjs/test-bridge.js +110 -0
  61. package/dist/cjs/types/api.types.d.ts +21 -6
  62. package/dist/cjs/types/api.types.js +21 -6
  63. package/dist/cjs/types/config.types.d.ts +22 -84
  64. package/dist/cjs/types/emitter.types.d.ts +11 -0
  65. package/dist/cjs/types/emitter.types.js +8 -0
  66. package/dist/cjs/types/event.types.d.ts +8 -11
  67. package/dist/cjs/types/index.d.ts +3 -1
  68. package/dist/cjs/types/index.js +3 -1
  69. package/dist/cjs/types/queue.types.d.ts +1 -0
  70. package/dist/cjs/types/session.types.d.ts +0 -64
  71. package/dist/cjs/types/state.types.d.ts +1 -0
  72. package/dist/cjs/types/test-bridge.types.d.ts +38 -0
  73. package/dist/cjs/types/validation-error.types.d.ts +7 -0
  74. package/dist/cjs/types/validation-error.types.js +11 -1
  75. package/dist/cjs/types/window.types.d.ts +1 -8
  76. package/dist/cjs/utils/data/uuid.utils.d.ts +1 -1
  77. package/dist/cjs/utils/data/uuid.utils.js +7 -5
  78. package/dist/cjs/utils/emitter.utils.d.ts +8 -0
  79. package/dist/cjs/utils/emitter.utils.js +33 -0
  80. package/dist/cjs/utils/index.d.ts +1 -0
  81. package/dist/cjs/utils/index.js +1 -0
  82. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +10 -51
  83. package/dist/cjs/utils/logging/debug-logger.utils.js +36 -127
  84. package/dist/cjs/utils/network/fetch-with-timeout.utils.d.ts +4 -0
  85. package/dist/cjs/utils/network/fetch-with-timeout.utils.js +25 -0
  86. package/dist/cjs/utils/network/index.d.ts +1 -0
  87. package/dist/cjs/utils/network/index.js +1 -0
  88. package/dist/cjs/utils/network/url.utils.js +2 -42
  89. package/dist/cjs/utils/security/sanitize.utils.d.ts +1 -8
  90. package/dist/cjs/utils/security/sanitize.utils.js +7 -41
  91. package/dist/cjs/utils/validations/config-validations.utils.d.ts +7 -0
  92. package/dist/cjs/utils/validations/config-validations.utils.js +77 -22
  93. package/dist/esm/api.d.ts +33 -19
  94. package/dist/esm/api.js +105 -118
  95. package/dist/esm/app.constants.d.ts +80 -1
  96. package/dist/esm/app.constants.js +89 -1
  97. package/dist/esm/app.d.ts +29 -44
  98. package/dist/esm/app.js +115 -213
  99. package/dist/esm/app.types.d.ts +2 -7
  100. package/dist/esm/app.types.js +1 -7
  101. package/dist/esm/constants/api.constants.js +10 -4
  102. package/dist/esm/constants/config.constants.d.ts +75 -0
  103. package/dist/esm/constants/config.constants.js +174 -0
  104. package/dist/esm/constants/error.constants.d.ts +29 -0
  105. package/dist/esm/constants/error.constants.js +47 -0
  106. package/dist/esm/constants/index.d.ts +3 -6
  107. package/dist/esm/constants/index.js +3 -6
  108. package/dist/esm/constants/performance.constants.d.ts +28 -0
  109. package/dist/esm/constants/performance.constants.js +40 -0
  110. package/dist/esm/handlers/click.handler.d.ts +1 -0
  111. package/dist/esm/handlers/click.handler.js +30 -49
  112. package/dist/esm/handlers/error.handler.d.ts +11 -6
  113. package/dist/esm/handlers/error.handler.js +91 -51
  114. package/dist/esm/handlers/page-view.handler.js +38 -29
  115. package/dist/esm/handlers/performance.handler.d.ts +3 -0
  116. package/dist/esm/handlers/performance.handler.js +71 -32
  117. package/dist/esm/handlers/scroll.handler.d.ts +15 -0
  118. package/dist/esm/handlers/scroll.handler.js +106 -32
  119. package/dist/esm/handlers/session.handler.d.ts +6 -20
  120. package/dist/esm/handlers/session.handler.js +38 -326
  121. package/dist/esm/integrations/google-analytics.integration.d.ts +0 -1
  122. package/dist/esm/integrations/google-analytics.integration.js +27 -98
  123. package/dist/esm/listeners/input-listener-managers.d.ts +18 -9
  124. package/dist/esm/listeners/input-listener-managers.js +23 -32
  125. package/dist/esm/listeners/touch-listener-manager.d.ts +1 -3
  126. package/dist/esm/listeners/touch-listener-manager.js +1 -23
  127. package/dist/esm/listeners/visibility-listener-manager.d.ts +1 -4
  128. package/dist/esm/listeners/visibility-listener-manager.js +6 -42
  129. package/dist/esm/managers/api.manager.d.ts +13 -3
  130. package/dist/esm/managers/api.manager.js +34 -3
  131. package/dist/esm/managers/config.manager.d.ts +53 -3
  132. package/dist/esm/managers/config.manager.js +133 -64
  133. package/dist/esm/managers/event.manager.d.ts +57 -36
  134. package/dist/esm/managers/event.manager.js +268 -419
  135. package/dist/esm/managers/sender.manager.d.ts +40 -22
  136. package/dist/esm/managers/sender.manager.js +201 -199
  137. package/dist/esm/managers/session.manager.d.ts +80 -66
  138. package/dist/esm/managers/session.manager.js +269 -524
  139. package/dist/esm/managers/state.manager.d.ts +33 -0
  140. package/dist/esm/managers/state.manager.js +78 -6
  141. package/dist/esm/managers/storage.manager.d.ts +26 -2
  142. package/dist/esm/managers/storage.manager.js +66 -33
  143. package/dist/esm/managers/tags.manager.d.ts +31 -7
  144. package/dist/esm/managers/tags.manager.js +124 -242
  145. package/dist/esm/managers/user.manager.d.ts +14 -5
  146. package/dist/esm/managers/user.manager.js +17 -9
  147. package/dist/esm/public-api.d.ts +10 -1
  148. package/dist/esm/public-api.js +14 -1
  149. package/dist/esm/test-bridge.d.ts +48 -0
  150. package/dist/esm/test-bridge.js +106 -0
  151. package/dist/esm/types/api.types.d.ts +21 -6
  152. package/dist/esm/types/api.types.js +21 -6
  153. package/dist/esm/types/config.types.d.ts +22 -84
  154. package/dist/esm/types/emitter.types.d.ts +11 -0
  155. package/dist/esm/types/emitter.types.js +5 -0
  156. package/dist/esm/types/event.types.d.ts +8 -11
  157. package/dist/esm/types/index.d.ts +3 -1
  158. package/dist/esm/types/index.js +3 -1
  159. package/dist/esm/types/queue.types.d.ts +1 -0
  160. package/dist/esm/types/session.types.d.ts +0 -64
  161. package/dist/esm/types/state.types.d.ts +1 -0
  162. package/dist/esm/types/test-bridge.types.d.ts +38 -0
  163. package/dist/esm/types/validation-error.types.d.ts +7 -0
  164. package/dist/esm/types/validation-error.types.js +9 -0
  165. package/dist/esm/types/window.types.d.ts +1 -8
  166. package/dist/esm/utils/data/uuid.utils.d.ts +1 -1
  167. package/dist/esm/utils/data/uuid.utils.js +7 -5
  168. package/dist/esm/utils/emitter.utils.d.ts +8 -0
  169. package/dist/esm/utils/emitter.utils.js +29 -0
  170. package/dist/esm/utils/index.d.ts +1 -0
  171. package/dist/esm/utils/index.js +1 -0
  172. package/dist/esm/utils/logging/debug-logger.utils.d.ts +10 -51
  173. package/dist/esm/utils/logging/debug-logger.utils.js +36 -127
  174. package/dist/esm/utils/network/fetch-with-timeout.utils.d.ts +4 -0
  175. package/dist/esm/utils/network/fetch-with-timeout.utils.js +22 -0
  176. package/dist/esm/utils/network/index.d.ts +1 -0
  177. package/dist/esm/utils/network/index.js +1 -0
  178. package/dist/esm/utils/network/url.utils.js +2 -42
  179. package/dist/esm/utils/security/sanitize.utils.d.ts +1 -8
  180. package/dist/esm/utils/security/sanitize.utils.js +6 -39
  181. package/dist/esm/utils/validations/config-validations.utils.d.ts +7 -0
  182. package/dist/esm/utils/validations/config-validations.utils.js +76 -22
  183. package/package.json +23 -16
  184. package/dist/browser/web-vitals-CCnqwnC8.mjs +0 -198
  185. package/dist/cjs/constants/browser.constants.d.ts +0 -3
  186. package/dist/cjs/constants/browser.constants.js +0 -41
  187. package/dist/cjs/constants/initialization.constants.d.ts +0 -40
  188. package/dist/cjs/constants/initialization.constants.js +0 -48
  189. package/dist/cjs/constants/limits.constants.d.ts +0 -25
  190. package/dist/cjs/constants/limits.constants.js +0 -40
  191. package/dist/cjs/constants/security.constants.d.ts +0 -1
  192. package/dist/cjs/constants/security.constants.js +0 -12
  193. package/dist/cjs/constants/timing.constants.d.ts +0 -22
  194. package/dist/cjs/constants/timing.constants.js +0 -34
  195. package/dist/cjs/constants/validation.constants.d.ts +0 -13
  196. package/dist/cjs/constants/validation.constants.js +0 -31
  197. package/dist/cjs/handlers/network.handler.d.ts +0 -16
  198. package/dist/cjs/handlers/network.handler.js +0 -136
  199. package/dist/cjs/managers/cross-tab-session.manager.d.ts +0 -170
  200. package/dist/cjs/managers/cross-tab-session.manager.js +0 -730
  201. package/dist/cjs/managers/sampling.manager.d.ts +0 -8
  202. package/dist/cjs/managers/sampling.manager.js +0 -53
  203. package/dist/cjs/managers/session-recovery.manager.d.ts +0 -65
  204. package/dist/cjs/managers/session-recovery.manager.js +0 -237
  205. package/dist/cjs/types/web-vitals.types.d.ts +0 -6
  206. package/dist/esm/constants/browser.constants.d.ts +0 -3
  207. package/dist/esm/constants/browser.constants.js +0 -38
  208. package/dist/esm/constants/initialization.constants.d.ts +0 -40
  209. package/dist/esm/constants/initialization.constants.js +0 -45
  210. package/dist/esm/constants/limits.constants.d.ts +0 -25
  211. package/dist/esm/constants/limits.constants.js +0 -37
  212. package/dist/esm/constants/security.constants.d.ts +0 -1
  213. package/dist/esm/constants/security.constants.js +0 -9
  214. package/dist/esm/constants/timing.constants.d.ts +0 -22
  215. package/dist/esm/constants/timing.constants.js +0 -31
  216. package/dist/esm/constants/validation.constants.d.ts +0 -13
  217. package/dist/esm/constants/validation.constants.js +0 -28
  218. package/dist/esm/handlers/network.handler.d.ts +0 -16
  219. package/dist/esm/handlers/network.handler.js +0 -132
  220. package/dist/esm/managers/cross-tab-session.manager.d.ts +0 -170
  221. package/dist/esm/managers/cross-tab-session.manager.js +0 -726
  222. package/dist/esm/managers/sampling.manager.d.ts +0 -8
  223. package/dist/esm/managers/sampling.manager.js +0 -49
  224. package/dist/esm/managers/session-recovery.manager.d.ts +0 -65
  225. package/dist/esm/managers/session-recovery.manager.js +0 -233
  226. package/dist/esm/types/web-vitals.types.d.ts +0 -6
  227. /package/dist/cjs/types/{web-vitals.types.js → test-bridge.types.js} +0 -0
  228. /package/dist/esm/types/{web-vitals.types.js → test-bridge.types.js} +0 -0
@@ -1,504 +1,353 @@
1
- import { EVENT_SENT_INTERVAL_MS, EVENT_SENT_INTERVAL_TEST_MS, MAX_EVENTS_QUEUE_LENGTH, DUPLICATE_EVENT_THRESHOLD_MS, CIRCUIT_BREAKER_CONSTANTS, MAX_FINGERPRINTS, FINGERPRINT_CLEANUP_MULTIPLIER, CLICK_COORDINATE_PRECISION, EVENT_PERSISTENCE_MAX_AGE_MS, } from '../constants';
2
- import { EventType } from '../types';
3
- import { getUTMParameters, isUrlPathExcluded } from '../utils';
4
- import { debugLog } from '../utils/logging';
1
+ import { EVENT_SENT_INTERVAL_MS, MAX_EVENTS_QUEUE_LENGTH, DUPLICATE_EVENT_THRESHOLD_MS, } from '../constants/config.constants';
2
+ import { EmitterEvent, EventType } from '../types';
3
+ import { getUTMParameters, isUrlPathExcluded, debugLog } from '../utils';
5
4
  import { SenderManager } from './sender.manager';
6
- import { SamplingManager } from './sampling.manager';
7
5
  import { StateManager } from './state.manager';
8
- import { TagsManager } from './tags.manager';
6
+ /**
7
+ * EventManager - Core event tracking and queue management
8
+ *
9
+ * Responsibilities:
10
+ * - Track user events (clicks, scrolls, page views, custom events)
11
+ * - Queue events and batch send them to the analytics API
12
+ * - Handle deduplication of similar events
13
+ * - Manage event sending intervals and retry logic
14
+ * - Integrate with Google Analytics when configured
15
+ */
9
16
  export class EventManager extends StateManager {
10
- constructor(storeManager, googleAnalytics = null) {
17
+ constructor(storeManager, googleAnalytics = null, emitter = null) {
11
18
  super();
12
19
  this.eventsQueue = [];
13
- this.lastEvent = null;
14
- this.eventsQueueIntervalId = null;
15
- this.intervalActive = false;
16
- // Circuit breaker properties
17
- this.failureCount = 0;
18
- this.MAX_FAILURES = CIRCUIT_BREAKER_CONSTANTS.MAX_FAILURES;
19
- this.circuitOpen = false;
20
- this.circuitOpenTime = 0;
21
- this.backoffDelay = CIRCUIT_BREAKER_CONSTANTS.INITIAL_BACKOFF_DELAY_MS;
22
- this.circuitResetTimeoutId = null;
23
- // Event deduplication properties
24
- this.eventFingerprints = new Map();
25
- // Persistence storage key
26
- this.PERSISTENCE_KEY = 'tl:circuit_breaker_events';
27
- this.storageManager = storeManager;
20
+ this.lastEventFingerprint = null;
21
+ this.lastEventTime = 0;
22
+ this.sendIntervalId = null;
28
23
  this.googleAnalytics = googleAnalytics;
29
- this.samplingManager = new SamplingManager();
30
- this.tagsManager = new TagsManager();
31
24
  this.dataSender = new SenderManager(storeManager);
32
- // Restore any persisted events on initialization
33
- this.restoreEventsFromStorage();
34
- debugLog.debug('EventManager', 'EventManager initialized', {
35
- hasGoogleAnalytics: !!googleAnalytics,
36
- restoredEventsCount: this.eventsQueue.length,
37
- });
25
+ this.emitter = emitter;
38
26
  }
39
- track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, session_end_reason, session_start_recovered, }) {
40
- debugLog.info('EventManager', `📥 Event captured: ${type}`, {
41
- type,
42
- page_url,
43
- hasCustomEvent: !!custom_event,
44
- hasClickData: !!click_data,
45
- hasScrollData: !!scroll_data,
46
- hasWebVitals: !!web_vitals,
27
+ /**
28
+ * Recovers persisted events from localStorage
29
+ * Should be called after initialization to recover any events that failed to send
30
+ */
31
+ async recoverPersistedEvents() {
32
+ await this.dataSender.recoverPersistedEvents({
33
+ onSuccess: (_eventCount, recoveredEvents) => {
34
+ if (recoveredEvents && recoveredEvents.length > 0) {
35
+ const eventIds = recoveredEvents.map((e) => e.timestamp + '_' + e.type);
36
+ this.removeProcessedEvents(eventIds);
37
+ }
38
+ },
39
+ onFailure: async () => {
40
+ debugLog.warn('EventManager', 'Failed to recover persisted events');
41
+ },
47
42
  });
48
- if (!this.samplingManager.shouldSampleEvent(type, web_vitals)) {
49
- debugLog.debug('EventManager', 'Event filtered by sampling', { type, samplingActive: true });
43
+ }
44
+ /**
45
+ * Track user events with automatic deduplication and queueing
46
+ */
47
+ track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, error_data, session_end_reason, session_start_recovered, }) {
48
+ if (!type) {
49
+ debugLog.warn('EventManager', 'Event type is required');
50
50
  return;
51
51
  }
52
- const isDuplicatedEvent = this.isDuplicatedEvent({
53
- type,
54
- page_url,
52
+ const eventType = type;
53
+ const isSessionStart = eventType === EventType.SESSION_START;
54
+ const isSessionEnd = eventType === EventType.SESSION_END;
55
+ const isCriticalEvent = isSessionStart || isSessionEnd;
56
+ // Build event payload
57
+ const currentPageUrl = page_url || this.get('pageUrl');
58
+ const payload = this.buildEventPayload({
59
+ type: eventType,
60
+ page_url: currentPageUrl,
61
+ from_page_url,
55
62
  scroll_data,
56
63
  click_data,
57
64
  custom_event,
58
65
  web_vitals,
66
+ error_data,
59
67
  session_end_reason,
60
68
  session_start_recovered,
61
69
  });
62
- if (isDuplicatedEvent) {
63
- const now = Date.now();
64
- if (this.eventsQueue && this.eventsQueue.length > 0) {
65
- const lastEvent = this.eventsQueue.at(-1);
66
- if (lastEvent) {
67
- lastEvent.timestamp = now;
68
- }
69
- }
70
- if (this.lastEvent) {
71
- this.lastEvent.timestamp = now;
72
- }
73
- debugLog.debug('EventManager', 'Duplicate event detected, timestamp updated', {
74
- type,
75
- queueLength: this.eventsQueue.length,
76
- });
70
+ // Check URL exclusions
71
+ if (this.isEventExcluded(payload)) {
77
72
  return;
78
73
  }
79
- const effectivePageUrl = page_url || this.get('pageUrl');
80
- const isRouteExcluded = isUrlPathExcluded(effectivePageUrl, this.get('config').excludedUrlPaths);
81
- const hasStartSession = this.get('hasStartSession');
82
- const isSessionEndEvent = type == EventType.SESSION_END;
83
- if (isRouteExcluded && (!isSessionEndEvent || (isSessionEndEvent && !hasStartSession))) {
84
- if (this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug') {
85
- debugLog.debug('EventManager', `Event ${type} on excluded route: ${page_url}`);
86
- }
74
+ // Skip sampling for critical events, apply for the rest
75
+ if (!isCriticalEvent && !this.shouldSample()) {
87
76
  return;
88
77
  }
89
- const isSessionStartEvent = type === EventType.SESSION_START;
90
- if (isSessionStartEvent) {
78
+ if (isSessionStart) {
79
+ const currentSessionId = this.get('sessionId');
80
+ if (!currentSessionId) {
81
+ debugLog.warn('EventManager', 'Session start event ignored: missing sessionId');
82
+ return;
83
+ }
84
+ if (this.get('hasStartSession')) {
85
+ debugLog.warn('EventManager', 'Duplicate session_start detected', {
86
+ sessionId: currentSessionId,
87
+ });
88
+ return;
89
+ }
91
90
  this.set('hasStartSession', true);
92
91
  }
93
- const utmParams = isSessionStartEvent ? getUTMParameters() : undefined;
94
- const payload = {
95
- type: type,
96
- page_url: isRouteExcluded ? 'excluded' : effectivePageUrl,
97
- timestamp: Date.now(),
98
- ...(isSessionStartEvent && { referrer: document.referrer || 'Direct' }),
99
- ...(from_page_url && !isRouteExcluded ? { from_page_url } : {}),
100
- ...(scroll_data && { scroll_data }),
101
- ...(click_data && { click_data }),
102
- ...(custom_event && { custom_event }),
103
- ...(utmParams && { utm: utmParams }),
104
- ...(web_vitals && { web_vitals }),
105
- ...(session_end_reason && { session_end_reason }),
106
- ...(session_start_recovered && { session_start_recovered }),
107
- };
108
- if (this.get('config')?.tags?.length) {
109
- const matchedTags = this.tagsManager.getEventTagsIds(payload, this.get('device'));
110
- if (matchedTags?.length) {
111
- payload.tags =
112
- this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug'
113
- ? matchedTags.map((id) => ({
114
- id,
115
- key: this.get('config')?.tags?.find((t) => t.id === id)?.key ?? '',
116
- }))
117
- : matchedTags;
118
- }
92
+ // Check for duplicates
93
+ if (this.isDuplicateEvent(payload)) {
94
+ return;
119
95
  }
120
- this.lastEvent = payload;
121
- this.processAndSend(payload);
96
+ // Add to queue and schedule sending
97
+ this.addToQueue(payload);
122
98
  }
123
99
  stop() {
124
- // Clear interval and reset interval state
125
- if (this.eventsQueueIntervalId) {
126
- clearInterval(this.eventsQueueIntervalId);
127
- this.eventsQueueIntervalId = null;
128
- this.intervalActive = false;
100
+ // Clear interval
101
+ if (this.sendIntervalId) {
102
+ clearInterval(this.sendIntervalId);
103
+ this.sendIntervalId = null;
129
104
  }
130
- // Clean up circuit breaker timeout
131
- if (this.circuitResetTimeoutId) {
132
- clearTimeout(this.circuitResetTimeoutId);
133
- this.circuitResetTimeoutId = null;
134
- }
135
- // Persist any remaining events before stopping
136
- if (this.eventsQueue.length > 0) {
137
- this.persistEventsToStorage();
138
- }
139
- // Clean up all state variables
140
- this.eventFingerprints.clear();
141
- this.circuitOpen = false;
142
- this.circuitOpenTime = 0;
143
- this.failureCount = 0;
144
- this.backoffDelay = CIRCUIT_BREAKER_CONSTANTS.INITIAL_BACKOFF_DELAY_MS;
145
- this.lastEvent = null;
146
- // Stop the data sender to clean up retry timeouts
105
+ // Reset state
106
+ this.eventsQueue = [];
107
+ this.lastEventFingerprint = null;
108
+ this.lastEventTime = 0;
109
+ // Stop sender
147
110
  this.dataSender.stop();
148
111
  }
149
- processAndSend(payload) {
150
- debugLog.info('EventManager', `🔄 Event processed and queued: ${payload.type}`, {
151
- type: payload.type,
152
- timestamp: payload.timestamp,
153
- page_url: payload.page_url,
154
- queueLengthBefore: this.eventsQueue.length,
155
- });
156
- if (this.get('config').ipExcluded) {
157
- debugLog.info('EventManager', `❌ Event blocked: IP excluded`);
158
- return;
159
- }
160
- this.eventsQueue.push(payload);
161
- if (this.eventsQueue.length > MAX_EVENTS_QUEUE_LENGTH) {
162
- const removedEvent = this.eventsQueue.shift();
163
- debugLog.warn('EventManager', 'Event queue overflow, oldest event removed', {
164
- maxLength: MAX_EVENTS_QUEUE_LENGTH,
165
- currentLength: this.eventsQueue.length,
166
- removedEventType: removedEvent?.type,
167
- });
168
- }
169
- if (!this.eventsQueueIntervalId) {
170
- this.initEventsQueueInterval();
171
- debugLog.info('EventManager', `⏰ Event sender initialized - queue will be sent periodically`, {
172
- queueLength: this.eventsQueue.length,
173
- });
174
- }
175
- if (this.googleAnalytics && payload.type === EventType.CUSTOM) {
176
- const customEvent = payload.custom_event;
177
- this.trackGoogleAnalyticsEvent(customEvent);
178
- }
179
- }
180
- trackGoogleAnalyticsEvent(customEvent) {
181
- if (this.get('config').mode === 'qa' || this.get('config').mode === 'debug') {
182
- debugLog.debug('EventManager', `Google Analytics event: ${JSON.stringify(customEvent)}`);
183
- }
184
- else if (this.googleAnalytics) {
185
- this.googleAnalytics.trackEvent(customEvent.name, customEvent.metadata ?? {});
186
- }
187
- }
188
- initEventsQueueInterval() {
189
- if (this.eventsQueueIntervalId || this.intervalActive) {
190
- return;
191
- }
192
- const interval = this.get('config')?.id === 'test' ? EVENT_SENT_INTERVAL_TEST_MS : EVENT_SENT_INTERVAL_MS;
193
- this.eventsQueueIntervalId = window.setInterval(() => {
194
- if (this.eventsQueue.length > 0) {
195
- this.sendEventsQueue();
196
- }
197
- }, interval);
198
- this.intervalActive = true;
199
- }
112
+ /**
113
+ * Flush all queued events immediately (async)
114
+ */
200
115
  async flushImmediately() {
201
- if (this.eventsQueue.length === 0) {
202
- return true;
203
- }
204
- const body = this.buildEventsPayload();
205
- const success = await this.dataSender.sendEventsQueueAsync(body);
206
- if (success) {
207
- this.eventsQueue = [];
208
- this.clearQueueInterval();
209
- }
210
- return success;
116
+ return this.flushEvents(false);
211
117
  }
118
+ /**
119
+ * Flush all queued events immediately (sync)
120
+ */
212
121
  flushImmediatelySync() {
213
- if (this.eventsQueue.length === 0) {
214
- return true;
215
- }
216
- const body = this.buildEventsPayload();
217
- const success = this.dataSender.sendEventsQueueSync(body);
218
- if (success) {
219
- this.eventsQueue = [];
220
- this.clearQueueInterval();
221
- }
222
- return success;
122
+ return this.flushEvents(true);
223
123
  }
124
+ /**
125
+ * Queue management and sending intervals
126
+ */
224
127
  getQueueLength() {
225
128
  return this.eventsQueue.length;
226
129
  }
227
- sendEventsQueue() {
228
- if (this.eventsQueue.length === 0) {
229
- return;
230
- }
231
- debugLog.info('EventManager', `📤 Preparing to send event queue`, {
232
- queueLength: this.eventsQueue.length,
233
- hasSessionId: !!this.get('sessionId'),
234
- circuitOpen: this.circuitOpen,
235
- });
236
- // Circuit breaker: check if it should be reset or continue blocking
237
- if (this.circuitOpen) {
238
- const timeSinceOpen = Date.now() - this.circuitOpenTime;
239
- if (timeSinceOpen >= CIRCUIT_BREAKER_CONSTANTS.RECOVERY_TIME_MS) {
240
- this.resetCircuitBreaker();
241
- debugLog.info('EventManager', 'Circuit breaker reset after timeout', {
242
- timeSinceOpen,
243
- recoveryTime: CIRCUIT_BREAKER_CONSTANTS.RECOVERY_TIME_MS,
244
- });
245
- }
246
- else {
247
- debugLog.debug('EventManager', 'Circuit breaker is open - skipping event sending', {
248
- queueLength: this.eventsQueue.length,
249
- failureCount: this.failureCount,
250
- timeSinceOpen,
251
- recoveryTime: CIRCUIT_BREAKER_CONSTANTS.RECOVERY_TIME_MS,
252
- });
253
- return;
254
- }
130
+ clearSendInterval() {
131
+ if (this.sendIntervalId) {
132
+ clearInterval(this.sendIntervalId);
133
+ this.sendIntervalId = null;
255
134
  }
256
- if (!this.get('sessionId')) {
257
- debugLog.info('EventManager', `⏳ Queue waiting: ${this.eventsQueue.length} events waiting for active session`);
258
- return;
135
+ }
136
+ /**
137
+ * Shared flush implementation for both sync and async modes
138
+ */
139
+ flushEvents(isSync) {
140
+ if (this.eventsQueue.length === 0) {
141
+ return isSync ? true : Promise.resolve(true);
259
142
  }
260
143
  const body = this.buildEventsPayload();
261
- const success = this.dataSender.sendEventsQueue(body);
262
- if (success) {
263
- debugLog.info('EventManager', `✅ Event queue sent successfully`, {
264
- eventsCount: body.events.length,
265
- sessionId: body.session_id,
266
- uniqueEventsAfterDedup: body.events.length,
267
- });
268
- this.eventsQueue = [];
269
- this.failureCount = 0;
270
- this.backoffDelay = CIRCUIT_BREAKER_CONSTANTS.INITIAL_BACKOFF_DELAY_MS;
271
- // Clear any persisted events on successful send
272
- this.clearPersistedEvents();
144
+ const eventsToSend = [...this.eventsQueue];
145
+ const eventIds = eventsToSend.map((e) => `${e.timestamp}_${e.type}`);
146
+ if (isSync) {
147
+ const success = this.dataSender.sendEventsQueueSync(body);
148
+ if (success) {
149
+ this.removeProcessedEvents(eventIds);
150
+ this.clearSendInterval();
151
+ }
152
+ return success;
273
153
  }
274
154
  else {
275
- debugLog.info('EventManager', `❌ Failed to send event queue`, {
276
- eventsCount: body.events.length,
277
- failureCount: this.failureCount + 1,
278
- willOpenCircuit: this.failureCount + 1 >= this.MAX_FAILURES,
155
+ return this.dataSender.sendEventsQueue(body, {
156
+ onSuccess: () => {
157
+ this.removeProcessedEvents(eventIds);
158
+ this.clearSendInterval();
159
+ this.emitEventsQueue(body);
160
+ },
161
+ onFailure: () => {
162
+ debugLog.warn('EventManager', 'Async flush failed', {
163
+ eventCount: eventsToSend.length,
164
+ });
165
+ },
279
166
  });
280
- // Persist failed events for recovery instead of restoring queue to prevent duplicates
281
- this.persistEventsToStorage();
282
- this.eventsQueue = [];
283
- this.failureCount++;
284
- if (this.failureCount >= this.MAX_FAILURES) {
285
- this.openCircuitBreaker();
286
- }
287
167
  }
288
168
  }
169
+ /**
170
+ * Send queued events to the API
171
+ */
172
+ async sendEventsQueue() {
173
+ if (!this.get('sessionId') || this.eventsQueue.length === 0) {
174
+ return;
175
+ }
176
+ const body = this.buildEventsPayload();
177
+ const eventsToSend = [...this.eventsQueue];
178
+ const eventIds = eventsToSend.map((e) => `${e.timestamp}_${e.type}`);
179
+ await this.dataSender.sendEventsQueue(body, {
180
+ onSuccess: () => {
181
+ this.removeProcessedEvents(eventIds);
182
+ this.emitEventsQueue(body);
183
+ },
184
+ onFailure: async () => {
185
+ debugLog.warn('EventManager', 'Events send failed, keeping in queue', {
186
+ eventCount: eventsToSend.length,
187
+ });
188
+ },
189
+ });
190
+ }
191
+ /**
192
+ * Build the payload for sending events to the API
193
+ * Includes basic deduplication and sorting
194
+ */
289
195
  buildEventsPayload() {
290
- const uniqueEvents = new Map();
196
+ const eventMap = new Map();
197
+ const order = [];
291
198
  for (const event of this.eventsQueue) {
292
- let key = `${event.type}_${event.page_url}`;
293
- if (event.click_data) {
294
- key += `_${event.click_data.x}_${event.click_data.y}`;
295
- }
296
- if (event.scroll_data) {
297
- key += `_${event.scroll_data.depth}_${event.scroll_data.direction}`;
298
- }
299
- if (event.custom_event) {
300
- key += `_${event.custom_event.name}`;
301
- }
302
- if (event.web_vitals) {
303
- key += `_${event.web_vitals.type}`;
304
- }
305
- if (!uniqueEvents.has(key)) {
306
- uniqueEvents.set(key, event);
199
+ const signature = this.createEventSignature(event);
200
+ if (!eventMap.has(signature)) {
201
+ order.push(signature);
307
202
  }
203
+ eventMap.set(signature, event);
308
204
  }
309
- const deduplicatedEvents = [...uniqueEvents.values()];
310
- deduplicatedEvents.sort((a, b) => a.timestamp - b.timestamp);
205
+ const events = order
206
+ .map((signature) => eventMap.get(signature))
207
+ .filter((event) => Boolean(event))
208
+ .sort((a, b) => a.timestamp - b.timestamp);
311
209
  return {
312
210
  user_id: this.get('userId'),
313
211
  session_id: this.get('sessionId'),
314
212
  device: this.get('device'),
315
- events: deduplicatedEvents,
213
+ events,
316
214
  ...(this.get('config')?.globalMetadata && { global_metadata: this.get('config')?.globalMetadata }),
317
215
  };
318
216
  }
319
- clearQueueInterval() {
320
- if (this.eventsQueueIntervalId) {
321
- clearInterval(this.eventsQueueIntervalId);
322
- this.eventsQueueIntervalId = null;
323
- this.intervalActive = false;
217
+ /**
218
+ * Helper methods for event processing
219
+ */
220
+ buildEventPayload(data) {
221
+ const isSessionStart = data.type === EventType.SESSION_START;
222
+ const currentPageUrl = data.page_url ?? this.get('pageUrl');
223
+ const payload = {
224
+ type: data.type,
225
+ page_url: currentPageUrl,
226
+ timestamp: Date.now(),
227
+ ...(isSessionStart && { referrer: document.referrer || 'Direct' }),
228
+ ...(data.from_page_url && { from_page_url: data.from_page_url }),
229
+ ...(data.scroll_data && { scroll_data: data.scroll_data }),
230
+ ...(data.click_data && { click_data: data.click_data }),
231
+ ...(data.custom_event && { custom_event: data.custom_event }),
232
+ ...(data.web_vitals && { web_vitals: data.web_vitals }),
233
+ ...(data.error_data && { error_data: data.error_data }),
234
+ ...(data.session_end_reason && { session_end_reason: data.session_end_reason }),
235
+ ...(data.session_start_recovered && { session_start_recovered: data.session_start_recovered }),
236
+ ...(isSessionStart && getUTMParameters() && { utm: getUTMParameters() }),
237
+ };
238
+ // Add project tags
239
+ const projectTags = this.get('config')?.tags;
240
+ if (projectTags?.length) {
241
+ payload.tags = projectTags;
324
242
  }
243
+ return payload;
325
244
  }
326
- getEventFingerprint(event) {
327
- const key = `${event.type}_${event.page_url}`;
328
- if (event.click_data) {
329
- // Round coordinates to reduce false positives
330
- const x = Math.round((event.click_data.x || 0) / CLICK_COORDINATE_PRECISION) * CLICK_COORDINATE_PRECISION;
331
- const y = Math.round((event.click_data.y || 0) / CLICK_COORDINATE_PRECISION) * CLICK_COORDINATE_PRECISION;
332
- return `${key}_${x}_${y}_${event.click_data.tag}_${event.click_data.id}`;
333
- }
334
- if (event.scroll_data) {
335
- return `${key}_${event.scroll_data.depth}_${event.scroll_data.direction}`;
336
- }
337
- if (event.custom_event) {
338
- return `${key}_${event.custom_event.name}`;
339
- }
340
- if (event.web_vitals) {
341
- return `${key}_${event.web_vitals.type}`;
342
- }
343
- if (event.session_end_reason) {
344
- return `${key}_${event.session_end_reason}`;
345
- }
346
- if (event.session_start_recovered !== undefined) {
347
- return `${key}_${event.session_start_recovered}`;
245
+ isEventExcluded(event) {
246
+ const config = this.get('config');
247
+ const isRouteExcluded = isUrlPathExcluded(event.page_url, config?.excludedUrlPaths ?? []);
248
+ const hasStartSession = this.get('hasStartSession');
249
+ const isSessionEndEvent = event.type === EventType.SESSION_END;
250
+ const isSessionStartEvent = event.type === EventType.SESSION_START;
251
+ if (isRouteExcluded && !isSessionStartEvent && !(isSessionEndEvent && hasStartSession)) {
252
+ return true;
348
253
  }
349
- return key;
254
+ return config?.ipExcluded === true;
350
255
  }
351
- isDuplicatedEvent(event) {
352
- const fingerprint = this.getEventFingerprint(event);
353
- const lastTime = this.eventFingerprints.get(fingerprint) ?? 0;
256
+ isDuplicateEvent(event) {
354
257
  const now = Date.now();
355
- if (now - lastTime < DUPLICATE_EVENT_THRESHOLD_MS) {
258
+ const fingerprint = this.createEventFingerprint(event);
259
+ // Check if this is a duplicate within the threshold
260
+ if (this.lastEventFingerprint === fingerprint && now - this.lastEventTime < DUPLICATE_EVENT_THRESHOLD_MS) {
356
261
  return true;
357
262
  }
358
- this.eventFingerprints.set(fingerprint, now);
359
- // Clean up old fingerprints to prevent memory leaks
360
- this.cleanupOldFingerprints();
263
+ // Update tracking
264
+ this.lastEventFingerprint = fingerprint;
265
+ this.lastEventTime = now;
361
266
  return false;
362
267
  }
363
- /**
364
- * Cleans up old fingerprints to prevent memory leaks
365
- */
366
- cleanupOldFingerprints() {
367
- if (this.eventFingerprints.size <= MAX_FINGERPRINTS) {
368
- return;
268
+ createEventFingerprint(event) {
269
+ let fingerprint = `${event.type}_${event.page_url}`;
270
+ if (event.click_data) {
271
+ // Round coordinates to reduce false duplicates
272
+ const x = Math.round((event.click_data.x || 0) / 10) * 10;
273
+ const y = Math.round((event.click_data.y || 0) / 10) * 10;
274
+ fingerprint += `_click_${x}_${y}`;
369
275
  }
370
- const now = Date.now();
371
- const cleanupThreshold = DUPLICATE_EVENT_THRESHOLD_MS * FINGERPRINT_CLEANUP_MULTIPLIER;
372
- const keysToDelete = [];
373
- for (const [key, timestamp] of this.eventFingerprints) {
374
- if (now - timestamp > cleanupThreshold) {
375
- keysToDelete.push(key);
376
- }
276
+ if (event.scroll_data) {
277
+ fingerprint += `_scroll_${event.scroll_data.depth}_${event.scroll_data.direction}`;
377
278
  }
378
- // Delete old entries
379
- for (const key of keysToDelete) {
380
- this.eventFingerprints.delete(key);
279
+ if (event.custom_event) {
280
+ fingerprint += `_custom_${event.custom_event.name}`;
381
281
  }
382
- debugLog.debug('EventManager', 'Cleaned up old event fingerprints', {
383
- totalFingerprints: this.eventFingerprints.size + keysToDelete.length,
384
- cleanedCount: keysToDelete.length,
385
- remainingCount: this.eventFingerprints.size,
386
- cleanupThreshold,
387
- });
282
+ if (event.web_vitals) {
283
+ fingerprint += `_vitals_${event.web_vitals.type}`;
284
+ }
285
+ return fingerprint;
388
286
  }
389
- /**
390
- * Opens the circuit breaker with time-based recovery and event persistence
391
- */
392
- openCircuitBreaker() {
393
- this.circuitOpen = true;
394
- this.circuitOpenTime = Date.now();
395
- // Persist events before clearing queue to prevent data loss
396
- this.persistEventsToStorage();
397
- const eventsCount = this.eventsQueue.length;
398
- this.eventsQueue = []; // Clear memory queue
399
- debugLog.warn('EventManager', 'Circuit breaker opened with time-based recovery', {
400
- maxFailures: this.MAX_FAILURES,
401
- persistedEvents: eventsCount,
402
- failureCount: this.failureCount,
403
- recoveryTime: CIRCUIT_BREAKER_CONSTANTS.RECOVERY_TIME_MS,
404
- openTime: this.circuitOpenTime,
405
- });
406
- // Increase backoff for next failure
407
- this.backoffDelay = Math.min(this.backoffDelay * CIRCUIT_BREAKER_CONSTANTS.BACKOFF_MULTIPLIER, CIRCUIT_BREAKER_CONSTANTS.MAX_BACKOFF_DELAY_MS);
287
+ createEventSignature(event) {
288
+ return this.createEventFingerprint(event);
408
289
  }
409
- /**
410
- * Resets the circuit breaker and attempts to restore persisted events
411
- */
412
- resetCircuitBreaker() {
413
- this.circuitOpen = false;
414
- this.circuitOpenTime = 0;
415
- this.failureCount = 0;
416
- this.circuitResetTimeoutId = null;
417
- debugLog.info('EventManager', 'Circuit breaker reset - attempting to restore events', {
418
- currentQueueLength: this.eventsQueue.length,
419
- });
420
- // Restore persisted events
421
- this.restoreEventsFromStorage();
422
- debugLog.info('EventManager', 'Circuit breaker reset completed', {
423
- restoredQueueLength: this.eventsQueue.length,
424
- backoffDelay: this.backoffDelay,
425
- });
426
- }
427
- /**
428
- * Persists current events queue to localStorage for recovery
429
- */
430
- persistEventsToStorage() {
431
- try {
432
- if (this.eventsQueue.length === 0) {
433
- return;
434
- }
435
- const persistData = {
436
- events: this.eventsQueue,
437
- timestamp: Date.now(),
438
- failureCount: this.failureCount,
439
- };
440
- this.storageManager.setItem(this.PERSISTENCE_KEY, JSON.stringify(persistData));
441
- debugLog.debug('EventManager', 'Events persisted to storage for recovery', {
442
- eventsCount: this.eventsQueue.length,
443
- failureCount: this.failureCount,
290
+ addToQueue(event) {
291
+ this.eventsQueue.push(event);
292
+ debugLog.info('EventManager', 'Event added to queue', event);
293
+ this.emitEvent(event);
294
+ // Prevent queue overflow
295
+ if (this.eventsQueue.length > MAX_EVENTS_QUEUE_LENGTH) {
296
+ const removedEvent = this.eventsQueue.shift();
297
+ debugLog.warn('EventManager', 'Event queue overflow, oldest event removed', {
298
+ maxLength: MAX_EVENTS_QUEUE_LENGTH,
299
+ currentLength: this.eventsQueue.length,
300
+ removedEventType: removedEvent?.type,
444
301
  });
445
302
  }
446
- catch (error) {
447
- debugLog.warn('EventManager', 'Failed to persist events to storage', {
448
- error: error instanceof Error ? error.message : 'Unknown error',
449
- eventsCount: this.eventsQueue.length,
450
- });
303
+ if (!this.sendIntervalId) {
304
+ this.startSendInterval();
451
305
  }
306
+ // Google Analytics integration
307
+ this.handleGoogleAnalyticsIntegration(event);
452
308
  }
453
- /**
454
- * Restores events from localStorage if available and not expired
455
- */
456
- restoreEventsFromStorage() {
457
- try {
458
- const persistedData = this.storageManager.getItem(this.PERSISTENCE_KEY);
459
- if (!persistedData) {
460
- return;
309
+ startSendInterval() {
310
+ this.sendIntervalId = window.setInterval(() => {
311
+ if (this.eventsQueue.length > 0) {
312
+ this.sendEventsQueue();
461
313
  }
462
- const parsed = JSON.parse(persistedData);
463
- const now = Date.now();
464
- const maxAge = EVENT_PERSISTENCE_MAX_AGE_MS;
465
- // Check if persisted data is not too old
466
- if (now - parsed.timestamp > maxAge) {
467
- this.clearPersistedEvents();
468
- debugLog.debug('EventManager', 'Cleared expired persisted events', {
469
- age: now - parsed.timestamp,
470
- maxAge,
471
- });
472
- return;
314
+ }, EVENT_SENT_INTERVAL_MS);
315
+ }
316
+ handleGoogleAnalyticsIntegration(event) {
317
+ if (this.googleAnalytics && event.type === EventType.CUSTOM && event.custom_event) {
318
+ if (this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug') {
319
+ // Skip GA tracking in QA/debug modes
473
320
  }
474
- // Restore events if we don't already have events in queue
475
- if (Array.isArray(parsed.events) && parsed.events.length > 0 && this.eventsQueue.length === 0) {
476
- this.eventsQueue = parsed.events;
477
- debugLog.info('EventManager', 'Restored events from storage', {
478
- restoredCount: parsed.events.length,
479
- originalFailureCount: parsed.failureCount ?? 0,
480
- });
321
+ else {
322
+ this.googleAnalytics.trackEvent(event.custom_event.name, event.custom_event.metadata ?? {});
481
323
  }
482
324
  }
483
- catch (error) {
484
- debugLog.warn('EventManager', 'Failed to restore events from storage', {
485
- error: error instanceof Error ? error.message : 'Unknown error',
486
- });
487
- this.clearPersistedEvents();
488
- }
325
+ }
326
+ shouldSample() {
327
+ const samplingRate = this.get('config')?.samplingRate ?? 1;
328
+ return Math.random() < samplingRate;
329
+ }
330
+ removeProcessedEvents(eventIds) {
331
+ const eventIdSet = new Set(eventIds);
332
+ this.eventsQueue = this.eventsQueue.filter((event) => {
333
+ const eventId = `${event.timestamp}_${event.type}`;
334
+ return !eventIdSet.has(eventId);
335
+ });
489
336
  }
490
337
  /**
491
- * Clears persisted events from localStorage
338
+ * Emit event for external listeners
492
339
  */
493
- clearPersistedEvents() {
494
- try {
495
- this.storageManager.removeItem(this.PERSISTENCE_KEY);
496
- debugLog.debug('EventManager', 'Cleared persisted events from storage');
340
+ emitEvent(eventData) {
341
+ if (this.emitter) {
342
+ this.emitter.emit(EmitterEvent.EVENT, eventData);
497
343
  }
498
- catch (error) {
499
- debugLog.warn('EventManager', 'Failed to clear persisted events', {
500
- error: error instanceof Error ? error.message : 'Unknown error',
501
- });
344
+ }
345
+ /**
346
+ * Emit events queue for external listeners
347
+ */
348
+ emitEventsQueue(queue) {
349
+ if (this.emitter) {
350
+ this.emitter.emit(EmitterEvent.QUEUE, queue);
502
351
  }
503
352
  }
504
353
  }