@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,300 @@
|
|
|
1
|
+
import { QUEUE_KEY, RETRY_BACKOFF_INITIAL, RETRY_BACKOFF_MAX, RATE_LIMIT_INTERVAL, EVENT_EXPIRY_HOURS, SYNC_XHR_TIMEOUT_MS, API_BASE_URL, } from '../constants';
|
|
2
|
+
import { SpecialProjectId, Mode } from '../types';
|
|
3
|
+
import { debugLog } from '../utils/logging';
|
|
4
|
+
import { StateManager } from './state.manager';
|
|
5
|
+
export class SenderManager extends StateManager {
|
|
6
|
+
constructor(storeManager) {
|
|
7
|
+
super();
|
|
8
|
+
this.retryDelay = RETRY_BACKOFF_INITIAL;
|
|
9
|
+
this.retryTimeoutId = null;
|
|
10
|
+
this.lastAsyncSend = 0;
|
|
11
|
+
this.lastSyncSend = 0;
|
|
12
|
+
this.storeManager = storeManager;
|
|
13
|
+
this.queueStorageKey = `${QUEUE_KEY(this.get('config')?.id)}:${this.get('userId')}`;
|
|
14
|
+
this.recoverPersistedEvents();
|
|
15
|
+
}
|
|
16
|
+
async sendEventsQueueAsync(body) {
|
|
17
|
+
return this.executeSend(body, () => this.sendQueueAsync(body));
|
|
18
|
+
}
|
|
19
|
+
sendEventsQueueSync(body) {
|
|
20
|
+
return this.executeSendSync(body, () => this.sendQueueSync(body));
|
|
21
|
+
}
|
|
22
|
+
sendEventsQueue(body) {
|
|
23
|
+
return this.executeSendSync(body, () => this.sendQueue(body));
|
|
24
|
+
}
|
|
25
|
+
recoverPersistedEvents() {
|
|
26
|
+
try {
|
|
27
|
+
const persistedData = this.getPersistedData();
|
|
28
|
+
if (!persistedData || !this.isDataRecent(persistedData) || persistedData.events.length === 0) {
|
|
29
|
+
this.clearPersistedEvents();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const recoveryBody = this.createRecoveryBody(persistedData);
|
|
33
|
+
const success = this.sendRecoveredEvents(recoveryBody);
|
|
34
|
+
if (success) {
|
|
35
|
+
debugLog.info('SenderManager', 'Persisted events recovered successfully', {
|
|
36
|
+
eventsCount: persistedData.events.length,
|
|
37
|
+
sessionId: persistedData.sessionId,
|
|
38
|
+
});
|
|
39
|
+
this.clearPersistedEvents();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
debugLog.warn('SenderManager', 'Failed to recover persisted events, scheduling retry', {
|
|
43
|
+
eventsCount: persistedData.events.length,
|
|
44
|
+
});
|
|
45
|
+
this.scheduleRetryForRecoveredEvents(recoveryBody);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
debugLog.error('SenderManager', 'Failed to recover persisted events', { error });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
stop() {
|
|
53
|
+
this.clearRetryTimeout();
|
|
54
|
+
this.resetRetryState();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Sends recovered events without re-deduplication since they were already processed
|
|
58
|
+
*/
|
|
59
|
+
sendRecoveredEvents(body) {
|
|
60
|
+
return this.executeSendSync(body, () => this.sendQueue(body));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Schedules retry for recovered events using the specific recovery method
|
|
64
|
+
*/
|
|
65
|
+
scheduleRetryForRecoveredEvents(body) {
|
|
66
|
+
if (this.retryTimeoutId !== null) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.retryTimeoutId = window.setTimeout(() => {
|
|
70
|
+
this.retryTimeoutId = null;
|
|
71
|
+
this.sendRecoveredEvents(body);
|
|
72
|
+
}, this.retryDelay);
|
|
73
|
+
this.retryDelay = Math.min(this.retryDelay * 2, RETRY_BACKOFF_MAX);
|
|
74
|
+
}
|
|
75
|
+
canSendAsync() {
|
|
76
|
+
return Date.now() - this.lastAsyncSend >= RATE_LIMIT_INTERVAL;
|
|
77
|
+
}
|
|
78
|
+
canSendSync() {
|
|
79
|
+
return Date.now() - this.lastSyncSend >= RATE_LIMIT_INTERVAL;
|
|
80
|
+
}
|
|
81
|
+
async sendQueueAsync(body) {
|
|
82
|
+
const { url, payload } = this.prepareRequest(body);
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(url, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
},
|
|
89
|
+
body: payload,
|
|
90
|
+
});
|
|
91
|
+
return response.ok;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
debugLog.error('SenderManager', 'Failed to send events async', { error });
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
sendQueueSync(body) {
|
|
99
|
+
const { url, payload } = this.prepareRequest(body);
|
|
100
|
+
if (this.isSendBeaconAvailable() && navigator.sendBeacon(url, payload)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return this.sendSyncXHR(url, payload);
|
|
104
|
+
}
|
|
105
|
+
sendQueue(body) {
|
|
106
|
+
if (!this.isSendBeaconAvailable()) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const { url, payload } = this.prepareRequest(body);
|
|
110
|
+
return navigator.sendBeacon(url, payload);
|
|
111
|
+
}
|
|
112
|
+
sendSyncXHR(url, payload) {
|
|
113
|
+
try {
|
|
114
|
+
const xhr = new XMLHttpRequest();
|
|
115
|
+
xhr.open('POST', url, false);
|
|
116
|
+
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
117
|
+
xhr.timeout = SYNC_XHR_TIMEOUT_MS;
|
|
118
|
+
xhr.send(payload);
|
|
119
|
+
return xhr.status >= 200 && xhr.status < 300;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
debugLog.error('SenderManager', 'Sync XHR failed', { error });
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
prepareRequest(body) {
|
|
127
|
+
const useLocalServer = this.get('config').id === SpecialProjectId.HttpLocal;
|
|
128
|
+
const baseUrl = useLocalServer ? window.location.origin : (this.get('apiUrl') ?? API_BASE_URL);
|
|
129
|
+
return {
|
|
130
|
+
url: `${baseUrl}/events`,
|
|
131
|
+
payload: JSON.stringify(body),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
getPersistedData() {
|
|
135
|
+
const persistedDataString = this.storeManager.getItem(this.queueStorageKey);
|
|
136
|
+
return persistedDataString ? JSON.parse(persistedDataString) : null;
|
|
137
|
+
}
|
|
138
|
+
isDataRecent(data) {
|
|
139
|
+
const ageInHours = (Date.now() - data.timestamp) / (1000 * 60 * 60);
|
|
140
|
+
return ageInHours < EVENT_EXPIRY_HOURS;
|
|
141
|
+
}
|
|
142
|
+
createRecoveryBody(data) {
|
|
143
|
+
return {
|
|
144
|
+
user_id: data.userId,
|
|
145
|
+
session_id: data.sessionId,
|
|
146
|
+
device: data.device,
|
|
147
|
+
events: data.events,
|
|
148
|
+
...(data.global_metadata && { global_metadata: data.global_metadata }),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
logQueue(queue) {
|
|
152
|
+
debugLog.info('SenderManager', ` ⏩ Queue snapshot`, queue);
|
|
153
|
+
}
|
|
154
|
+
handleSendFailure(body) {
|
|
155
|
+
this.persistFailedEvents(body);
|
|
156
|
+
this.scheduleRetry(body);
|
|
157
|
+
}
|
|
158
|
+
persistFailedEvents(body) {
|
|
159
|
+
try {
|
|
160
|
+
const persistedData = {
|
|
161
|
+
userId: body.user_id,
|
|
162
|
+
sessionId: body.session_id,
|
|
163
|
+
device: body.device,
|
|
164
|
+
events: body.events,
|
|
165
|
+
timestamp: Date.now(),
|
|
166
|
+
...(body.global_metadata && { global_metadata: body.global_metadata }),
|
|
167
|
+
};
|
|
168
|
+
this.storeManager.setItem(this.queueStorageKey, JSON.stringify(persistedData));
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
debugLog.error('SenderManager', 'Failed to persist events', { error });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
clearPersistedEvents() {
|
|
175
|
+
this.storeManager.removeItem(this.queueStorageKey);
|
|
176
|
+
}
|
|
177
|
+
resetRetryState() {
|
|
178
|
+
this.retryDelay = RETRY_BACKOFF_INITIAL;
|
|
179
|
+
this.clearRetryTimeout();
|
|
180
|
+
}
|
|
181
|
+
scheduleRetry(body) {
|
|
182
|
+
if (this.retryTimeoutId !== null) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.retryTimeoutId = window.setTimeout(() => {
|
|
186
|
+
this.retryTimeoutId = null;
|
|
187
|
+
this.sendEventsQueue(body);
|
|
188
|
+
}, this.retryDelay);
|
|
189
|
+
this.retryDelay = Math.min(this.retryDelay * 2, RETRY_BACKOFF_MAX);
|
|
190
|
+
}
|
|
191
|
+
async executeSend(body, sendFn) {
|
|
192
|
+
if (this.shouldSkipSend()) {
|
|
193
|
+
this.logQueue(body);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
if (!this.canSendAsync()) {
|
|
197
|
+
debugLog.info('SenderManager', `⏱️ Rate limited - skipping async send`, {
|
|
198
|
+
eventsCount: body.events.length,
|
|
199
|
+
timeSinceLastSend: Date.now() - this.lastAsyncSend,
|
|
200
|
+
});
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
debugLog.info('SenderManager', `🌐 Sending events to server (async)`, {
|
|
204
|
+
eventsCount: body.events.length,
|
|
205
|
+
sessionId: body.session_id,
|
|
206
|
+
userId: body.user_id,
|
|
207
|
+
});
|
|
208
|
+
this.lastAsyncSend = Date.now();
|
|
209
|
+
try {
|
|
210
|
+
const success = await sendFn();
|
|
211
|
+
if (success) {
|
|
212
|
+
debugLog.info('SenderManager', `✅ Successfully sent events to server`, {
|
|
213
|
+
eventsCount: body.events.length,
|
|
214
|
+
method: 'async',
|
|
215
|
+
});
|
|
216
|
+
this.resetRetryState();
|
|
217
|
+
this.clearPersistedEvents();
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
debugLog.warn('SenderManager', 'Failed to send events', {
|
|
221
|
+
eventsCount: body.events.length,
|
|
222
|
+
method: 'async',
|
|
223
|
+
});
|
|
224
|
+
this.handleSendFailure(body);
|
|
225
|
+
}
|
|
226
|
+
return success;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
this.handleSendFailure(body);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
executeSendSync(body, sendFn) {
|
|
234
|
+
if (this.shouldSkipSend()) {
|
|
235
|
+
this.logQueue(body);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
if (!this.canSendSync()) {
|
|
239
|
+
debugLog.info('SenderManager', `⏱️ Rate limited - skipping sync send`, {
|
|
240
|
+
eventsCount: body.events.length,
|
|
241
|
+
timeSinceLastSend: Date.now() - this.lastSyncSend,
|
|
242
|
+
});
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
debugLog.info('SenderManager', `🌐 Sending events to server (sync)`, {
|
|
246
|
+
eventsCount: body.events.length,
|
|
247
|
+
sessionId: body.session_id,
|
|
248
|
+
userId: body.user_id,
|
|
249
|
+
method: 'sendBeacon/XHR',
|
|
250
|
+
});
|
|
251
|
+
this.lastSyncSend = Date.now();
|
|
252
|
+
try {
|
|
253
|
+
const success = sendFn();
|
|
254
|
+
if (success) {
|
|
255
|
+
debugLog.info('SenderManager', `✅ Successfully sent events to server`, {
|
|
256
|
+
eventsCount: body.events.length,
|
|
257
|
+
method: 'sync',
|
|
258
|
+
});
|
|
259
|
+
this.resetRetryState();
|
|
260
|
+
this.clearPersistedEvents();
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
debugLog.warn('SenderManager', 'Failed to send events', {
|
|
264
|
+
eventsCount: body.events.length,
|
|
265
|
+
method: 'sync',
|
|
266
|
+
});
|
|
267
|
+
this.handleSendFailure(body);
|
|
268
|
+
}
|
|
269
|
+
return success;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
debugLog.info('SenderManager', `💥 Exception during event sending`, {
|
|
273
|
+
eventsCount: body.events.length,
|
|
274
|
+
method: 'sync',
|
|
275
|
+
});
|
|
276
|
+
this.handleSendFailure(body);
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
shouldSkipSend() {
|
|
281
|
+
const { id, mode } = this.get('config');
|
|
282
|
+
const specialModes = [Mode.QA, Mode.DEBUG];
|
|
283
|
+
if (id === SpecialProjectId.HttpSkip) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
return !!mode && specialModes.includes(mode) && id !== SpecialProjectId.HttpLocal;
|
|
287
|
+
}
|
|
288
|
+
isSendBeaconAvailable() {
|
|
289
|
+
if (typeof navigator.sendBeacon !== 'function') {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
clearRetryTimeout() {
|
|
295
|
+
if (this.retryTimeoutId !== null) {
|
|
296
|
+
clearTimeout(this.retryTimeoutId);
|
|
297
|
+
this.retryTimeoutId = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SessionRecoveryConfig, SessionContext } from '../types/session.types';
|
|
2
|
+
import { StateManager } from './state.manager';
|
|
3
|
+
import { StorageManager } from './storage.manager';
|
|
4
|
+
import { EventManager } from './event.manager';
|
|
5
|
+
export declare class SessionRecoveryManager extends StateManager {
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly storageManager;
|
|
8
|
+
private readonly eventManager;
|
|
9
|
+
private readonly projectId;
|
|
10
|
+
private readonly debugMode;
|
|
11
|
+
constructor(storageManager: StorageManager, projectId: string, eventManager?: EventManager, config?: Partial<SessionRecoveryConfig>);
|
|
12
|
+
/**
|
|
13
|
+
* Attempt to recover a session
|
|
14
|
+
*/
|
|
15
|
+
attemptSessionRecovery(currentSessionId?: string): {
|
|
16
|
+
recovered: boolean;
|
|
17
|
+
recoveredSessionId?: string;
|
|
18
|
+
context?: SessionContext;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Calculate the recovery window with bounds checking
|
|
22
|
+
*/
|
|
23
|
+
private calculateRecoveryWindow;
|
|
24
|
+
/**
|
|
25
|
+
* Check if session recovery can be attempted
|
|
26
|
+
*/
|
|
27
|
+
private canAttemptRecovery;
|
|
28
|
+
/**
|
|
29
|
+
* Store session context for potential recovery
|
|
30
|
+
*/
|
|
31
|
+
storeSessionContextForRecovery(sessionContext: SessionContext): void;
|
|
32
|
+
/**
|
|
33
|
+
* Get stored recovery attempts
|
|
34
|
+
*/
|
|
35
|
+
private getStoredRecoveryAttempts;
|
|
36
|
+
/**
|
|
37
|
+
* Store recovery attempts
|
|
38
|
+
*/
|
|
39
|
+
private storeRecoveryAttempts;
|
|
40
|
+
/**
|
|
41
|
+
* Get the last recovery attempt
|
|
42
|
+
*/
|
|
43
|
+
private getLastRecoveryAttempt;
|
|
44
|
+
/**
|
|
45
|
+
* Clean up old recovery attempts
|
|
46
|
+
*/
|
|
47
|
+
cleanupOldRecoveryAttempts(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Check if there's a recoverable session.
|
|
50
|
+
* Returns false when no recovery attempts are stored.
|
|
51
|
+
*/
|
|
52
|
+
hasRecoverableSession(): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Get recovery window in milliseconds
|
|
55
|
+
*/
|
|
56
|
+
getRecoveryWindowMs(): number;
|
|
57
|
+
/**
|
|
58
|
+
* Get max recovery attempts
|
|
59
|
+
*/
|
|
60
|
+
getMaxRecoveryAttempts(): number;
|
|
61
|
+
/**
|
|
62
|
+
* Clear all stored recovery data
|
|
63
|
+
*/
|
|
64
|
+
clearRecoveryData(): void;
|
|
65
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { DEFAULT_SESSION_TIMEOUT_MS, SESSION_RECOVERY_WINDOW_MULTIPLIER, MAX_SESSION_RECOVERY_ATTEMPTS, MAX_SESSION_RECOVERY_WINDOW_MS, MIN_SESSION_RECOVERY_WINDOW_MS, } from '../constants';
|
|
2
|
+
import { SESSION_RECOVERY_KEY } from '../constants/storage.constants';
|
|
3
|
+
import { debugLog } from '../utils/logging';
|
|
4
|
+
import { StateManager } from './state.manager';
|
|
5
|
+
export class SessionRecoveryManager extends StateManager {
|
|
6
|
+
constructor(storageManager, projectId, eventManager, config) {
|
|
7
|
+
super();
|
|
8
|
+
this.storageManager = storageManager;
|
|
9
|
+
this.eventManager = eventManager ?? null;
|
|
10
|
+
this.projectId = projectId;
|
|
11
|
+
this.debugMode = (this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug') ?? false;
|
|
12
|
+
this.config = {
|
|
13
|
+
recoveryWindowMs: this.calculateRecoveryWindow(),
|
|
14
|
+
maxRecoveryAttempts: MAX_SESSION_RECOVERY_ATTEMPTS,
|
|
15
|
+
contextPreservation: true,
|
|
16
|
+
...config,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Attempt to recover a session
|
|
21
|
+
*/
|
|
22
|
+
attemptSessionRecovery(currentSessionId) {
|
|
23
|
+
if (this.debugMode) {
|
|
24
|
+
debugLog.debug('SessionRecovery', 'Attempting session recovery');
|
|
25
|
+
}
|
|
26
|
+
// Get stored recovery attempts
|
|
27
|
+
const recoveryAttempts = this.getStoredRecoveryAttempts();
|
|
28
|
+
const lastAttempt = this.getLastRecoveryAttempt();
|
|
29
|
+
// Check if we can recover
|
|
30
|
+
if (!this.canAttemptRecovery(lastAttempt)) {
|
|
31
|
+
if (this.debugMode) {
|
|
32
|
+
debugLog.debug('SessionRecovery', 'Session recovery not possible - outside recovery window or max attempts reached');
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
recovered: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Get the last session context
|
|
39
|
+
const lastSessionContext = lastAttempt?.context;
|
|
40
|
+
if (!lastSessionContext) {
|
|
41
|
+
if (this.debugMode) {
|
|
42
|
+
debugLog.debug('SessionRecovery', 'No session context available for recovery');
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
recovered: false,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Check if recovery is possible
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const timeSinceLastActivity = now - lastSessionContext.lastActivity;
|
|
51
|
+
if (timeSinceLastActivity > this.config.recoveryWindowMs) {
|
|
52
|
+
if (this.debugMode) {
|
|
53
|
+
debugLog.debug('SessionRecovery', 'Session recovery failed - outside recovery window');
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
recovered: false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Perform recovery
|
|
60
|
+
const recoveredSessionId = lastSessionContext.sessionId;
|
|
61
|
+
const attemptNumber = (lastAttempt?.attempt ?? 0) + 1;
|
|
62
|
+
// Create recovery attempt record
|
|
63
|
+
const recoveryAttempt = {
|
|
64
|
+
sessionId: currentSessionId ?? recoveredSessionId,
|
|
65
|
+
timestamp: now,
|
|
66
|
+
attempt: attemptNumber,
|
|
67
|
+
context: {
|
|
68
|
+
...lastSessionContext,
|
|
69
|
+
recoveryAttempts: attemptNumber,
|
|
70
|
+
lastActivity: now,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
recoveryAttempts.push(recoveryAttempt);
|
|
74
|
+
this.storeRecoveryAttempts(recoveryAttempts);
|
|
75
|
+
if (this.debugMode) {
|
|
76
|
+
debugLog.debug('SessionRecovery', `Session recovery successful: recovery of session ${recoveredSessionId}`);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
recovered: true,
|
|
80
|
+
recoveredSessionId,
|
|
81
|
+
context: recoveryAttempt.context,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Calculate the recovery window with bounds checking
|
|
86
|
+
*/
|
|
87
|
+
calculateRecoveryWindow() {
|
|
88
|
+
const sessionTimeout = this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT_MS;
|
|
89
|
+
const calculatedRecoveryWindow = sessionTimeout * SESSION_RECOVERY_WINDOW_MULTIPLIER;
|
|
90
|
+
const boundedRecoveryWindow = Math.max(Math.min(calculatedRecoveryWindow, MAX_SESSION_RECOVERY_WINDOW_MS), MIN_SESSION_RECOVERY_WINDOW_MS);
|
|
91
|
+
if (this.debugMode) {
|
|
92
|
+
if (calculatedRecoveryWindow > MAX_SESSION_RECOVERY_WINDOW_MS) {
|
|
93
|
+
debugLog.warn('SessionRecovery', `Recovery window capped at ${MAX_SESSION_RECOVERY_WINDOW_MS}ms (24h). Calculated: ${calculatedRecoveryWindow}ms`);
|
|
94
|
+
}
|
|
95
|
+
else if (calculatedRecoveryWindow < MIN_SESSION_RECOVERY_WINDOW_MS) {
|
|
96
|
+
debugLog.warn('SessionRecovery', `Recovery window increased to minimum ${MIN_SESSION_RECOVERY_WINDOW_MS}ms (2min). Calculated: ${calculatedRecoveryWindow}ms`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return boundedRecoveryWindow;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if session recovery can be attempted
|
|
103
|
+
*/
|
|
104
|
+
canAttemptRecovery(lastAttempt) {
|
|
105
|
+
if (!lastAttempt) {
|
|
106
|
+
return true; // First recovery attempt
|
|
107
|
+
}
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
const timeSinceLastActivity = now - lastAttempt.context.lastActivity;
|
|
110
|
+
// Check if within recovery window
|
|
111
|
+
if (timeSinceLastActivity > this.config.recoveryWindowMs) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Check max attempts
|
|
115
|
+
if (lastAttempt.attempt >= this.config.maxRecoveryAttempts) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Store session context for potential recovery
|
|
122
|
+
*/
|
|
123
|
+
storeSessionContextForRecovery(sessionContext) {
|
|
124
|
+
try {
|
|
125
|
+
const recoveryAttempts = this.getStoredRecoveryAttempts();
|
|
126
|
+
// Create a recovery attempt record
|
|
127
|
+
const recoveryAttempt = {
|
|
128
|
+
sessionId: sessionContext.sessionId,
|
|
129
|
+
timestamp: Date.now(),
|
|
130
|
+
attempt: 0,
|
|
131
|
+
context: sessionContext,
|
|
132
|
+
};
|
|
133
|
+
// Add to recovery attempts (keep only the latest few)
|
|
134
|
+
recoveryAttempts.push(recoveryAttempt);
|
|
135
|
+
const maxStoredRecoveryAttempts = 5;
|
|
136
|
+
if (recoveryAttempts.length > maxStoredRecoveryAttempts) {
|
|
137
|
+
recoveryAttempts.splice(0, recoveryAttempts.length - maxStoredRecoveryAttempts);
|
|
138
|
+
}
|
|
139
|
+
this.storeRecoveryAttempts(recoveryAttempts);
|
|
140
|
+
if (this.debugMode) {
|
|
141
|
+
debugLog.debug('SessionRecovery', `Stored session context for recovery: ${sessionContext.sessionId}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (this.debugMode) {
|
|
146
|
+
debugLog.warn('SessionRecovery', 'Failed to store session context for recovery', { error });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get stored recovery attempts
|
|
152
|
+
*/
|
|
153
|
+
getStoredRecoveryAttempts() {
|
|
154
|
+
try {
|
|
155
|
+
const stored = this.storageManager.getItem(SESSION_RECOVERY_KEY(this.projectId));
|
|
156
|
+
return stored ? JSON.parse(stored) : [];
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (this.debugMode) {
|
|
160
|
+
const stored = this.storageManager.getItem(SESSION_RECOVERY_KEY(this.projectId));
|
|
161
|
+
debugLog.warn('SessionRecovery', `Failed to parse stored recovery attempts for projectId ${this.projectId}. Data: ${stored}`, { error });
|
|
162
|
+
}
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Store recovery attempts
|
|
168
|
+
*/
|
|
169
|
+
storeRecoveryAttempts(attempts) {
|
|
170
|
+
try {
|
|
171
|
+
this.storageManager.setItem(SESSION_RECOVERY_KEY(this.projectId), JSON.stringify(attempts));
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (this.debugMode) {
|
|
175
|
+
debugLog.warn('SessionRecovery', 'Failed to store recovery attempts', { error });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get the last recovery attempt
|
|
181
|
+
*/
|
|
182
|
+
getLastRecoveryAttempt() {
|
|
183
|
+
const attempts = this.getStoredRecoveryAttempts();
|
|
184
|
+
return attempts.length > 0 ? attempts[attempts.length - 1] : null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Clean up old recovery attempts
|
|
188
|
+
*/
|
|
189
|
+
cleanupOldRecoveryAttempts() {
|
|
190
|
+
const attempts = this.getStoredRecoveryAttempts();
|
|
191
|
+
const now = Date.now();
|
|
192
|
+
// Remove attempts older than recovery window
|
|
193
|
+
const validAttempts = attempts.filter((attempt) => now - attempt.timestamp <= this.config.recoveryWindowMs);
|
|
194
|
+
if (validAttempts.length !== attempts.length) {
|
|
195
|
+
this.storeRecoveryAttempts(validAttempts);
|
|
196
|
+
if (this.debugMode) {
|
|
197
|
+
debugLog.debug('SessionRecovery', `Cleaned up ${attempts.length - validAttempts.length} old recovery attempts`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check if there's a recoverable session.
|
|
203
|
+
* Returns false when no recovery attempts are stored.
|
|
204
|
+
*/
|
|
205
|
+
hasRecoverableSession() {
|
|
206
|
+
const lastAttempt = this.getLastRecoveryAttempt();
|
|
207
|
+
if (!lastAttempt) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return this.canAttemptRecovery(lastAttempt);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get recovery window in milliseconds
|
|
214
|
+
*/
|
|
215
|
+
getRecoveryWindowMs() {
|
|
216
|
+
return this.config.recoveryWindowMs;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get max recovery attempts
|
|
220
|
+
*/
|
|
221
|
+
getMaxRecoveryAttempts() {
|
|
222
|
+
return this.config.maxRecoveryAttempts;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Clear all stored recovery data
|
|
226
|
+
*/
|
|
227
|
+
clearRecoveryData() {
|
|
228
|
+
this.storageManager.removeItem(SESSION_RECOVERY_KEY(this.projectId));
|
|
229
|
+
if (this.debugMode) {
|
|
230
|
+
debugLog.debug('SessionRecovery', 'Cleared all recovery data');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SessionEndReason, SessionEndConfig, SessionEndResult } from '../types/session.types';
|
|
2
|
+
import { StateManager } from './state.manager';
|
|
3
|
+
import { EventManager } from './event.manager';
|
|
4
|
+
import { StorageManager } from './storage.manager';
|
|
5
|
+
export declare class SessionManager extends StateManager {
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly eventManager;
|
|
8
|
+
private readonly storageManager;
|
|
9
|
+
private readonly listenerManagers;
|
|
10
|
+
private readonly deviceCapabilities;
|
|
11
|
+
private readonly onActivity;
|
|
12
|
+
private readonly onInactivity;
|
|
13
|
+
private recoveryManager;
|
|
14
|
+
private isSessionActive;
|
|
15
|
+
private lastActivityTime;
|
|
16
|
+
private inactivityTimer;
|
|
17
|
+
private sessionStartTime;
|
|
18
|
+
private throttleTimeout;
|
|
19
|
+
private visibilityChangeTimeout;
|
|
20
|
+
private pendingSessionEnd;
|
|
21
|
+
private sessionEndPromise;
|
|
22
|
+
private sessionEndLock;
|
|
23
|
+
private cleanupHandlers;
|
|
24
|
+
private readonly sessionEndConfig;
|
|
25
|
+
private sessionEndReason;
|
|
26
|
+
private readonly sessionEndPriority;
|
|
27
|
+
private readonly sessionEndStats;
|
|
28
|
+
private readonly sessionHealth;
|
|
29
|
+
constructor(onActivity: () => void, onInactivity: () => void, eventManager?: EventManager, storageManager?: StorageManager, sessionEndConfig?: Partial<SessionEndConfig>);
|
|
30
|
+
/**
|
|
31
|
+
* Initialize recovery manager
|
|
32
|
+
*/
|
|
33
|
+
private initializeRecoveryManager;
|
|
34
|
+
/**
|
|
35
|
+
* Store session context for recovery
|
|
36
|
+
*/
|
|
37
|
+
private storeSessionContextForRecovery;
|
|
38
|
+
startSession(): {
|
|
39
|
+
sessionId: string;
|
|
40
|
+
recovered?: boolean;
|
|
41
|
+
};
|
|
42
|
+
endSession(): number;
|
|
43
|
+
destroy(): void;
|
|
44
|
+
private detectDeviceCapabilities;
|
|
45
|
+
private initializeListenerManagers;
|
|
46
|
+
private setupAllListeners;
|
|
47
|
+
private cleanupAllListeners;
|
|
48
|
+
private clearTimers;
|
|
49
|
+
private resetState;
|
|
50
|
+
private readonly handleActivity;
|
|
51
|
+
private readonly handleInactivity;
|
|
52
|
+
private readonly handleVisibilityChange;
|
|
53
|
+
private readonly resetInactivityTimer;
|
|
54
|
+
clearInactivityTimer(): void;
|
|
55
|
+
private shouldProceedWithSessionEnd;
|
|
56
|
+
private waitForCompletion;
|
|
57
|
+
endSessionManaged(reason: SessionEndReason): Promise<SessionEndResult>;
|
|
58
|
+
endSessionSafely(reason: SessionEndReason, options?: {
|
|
59
|
+
forceSync?: boolean;
|
|
60
|
+
allowSync?: boolean;
|
|
61
|
+
}): Promise<SessionEndResult> | SessionEndResult;
|
|
62
|
+
isPendingSessionEnd(): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Track session health events for monitoring and diagnostics
|
|
65
|
+
*/
|
|
66
|
+
trackSessionHealth(event: 'recovery' | 'timeout' | 'conflict'): void;
|
|
67
|
+
private performSessionEnd;
|
|
68
|
+
private cleanupSession;
|
|
69
|
+
private endSessionManagedSync;
|
|
70
|
+
private performSessionEndSync;
|
|
71
|
+
private setupPageUnloadHandlers;
|
|
72
|
+
}
|