@tracelog/lib 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +86 -26
  2. package/dist/browser/tracelog.esm.js +2737 -0
  3. package/dist/browser/tracelog.js +2 -2765
  4. package/dist/cjs/api.d.ts +1 -53
  5. package/dist/cjs/api.js +0 -59
  6. package/dist/cjs/app.constants.d.ts +1 -1
  7. package/dist/cjs/app.d.ts +1 -5
  8. package/dist/cjs/app.js +4 -12
  9. package/dist/cjs/constants/api.constants.d.ts +5 -2
  10. package/dist/cjs/constants/api.constants.js +5 -14
  11. package/dist/cjs/constants/config.constants.d.ts +3 -3
  12. package/dist/cjs/constants/config.constants.js +3 -3
  13. package/dist/cjs/constants/error.constants.d.ts +7 -2
  14. package/dist/cjs/constants/error.constants.js +13 -2
  15. package/dist/cjs/constants/performance.constants.d.ts +5 -5
  16. package/dist/cjs/constants/performance.constants.js +6 -6
  17. package/dist/cjs/handlers/click.handler.js +0 -6
  18. package/dist/cjs/handlers/error.handler.js +9 -0
  19. package/dist/cjs/handlers/scroll.handler.js +0 -5
  20. package/dist/cjs/handlers/session.handler.js +5 -2
  21. package/dist/cjs/integrations/google-analytics.integration.d.ts +1 -1
  22. package/dist/cjs/integrations/google-analytics.integration.js +2 -1
  23. package/dist/cjs/managers/api.manager.d.ts +1 -1
  24. package/dist/cjs/managers/api.manager.js +3 -3
  25. package/dist/cjs/managers/config.builder.d.ts +33 -0
  26. package/dist/cjs/managers/config.builder.js +116 -0
  27. package/dist/cjs/managers/config.manager.d.ts +13 -14
  28. package/dist/cjs/managers/config.manager.js +52 -58
  29. package/dist/cjs/managers/event.manager.d.ts +0 -45
  30. package/dist/cjs/managers/event.manager.js +14 -67
  31. package/dist/cjs/managers/sender.manager.d.ts +1 -28
  32. package/dist/cjs/managers/sender.manager.js +43 -73
  33. package/dist/cjs/managers/session.manager.d.ts +2 -49
  34. package/dist/cjs/managers/session.manager.js +37 -79
  35. package/dist/cjs/managers/state.manager.d.ts +1 -28
  36. package/dist/cjs/managers/state.manager.js +5 -33
  37. package/dist/cjs/managers/storage.manager.d.ts +6 -0
  38. package/dist/cjs/managers/storage.manager.js +18 -1
  39. package/dist/cjs/public-api.d.ts +1 -1
  40. package/dist/cjs/test-bridge.d.ts +3 -2
  41. package/dist/cjs/test-bridge.js +34 -7
  42. package/dist/cjs/types/api.types.d.ts +24 -8
  43. package/dist/cjs/types/api.types.js +24 -8
  44. package/dist/cjs/types/event.types.d.ts +2 -3
  45. package/dist/cjs/types/event.types.js +0 -1
  46. package/dist/cjs/types/test-bridge.types.d.ts +2 -1
  47. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +1 -2
  48. package/dist/cjs/utils/logging/debug-logger.utils.js +2 -3
  49. package/dist/cjs/utils/validations/config-validations.utils.d.ts +1 -26
  50. package/dist/cjs/utils/validations/config-validations.utils.js +5 -117
  51. package/dist/cjs/utils/validations/event-validations.utils.d.ts +2 -2
  52. package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +3 -3
  53. package/dist/cjs/utils/validations/metadata-validations.utils.js +41 -3
  54. package/dist/esm/api.d.ts +1 -53
  55. package/dist/esm/api.js +0 -59
  56. package/dist/esm/app.constants.d.ts +1 -1
  57. package/dist/esm/app.d.ts +1 -5
  58. package/dist/esm/app.js +5 -13
  59. package/dist/esm/constants/api.constants.d.ts +5 -2
  60. package/dist/esm/constants/api.constants.js +5 -13
  61. package/dist/esm/constants/config.constants.d.ts +3 -3
  62. package/dist/esm/constants/config.constants.js +3 -3
  63. package/dist/esm/constants/error.constants.d.ts +7 -2
  64. package/dist/esm/constants/error.constants.js +12 -1
  65. package/dist/esm/constants/performance.constants.d.ts +5 -5
  66. package/dist/esm/constants/performance.constants.js +6 -6
  67. package/dist/esm/handlers/click.handler.js +0 -6
  68. package/dist/esm/handlers/error.handler.js +10 -1
  69. package/dist/esm/handlers/scroll.handler.js +0 -5
  70. package/dist/esm/handlers/session.handler.js +5 -2
  71. package/dist/esm/integrations/google-analytics.integration.d.ts +1 -1
  72. package/dist/esm/integrations/google-analytics.integration.js +2 -1
  73. package/dist/esm/managers/api.manager.d.ts +1 -1
  74. package/dist/esm/managers/api.manager.js +3 -3
  75. package/dist/esm/managers/config.builder.d.ts +33 -0
  76. package/dist/esm/managers/config.builder.js +112 -0
  77. package/dist/esm/managers/config.manager.d.ts +13 -14
  78. package/dist/esm/managers/config.manager.js +54 -60
  79. package/dist/esm/managers/event.manager.d.ts +0 -45
  80. package/dist/esm/managers/event.manager.js +14 -67
  81. package/dist/esm/managers/sender.manager.d.ts +1 -28
  82. package/dist/esm/managers/sender.manager.js +44 -74
  83. package/dist/esm/managers/session.manager.d.ts +2 -49
  84. package/dist/esm/managers/session.manager.js +37 -79
  85. package/dist/esm/managers/state.manager.d.ts +1 -28
  86. package/dist/esm/managers/state.manager.js +4 -33
  87. package/dist/esm/managers/storage.manager.d.ts +6 -0
  88. package/dist/esm/managers/storage.manager.js +18 -1
  89. package/dist/esm/public-api.d.ts +1 -1
  90. package/dist/esm/test-bridge.d.ts +3 -2
  91. package/dist/esm/test-bridge.js +34 -7
  92. package/dist/esm/types/api.types.d.ts +24 -8
  93. package/dist/esm/types/api.types.js +24 -8
  94. package/dist/esm/types/event.types.d.ts +2 -3
  95. package/dist/esm/types/event.types.js +0 -1
  96. package/dist/esm/types/test-bridge.types.d.ts +2 -1
  97. package/dist/esm/utils/logging/debug-logger.utils.d.ts +1 -2
  98. package/dist/esm/utils/logging/debug-logger.utils.js +3 -4
  99. package/dist/esm/utils/validations/config-validations.utils.d.ts +1 -26
  100. package/dist/esm/utils/validations/config-validations.utils.js +5 -114
  101. package/dist/esm/utils/validations/event-validations.utils.d.ts +2 -2
  102. package/dist/esm/utils/validations/metadata-validations.utils.d.ts +3 -3
  103. package/dist/esm/utils/validations/metadata-validations.utils.js +41 -3
  104. package/package.json +3 -3
@@ -1,44 +1,53 @@
1
- import { DEFAULT_API_CONFIG, DEFAULT_CONFIG, REQUEST_TIMEOUT_MS } from '../constants';
1
+ import { DEFAULT_API_CONFIG, REQUEST_TIMEOUT_MS } from '../constants';
2
2
  import { Mode, SpecialProjectId } from '../types';
3
- import { sanitizeApiConfig, fetchWithTimeout, normalizeConfig } from '../utils';
3
+ import { sanitizeApiConfig, fetchWithTimeout } from '../utils';
4
4
  import { debugLog } from '../utils/logging';
5
+ import { ConfigBuilder } from './config.builder';
5
6
  /**
6
7
  * Configuration manager responsible for loading and merging application configuration.
7
8
  *
8
- * Handles three configuration sources:
9
- * 1. Default configuration (fallback values)
10
- * 2. API configuration (server-side settings)
11
- * 3. App configuration (client initialization settings)
9
+ * Handles configuration from two sources:
10
+ * 1. API configuration (server-side settings)
11
+ * 2. App configuration (client initialization settings)
12
+ *
13
+ * Uses ConfigBuilder for centralized merge logic.
12
14
  *
13
15
  * Supports special project IDs for development and testing:
14
16
  * - 'skip': Bypasses all network calls, uses defaults
15
- * - 'localhost:PORT': Loads config from local development server
17
+ * - 'localhost:8080': Loads config from local development server
16
18
  */
17
19
  export class ConfigManager {
18
20
  /**
19
- * Gets complete configuration by merging default, API, and app configurations.
21
+ * Gets complete configuration by loading API config and building final config.
20
22
  *
21
23
  * @param apiUrl - Base URL for the configuration API
22
24
  * @param appConfig - Client-side configuration from init()
23
25
  * @returns Promise<Config> - Merged configuration object
24
26
  */
25
27
  async get(apiUrl, appConfig) {
26
- // Handle skip mode - no network calls
27
- if (appConfig.id === SpecialProjectId.Skip) {
28
+ // Handle skip mode - no network calls for config
29
+ // Support 'skip' or any ID starting with 'skip-' (e.g., 'skip-1', 'skip-2')
30
+ // Also handle 'fail' mode (SpecialProjectId.Fail) - skip config but fail event sends
31
+ if (appConfig.id === SpecialProjectId.Skip ||
32
+ appConfig.id === SpecialProjectId.Fail ||
33
+ appConfig.id.toLowerCase().startsWith('skip-')) {
28
34
  return this.createDefaultConfig(appConfig);
29
35
  }
30
- const config = await this.loadFromApi(apiUrl, appConfig);
31
- const { config: normalizedConfig } = normalizeConfig(config);
36
+ const apiConfig = await this.loadFromApi(apiUrl, appConfig);
37
+ // Apply QA mode from URL parameter if set
38
+ const finalApiConfig = this.applyQaModeIfEnabled(apiConfig);
39
+ const config = ConfigBuilder.build(appConfig, finalApiConfig);
32
40
  debugLog.info('ConfigManager', 'Configuration loaded', {
33
- projectId: appConfig.id,
34
- mode: normalizedConfig.mode,
35
- hasTags: !!normalizedConfig.tags?.length,
36
- hasExclusions: !!normalizedConfig.excludedUrlPaths?.length,
41
+ projectId: config.id,
42
+ mode: config.mode,
43
+ hasTags: !!config.tags?.length,
44
+ hasExclusions: !!config.excludedUrlPaths?.length,
37
45
  });
38
- return normalizedConfig;
46
+ return config;
39
47
  }
40
48
  /**
41
- * Loads configuration from API and merges with app config.
49
+ * Loads configuration from API and returns sanitized API config.
50
+ * Only returns values explicitly provided by the API.
42
51
  */
43
52
  async loadFromApi(apiUrl, appConfig) {
44
53
  try {
@@ -53,7 +62,13 @@ export class ConfigManager {
53
62
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
54
63
  }
55
64
  const rawData = await this.parseJsonResponse(response);
56
- return this.mergeConfigurations(rawData, appConfig);
65
+ const apiConfig = sanitizeApiConfig(rawData);
66
+ // Only merge defaults for fields that are arrays (to ensure they're never undefined)
67
+ return {
68
+ ...apiConfig,
69
+ excludedUrlPaths: apiConfig.excludedUrlPaths ?? DEFAULT_API_CONFIG.excludedUrlPaths,
70
+ tags: apiConfig.tags ?? DEFAULT_API_CONFIG.tags,
71
+ };
57
72
  }
58
73
  catch (error) {
59
74
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
@@ -69,9 +84,8 @@ export class ConfigManager {
69
84
  * Builds the configuration URL based on project type and QA mode.
70
85
  */
71
86
  buildConfigUrl(apiUrl, appConfig) {
72
- const isLocalhost = appConfig.id.startsWith(SpecialProjectId.Localhost);
87
+ const isLocalhost = appConfig.id === SpecialProjectId.Localhost || appConfig.id === SpecialProjectId.Fail;
73
88
  if (isLocalhost) {
74
- this.validateLocalhostProjectId(appConfig.id);
75
89
  return `http://${appConfig.id}/config`;
76
90
  }
77
91
  const baseUrl = `${apiUrl}/config`;
@@ -80,15 +94,13 @@ export class ConfigManager {
80
94
  }
81
95
  /**
82
96
  * Builds request headers based on project configuration.
97
+ * Always includes X-TraceLog-Project header for consistent identification.
83
98
  */
84
99
  buildHeaders(appConfig) {
85
- const headers = {
100
+ return {
86
101
  'Content-Type': 'application/json',
102
+ 'X-TraceLog-Project': appConfig.id,
87
103
  };
88
- if (appConfig.id.startsWith(SpecialProjectId.Localhost)) {
89
- headers['X-TraceLog-Project'] = appConfig.id;
90
- }
91
- return headers;
92
104
  }
93
105
  /**
94
106
  * Parses and validates JSON response from config API.
@@ -104,18 +116,6 @@ export class ConfigManager {
104
116
  }
105
117
  return rawData;
106
118
  }
107
- /**
108
- * Validates localhost project ID format and port range.
109
- */
110
- validateLocalhostProjectId(projectId) {
111
- if (!ConfigManager.LOCALHOST_PATTERN.test(projectId)) {
112
- throw new Error(`Invalid localhost format. Expected 'localhost:PORT', got '${projectId}'`);
113
- }
114
- const port = parseInt(projectId.split(':')[1], 10);
115
- if (port < 1 || port > 65535) {
116
- throw new Error(`Port must be between 1 and 65535, got ${port}`);
117
- }
118
- }
119
119
  /**
120
120
  * Checks if QA mode is enabled via URL parameter.
121
121
  */
@@ -124,36 +124,30 @@ export class ConfigManager {
124
124
  return params.get('qaMode') === 'true';
125
125
  }
126
126
  /**
127
- * Merges API configuration with app configuration and applies mode-specific settings.
127
+ * Applies QA mode to API config if enabled via URL parameter.
128
128
  */
129
- mergeConfigurations(rawApiConfig, appConfig) {
130
- const safeApiConfig = sanitizeApiConfig(rawApiConfig);
131
- const apiConfig = { ...DEFAULT_API_CONFIG, ...safeApiConfig };
132
- const mergedConfig = DEFAULT_CONFIG({ ...appConfig, ...apiConfig });
133
- const { config: normalizedConfig } = normalizeConfig(mergedConfig);
134
- // Apply QA mode if enabled via URL parameter
135
- if (this.isQaModeEnabled() && !normalizedConfig.mode) {
136
- normalizedConfig.mode = Mode.QA;
129
+ applyQaModeIfEnabled(apiConfig) {
130
+ if (this.isQaModeEnabled() && !apiConfig.mode) {
137
131
  debugLog.info('ConfigManager', 'QA mode enabled via URL parameter');
132
+ return { ...apiConfig, mode: Mode.QA };
138
133
  }
139
- // Set error sampling based on mode
140
- const errorSampling = Object.values(Mode).includes(normalizedConfig.mode)
141
- ? 1 // Full sampling for debug/qa modes
142
- : (normalizedConfig.errorSampling ?? 0.1); // Default sampling for production
143
- return { ...normalizedConfig, errorSampling };
134
+ return apiConfig;
144
135
  }
145
136
  /**
146
137
  * Creates default configuration for skip mode and fallback scenarios.
138
+ * Only uses API defaults for fields not provided by the app config.
147
139
  */
148
140
  createDefaultConfig(appConfig) {
149
- const defaultConfig = DEFAULT_CONFIG({
150
- ...appConfig,
151
- errorSampling: 1,
152
- ...(appConfig.id === SpecialProjectId.Skip && { mode: Mode.DEBUG }),
153
- });
154
- const { config } = normalizeConfig(defaultConfig);
155
- return config;
141
+ // Only use DEFAULT_API_CONFIG for fields not provided in appConfig
142
+ const apiConfig = {
143
+ // Only use defaults if app config doesn't provide these values
144
+ tags: DEFAULT_API_CONFIG.tags,
145
+ ipExcluded: DEFAULT_API_CONFIG.ipExcluded,
146
+ ...(appConfig.samplingRate === undefined && { samplingRate: DEFAULT_API_CONFIG.samplingRate }),
147
+ // Don't override excludedUrlPaths if provided by app config
148
+ // ConfigBuilder will handle the fallback to [] if both are undefined
149
+ };
150
+ return ConfigBuilder.build(appConfig, apiConfig);
156
151
  }
157
152
  }
158
- ConfigManager.LOCALHOST_PATTERN = /^localhost:\d{1,5}$/;
159
153
  ConfigManager.PRODUCTION_DOMAINS = [/^https:\/\/.*\.tracelog\.app$/, /^https:\/\/.*\.tracelog\.dev$/];
@@ -3,16 +3,6 @@ import { Emitter } from '../utils';
3
3
  import { StateManager } from './state.manager';
4
4
  import { StorageManager } from './storage.manager';
5
5
  import { GoogleAnalyticsIntegration } from '../integrations/google-analytics.integration';
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
- */
16
6
  export declare class EventManager extends StateManager {
17
7
  private readonly googleAnalytics;
18
8
  private readonly dataSender;
@@ -22,45 +12,16 @@ export declare class EventManager extends StateManager {
22
12
  private lastEventTime;
23
13
  private sendIntervalId;
24
14
  constructor(storeManager: StorageManager, googleAnalytics?: GoogleAnalyticsIntegration | null, emitter?: Emitter | null);
25
- /**
26
- * Recovers persisted events from localStorage
27
- * Should be called after initialization to recover any events that failed to send
28
- */
29
15
  recoverPersistedEvents(): Promise<void>;
30
- /**
31
- * Track user events with automatic deduplication and queueing
32
- */
33
16
  track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, error_data, session_end_reason, }: Partial<EventData>): void;
34
17
  stop(): void;
35
- /**
36
- * Flush all queued events immediately (async)
37
- */
38
18
  flushImmediately(): Promise<boolean>;
39
- /**
40
- * Flush all queued events immediately (sync)
41
- */
42
19
  flushImmediatelySync(): boolean;
43
- /**
44
- * Queue management and sending intervals
45
- */
46
20
  getQueueLength(): number;
47
21
  private clearSendInterval;
48
- /**
49
- * Shared flush implementation for both sync and async modes
50
- */
51
22
  private flushEvents;
52
- /**
53
- * Send queued events to the API
54
- */
55
23
  private sendEventsQueue;
56
- /**
57
- * Build the payload for sending events to the API
58
- * Includes basic deduplication and sorting
59
- */
60
24
  private buildEventsPayload;
61
- /**
62
- * Helper methods for event processing
63
- */
64
25
  private buildEventPayload;
65
26
  private isEventExcluded;
66
27
  private isDuplicateEvent;
@@ -71,12 +32,6 @@ export declare class EventManager extends StateManager {
71
32
  private handleGoogleAnalyticsIntegration;
72
33
  private shouldSample;
73
34
  private removeProcessedEvents;
74
- /**
75
- * Emit event for external listeners
76
- */
77
35
  private emitEvent;
78
- /**
79
- * Emit events queue for external listeners
80
- */
81
36
  private emitEventsQueue;
82
37
  }
@@ -3,16 +3,6 @@ import { EmitterEvent, EventType } from '../types';
3
3
  import { getUTMParameters, isUrlPathExcluded, debugLog } from '../utils';
4
4
  import { SenderManager } from './sender.manager';
5
5
  import { StateManager } from './state.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
- */
16
6
  export class EventManager extends StateManager {
17
7
  constructor(storeManager, googleAnalytics = null, emitter = null) {
18
8
  super();
@@ -24,16 +14,15 @@ export class EventManager extends StateManager {
24
14
  this.dataSender = new SenderManager(storeManager);
25
15
  this.emitter = emitter;
26
16
  }
27
- /**
28
- * Recovers persisted events from localStorage
29
- * Should be called after initialization to recover any events that failed to send
30
- */
31
17
  async recoverPersistedEvents() {
32
18
  await this.dataSender.recoverPersistedEvents({
33
- onSuccess: (_eventCount, recoveredEvents) => {
19
+ onSuccess: (_eventCount, recoveredEvents, body) => {
34
20
  if (recoveredEvents && recoveredEvents.length > 0) {
35
21
  const eventIds = recoveredEvents.map((e) => e.timestamp + '_' + e.type);
36
22
  this.removeProcessedEvents(eventIds);
23
+ if (body) {
24
+ this.emitEventsQueue(body);
25
+ }
37
26
  }
38
27
  },
39
28
  onFailure: async () => {
@@ -41,9 +30,6 @@ export class EventManager extends StateManager {
41
30
  },
42
31
  });
43
32
  }
44
- /**
45
- * Track user events with automatic deduplication and queueing
46
- */
47
33
  track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, error_data, session_end_reason, }) {
48
34
  if (!type) {
49
35
  debugLog.warn('EventManager', 'Event type is required');
@@ -53,7 +39,6 @@ export class EventManager extends StateManager {
53
39
  const isSessionStart = eventType === EventType.SESSION_START;
54
40
  const isSessionEnd = eventType === EventType.SESSION_END;
55
41
  const isCriticalEvent = isSessionStart || isSessionEnd;
56
- // Build event payload
57
42
  const currentPageUrl = page_url || this.get('pageUrl');
58
43
  const payload = this.buildEventPayload({
59
44
  type: eventType,
@@ -66,11 +51,9 @@ export class EventManager extends StateManager {
66
51
  error_data,
67
52
  session_end_reason,
68
53
  });
69
- // Check URL exclusions
70
54
  if (this.isEventExcluded(payload)) {
71
55
  return;
72
56
  }
73
- // Skip sampling for critical events, apply for the rest
74
57
  if (!isCriticalEvent && !this.shouldSample()) {
75
58
  return;
76
59
  }
@@ -88,41 +71,27 @@ export class EventManager extends StateManager {
88
71
  }
89
72
  this.set('hasStartSession', true);
90
73
  }
91
- // Check for duplicates
92
74
  if (this.isDuplicateEvent(payload)) {
93
75
  return;
94
76
  }
95
- // Add to queue and schedule sending
96
77
  this.addToQueue(payload);
97
78
  }
98
79
  stop() {
99
- // Clear interval
100
80
  if (this.sendIntervalId) {
101
81
  clearInterval(this.sendIntervalId);
102
82
  this.sendIntervalId = null;
103
83
  }
104
- // Reset state
105
84
  this.eventsQueue = [];
106
85
  this.lastEventFingerprint = null;
107
86
  this.lastEventTime = 0;
108
- // Stop sender
109
87
  this.dataSender.stop();
110
88
  }
111
- /**
112
- * Flush all queued events immediately (async)
113
- */
114
89
  async flushImmediately() {
115
90
  return this.flushEvents(false);
116
91
  }
117
- /**
118
- * Flush all queued events immediately (sync)
119
- */
120
92
  flushImmediatelySync() {
121
93
  return this.flushEvents(true);
122
94
  }
123
- /**
124
- * Queue management and sending intervals
125
- */
126
95
  getQueueLength() {
127
96
  return this.eventsQueue.length;
128
97
  }
@@ -132,9 +101,6 @@ export class EventManager extends StateManager {
132
101
  this.sendIntervalId = null;
133
102
  }
134
103
  }
135
- /**
136
- * Shared flush implementation for both sync and async modes
137
- */
138
104
  flushEvents(isSync) {
139
105
  if (this.eventsQueue.length === 0) {
140
106
  return isSync ? true : Promise.resolve(true);
@@ -147,6 +113,7 @@ export class EventManager extends StateManager {
147
113
  if (success) {
148
114
  this.removeProcessedEvents(eventIds);
149
115
  this.clearSendInterval();
116
+ this.emitEventsQueue(body);
150
117
  }
151
118
  return success;
152
119
  }
@@ -165,9 +132,6 @@ export class EventManager extends StateManager {
165
132
  });
166
133
  }
167
134
  }
168
- /**
169
- * Send queued events to the API
170
- */
171
135
  async sendEventsQueue() {
172
136
  if (!this.get('sessionId') || this.eventsQueue.length === 0) {
173
137
  return;
@@ -187,10 +151,6 @@ export class EventManager extends StateManager {
187
151
  },
188
152
  });
189
153
  }
190
- /**
191
- * Build the payload for sending events to the API
192
- * Includes basic deduplication and sorting
193
- */
194
154
  buildEventsPayload() {
195
155
  const eventMap = new Map();
196
156
  const order = [];
@@ -213,9 +173,6 @@ export class EventManager extends StateManager {
213
173
  ...(this.get('config')?.globalMetadata && { global_metadata: this.get('config')?.globalMetadata }),
214
174
  };
215
175
  }
216
- /**
217
- * Helper methods for event processing
218
- */
219
176
  buildEventPayload(data) {
220
177
  const isSessionStart = data.type === EventType.SESSION_START;
221
178
  const currentPageUrl = data.page_url ?? this.get('pageUrl');
@@ -233,7 +190,6 @@ export class EventManager extends StateManager {
233
190
  ...(data.session_end_reason && { session_end_reason: data.session_end_reason }),
234
191
  ...(isSessionStart && getUTMParameters() && { utm: getUTMParameters() }),
235
192
  };
236
- // Add project tags
237
193
  const projectTags = this.get('config')?.tags;
238
194
  if (projectTags?.length) {
239
195
  payload.tags = projectTags;
@@ -254,11 +210,9 @@ export class EventManager extends StateManager {
254
210
  isDuplicateEvent(event) {
255
211
  const now = Date.now();
256
212
  const fingerprint = this.createEventFingerprint(event);
257
- // Check if this is a duplicate within the threshold
258
213
  if (this.lastEventFingerprint === fingerprint && now - this.lastEventTime < DUPLICATE_EVENT_THRESHOLD_MS) {
259
214
  return true;
260
215
  }
261
- // Update tracking
262
216
  this.lastEventFingerprint = fingerprint;
263
217
  this.lastEventTime = now;
264
218
  return false;
@@ -266,7 +220,6 @@ export class EventManager extends StateManager {
266
220
  createEventFingerprint(event) {
267
221
  let fingerprint = `${event.type}_${event.page_url}`;
268
222
  if (event.click_data) {
269
- // Round coordinates to reduce false duplicates
270
223
  const x = Math.round((event.click_data.x || 0) / 10) * 10;
271
224
  const y = Math.round((event.click_data.y || 0) / 10) * 10;
272
225
  fingerprint += `_click_${x}_${y}`;
@@ -280,6 +233,9 @@ export class EventManager extends StateManager {
280
233
  if (event.web_vitals) {
281
234
  fingerprint += `_vitals_${event.web_vitals.type}`;
282
235
  }
236
+ if (event.error_data) {
237
+ fingerprint += `_error_${event.error_data.type}_${event.error_data.message}`;
238
+ }
283
239
  return fingerprint;
284
240
  }
285
241
  createEventSignature(event) {
@@ -287,21 +243,20 @@ export class EventManager extends StateManager {
287
243
  }
288
244
  addToQueue(event) {
289
245
  this.eventsQueue.push(event);
290
- debugLog.info('EventManager', 'Event added to queue', event);
291
246
  this.emitEvent(event);
292
- // Prevent queue overflow
293
247
  if (this.eventsQueue.length > MAX_EVENTS_QUEUE_LENGTH) {
294
- const removedEvent = this.eventsQueue.shift();
295
- debugLog.warn('EventManager', 'Event queue overflow, oldest event removed', {
248
+ const nonCriticalIndex = this.eventsQueue.findIndex((e) => e.type !== EventType.SESSION_START && e.type !== EventType.SESSION_END);
249
+ const removedEvent = nonCriticalIndex >= 0 ? this.eventsQueue.splice(nonCriticalIndex, 1)[0] : this.eventsQueue.shift();
250
+ debugLog.warn('EventManager', 'Event queue overflow, oldest non-critical event removed', {
296
251
  maxLength: MAX_EVENTS_QUEUE_LENGTH,
297
252
  currentLength: this.eventsQueue.length,
298
253
  removedEventType: removedEvent?.type,
254
+ wasCritical: removedEvent?.type === EventType.SESSION_START || removedEvent?.type === EventType.SESSION_END,
299
255
  });
300
256
  }
301
257
  if (!this.sendIntervalId) {
302
258
  this.startSendInterval();
303
259
  }
304
- // Google Analytics integration
305
260
  this.handleGoogleAnalyticsIntegration(event);
306
261
  }
307
262
  startSendInterval() {
@@ -314,11 +269,9 @@ export class EventManager extends StateManager {
314
269
  handleGoogleAnalyticsIntegration(event) {
315
270
  if (this.googleAnalytics && event.type === EventType.CUSTOM && event.custom_event) {
316
271
  if (this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug') {
317
- // Skip GA tracking in QA/debug modes
318
- }
319
- else {
320
- this.googleAnalytics.trackEvent(event.custom_event.name, event.custom_event.metadata ?? {});
272
+ return;
321
273
  }
274
+ this.googleAnalytics.trackEvent(event.custom_event.name, event.custom_event.metadata ?? {});
322
275
  }
323
276
  }
324
277
  shouldSample() {
@@ -332,17 +285,11 @@ export class EventManager extends StateManager {
332
285
  return !eventIdSet.has(eventId);
333
286
  });
334
287
  }
335
- /**
336
- * Emit event for external listeners
337
- */
338
288
  emitEvent(eventData) {
339
289
  if (this.emitter) {
340
290
  this.emitter.emit(EmitterEvent.EVENT, eventData);
341
291
  }
342
292
  }
343
- /**
344
- * Emit events queue for external listeners
345
- */
346
293
  emitEventsQueue(queue) {
347
294
  if (this.emitter) {
348
295
  this.emitter.emit(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;