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