@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,46 +1,64 @@
|
|
|
1
1
|
import { BaseEventsQueueDto } from '../types';
|
|
2
2
|
import { StorageManager } from './storage.manager';
|
|
3
3
|
import { StateManager } from './state.manager';
|
|
4
|
+
interface SendCallbacks {
|
|
5
|
+
onSuccess?: (eventCount?: number, events?: any[]) => void;
|
|
6
|
+
onFailure?: () => void;
|
|
7
|
+
}
|
|
4
8
|
export declare class SenderManager extends StateManager {
|
|
5
9
|
private readonly storeManager;
|
|
6
|
-
private readonly queueStorageKey;
|
|
7
|
-
private retryDelay;
|
|
8
10
|
private retryTimeoutId;
|
|
9
|
-
private
|
|
10
|
-
private
|
|
11
|
+
private retryCount;
|
|
12
|
+
private isRetrying;
|
|
11
13
|
constructor(storeManager: StorageManager);
|
|
12
|
-
|
|
14
|
+
private getQueueStorageKey;
|
|
15
|
+
/**
|
|
16
|
+
* Send events synchronously using sendBeacon or XHR fallback
|
|
17
|
+
* Used primarily for page unload scenarios
|
|
18
|
+
*/
|
|
13
19
|
sendEventsQueueSync(body: BaseEventsQueueDto): boolean;
|
|
14
|
-
sendEventsQueue(body: BaseEventsQueueDto): boolean;
|
|
15
|
-
recoverPersistedEvents(): void;
|
|
16
|
-
stop(): void;
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
21
|
+
* Send events asynchronously with persistence and retry logic
|
|
22
|
+
* Main method for sending events during normal operation
|
|
23
|
+
*/
|
|
24
|
+
sendEventsQueue(body: BaseEventsQueueDto, callbacks?: SendCallbacks): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Recover and send previously persisted events
|
|
27
|
+
* Called during initialization to handle events from previous session
|
|
19
28
|
*/
|
|
20
|
-
|
|
29
|
+
recoverPersistedEvents(callbacks?: SendCallbacks): Promise<void>;
|
|
21
30
|
/**
|
|
22
|
-
*
|
|
31
|
+
* Persist events for recovery in case of failure
|
|
23
32
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
persistEventsForRecovery(body: BaseEventsQueueDto): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Legacy method for backward compatibility
|
|
36
|
+
* @deprecated Use sendEventsQueue instead
|
|
37
|
+
*/
|
|
38
|
+
sendEventsQueueAsync(body: BaseEventsQueueDto): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Stop the sender manager and clean up resources
|
|
41
|
+
*/
|
|
42
|
+
stop(): void;
|
|
43
|
+
private send;
|
|
44
|
+
private sendWithTimeout;
|
|
45
|
+
private sendQueueSyncInternal;
|
|
30
46
|
private sendSyncXHR;
|
|
31
47
|
private prepareRequest;
|
|
32
48
|
private getPersistedData;
|
|
33
49
|
private isDataRecent;
|
|
34
50
|
private createRecoveryBody;
|
|
35
|
-
private
|
|
36
|
-
private handleSendFailure;
|
|
37
|
-
private persistFailedEvents;
|
|
51
|
+
private persistEvents;
|
|
38
52
|
private clearPersistedEvents;
|
|
39
53
|
private resetRetryState;
|
|
40
54
|
private scheduleRetry;
|
|
41
|
-
private executeSend;
|
|
42
|
-
private executeSendSync;
|
|
43
55
|
private shouldSkipSend;
|
|
56
|
+
/**
|
|
57
|
+
* Simulate a successful send operation for skip mode
|
|
58
|
+
* Provides realistic timing and behavior without making HTTP requests
|
|
59
|
+
*/
|
|
60
|
+
private simulateSuccessfulSend;
|
|
44
61
|
private isSendBeaconAvailable;
|
|
45
62
|
private clearRetryTimeout;
|
|
46
63
|
}
|
|
64
|
+
export {};
|
|
@@ -1,111 +1,154 @@
|
|
|
1
|
-
import { QUEUE_KEY,
|
|
2
|
-
import { SpecialProjectId
|
|
3
|
-
import { debugLog } from '../utils
|
|
1
|
+
import { QUEUE_KEY, EVENT_EXPIRY_HOURS, SYNC_XHR_TIMEOUT_MS, MAX_RETRIES, RETRY_DELAY_MS, REQUEST_TIMEOUT_MS, } from '../constants';
|
|
2
|
+
import { SpecialProjectId } from '../types';
|
|
3
|
+
import { debugLog } from '../utils';
|
|
4
4
|
import { StateManager } from './state.manager';
|
|
5
5
|
export class SenderManager extends StateManager {
|
|
6
6
|
constructor(storeManager) {
|
|
7
7
|
super();
|
|
8
|
-
this.retryDelay = RETRY_BACKOFF_INITIAL;
|
|
9
8
|
this.retryTimeoutId = null;
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
9
|
+
this.retryCount = 0;
|
|
10
|
+
this.isRetrying = false;
|
|
12
11
|
this.storeManager = storeManager;
|
|
13
|
-
this.queueStorageKey = `${QUEUE_KEY(this.get('config')?.id)}:${this.get('userId')}`;
|
|
14
|
-
this.recoverPersistedEvents();
|
|
15
12
|
}
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
getQueueStorageKey() {
|
|
14
|
+
const projectId = this.get('config')?.id || 'default';
|
|
15
|
+
const userId = this.get('userId') || 'anonymous';
|
|
16
|
+
return `${QUEUE_KEY(projectId)}:${userId}`;
|
|
18
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Send events synchronously using sendBeacon or XHR fallback
|
|
20
|
+
* Used primarily for page unload scenarios
|
|
21
|
+
*/
|
|
19
22
|
sendEventsQueueSync(body) {
|
|
20
|
-
|
|
23
|
+
// For skip mode, simulate success immediately (sync version)
|
|
24
|
+
if (this.shouldSkipSend()) {
|
|
25
|
+
this.clearPersistedEvents();
|
|
26
|
+
this.resetRetryState();
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const success = this.sendQueueSyncInternal(body);
|
|
30
|
+
if (success) {
|
|
31
|
+
this.clearPersistedEvents();
|
|
32
|
+
this.resetRetryState();
|
|
33
|
+
}
|
|
34
|
+
return success;
|
|
21
35
|
}
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Send events asynchronously with persistence and retry logic
|
|
38
|
+
* Main method for sending events during normal operation
|
|
39
|
+
*/
|
|
40
|
+
async sendEventsQueue(body, callbacks) {
|
|
41
|
+
// First, try to persist events for recovery (even in skip mode for consistency)
|
|
42
|
+
const persisted = this.persistEvents(body);
|
|
43
|
+
if (!persisted && !this.shouldSkipSend()) {
|
|
44
|
+
debugLog.warn('SenderManager', 'Failed to persist events, attempting immediate send');
|
|
45
|
+
}
|
|
46
|
+
// Attempt to send events (or simulate in skip mode)
|
|
47
|
+
const success = await this.send(body);
|
|
48
|
+
if (success) {
|
|
49
|
+
this.clearPersistedEvents();
|
|
50
|
+
this.resetRetryState();
|
|
51
|
+
callbacks?.onSuccess?.(body.events.length);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.scheduleRetry(body, callbacks);
|
|
55
|
+
callbacks?.onFailure?.();
|
|
56
|
+
}
|
|
57
|
+
return success;
|
|
24
58
|
}
|
|
25
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Recover and send previously persisted events
|
|
61
|
+
* Called during initialization to handle events from previous session
|
|
62
|
+
*/
|
|
63
|
+
async recoverPersistedEvents(callbacks) {
|
|
26
64
|
try {
|
|
27
65
|
const persistedData = this.getPersistedData();
|
|
28
66
|
if (!persistedData || !this.isDataRecent(persistedData) || persistedData.events.length === 0) {
|
|
29
67
|
this.clearPersistedEvents();
|
|
30
68
|
return;
|
|
31
69
|
}
|
|
32
|
-
const
|
|
33
|
-
const success = this.
|
|
70
|
+
const body = this.createRecoveryBody(persistedData);
|
|
71
|
+
const success = await this.send(body);
|
|
34
72
|
if (success) {
|
|
35
|
-
debugLog.info('SenderManager', 'Persisted events recovered successfully', {
|
|
36
|
-
eventsCount: persistedData.events.length,
|
|
37
|
-
sessionId: persistedData.sessionId,
|
|
38
|
-
});
|
|
39
73
|
this.clearPersistedEvents();
|
|
74
|
+
this.resetRetryState();
|
|
75
|
+
callbacks?.onSuccess?.(persistedData.events.length);
|
|
40
76
|
}
|
|
41
77
|
else {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
this.scheduleRetryForRecoveredEvents(recoveryBody);
|
|
78
|
+
this.scheduleRetry(body, callbacks);
|
|
79
|
+
callbacks?.onFailure?.();
|
|
46
80
|
}
|
|
47
81
|
}
|
|
48
82
|
catch (error) {
|
|
49
83
|
debugLog.error('SenderManager', 'Failed to recover persisted events', { error });
|
|
84
|
+
this.clearPersistedEvents(); // Clean up corrupted data
|
|
50
85
|
}
|
|
51
86
|
}
|
|
52
|
-
stop() {
|
|
53
|
-
this.clearRetryTimeout();
|
|
54
|
-
this.resetRetryState();
|
|
55
|
-
}
|
|
56
87
|
/**
|
|
57
|
-
*
|
|
88
|
+
* Persist events for recovery in case of failure
|
|
58
89
|
*/
|
|
59
|
-
|
|
60
|
-
return this.
|
|
90
|
+
persistEventsForRecovery(body) {
|
|
91
|
+
return this.persistEvents(body);
|
|
61
92
|
}
|
|
62
93
|
/**
|
|
63
|
-
*
|
|
94
|
+
* Legacy method for backward compatibility
|
|
95
|
+
* @deprecated Use sendEventsQueue instead
|
|
64
96
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
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;
|
|
97
|
+
async sendEventsQueueAsync(body) {
|
|
98
|
+
return this.sendEventsQueue(body);
|
|
77
99
|
}
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Stop the sender manager and clean up resources
|
|
102
|
+
*/
|
|
103
|
+
stop() {
|
|
104
|
+
this.clearRetryTimeout();
|
|
105
|
+
this.resetRetryState();
|
|
106
|
+
// Clear any persisted events on shutdown to prevent stale data
|
|
107
|
+
this.clearPersistedEvents();
|
|
80
108
|
}
|
|
81
|
-
async
|
|
109
|
+
async send(body) {
|
|
110
|
+
if (this.shouldSkipSend()) {
|
|
111
|
+
return this.simulateSuccessfulSend();
|
|
112
|
+
}
|
|
82
113
|
const { url, payload } = this.prepareRequest(body);
|
|
83
114
|
try {
|
|
84
|
-
const response = await
|
|
85
|
-
method: 'POST',
|
|
86
|
-
mode: 'cors',
|
|
87
|
-
credentials: 'include',
|
|
88
|
-
body: payload,
|
|
89
|
-
headers: {
|
|
90
|
-
'Content-Type': 'application/json',
|
|
91
|
-
Origin: window.location.origin,
|
|
92
|
-
Referer: window.location.href,
|
|
93
|
-
},
|
|
94
|
-
});
|
|
115
|
+
const response = await this.sendWithTimeout(url, payload);
|
|
95
116
|
return response.ok;
|
|
96
117
|
}
|
|
97
118
|
catch (error) {
|
|
98
119
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
99
|
-
|
|
100
|
-
debugLog.error('SenderManager', 'Failed to send events async', {
|
|
120
|
+
debugLog.error('SenderManager', 'Send request failed', {
|
|
101
121
|
error: errorMessage,
|
|
102
|
-
|
|
103
|
-
url: url.replace(/\/\/[^/]+/, '//[DOMAIN]'),
|
|
122
|
+
events: body.events.length,
|
|
123
|
+
url: url.replace(/\/\/[^/]+/, '//[DOMAIN]'), // Hide domain for privacy
|
|
104
124
|
});
|
|
105
125
|
return false;
|
|
106
126
|
}
|
|
107
127
|
}
|
|
108
|
-
|
|
128
|
+
async sendWithTimeout(url, payload) {
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch(url, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
},
|
|
137
|
+
body: payload,
|
|
138
|
+
keepalive: true,
|
|
139
|
+
credentials: 'include',
|
|
140
|
+
signal: controller.signal,
|
|
141
|
+
});
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
144
|
+
}
|
|
145
|
+
return response;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
clearTimeout(timeoutId);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
sendQueueSyncInternal(body) {
|
|
109
152
|
const { url, payload } = this.prepareRequest(body);
|
|
110
153
|
const blob = new Blob([payload], { type: 'application/json' });
|
|
111
154
|
if (this.isSendBeaconAvailable() && navigator.sendBeacon(url, blob)) {
|
|
@@ -113,54 +156,61 @@ export class SenderManager extends StateManager {
|
|
|
113
156
|
}
|
|
114
157
|
return this.sendSyncXHR(url, payload);
|
|
115
158
|
}
|
|
116
|
-
sendQueue(body) {
|
|
117
|
-
if (!this.isSendBeaconAvailable()) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
const { url, payload } = this.prepareRequest(body);
|
|
121
|
-
const blob = new Blob([payload], { type: 'application/json' });
|
|
122
|
-
return navigator.sendBeacon(url, blob);
|
|
123
|
-
}
|
|
124
159
|
sendSyncXHR(url, payload) {
|
|
125
160
|
const xhr = new XMLHttpRequest();
|
|
126
161
|
try {
|
|
127
162
|
xhr.open('POST', url, false);
|
|
128
163
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
129
|
-
xhr.setRequestHeader('Origin', window.location.origin);
|
|
130
|
-
xhr.setRequestHeader('Referer', window.location.href);
|
|
131
164
|
xhr.withCredentials = true;
|
|
132
165
|
xhr.timeout = SYNC_XHR_TIMEOUT_MS;
|
|
133
166
|
xhr.send(payload);
|
|
134
|
-
|
|
167
|
+
const success = xhr.status >= 200 && xhr.status < 300;
|
|
168
|
+
if (!success) {
|
|
169
|
+
debugLog.warn('SenderManager', 'Sync XHR failed', {
|
|
170
|
+
status: xhr.status,
|
|
171
|
+
statusText: xhr.statusText || 'Unknown error',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return success;
|
|
135
175
|
}
|
|
136
176
|
catch (error) {
|
|
137
177
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
138
|
-
|
|
139
|
-
debugLog.error('SenderManager', 'Sync XHR failed', {
|
|
178
|
+
debugLog.warn('SenderManager', 'Sync XHR error', {
|
|
140
179
|
error: errorMessage,
|
|
141
|
-
|
|
142
|
-
status: xhr.status ?? 'unknown',
|
|
143
|
-
url: url.replace(/\/\/[^/]+/, '//[DOMAIN]'),
|
|
180
|
+
status: xhr.status || 'unknown',
|
|
144
181
|
});
|
|
145
182
|
return false;
|
|
146
183
|
}
|
|
147
184
|
}
|
|
148
185
|
prepareRequest(body) {
|
|
149
|
-
const
|
|
150
|
-
const baseUrl = useLocalServer ? window.location.origin : this.get('apiUrl');
|
|
151
|
-
const url = `${baseUrl}/collect`;
|
|
186
|
+
const url = `${this.get('apiUrl')}/collect`;
|
|
152
187
|
return {
|
|
153
188
|
url,
|
|
154
189
|
payload: JSON.stringify(body),
|
|
155
190
|
};
|
|
156
191
|
}
|
|
157
192
|
getPersistedData() {
|
|
158
|
-
|
|
159
|
-
|
|
193
|
+
try {
|
|
194
|
+
const storageKey = this.getQueueStorageKey();
|
|
195
|
+
const persistedDataString = this.storeManager.getItem(storageKey);
|
|
196
|
+
if (persistedDataString) {
|
|
197
|
+
return JSON.parse(persistedDataString);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
debugLog.warn('SenderManager', 'Failed to parse persisted data', { error });
|
|
202
|
+
// Clean up corrupted data
|
|
203
|
+
this.clearPersistedEvents();
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
160
206
|
}
|
|
161
207
|
isDataRecent(data) {
|
|
208
|
+
if (!data.timestamp || typeof data.timestamp !== 'number') {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
162
211
|
const ageInHours = (Date.now() - data.timestamp) / (1000 * 60 * 60);
|
|
163
|
-
|
|
212
|
+
const isRecent = ageInHours < EVENT_EXPIRY_HOURS;
|
|
213
|
+
return isRecent;
|
|
164
214
|
}
|
|
165
215
|
createRecoveryBody(data) {
|
|
166
216
|
return {
|
|
@@ -171,14 +221,7 @@ export class SenderManager extends StateManager {
|
|
|
171
221
|
...(data.global_metadata && { global_metadata: data.global_metadata }),
|
|
172
222
|
};
|
|
173
223
|
}
|
|
174
|
-
|
|
175
|
-
debugLog.info('SenderManager', ` ⏩ Queue snapshot`, queue);
|
|
176
|
-
}
|
|
177
|
-
handleSendFailure(body) {
|
|
178
|
-
this.persistFailedEvents(body);
|
|
179
|
-
this.scheduleRetry(body);
|
|
180
|
-
}
|
|
181
|
-
persistFailedEvents(body) {
|
|
224
|
+
persistEvents(body) {
|
|
182
225
|
try {
|
|
183
226
|
const persistedData = {
|
|
184
227
|
userId: body.user_id,
|
|
@@ -188,131 +231,90 @@ export class SenderManager extends StateManager {
|
|
|
188
231
|
timestamp: Date.now(),
|
|
189
232
|
...(body.global_metadata && { global_metadata: body.global_metadata }),
|
|
190
233
|
};
|
|
191
|
-
this.
|
|
234
|
+
const storageKey = this.getQueueStorageKey();
|
|
235
|
+
this.storeManager.setItem(storageKey, JSON.stringify(persistedData));
|
|
236
|
+
return !!this.storeManager.getItem(storageKey);
|
|
192
237
|
}
|
|
193
238
|
catch (error) {
|
|
194
|
-
debugLog.
|
|
239
|
+
debugLog.warn('SenderManager', 'Failed to persist events', { error });
|
|
240
|
+
return false;
|
|
195
241
|
}
|
|
196
242
|
}
|
|
197
243
|
clearPersistedEvents() {
|
|
198
|
-
|
|
244
|
+
try {
|
|
245
|
+
this.storeManager.removeItem(this.getQueueStorageKey());
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
debugLog.warn('SenderManager', 'Failed to clear persisted events', { error });
|
|
249
|
+
}
|
|
199
250
|
}
|
|
200
251
|
resetRetryState() {
|
|
201
|
-
this.
|
|
252
|
+
this.retryCount = 0;
|
|
253
|
+
this.isRetrying = false;
|
|
202
254
|
this.clearRetryTimeout();
|
|
203
255
|
}
|
|
204
|
-
scheduleRetry(body) {
|
|
205
|
-
if (this.retryTimeoutId !== null) {
|
|
256
|
+
scheduleRetry(body, originalCallbacks) {
|
|
257
|
+
if (this.retryTimeoutId !== null || this.isRetrying) {
|
|
206
258
|
return;
|
|
207
259
|
}
|
|
208
|
-
this.
|
|
209
|
-
this.
|
|
210
|
-
this.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
async executeSend(body, sendFn) {
|
|
215
|
-
if (this.shouldSkipSend()) {
|
|
216
|
-
this.logQueue(body);
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
219
|
-
if (!this.canSendAsync()) {
|
|
220
|
-
debugLog.info('SenderManager', `⏱️ Rate limited - skipping async send`, {
|
|
221
|
-
eventsCount: body.events.length,
|
|
222
|
-
timeSinceLastSend: Date.now() - this.lastAsyncSend,
|
|
223
|
-
});
|
|
224
|
-
return false;
|
|
260
|
+
if (this.retryCount >= MAX_RETRIES) {
|
|
261
|
+
debugLog.warn('SenderManager', 'Max retries reached, giving up', { retryCount: this.retryCount });
|
|
262
|
+
this.clearPersistedEvents();
|
|
263
|
+
this.resetRetryState();
|
|
264
|
+
originalCallbacks?.onFailure?.();
|
|
265
|
+
return;
|
|
225
266
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.lastAsyncSend = Date.now();
|
|
232
|
-
try {
|
|
233
|
-
const success = await sendFn();
|
|
234
|
-
if (success) {
|
|
235
|
-
debugLog.info('SenderManager', `✅ Successfully sent events to server`, {
|
|
236
|
-
eventsCount: body.events.length,
|
|
237
|
-
method: 'async',
|
|
238
|
-
});
|
|
239
|
-
this.resetRetryState();
|
|
240
|
-
this.clearPersistedEvents();
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
debugLog.warn('SenderManager', 'Failed to send events', {
|
|
244
|
-
eventsCount: body.events.length,
|
|
245
|
-
method: 'async',
|
|
246
|
-
});
|
|
247
|
-
this.handleSendFailure(body);
|
|
267
|
+
const retryDelay = RETRY_DELAY_MS * Math.pow(2, this.retryCount); // Exponential backoff
|
|
268
|
+
this.retryTimeoutId = window.setTimeout(async () => {
|
|
269
|
+
this.retryTimeoutId = null;
|
|
270
|
+
if (this.isRetrying) {
|
|
271
|
+
return;
|
|
248
272
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
debugLog.info('SenderManager', `🌐 Sending events to server (sync)`, {
|
|
269
|
-
eventsCount: body.events.length,
|
|
270
|
-
sessionId: body.session_id,
|
|
271
|
-
userId: body.user_id,
|
|
272
|
-
method: 'sendBeacon/XHR',
|
|
273
|
-
});
|
|
274
|
-
this.lastSyncSend = Date.now();
|
|
275
|
-
try {
|
|
276
|
-
const success = sendFn();
|
|
277
|
-
if (success) {
|
|
278
|
-
debugLog.info('SenderManager', `✅ Successfully sent events to server`, {
|
|
279
|
-
eventsCount: body.events.length,
|
|
280
|
-
method: 'sync',
|
|
281
|
-
});
|
|
282
|
-
this.resetRetryState();
|
|
283
|
-
this.clearPersistedEvents();
|
|
273
|
+
this.retryCount++;
|
|
274
|
+
this.isRetrying = true;
|
|
275
|
+
try {
|
|
276
|
+
const success = await this.send(body);
|
|
277
|
+
if (success) {
|
|
278
|
+
this.clearPersistedEvents();
|
|
279
|
+
this.resetRetryState();
|
|
280
|
+
originalCallbacks?.onSuccess?.(body.events.length);
|
|
281
|
+
}
|
|
282
|
+
else if (this.retryCount >= MAX_RETRIES) {
|
|
283
|
+
this.clearPersistedEvents();
|
|
284
|
+
this.resetRetryState();
|
|
285
|
+
originalCallbacks?.onFailure?.();
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
this.scheduleRetry(body, originalCallbacks);
|
|
289
|
+
}
|
|
284
290
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
eventsCount: body.events.length,
|
|
288
|
-
method: 'sync',
|
|
289
|
-
});
|
|
290
|
-
this.handleSendFailure(body);
|
|
291
|
+
finally {
|
|
292
|
+
this.isRetrying = false;
|
|
291
293
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
});
|
|
299
|
-
this.handleSendFailure(body);
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
294
|
+
}, retryDelay);
|
|
295
|
+
debugLog.debug('SenderManager', 'Retry scheduled', {
|
|
296
|
+
attempt: this.retryCount + 1,
|
|
297
|
+
delay: retryDelay,
|
|
298
|
+
events: body.events.length,
|
|
299
|
+
});
|
|
302
300
|
}
|
|
303
301
|
shouldSkipSend() {
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
302
|
+
const config = this.get('config');
|
|
303
|
+
const { id } = config || {};
|
|
304
|
+
return id === SpecialProjectId.Skip;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Simulate a successful send operation for skip mode
|
|
308
|
+
* Provides realistic timing and behavior without making HTTP requests
|
|
309
|
+
*/
|
|
310
|
+
async simulateSuccessfulSend() {
|
|
311
|
+
// Simulate realistic network delay (100-500ms)
|
|
312
|
+
const delay = Math.random() * 400 + 100;
|
|
313
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
314
|
+
return true; // Always successful in skip mode
|
|
310
315
|
}
|
|
311
316
|
isSendBeaconAvailable() {
|
|
312
|
-
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
return true;
|
|
317
|
+
return typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function';
|
|
316
318
|
}
|
|
317
319
|
clearRetryTimeout() {
|
|
318
320
|
if (this.retryTimeoutId !== null) {
|