@tracelog/lib 0.0.1
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/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/browser/tracelog.js +4040 -0
- package/dist/browser/web-vitals-CCnqwnC8.mjs +198 -0
- package/dist/cjs/api.d.ts +46 -0
- package/dist/cjs/api.js +224 -0
- package/dist/cjs/app.constants.d.ts +1 -0
- package/dist/cjs/app.constants.js +5 -0
- package/dist/cjs/app.d.ts +59 -0
- package/dist/cjs/app.js +272 -0
- package/dist/cjs/app.types.d.ts +6 -0
- package/dist/cjs/app.types.js +22 -0
- package/dist/cjs/constants/api.constants.d.ts +4 -0
- package/dist/cjs/constants/api.constants.js +18 -0
- package/dist/cjs/constants/browser.constants.d.ts +3 -0
- package/dist/cjs/constants/browser.constants.js +41 -0
- package/dist/cjs/constants/index.d.ts +8 -0
- package/dist/cjs/constants/index.js +24 -0
- package/dist/cjs/constants/initialization.constants.d.ts +40 -0
- package/dist/cjs/constants/initialization.constants.js +48 -0
- package/dist/cjs/constants/limits.constants.d.ts +25 -0
- package/dist/cjs/constants/limits.constants.js +40 -0
- package/dist/cjs/constants/security.constants.d.ts +1 -0
- package/dist/cjs/constants/security.constants.js +12 -0
- package/dist/cjs/constants/storage.constants.d.ts +9 -0
- package/dist/cjs/constants/storage.constants.js +22 -0
- package/dist/cjs/constants/timing.constants.d.ts +22 -0
- package/dist/cjs/constants/timing.constants.js +34 -0
- package/dist/cjs/constants/validation.constants.d.ts +13 -0
- package/dist/cjs/constants/validation.constants.js +31 -0
- package/dist/cjs/handlers/click.handler.d.ts +17 -0
- package/dist/cjs/handlers/click.handler.js +199 -0
- package/dist/cjs/handlers/error.handler.d.ts +15 -0
- package/dist/cjs/handlers/error.handler.js +97 -0
- package/dist/cjs/handlers/network.handler.d.ts +16 -0
- package/dist/cjs/handlers/network.handler.js +136 -0
- package/dist/cjs/handlers/page-view.handler.d.ts +15 -0
- package/dist/cjs/handlers/page-view.handler.js +83 -0
- package/dist/cjs/handlers/performance.handler.d.ts +19 -0
- package/dist/cjs/handlers/performance.handler.js +255 -0
- package/dist/cjs/handlers/scroll.handler.d.ts +16 -0
- package/dist/cjs/handlers/scroll.handler.js +138 -0
- package/dist/cjs/handlers/session.handler.d.ts +29 -0
- package/dist/cjs/handlers/session.handler.js +357 -0
- package/dist/cjs/integrations/google-analytics.integration.d.ts +18 -0
- package/dist/cjs/integrations/google-analytics.integration.js +159 -0
- package/dist/cjs/listeners/activity-listener-manager.d.ts +8 -0
- package/dist/cjs/listeners/activity-listener-manager.js +32 -0
- package/dist/cjs/listeners/index.d.ts +6 -0
- package/dist/cjs/listeners/index.js +14 -0
- package/dist/cjs/listeners/input-listener-managers.d.ts +15 -0
- package/dist/cjs/listeners/input-listener-managers.js +58 -0
- package/dist/cjs/listeners/listeners.types.d.ts +4 -0
- package/dist/cjs/listeners/listeners.types.js +2 -0
- package/dist/cjs/listeners/touch-listener-manager.d.ts +10 -0
- package/dist/cjs/listeners/touch-listener-manager.js +56 -0
- package/dist/cjs/listeners/unload-listener-manager.d.ts +8 -0
- package/dist/cjs/listeners/unload-listener-manager.js +30 -0
- package/dist/cjs/listeners/visibility-listener-manager.d.ts +12 -0
- package/dist/cjs/listeners/visibility-listener-manager.js +83 -0
- package/dist/cjs/managers/api.manager.d.ts +3 -0
- package/dist/cjs/managers/api.manager.js +14 -0
- package/dist/cjs/managers/config.manager.d.ts +7 -0
- package/dist/cjs/managers/config.manager.js +94 -0
- package/dist/cjs/managers/cross-tab-session.manager.d.ts +170 -0
- package/dist/cjs/managers/cross-tab-session.manager.js +730 -0
- package/dist/cjs/managers/event.manager.d.ts +61 -0
- package/dist/cjs/managers/event.manager.js +508 -0
- package/dist/cjs/managers/sampling.manager.d.ts +8 -0
- package/dist/cjs/managers/sampling.manager.js +53 -0
- package/dist/cjs/managers/sender.manager.d.ts +46 -0
- package/dist/cjs/managers/sender.manager.js +304 -0
- package/dist/cjs/managers/session-recovery.manager.d.ts +65 -0
- package/dist/cjs/managers/session-recovery.manager.js +237 -0
- package/dist/cjs/managers/session.manager.d.ts +72 -0
- package/dist/cjs/managers/session.manager.js +587 -0
- package/dist/cjs/managers/state.manager.d.ts +5 -0
- package/dist/cjs/managers/state.manager.js +23 -0
- package/dist/cjs/managers/storage.manager.d.ts +10 -0
- package/dist/cjs/managers/storage.manager.js +81 -0
- package/dist/cjs/managers/tags.manager.d.ts +12 -0
- package/dist/cjs/managers/tags.manager.js +289 -0
- package/dist/cjs/managers/user.manager.d.ts +7 -0
- package/dist/cjs/managers/user.manager.js +22 -0
- package/dist/cjs/public-api.d.ts +1 -0
- package/dist/cjs/public-api.js +37 -0
- package/dist/cjs/types/api.types.d.ts +21 -0
- package/dist/cjs/types/api.types.js +25 -0
- package/dist/cjs/types/common.types.d.ts +1 -0
- package/dist/cjs/types/common.types.js +2 -0
- package/dist/cjs/types/config.types.d.ts +104 -0
- package/dist/cjs/types/config.types.js +2 -0
- package/dist/cjs/types/device.types.d.ts +6 -0
- package/dist/cjs/types/device.types.js +10 -0
- package/dist/cjs/types/event.types.d.ts +104 -0
- package/dist/cjs/types/event.types.js +25 -0
- package/dist/cjs/types/index.d.ts +13 -0
- package/dist/cjs/types/index.js +29 -0
- package/dist/cjs/types/log.types.d.ts +4 -0
- package/dist/cjs/types/log.types.js +2 -0
- package/dist/cjs/types/mode.types.d.ts +7 -0
- package/dist/cjs/types/mode.types.js +11 -0
- package/dist/cjs/types/queue.types.d.ts +23 -0
- package/dist/cjs/types/queue.types.js +2 -0
- package/dist/cjs/types/session.types.d.ts +65 -0
- package/dist/cjs/types/session.types.js +2 -0
- package/dist/cjs/types/state.types.d.ts +12 -0
- package/dist/cjs/types/state.types.js +2 -0
- package/dist/cjs/types/tag.types.d.ts +43 -0
- package/dist/cjs/types/tag.types.js +31 -0
- package/dist/cjs/types/validation-error.types.d.ts +42 -0
- package/dist/cjs/types/validation-error.types.js +68 -0
- package/dist/cjs/types/web-vitals.types.d.ts +6 -0
- package/dist/cjs/types/web-vitals.types.js +2 -0
- package/dist/cjs/types/window.types.d.ts +17 -0
- package/dist/cjs/types/window.types.js +2 -0
- package/dist/cjs/utils/browser/device-detector.utils.d.ts +6 -0
- package/dist/cjs/utils/browser/device-detector.utils.js +71 -0
- package/dist/cjs/utils/browser/index.d.ts +2 -0
- package/dist/cjs/utils/browser/index.js +18 -0
- package/dist/cjs/utils/browser/utm-params.utils.d.ts +6 -0
- package/dist/cjs/utils/browser/utm-params.utils.js +37 -0
- package/dist/cjs/utils/data/index.d.ts +1 -0
- package/dist/cjs/utils/data/index.js +17 -0
- package/dist/cjs/utils/data/uuid.utils.d.ts +5 -0
- package/dist/cjs/utils/data/uuid.utils.js +18 -0
- package/dist/cjs/utils/index.d.ts +6 -0
- package/dist/cjs/utils/index.js +22 -0
- package/dist/cjs/utils/logging/debug-logger.utils.d.ts +56 -0
- package/dist/cjs/utils/logging/debug-logger.utils.js +139 -0
- package/dist/cjs/utils/logging/index.d.ts +1 -0
- package/dist/cjs/utils/logging/index.js +5 -0
- package/dist/cjs/utils/network/index.d.ts +1 -0
- package/dist/cjs/utils/network/index.js +17 -0
- package/dist/cjs/utils/network/url.utils.d.ts +20 -0
- package/dist/cjs/utils/network/url.utils.js +172 -0
- package/dist/cjs/utils/security/index.d.ts +1 -0
- package/dist/cjs/utils/security/index.js +17 -0
- package/dist/cjs/utils/security/sanitize.utils.d.ts +32 -0
- package/dist/cjs/utils/security/sanitize.utils.js +319 -0
- package/dist/cjs/utils/validations/config-validations.utils.d.ts +42 -0
- package/dist/cjs/utils/validations/config-validations.utils.js +297 -0
- package/dist/cjs/utils/validations/event-validations.utils.d.ts +12 -0
- package/dist/cjs/utils/validations/event-validations.utils.js +30 -0
- package/dist/cjs/utils/validations/index.d.ts +5 -0
- package/dist/cjs/utils/validations/index.js +21 -0
- package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +22 -0
- package/dist/cjs/utils/validations/metadata-validations.utils.js +115 -0
- package/dist/cjs/utils/validations/type-guards.utils.d.ts +6 -0
- package/dist/cjs/utils/validations/type-guards.utils.js +31 -0
- package/dist/cjs/utils/validations/url-validations.utils.d.ts +15 -0
- package/dist/cjs/utils/validations/url-validations.utils.js +47 -0
- package/dist/esm/api.d.ts +46 -0
- package/dist/esm/api.js +183 -0
- package/dist/esm/app.constants.d.ts +1 -0
- package/dist/esm/app.constants.js +1 -0
- package/dist/esm/app.d.ts +59 -0
- package/dist/esm/app.js +268 -0
- package/dist/esm/app.types.d.ts +6 -0
- package/dist/esm/app.types.js +6 -0
- package/dist/esm/constants/api.constants.d.ts +4 -0
- package/dist/esm/constants/api.constants.js +14 -0
- package/dist/esm/constants/browser.constants.d.ts +3 -0
- package/dist/esm/constants/browser.constants.js +38 -0
- package/dist/esm/constants/index.d.ts +8 -0
- package/dist/esm/constants/index.js +8 -0
- package/dist/esm/constants/initialization.constants.d.ts +40 -0
- package/dist/esm/constants/initialization.constants.js +45 -0
- package/dist/esm/constants/limits.constants.d.ts +25 -0
- package/dist/esm/constants/limits.constants.js +37 -0
- package/dist/esm/constants/security.constants.d.ts +1 -0
- package/dist/esm/constants/security.constants.js +9 -0
- package/dist/esm/constants/storage.constants.d.ts +9 -0
- package/dist/esm/constants/storage.constants.js +11 -0
- package/dist/esm/constants/timing.constants.d.ts +22 -0
- package/dist/esm/constants/timing.constants.js +31 -0
- package/dist/esm/constants/validation.constants.d.ts +13 -0
- package/dist/esm/constants/validation.constants.js +28 -0
- package/dist/esm/handlers/click.handler.d.ts +17 -0
- package/dist/esm/handlers/click.handler.js +195 -0
- package/dist/esm/handlers/error.handler.d.ts +15 -0
- package/dist/esm/handlers/error.handler.js +93 -0
- package/dist/esm/handlers/network.handler.d.ts +16 -0
- package/dist/esm/handlers/network.handler.js +132 -0
- package/dist/esm/handlers/page-view.handler.d.ts +15 -0
- package/dist/esm/handlers/page-view.handler.js +79 -0
- package/dist/esm/handlers/performance.handler.d.ts +19 -0
- package/dist/esm/handlers/performance.handler.js +218 -0
- package/dist/esm/handlers/scroll.handler.d.ts +16 -0
- package/dist/esm/handlers/scroll.handler.js +134 -0
- package/dist/esm/handlers/session.handler.d.ts +29 -0
- package/dist/esm/handlers/session.handler.js +353 -0
- package/dist/esm/integrations/google-analytics.integration.d.ts +18 -0
- package/dist/esm/integrations/google-analytics.integration.js +155 -0
- package/dist/esm/listeners/activity-listener-manager.d.ts +8 -0
- package/dist/esm/listeners/activity-listener-manager.js +28 -0
- package/dist/esm/listeners/index.d.ts +6 -0
- package/dist/esm/listeners/index.js +5 -0
- package/dist/esm/listeners/input-listener-managers.d.ts +15 -0
- package/dist/esm/listeners/input-listener-managers.js +53 -0
- package/dist/esm/listeners/listeners.types.d.ts +4 -0
- package/dist/esm/listeners/listeners.types.js +1 -0
- package/dist/esm/listeners/touch-listener-manager.d.ts +10 -0
- package/dist/esm/listeners/touch-listener-manager.js +52 -0
- package/dist/esm/listeners/unload-listener-manager.d.ts +8 -0
- package/dist/esm/listeners/unload-listener-manager.js +26 -0
- package/dist/esm/listeners/visibility-listener-manager.d.ts +12 -0
- package/dist/esm/listeners/visibility-listener-manager.js +79 -0
- package/dist/esm/managers/api.manager.d.ts +3 -0
- package/dist/esm/managers/api.manager.js +10 -0
- package/dist/esm/managers/config.manager.d.ts +7 -0
- package/dist/esm/managers/config.manager.js +90 -0
- package/dist/esm/managers/cross-tab-session.manager.d.ts +170 -0
- package/dist/esm/managers/cross-tab-session.manager.js +726 -0
- package/dist/esm/managers/event.manager.d.ts +61 -0
- package/dist/esm/managers/event.manager.js +504 -0
- package/dist/esm/managers/sampling.manager.d.ts +8 -0
- package/dist/esm/managers/sampling.manager.js +49 -0
- package/dist/esm/managers/sender.manager.d.ts +46 -0
- package/dist/esm/managers/sender.manager.js +300 -0
- package/dist/esm/managers/session-recovery.manager.d.ts +65 -0
- package/dist/esm/managers/session-recovery.manager.js +233 -0
- package/dist/esm/managers/session.manager.d.ts +72 -0
- package/dist/esm/managers/session.manager.js +583 -0
- package/dist/esm/managers/state.manager.d.ts +5 -0
- package/dist/esm/managers/state.manager.js +19 -0
- package/dist/esm/managers/storage.manager.d.ts +10 -0
- package/dist/esm/managers/storage.manager.js +77 -0
- package/dist/esm/managers/tags.manager.d.ts +12 -0
- package/dist/esm/managers/tags.manager.js +285 -0
- package/dist/esm/managers/user.manager.d.ts +7 -0
- package/dist/esm/managers/user.manager.js +18 -0
- package/dist/esm/public-api.d.ts +1 -0
- package/dist/esm/public-api.js +1 -0
- package/dist/esm/types/api.types.d.ts +21 -0
- package/dist/esm/types/api.types.js +22 -0
- package/dist/esm/types/common.types.d.ts +1 -0
- package/dist/esm/types/common.types.js +1 -0
- package/dist/esm/types/config.types.d.ts +104 -0
- package/dist/esm/types/config.types.js +1 -0
- package/dist/esm/types/device.types.d.ts +6 -0
- package/dist/esm/types/device.types.js +7 -0
- package/dist/esm/types/event.types.d.ts +104 -0
- package/dist/esm/types/event.types.js +22 -0
- package/dist/esm/types/index.d.ts +13 -0
- package/dist/esm/types/index.js +13 -0
- package/dist/esm/types/log.types.d.ts +4 -0
- package/dist/esm/types/log.types.js +1 -0
- package/dist/esm/types/mode.types.d.ts +7 -0
- package/dist/esm/types/mode.types.js +8 -0
- package/dist/esm/types/queue.types.d.ts +23 -0
- package/dist/esm/types/queue.types.js +1 -0
- package/dist/esm/types/session.types.d.ts +65 -0
- package/dist/esm/types/session.types.js +1 -0
- package/dist/esm/types/state.types.d.ts +12 -0
- package/dist/esm/types/state.types.js +1 -0
- package/dist/esm/types/tag.types.d.ts +43 -0
- package/dist/esm/types/tag.types.js +28 -0
- package/dist/esm/types/validation-error.types.d.ts +42 -0
- package/dist/esm/types/validation-error.types.js +59 -0
- package/dist/esm/types/web-vitals.types.d.ts +6 -0
- package/dist/esm/types/web-vitals.types.js +1 -0
- package/dist/esm/types/window.types.d.ts +17 -0
- package/dist/esm/types/window.types.js +1 -0
- package/dist/esm/utils/browser/device-detector.utils.d.ts +6 -0
- package/dist/esm/utils/browser/device-detector.utils.js +67 -0
- package/dist/esm/utils/browser/index.d.ts +2 -0
- package/dist/esm/utils/browser/index.js +2 -0
- package/dist/esm/utils/browser/utm-params.utils.d.ts +6 -0
- package/dist/esm/utils/browser/utm-params.utils.js +33 -0
- package/dist/esm/utils/data/index.d.ts +1 -0
- package/dist/esm/utils/data/index.js +1 -0
- package/dist/esm/utils/data/uuid.utils.d.ts +5 -0
- package/dist/esm/utils/data/uuid.utils.js +14 -0
- package/dist/esm/utils/index.d.ts +6 -0
- package/dist/esm/utils/index.js +6 -0
- package/dist/esm/utils/logging/debug-logger.utils.d.ts +56 -0
- package/dist/esm/utils/logging/debug-logger.utils.js +136 -0
- package/dist/esm/utils/logging/index.d.ts +1 -0
- package/dist/esm/utils/logging/index.js +1 -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.d.ts +20 -0
- package/dist/esm/utils/network/url.utils.js +166 -0
- package/dist/esm/utils/security/index.d.ts +1 -0
- package/dist/esm/utils/security/index.js +1 -0
- package/dist/esm/utils/security/sanitize.utils.d.ts +32 -0
- package/dist/esm/utils/security/sanitize.utils.js +311 -0
- package/dist/esm/utils/validations/config-validations.utils.d.ts +42 -0
- package/dist/esm/utils/validations/config-validations.utils.js +289 -0
- package/dist/esm/utils/validations/event-validations.utils.d.ts +12 -0
- package/dist/esm/utils/validations/event-validations.utils.js +26 -0
- package/dist/esm/utils/validations/index.d.ts +5 -0
- package/dist/esm/utils/validations/index.js +5 -0
- package/dist/esm/utils/validations/metadata-validations.utils.d.ts +22 -0
- package/dist/esm/utils/validations/metadata-validations.utils.js +110 -0
- package/dist/esm/utils/validations/type-guards.utils.d.ts +6 -0
- package/dist/esm/utils/validations/type-guards.utils.js +27 -0
- package/dist/esm/utils/validations/url-validations.utils.d.ts +15 -0
- package/dist/esm/utils/validations/url-validations.utils.js +42 -0
- package/package.json +80 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { EventData } from '../types';
|
|
2
|
+
import { StateManager } from './state.manager';
|
|
3
|
+
import { StorageManager } from './storage.manager';
|
|
4
|
+
import { GoogleAnalyticsIntegration } from '../integrations/google-analytics.integration';
|
|
5
|
+
export declare class EventManager extends StateManager {
|
|
6
|
+
private readonly googleAnalytics;
|
|
7
|
+
private readonly samplingManager;
|
|
8
|
+
private readonly tagsManager;
|
|
9
|
+
private readonly dataSender;
|
|
10
|
+
private readonly storageManager;
|
|
11
|
+
private eventsQueue;
|
|
12
|
+
private lastEvent;
|
|
13
|
+
private eventsQueueIntervalId;
|
|
14
|
+
private intervalActive;
|
|
15
|
+
private failureCount;
|
|
16
|
+
private readonly MAX_FAILURES;
|
|
17
|
+
private circuitOpen;
|
|
18
|
+
private circuitOpenTime;
|
|
19
|
+
private backoffDelay;
|
|
20
|
+
private circuitResetTimeoutId;
|
|
21
|
+
private readonly eventFingerprints;
|
|
22
|
+
private readonly PERSISTENCE_KEY;
|
|
23
|
+
constructor(storeManager: StorageManager, googleAnalytics?: GoogleAnalyticsIntegration | null);
|
|
24
|
+
track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, session_end_reason, session_start_recovered, }: Partial<EventData>): void;
|
|
25
|
+
stop(): void;
|
|
26
|
+
private processAndSend;
|
|
27
|
+
private trackGoogleAnalyticsEvent;
|
|
28
|
+
private initEventsQueueInterval;
|
|
29
|
+
flushImmediately(): Promise<boolean>;
|
|
30
|
+
flushImmediatelySync(): boolean;
|
|
31
|
+
getQueueLength(): number;
|
|
32
|
+
private sendEventsQueue;
|
|
33
|
+
private buildEventsPayload;
|
|
34
|
+
private clearQueueInterval;
|
|
35
|
+
private getEventFingerprint;
|
|
36
|
+
private isDuplicatedEvent;
|
|
37
|
+
/**
|
|
38
|
+
* Cleans up old fingerprints to prevent memory leaks
|
|
39
|
+
*/
|
|
40
|
+
private cleanupOldFingerprints;
|
|
41
|
+
/**
|
|
42
|
+
* Opens the circuit breaker with time-based recovery and event persistence
|
|
43
|
+
*/
|
|
44
|
+
private openCircuitBreaker;
|
|
45
|
+
/**
|
|
46
|
+
* Resets the circuit breaker and attempts to restore persisted events
|
|
47
|
+
*/
|
|
48
|
+
private resetCircuitBreaker;
|
|
49
|
+
/**
|
|
50
|
+
* Persists current events queue to localStorage for recovery
|
|
51
|
+
*/
|
|
52
|
+
private persistEventsToStorage;
|
|
53
|
+
/**
|
|
54
|
+
* Restores events from localStorage if available and not expired
|
|
55
|
+
*/
|
|
56
|
+
private restoreEventsFromStorage;
|
|
57
|
+
/**
|
|
58
|
+
* Clears persisted events from localStorage
|
|
59
|
+
*/
|
|
60
|
+
private clearPersistedEvents;
|
|
61
|
+
}
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { EVENT_SENT_INTERVAL_MS, EVENT_SENT_INTERVAL_TEST_MS, MAX_EVENTS_QUEUE_LENGTH, DUPLICATE_EVENT_THRESHOLD_MS, CIRCUIT_BREAKER_CONSTANTS, MAX_FINGERPRINTS, FINGERPRINT_CLEANUP_MULTIPLIER, CLICK_COORDINATE_PRECISION, EVENT_PERSISTENCE_MAX_AGE_MS, } from '../constants';
|
|
2
|
+
import { EventType } from '../types';
|
|
3
|
+
import { getUTMParameters, isUrlPathExcluded } from '../utils';
|
|
4
|
+
import { debugLog } from '../utils/logging';
|
|
5
|
+
import { SenderManager } from './sender.manager';
|
|
6
|
+
import { SamplingManager } from './sampling.manager';
|
|
7
|
+
import { StateManager } from './state.manager';
|
|
8
|
+
import { TagsManager } from './tags.manager';
|
|
9
|
+
export class EventManager extends StateManager {
|
|
10
|
+
constructor(storeManager, googleAnalytics = null) {
|
|
11
|
+
super();
|
|
12
|
+
this.eventsQueue = [];
|
|
13
|
+
this.lastEvent = null;
|
|
14
|
+
this.eventsQueueIntervalId = null;
|
|
15
|
+
this.intervalActive = false;
|
|
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;
|
|
28
|
+
this.googleAnalytics = googleAnalytics;
|
|
29
|
+
this.samplingManager = new SamplingManager();
|
|
30
|
+
this.tagsManager = new TagsManager();
|
|
31
|
+
this.dataSender = new SenderManager(storeManager);
|
|
32
|
+
// Restore any persisted events on initialization
|
|
33
|
+
this.restoreEventsFromStorage();
|
|
34
|
+
debugLog.debug('EventManager', 'EventManager initialized', {
|
|
35
|
+
hasGoogleAnalytics: !!googleAnalytics,
|
|
36
|
+
restoredEventsCount: this.eventsQueue.length,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, session_end_reason, session_start_recovered, }) {
|
|
40
|
+
debugLog.info('EventManager', `📥 Event captured: ${type}`, {
|
|
41
|
+
type,
|
|
42
|
+
page_url,
|
|
43
|
+
hasCustomEvent: !!custom_event,
|
|
44
|
+
hasClickData: !!click_data,
|
|
45
|
+
hasScrollData: !!scroll_data,
|
|
46
|
+
hasWebVitals: !!web_vitals,
|
|
47
|
+
});
|
|
48
|
+
if (!this.samplingManager.shouldSampleEvent(type, web_vitals)) {
|
|
49
|
+
debugLog.debug('EventManager', 'Event filtered by sampling', { type, samplingActive: true });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const isDuplicatedEvent = this.isDuplicatedEvent({
|
|
53
|
+
type,
|
|
54
|
+
page_url,
|
|
55
|
+
scroll_data,
|
|
56
|
+
click_data,
|
|
57
|
+
custom_event,
|
|
58
|
+
web_vitals,
|
|
59
|
+
session_end_reason,
|
|
60
|
+
session_start_recovered,
|
|
61
|
+
});
|
|
62
|
+
if (isDuplicatedEvent) {
|
|
63
|
+
const now = Date.now();
|
|
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
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const effectivePageUrl = page_url || this.get('pageUrl');
|
|
80
|
+
const isRouteExcluded = isUrlPathExcluded(effectivePageUrl, this.get('config').excludedUrlPaths);
|
|
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
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const isSessionStartEvent = type === EventType.SESSION_START;
|
|
90
|
+
if (isSessionStartEvent) {
|
|
91
|
+
this.set('hasStartSession', true);
|
|
92
|
+
}
|
|
93
|
+
const utmParams = isSessionStartEvent ? getUTMParameters() : undefined;
|
|
94
|
+
const payload = {
|
|
95
|
+
type: type,
|
|
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
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.lastEvent = payload;
|
|
121
|
+
this.processAndSend(payload);
|
|
122
|
+
}
|
|
123
|
+
stop() {
|
|
124
|
+
// Clear interval and reset interval state
|
|
125
|
+
if (this.eventsQueueIntervalId) {
|
|
126
|
+
clearInterval(this.eventsQueueIntervalId);
|
|
127
|
+
this.eventsQueueIntervalId = null;
|
|
128
|
+
this.intervalActive = false;
|
|
129
|
+
}
|
|
130
|
+
// Clean up circuit breaker timeout
|
|
131
|
+
if (this.circuitResetTimeoutId) {
|
|
132
|
+
clearTimeout(this.circuitResetTimeoutId);
|
|
133
|
+
this.circuitResetTimeoutId = null;
|
|
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
|
|
147
|
+
this.dataSender.stop();
|
|
148
|
+
}
|
|
149
|
+
processAndSend(payload) {
|
|
150
|
+
debugLog.info('EventManager', `🔄 Event processed and queued: ${payload.type}`, {
|
|
151
|
+
type: payload.type,
|
|
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
|
+
}
|
|
200
|
+
async flushImmediately() {
|
|
201
|
+
if (this.eventsQueue.length === 0) {
|
|
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;
|
|
211
|
+
}
|
|
212
|
+
flushImmediatelySync() {
|
|
213
|
+
if (this.eventsQueue.length === 0) {
|
|
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;
|
|
223
|
+
}
|
|
224
|
+
getQueueLength() {
|
|
225
|
+
return this.eventsQueue.length;
|
|
226
|
+
}
|
|
227
|
+
sendEventsQueue() {
|
|
228
|
+
if (this.eventsQueue.length === 0) {
|
|
229
|
+
return;
|
|
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
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (!this.get('sessionId')) {
|
|
257
|
+
debugLog.info('EventManager', `⏳ Queue waiting: ${this.eventsQueue.length} events waiting for active session`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const body = this.buildEventsPayload();
|
|
261
|
+
const success = this.dataSender.sendEventsQueue(body);
|
|
262
|
+
if (success) {
|
|
263
|
+
debugLog.info('EventManager', `✅ Event queue sent successfully`, {
|
|
264
|
+
eventsCount: body.events.length,
|
|
265
|
+
sessionId: body.session_id,
|
|
266
|
+
uniqueEventsAfterDedup: body.events.length,
|
|
267
|
+
});
|
|
268
|
+
this.eventsQueue = [];
|
|
269
|
+
this.failureCount = 0;
|
|
270
|
+
this.backoffDelay = CIRCUIT_BREAKER_CONSTANTS.INITIAL_BACKOFF_DELAY_MS;
|
|
271
|
+
// Clear any persisted events on successful send
|
|
272
|
+
this.clearPersistedEvents();
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
debugLog.info('EventManager', `❌ Failed to send event queue`, {
|
|
276
|
+
eventsCount: body.events.length,
|
|
277
|
+
failureCount: this.failureCount + 1,
|
|
278
|
+
willOpenCircuit: this.failureCount + 1 >= this.MAX_FAILURES,
|
|
279
|
+
});
|
|
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
|
+
}
|
|
288
|
+
}
|
|
289
|
+
buildEventsPayload() {
|
|
290
|
+
const uniqueEvents = new Map();
|
|
291
|
+
for (const event of this.eventsQueue) {
|
|
292
|
+
let key = `${event.type}_${event.page_url}`;
|
|
293
|
+
if (event.click_data) {
|
|
294
|
+
key += `_${event.click_data.x}_${event.click_data.y}`;
|
|
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);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const deduplicatedEvents = [...uniqueEvents.values()];
|
|
310
|
+
deduplicatedEvents.sort((a, b) => a.timestamp - b.timestamp);
|
|
311
|
+
return {
|
|
312
|
+
user_id: this.get('userId'),
|
|
313
|
+
session_id: this.get('sessionId'),
|
|
314
|
+
device: this.get('device'),
|
|
315
|
+
events: deduplicatedEvents,
|
|
316
|
+
...(this.get('config')?.globalMetadata && { global_metadata: this.get('config')?.globalMetadata }),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
clearQueueInterval() {
|
|
320
|
+
if (this.eventsQueueIntervalId) {
|
|
321
|
+
clearInterval(this.eventsQueueIntervalId);
|
|
322
|
+
this.eventsQueueIntervalId = null;
|
|
323
|
+
this.intervalActive = false;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
getEventFingerprint(event) {
|
|
327
|
+
const key = `${event.type}_${event.page_url}`;
|
|
328
|
+
if (event.click_data) {
|
|
329
|
+
// Round coordinates to reduce false positives
|
|
330
|
+
const x = Math.round((event.click_data.x || 0) / CLICK_COORDINATE_PRECISION) * CLICK_COORDINATE_PRECISION;
|
|
331
|
+
const y = Math.round((event.click_data.y || 0) / CLICK_COORDINATE_PRECISION) * CLICK_COORDINATE_PRECISION;
|
|
332
|
+
return `${key}_${x}_${y}_${event.click_data.tag}_${event.click_data.id}`;
|
|
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}`;
|
|
348
|
+
}
|
|
349
|
+
return key;
|
|
350
|
+
}
|
|
351
|
+
isDuplicatedEvent(event) {
|
|
352
|
+
const fingerprint = this.getEventFingerprint(event);
|
|
353
|
+
const lastTime = this.eventFingerprints.get(fingerprint) ?? 0;
|
|
354
|
+
const now = Date.now();
|
|
355
|
+
if (now - lastTime < DUPLICATE_EVENT_THRESHOLD_MS) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
this.eventFingerprints.set(fingerprint, now);
|
|
359
|
+
// Clean up old fingerprints to prevent memory leaks
|
|
360
|
+
this.cleanupOldFingerprints();
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Cleans up old fingerprints to prevent memory leaks
|
|
365
|
+
*/
|
|
366
|
+
cleanupOldFingerprints() {
|
|
367
|
+
if (this.eventFingerprints.size <= MAX_FINGERPRINTS) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
const cleanupThreshold = DUPLICATE_EVENT_THRESHOLD_MS * FINGERPRINT_CLEANUP_MULTIPLIER;
|
|
372
|
+
const keysToDelete = [];
|
|
373
|
+
for (const [key, timestamp] of this.eventFingerprints) {
|
|
374
|
+
if (now - timestamp > cleanupThreshold) {
|
|
375
|
+
keysToDelete.push(key);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Delete old entries
|
|
379
|
+
for (const key of keysToDelete) {
|
|
380
|
+
this.eventFingerprints.delete(key);
|
|
381
|
+
}
|
|
382
|
+
debugLog.debug('EventManager', 'Cleaned up old event fingerprints', {
|
|
383
|
+
totalFingerprints: this.eventFingerprints.size + keysToDelete.length,
|
|
384
|
+
cleanedCount: keysToDelete.length,
|
|
385
|
+
remainingCount: this.eventFingerprints.size,
|
|
386
|
+
cleanupThreshold,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Opens the circuit breaker with time-based recovery and event persistence
|
|
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);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Resets the circuit breaker and attempts to restore persisted events
|
|
411
|
+
*/
|
|
412
|
+
resetCircuitBreaker() {
|
|
413
|
+
this.circuitOpen = false;
|
|
414
|
+
this.circuitOpenTime = 0;
|
|
415
|
+
this.failureCount = 0;
|
|
416
|
+
this.circuitResetTimeoutId = null;
|
|
417
|
+
debugLog.info('EventManager', 'Circuit breaker reset - attempting to restore events', {
|
|
418
|
+
currentQueueLength: this.eventsQueue.length,
|
|
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,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
debugLog.warn('EventManager', 'Failed to persist events to storage', {
|
|
448
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
449
|
+
eventsCount: this.eventsQueue.length,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Restores events from localStorage if available and not expired
|
|
455
|
+
*/
|
|
456
|
+
restoreEventsFromStorage() {
|
|
457
|
+
try {
|
|
458
|
+
const persistedData = this.storageManager.getItem(this.PERSISTENCE_KEY);
|
|
459
|
+
if (!persistedData) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const parsed = JSON.parse(persistedData);
|
|
463
|
+
const now = Date.now();
|
|
464
|
+
const maxAge = EVENT_PERSISTENCE_MAX_AGE_MS;
|
|
465
|
+
// Check if persisted data is not too old
|
|
466
|
+
if (now - parsed.timestamp > maxAge) {
|
|
467
|
+
this.clearPersistedEvents();
|
|
468
|
+
debugLog.debug('EventManager', 'Cleared expired persisted events', {
|
|
469
|
+
age: now - parsed.timestamp,
|
|
470
|
+
maxAge,
|
|
471
|
+
});
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
// Restore events if we don't already have events in queue
|
|
475
|
+
if (Array.isArray(parsed.events) && parsed.events.length > 0 && this.eventsQueue.length === 0) {
|
|
476
|
+
this.eventsQueue = parsed.events;
|
|
477
|
+
debugLog.info('EventManager', 'Restored events from storage', {
|
|
478
|
+
restoredCount: parsed.events.length,
|
|
479
|
+
originalFailureCount: parsed.failureCount ?? 0,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
debugLog.warn('EventManager', 'Failed to restore events from storage', {
|
|
485
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
486
|
+
});
|
|
487
|
+
this.clearPersistedEvents();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Clears persisted events from localStorage
|
|
492
|
+
*/
|
|
493
|
+
clearPersistedEvents() {
|
|
494
|
+
try {
|
|
495
|
+
this.storageManager.removeItem(this.PERSISTENCE_KEY);
|
|
496
|
+
debugLog.debug('EventManager', 'Cleared persisted events from storage');
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
debugLog.warn('EventManager', 'Failed to clear persisted events', {
|
|
500
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { EventType, WebVitalsData } from '../types';
|
|
2
|
+
import { StateManager } from './state.manager';
|
|
3
|
+
export declare class SamplingManager extends StateManager {
|
|
4
|
+
shouldSampleEvent(type: EventType, webVitals?: WebVitalsData): boolean;
|
|
5
|
+
private isSampledIn;
|
|
6
|
+
private isWebVitalEventSampledIn;
|
|
7
|
+
private getHash;
|
|
8
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DEFAULT_SAMPLING_RATE, WEB_VITALS_SAMPLING, WEB_VITALS_LONG_TASK_SAMPLING } from '../constants';
|
|
2
|
+
import { EventType } from '../types';
|
|
3
|
+
import { StateManager } from './state.manager';
|
|
4
|
+
export class SamplingManager extends StateManager {
|
|
5
|
+
shouldSampleEvent(type, webVitals) {
|
|
6
|
+
const isQaMode = this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug';
|
|
7
|
+
if (isQaMode) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (type === EventType.WEB_VITALS) {
|
|
11
|
+
return this.isWebVitalEventSampledIn(webVitals?.type);
|
|
12
|
+
}
|
|
13
|
+
return this.isSampledIn();
|
|
14
|
+
}
|
|
15
|
+
isSampledIn() {
|
|
16
|
+
const samplingRate = this.get('config').samplingRate ?? DEFAULT_SAMPLING_RATE;
|
|
17
|
+
if (samplingRate >= 1.0) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (samplingRate <= 0) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const userHash = this.getHash(this.get('userId'));
|
|
24
|
+
const userValue = (userHash % 100) / 100;
|
|
25
|
+
const isSampled = userValue < samplingRate;
|
|
26
|
+
return isSampled;
|
|
27
|
+
}
|
|
28
|
+
isWebVitalEventSampledIn(type) {
|
|
29
|
+
const isLongTask = type === 'LONG_TASK';
|
|
30
|
+
const rate = isLongTask ? WEB_VITALS_LONG_TASK_SAMPLING : WEB_VITALS_SAMPLING;
|
|
31
|
+
if (rate >= 1)
|
|
32
|
+
return true;
|
|
33
|
+
if (rate <= 0)
|
|
34
|
+
return false;
|
|
35
|
+
const seed = `${this.get('userId')}|${isLongTask ? 'long_task' : 'web_vitals'}`;
|
|
36
|
+
const hash = this.getHash(seed);
|
|
37
|
+
const value = (hash % 100) / 100;
|
|
38
|
+
return value < rate;
|
|
39
|
+
}
|
|
40
|
+
getHash(input) {
|
|
41
|
+
let hash = 0;
|
|
42
|
+
for (let i = 0; i < input.length; i++) {
|
|
43
|
+
const char = input.charCodeAt(i);
|
|
44
|
+
hash = (hash << 5) - hash + char;
|
|
45
|
+
hash |= 0;
|
|
46
|
+
}
|
|
47
|
+
return Math.abs(hash);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BaseEventsQueueDto } from '../types';
|
|
2
|
+
import { StorageManager } from './storage.manager';
|
|
3
|
+
import { StateManager } from './state.manager';
|
|
4
|
+
export declare class SenderManager extends StateManager {
|
|
5
|
+
private readonly storeManager;
|
|
6
|
+
private readonly queueStorageKey;
|
|
7
|
+
private retryDelay;
|
|
8
|
+
private retryTimeoutId;
|
|
9
|
+
private lastAsyncSend;
|
|
10
|
+
private lastSyncSend;
|
|
11
|
+
constructor(storeManager: StorageManager);
|
|
12
|
+
sendEventsQueueAsync(body: BaseEventsQueueDto): Promise<boolean>;
|
|
13
|
+
sendEventsQueueSync(body: BaseEventsQueueDto): boolean;
|
|
14
|
+
sendEventsQueue(body: BaseEventsQueueDto): boolean;
|
|
15
|
+
recoverPersistedEvents(): void;
|
|
16
|
+
stop(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Sends recovered events without re-deduplication since they were already processed
|
|
19
|
+
*/
|
|
20
|
+
private sendRecoveredEvents;
|
|
21
|
+
/**
|
|
22
|
+
* Schedules retry for recovered events using the specific recovery method
|
|
23
|
+
*/
|
|
24
|
+
private scheduleRetryForRecoveredEvents;
|
|
25
|
+
private canSendAsync;
|
|
26
|
+
private canSendSync;
|
|
27
|
+
private sendQueueAsync;
|
|
28
|
+
private sendQueueSync;
|
|
29
|
+
private sendQueue;
|
|
30
|
+
private sendSyncXHR;
|
|
31
|
+
private prepareRequest;
|
|
32
|
+
private getPersistedData;
|
|
33
|
+
private isDataRecent;
|
|
34
|
+
private createRecoveryBody;
|
|
35
|
+
private logQueue;
|
|
36
|
+
private handleSendFailure;
|
|
37
|
+
private persistFailedEvents;
|
|
38
|
+
private clearPersistedEvents;
|
|
39
|
+
private resetRetryState;
|
|
40
|
+
private scheduleRetry;
|
|
41
|
+
private executeSend;
|
|
42
|
+
private executeSendSync;
|
|
43
|
+
private shouldSkipSend;
|
|
44
|
+
private isSendBeaconAvailable;
|
|
45
|
+
private clearRetryTimeout;
|
|
46
|
+
}
|