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