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