@tracelog/lib 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/browser/tracelog.js +620 -658
  2. package/dist/cjs/api.d.ts +1 -53
  3. package/dist/cjs/api.js +0 -59
  4. package/dist/cjs/app.constants.d.ts +1 -1
  5. package/dist/cjs/app.d.ts +1 -5
  6. package/dist/cjs/app.js +4 -12
  7. package/dist/cjs/constants/api.constants.d.ts +5 -2
  8. package/dist/cjs/constants/api.constants.js +5 -14
  9. package/dist/cjs/constants/config.constants.d.ts +3 -3
  10. package/dist/cjs/constants/config.constants.js +3 -3
  11. package/dist/cjs/constants/error.constants.d.ts +7 -2
  12. package/dist/cjs/constants/error.constants.js +13 -2
  13. package/dist/cjs/handlers/click.handler.js +0 -6
  14. package/dist/cjs/handlers/error.handler.js +9 -0
  15. package/dist/cjs/handlers/scroll.handler.js +0 -5
  16. package/dist/cjs/handlers/session.handler.js +5 -2
  17. package/dist/cjs/integrations/google-analytics.integration.d.ts +1 -1
  18. package/dist/cjs/integrations/google-analytics.integration.js +2 -1
  19. package/dist/cjs/managers/api.manager.d.ts +1 -1
  20. package/dist/cjs/managers/api.manager.js +3 -3
  21. package/dist/cjs/managers/config.builder.d.ts +33 -0
  22. package/dist/cjs/managers/config.builder.js +116 -0
  23. package/dist/cjs/managers/config.manager.d.ts +13 -14
  24. package/dist/cjs/managers/config.manager.js +52 -58
  25. package/dist/cjs/managers/event.manager.d.ts +1 -46
  26. package/dist/cjs/managers/event.manager.js +15 -70
  27. package/dist/cjs/managers/sender.manager.d.ts +1 -28
  28. package/dist/cjs/managers/sender.manager.js +43 -73
  29. package/dist/cjs/managers/session.manager.d.ts +2 -49
  30. package/dist/cjs/managers/session.manager.js +42 -83
  31. package/dist/cjs/managers/state.manager.d.ts +1 -28
  32. package/dist/cjs/managers/state.manager.js +5 -33
  33. package/dist/cjs/managers/storage.manager.d.ts +6 -0
  34. package/dist/cjs/managers/storage.manager.js +18 -1
  35. package/dist/cjs/public-api.d.ts +1 -1
  36. package/dist/cjs/test-bridge.d.ts +3 -2
  37. package/dist/cjs/test-bridge.js +34 -7
  38. package/dist/cjs/types/api.types.d.ts +24 -8
  39. package/dist/cjs/types/api.types.js +24 -8
  40. package/dist/cjs/types/event.types.d.ts +2 -4
  41. package/dist/cjs/types/event.types.js +0 -1
  42. package/dist/cjs/types/test-bridge.types.d.ts +2 -1
  43. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +1 -2
  44. package/dist/cjs/utils/logging/debug-logger.utils.js +2 -3
  45. package/dist/cjs/utils/validations/config-validations.utils.d.ts +1 -26
  46. package/dist/cjs/utils/validations/config-validations.utils.js +5 -117
  47. package/dist/cjs/utils/validations/event-validations.utils.d.ts +2 -2
  48. package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +3 -3
  49. package/dist/cjs/utils/validations/metadata-validations.utils.js +41 -3
  50. package/dist/esm/api.d.ts +1 -53
  51. package/dist/esm/api.js +0 -59
  52. package/dist/esm/app.constants.d.ts +1 -1
  53. package/dist/esm/app.d.ts +1 -5
  54. package/dist/esm/app.js +5 -13
  55. package/dist/esm/constants/api.constants.d.ts +5 -2
  56. package/dist/esm/constants/api.constants.js +5 -13
  57. package/dist/esm/constants/config.constants.d.ts +3 -3
  58. package/dist/esm/constants/config.constants.js +3 -3
  59. package/dist/esm/constants/error.constants.d.ts +7 -2
  60. package/dist/esm/constants/error.constants.js +12 -1
  61. package/dist/esm/handlers/click.handler.js +0 -6
  62. package/dist/esm/handlers/error.handler.js +10 -1
  63. package/dist/esm/handlers/scroll.handler.js +0 -5
  64. package/dist/esm/handlers/session.handler.js +5 -2
  65. package/dist/esm/integrations/google-analytics.integration.d.ts +1 -1
  66. package/dist/esm/integrations/google-analytics.integration.js +2 -1
  67. package/dist/esm/managers/api.manager.d.ts +1 -1
  68. package/dist/esm/managers/api.manager.js +3 -3
  69. package/dist/esm/managers/config.builder.d.ts +33 -0
  70. package/dist/esm/managers/config.builder.js +112 -0
  71. package/dist/esm/managers/config.manager.d.ts +13 -14
  72. package/dist/esm/managers/config.manager.js +54 -60
  73. package/dist/esm/managers/event.manager.d.ts +1 -46
  74. package/dist/esm/managers/event.manager.js +15 -70
  75. package/dist/esm/managers/sender.manager.d.ts +1 -28
  76. package/dist/esm/managers/sender.manager.js +44 -74
  77. package/dist/esm/managers/session.manager.d.ts +2 -49
  78. package/dist/esm/managers/session.manager.js +42 -83
  79. package/dist/esm/managers/state.manager.d.ts +1 -28
  80. package/dist/esm/managers/state.manager.js +4 -33
  81. package/dist/esm/managers/storage.manager.d.ts +6 -0
  82. package/dist/esm/managers/storage.manager.js +18 -1
  83. package/dist/esm/public-api.d.ts +1 -1
  84. package/dist/esm/test-bridge.d.ts +3 -2
  85. package/dist/esm/test-bridge.js +34 -7
  86. package/dist/esm/types/api.types.d.ts +24 -8
  87. package/dist/esm/types/api.types.js +24 -8
  88. package/dist/esm/types/event.types.d.ts +2 -4
  89. package/dist/esm/types/event.types.js +0 -1
  90. package/dist/esm/types/test-bridge.types.d.ts +2 -1
  91. package/dist/esm/utils/logging/debug-logger.utils.d.ts +1 -2
  92. package/dist/esm/utils/logging/debug-logger.utils.js +3 -4
  93. package/dist/esm/utils/validations/config-validations.utils.d.ts +1 -26
  94. package/dist/esm/utils/validations/config-validations.utils.js +5 -114
  95. package/dist/esm/utils/validations/event-validations.utils.d.ts +2 -2
  96. package/dist/esm/utils/validations/metadata-validations.utils.d.ts +3 -3
  97. package/dist/esm/utils/validations/metadata-validations.utils.js +41 -3
  98. package/package.json +1 -1
@@ -1,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
- track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, error_data, session_end_reason, session_start_recovered, }: Partial<EventData>): void;
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,10 +30,7 @@ export class EventManager extends StateManager {
41
30
  },
42
31
  });
43
32
  }
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, }) {
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');
50
36
  return;
@@ -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,
@@ -65,13 +50,10 @@ export class EventManager extends StateManager {
65
50
  web_vitals,
66
51
  error_data,
67
52
  session_end_reason,
68
- session_start_recovered,
69
53
  });
70
- // Check URL exclusions
71
54
  if (this.isEventExcluded(payload)) {
72
55
  return;
73
56
  }
74
- // Skip sampling for critical events, apply for the rest
75
57
  if (!isCriticalEvent && !this.shouldSample()) {
76
58
  return;
77
59
  }
@@ -89,41 +71,27 @@ export class EventManager extends StateManager {
89
71
  }
90
72
  this.set('hasStartSession', true);
91
73
  }
92
- // Check for duplicates
93
74
  if (this.isDuplicateEvent(payload)) {
94
75
  return;
95
76
  }
96
- // Add to queue and schedule sending
97
77
  this.addToQueue(payload);
98
78
  }
99
79
  stop() {
100
- // Clear interval
101
80
  if (this.sendIntervalId) {
102
81
  clearInterval(this.sendIntervalId);
103
82
  this.sendIntervalId = null;
104
83
  }
105
- // Reset state
106
84
  this.eventsQueue = [];
107
85
  this.lastEventFingerprint = null;
108
86
  this.lastEventTime = 0;
109
- // Stop sender
110
87
  this.dataSender.stop();
111
88
  }
112
- /**
113
- * Flush all queued events immediately (async)
114
- */
115
89
  async flushImmediately() {
116
90
  return this.flushEvents(false);
117
91
  }
118
- /**
119
- * Flush all queued events immediately (sync)
120
- */
121
92
  flushImmediatelySync() {
122
93
  return this.flushEvents(true);
123
94
  }
124
- /**
125
- * Queue management and sending intervals
126
- */
127
95
  getQueueLength() {
128
96
  return this.eventsQueue.length;
129
97
  }
@@ -133,9 +101,6 @@ export class EventManager extends StateManager {
133
101
  this.sendIntervalId = null;
134
102
  }
135
103
  }
136
- /**
137
- * Shared flush implementation for both sync and async modes
138
- */
139
104
  flushEvents(isSync) {
140
105
  if (this.eventsQueue.length === 0) {
141
106
  return isSync ? true : Promise.resolve(true);
@@ -148,6 +113,7 @@ export class EventManager extends StateManager {
148
113
  if (success) {
149
114
  this.removeProcessedEvents(eventIds);
150
115
  this.clearSendInterval();
116
+ this.emitEventsQueue(body);
151
117
  }
152
118
  return success;
153
119
  }
@@ -166,9 +132,6 @@ export class EventManager extends StateManager {
166
132
  });
167
133
  }
168
134
  }
169
- /**
170
- * Send queued events to the API
171
- */
172
135
  async sendEventsQueue() {
173
136
  if (!this.get('sessionId') || this.eventsQueue.length === 0) {
174
137
  return;
@@ -188,10 +151,6 @@ export class EventManager extends StateManager {
188
151
  },
189
152
  });
190
153
  }
191
- /**
192
- * Build the payload for sending events to the API
193
- * Includes basic deduplication and sorting
194
- */
195
154
  buildEventsPayload() {
196
155
  const eventMap = new Map();
197
156
  const order = [];
@@ -214,9 +173,6 @@ export class EventManager extends StateManager {
214
173
  ...(this.get('config')?.globalMetadata && { global_metadata: this.get('config')?.globalMetadata }),
215
174
  };
216
175
  }
217
- /**
218
- * Helper methods for event processing
219
- */
220
176
  buildEventPayload(data) {
221
177
  const isSessionStart = data.type === EventType.SESSION_START;
222
178
  const currentPageUrl = data.page_url ?? this.get('pageUrl');
@@ -232,10 +188,8 @@ export class EventManager extends StateManager {
232
188
  ...(data.web_vitals && { web_vitals: data.web_vitals }),
233
189
  ...(data.error_data && { error_data: data.error_data }),
234
190
  ...(data.session_end_reason && { session_end_reason: data.session_end_reason }),
235
- ...(data.session_start_recovered && { session_start_recovered: data.session_start_recovered }),
236
191
  ...(isSessionStart && getUTMParameters() && { utm: getUTMParameters() }),
237
192
  };
238
- // Add project tags
239
193
  const projectTags = this.get('config')?.tags;
240
194
  if (projectTags?.length) {
241
195
  payload.tags = projectTags;
@@ -256,11 +210,9 @@ export class EventManager extends StateManager {
256
210
  isDuplicateEvent(event) {
257
211
  const now = Date.now();
258
212
  const fingerprint = this.createEventFingerprint(event);
259
- // Check if this is a duplicate within the threshold
260
213
  if (this.lastEventFingerprint === fingerprint && now - this.lastEventTime < DUPLICATE_EVENT_THRESHOLD_MS) {
261
214
  return true;
262
215
  }
263
- // Update tracking
264
216
  this.lastEventFingerprint = fingerprint;
265
217
  this.lastEventTime = now;
266
218
  return false;
@@ -268,7 +220,6 @@ export class EventManager extends StateManager {
268
220
  createEventFingerprint(event) {
269
221
  let fingerprint = `${event.type}_${event.page_url}`;
270
222
  if (event.click_data) {
271
- // Round coordinates to reduce false duplicates
272
223
  const x = Math.round((event.click_data.x || 0) / 10) * 10;
273
224
  const y = Math.round((event.click_data.y || 0) / 10) * 10;
274
225
  fingerprint += `_click_${x}_${y}`;
@@ -282,6 +233,9 @@ export class EventManager extends StateManager {
282
233
  if (event.web_vitals) {
283
234
  fingerprint += `_vitals_${event.web_vitals.type}`;
284
235
  }
236
+ if (event.error_data) {
237
+ fingerprint += `_error_${event.error_data.type}_${event.error_data.message}`;
238
+ }
285
239
  return fingerprint;
286
240
  }
287
241
  createEventSignature(event) {
@@ -289,21 +243,20 @@ export class EventManager extends StateManager {
289
243
  }
290
244
  addToQueue(event) {
291
245
  this.eventsQueue.push(event);
292
- debugLog.info('EventManager', 'Event added to queue', event);
293
246
  this.emitEvent(event);
294
- // Prevent queue overflow
295
247
  if (this.eventsQueue.length > MAX_EVENTS_QUEUE_LENGTH) {
296
- const removedEvent = this.eventsQueue.shift();
297
- 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', {
298
251
  maxLength: MAX_EVENTS_QUEUE_LENGTH,
299
252
  currentLength: this.eventsQueue.length,
300
253
  removedEventType: removedEvent?.type,
254
+ wasCritical: removedEvent?.type === EventType.SESSION_START || removedEvent?.type === EventType.SESSION_END,
301
255
  });
302
256
  }
303
257
  if (!this.sendIntervalId) {
304
258
  this.startSendInterval();
305
259
  }
306
- // Google Analytics integration
307
260
  this.handleGoogleAnalyticsIntegration(event);
308
261
  }
309
262
  startSendInterval() {
@@ -316,11 +269,9 @@ export class EventManager extends StateManager {
316
269
  handleGoogleAnalyticsIntegration(event) {
317
270
  if (this.googleAnalytics && event.type === EventType.CUSTOM && event.custom_event) {
318
271
  if (this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug') {
319
- // Skip GA tracking in QA/debug modes
320
- }
321
- else {
322
- this.googleAnalytics.trackEvent(event.custom_event.name, event.custom_event.metadata ?? {});
272
+ return;
323
273
  }
274
+ this.googleAnalytics.trackEvent(event.custom_event.name, event.custom_event.metadata ?? {});
324
275
  }
325
276
  }
326
277
  shouldSample() {
@@ -334,17 +285,11 @@ export class EventManager extends StateManager {
334
285
  return !eventIdSet.has(eventId);
335
286
  });
336
287
  }
337
- /**
338
- * Emit event for external listeners
339
- */
340
288
  emitEvent(eventData) {
341
289
  if (this.emitter) {
342
290
  this.emitter.emit(EmitterEvent.EVENT, eventData);
343
291
  }
344
292
  }
345
- /**
346
- * Emit events queue for external listeners
347
- */
348
293
  emitEventsQueue(queue) {
349
294
  if (this.emitter) {
350
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;