@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.
- package/dist/browser/tracelog.js +620 -658
- package/dist/cjs/api.d.ts +1 -53
- package/dist/cjs/api.js +0 -59
- package/dist/cjs/app.constants.d.ts +1 -1
- package/dist/cjs/app.d.ts +1 -5
- package/dist/cjs/app.js +4 -12
- package/dist/cjs/constants/api.constants.d.ts +5 -2
- package/dist/cjs/constants/api.constants.js +5 -14
- package/dist/cjs/constants/config.constants.d.ts +3 -3
- package/dist/cjs/constants/config.constants.js +3 -3
- package/dist/cjs/constants/error.constants.d.ts +7 -2
- package/dist/cjs/constants/error.constants.js +13 -2
- package/dist/cjs/handlers/click.handler.js +0 -6
- package/dist/cjs/handlers/error.handler.js +9 -0
- package/dist/cjs/handlers/scroll.handler.js +0 -5
- package/dist/cjs/handlers/session.handler.js +5 -2
- package/dist/cjs/integrations/google-analytics.integration.d.ts +1 -1
- package/dist/cjs/integrations/google-analytics.integration.js +2 -1
- package/dist/cjs/managers/api.manager.d.ts +1 -1
- package/dist/cjs/managers/api.manager.js +3 -3
- package/dist/cjs/managers/config.builder.d.ts +33 -0
- package/dist/cjs/managers/config.builder.js +116 -0
- package/dist/cjs/managers/config.manager.d.ts +13 -14
- package/dist/cjs/managers/config.manager.js +52 -58
- package/dist/cjs/managers/event.manager.d.ts +1 -46
- package/dist/cjs/managers/event.manager.js +15 -70
- package/dist/cjs/managers/sender.manager.d.ts +1 -28
- package/dist/cjs/managers/sender.manager.js +43 -73
- package/dist/cjs/managers/session.manager.d.ts +2 -49
- package/dist/cjs/managers/session.manager.js +42 -83
- package/dist/cjs/managers/state.manager.d.ts +1 -28
- package/dist/cjs/managers/state.manager.js +5 -33
- package/dist/cjs/managers/storage.manager.d.ts +6 -0
- package/dist/cjs/managers/storage.manager.js +18 -1
- package/dist/cjs/public-api.d.ts +1 -1
- package/dist/cjs/test-bridge.d.ts +3 -2
- package/dist/cjs/test-bridge.js +34 -7
- package/dist/cjs/types/api.types.d.ts +24 -8
- package/dist/cjs/types/api.types.js +24 -8
- package/dist/cjs/types/event.types.d.ts +2 -4
- package/dist/cjs/types/event.types.js +0 -1
- package/dist/cjs/types/test-bridge.types.d.ts +2 -1
- package/dist/cjs/utils/logging/debug-logger.utils.d.ts +1 -2
- package/dist/cjs/utils/logging/debug-logger.utils.js +2 -3
- package/dist/cjs/utils/validations/config-validations.utils.d.ts +1 -26
- package/dist/cjs/utils/validations/config-validations.utils.js +5 -117
- package/dist/cjs/utils/validations/event-validations.utils.d.ts +2 -2
- package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +3 -3
- package/dist/cjs/utils/validations/metadata-validations.utils.js +41 -3
- package/dist/esm/api.d.ts +1 -53
- package/dist/esm/api.js +0 -59
- package/dist/esm/app.constants.d.ts +1 -1
- package/dist/esm/app.d.ts +1 -5
- package/dist/esm/app.js +5 -13
- package/dist/esm/constants/api.constants.d.ts +5 -2
- package/dist/esm/constants/api.constants.js +5 -13
- package/dist/esm/constants/config.constants.d.ts +3 -3
- package/dist/esm/constants/config.constants.js +3 -3
- package/dist/esm/constants/error.constants.d.ts +7 -2
- package/dist/esm/constants/error.constants.js +12 -1
- package/dist/esm/handlers/click.handler.js +0 -6
- package/dist/esm/handlers/error.handler.js +10 -1
- package/dist/esm/handlers/scroll.handler.js +0 -5
- package/dist/esm/handlers/session.handler.js +5 -2
- package/dist/esm/integrations/google-analytics.integration.d.ts +1 -1
- package/dist/esm/integrations/google-analytics.integration.js +2 -1
- package/dist/esm/managers/api.manager.d.ts +1 -1
- package/dist/esm/managers/api.manager.js +3 -3
- package/dist/esm/managers/config.builder.d.ts +33 -0
- package/dist/esm/managers/config.builder.js +112 -0
- package/dist/esm/managers/config.manager.d.ts +13 -14
- package/dist/esm/managers/config.manager.js +54 -60
- package/dist/esm/managers/event.manager.d.ts +1 -46
- package/dist/esm/managers/event.manager.js +15 -70
- package/dist/esm/managers/sender.manager.d.ts +1 -28
- package/dist/esm/managers/sender.manager.js +44 -74
- package/dist/esm/managers/session.manager.d.ts +2 -49
- package/dist/esm/managers/session.manager.js +42 -83
- package/dist/esm/managers/state.manager.d.ts +1 -28
- package/dist/esm/managers/state.manager.js +4 -33
- package/dist/esm/managers/storage.manager.d.ts +6 -0
- package/dist/esm/managers/storage.manager.js +18 -1
- package/dist/esm/public-api.d.ts +1 -1
- package/dist/esm/test-bridge.d.ts +3 -2
- package/dist/esm/test-bridge.js +34 -7
- package/dist/esm/types/api.types.d.ts +24 -8
- package/dist/esm/types/api.types.js +24 -8
- package/dist/esm/types/event.types.d.ts +2 -4
- package/dist/esm/types/event.types.js +0 -1
- package/dist/esm/types/test-bridge.types.d.ts +2 -1
- package/dist/esm/utils/logging/debug-logger.utils.d.ts +1 -2
- package/dist/esm/utils/logging/debug-logger.utils.js +3 -4
- package/dist/esm/utils/validations/config-validations.utils.d.ts +1 -26
- package/dist/esm/utils/validations/config-validations.utils.js +5 -114
- package/dist/esm/utils/validations/event-validations.utils.d.ts +2 -2
- package/dist/esm/utils/validations/metadata-validations.utils.d.ts +3 -3
- package/dist/esm/utils/validations/metadata-validations.utils.js +41 -3
- package/package.json +1 -1
|
@@ -1,44 +1,53 @@
|
|
|
1
|
-
import { DEFAULT_API_CONFIG,
|
|
1
|
+
import { DEFAULT_API_CONFIG, REQUEST_TIMEOUT_MS } from '../constants';
|
|
2
2
|
import { Mode, SpecialProjectId } from '../types';
|
|
3
|
-
import { sanitizeApiConfig, fetchWithTimeout
|
|
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
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
*
|
|
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:
|
|
17
|
+
* - 'localhost:8080': Loads config from local development server
|
|
16
18
|
*/
|
|
17
19
|
export class ConfigManager {
|
|
18
20
|
/**
|
|
19
|
-
* Gets complete configuration by
|
|
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
|
-
|
|
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
|
|
31
|
-
|
|
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:
|
|
34
|
-
mode:
|
|
35
|
-
hasTags: !!
|
|
36
|
-
hasExclusions: !!
|
|
41
|
+
projectId: config.id,
|
|
42
|
+
mode: config.mode,
|
|
43
|
+
hasTags: !!config.tags?.length,
|
|
44
|
+
hasExclusions: !!config.excludedUrlPaths?.length,
|
|
37
45
|
});
|
|
38
|
-
return
|
|
46
|
+
return config;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
|
-
* Loads configuration from API and
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
*
|
|
127
|
+
* Applies QA mode to API config if enabled via URL parameter.
|
|
128
128
|
*/
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
297
|
-
|
|
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
|
-
|
|
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;
|