@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.
Files changed (228) hide show
  1. package/README.md +58 -24
  2. package/dist/browser/tracelog.js +1934 -3226
  3. package/dist/cjs/api.d.ts +33 -19
  4. package/dist/cjs/api.js +111 -156
  5. package/dist/cjs/app.constants.d.ts +80 -1
  6. package/dist/cjs/app.constants.js +90 -3
  7. package/dist/cjs/app.d.ts +29 -44
  8. package/dist/cjs/app.js +114 -212
  9. package/dist/cjs/app.types.d.ts +2 -7
  10. package/dist/cjs/app.types.js +10 -21
  11. package/dist/cjs/constants/api.constants.js +11 -5
  12. package/dist/cjs/constants/config.constants.d.ts +75 -0
  13. package/dist/cjs/constants/config.constants.js +178 -0
  14. package/dist/cjs/constants/error.constants.d.ts +29 -0
  15. package/dist/cjs/constants/error.constants.js +50 -0
  16. package/dist/cjs/constants/index.d.ts +3 -6
  17. package/dist/cjs/constants/index.js +3 -6
  18. package/dist/cjs/constants/performance.constants.d.ts +28 -0
  19. package/dist/cjs/constants/performance.constants.js +43 -0
  20. package/dist/cjs/handlers/click.handler.d.ts +1 -0
  21. package/dist/cjs/handlers/click.handler.js +30 -49
  22. package/dist/cjs/handlers/error.handler.d.ts +11 -6
  23. package/dist/cjs/handlers/error.handler.js +91 -51
  24. package/dist/cjs/handlers/page-view.handler.js +38 -29
  25. package/dist/cjs/handlers/performance.handler.d.ts +3 -0
  26. package/dist/cjs/handlers/performance.handler.js +76 -37
  27. package/dist/cjs/handlers/scroll.handler.d.ts +15 -0
  28. package/dist/cjs/handlers/scroll.handler.js +105 -31
  29. package/dist/cjs/handlers/session.handler.d.ts +6 -20
  30. package/dist/cjs/handlers/session.handler.js +38 -326
  31. package/dist/cjs/integrations/google-analytics.integration.d.ts +0 -1
  32. package/dist/cjs/integrations/google-analytics.integration.js +27 -98
  33. package/dist/cjs/listeners/input-listener-managers.d.ts +18 -9
  34. package/dist/cjs/listeners/input-listener-managers.js +24 -33
  35. package/dist/cjs/listeners/touch-listener-manager.d.ts +1 -3
  36. package/dist/cjs/listeners/touch-listener-manager.js +1 -23
  37. package/dist/cjs/listeners/visibility-listener-manager.d.ts +1 -4
  38. package/dist/cjs/listeners/visibility-listener-manager.js +6 -42
  39. package/dist/cjs/managers/api.manager.d.ts +13 -3
  40. package/dist/cjs/managers/api.manager.js +35 -5
  41. package/dist/cjs/managers/config.manager.d.ts +53 -3
  42. package/dist/cjs/managers/config.manager.js +131 -62
  43. package/dist/cjs/managers/event.manager.d.ts +57 -36
  44. package/dist/cjs/managers/event.manager.js +266 -417
  45. package/dist/cjs/managers/sender.manager.d.ts +40 -22
  46. package/dist/cjs/managers/sender.manager.js +200 -198
  47. package/dist/cjs/managers/session.manager.d.ts +80 -66
  48. package/dist/cjs/managers/session.manager.js +267 -522
  49. package/dist/cjs/managers/state.manager.d.ts +33 -0
  50. package/dist/cjs/managers/state.manager.js +79 -6
  51. package/dist/cjs/managers/storage.manager.d.ts +26 -2
  52. package/dist/cjs/managers/storage.manager.js +67 -34
  53. package/dist/cjs/managers/tags.manager.d.ts +31 -7
  54. package/dist/cjs/managers/tags.manager.js +123 -241
  55. package/dist/cjs/managers/user.manager.d.ts +14 -5
  56. package/dist/cjs/managers/user.manager.js +17 -9
  57. package/dist/cjs/public-api.d.ts +10 -1
  58. package/dist/cjs/public-api.js +18 -24
  59. package/dist/cjs/test-bridge.d.ts +48 -0
  60. package/dist/cjs/test-bridge.js +110 -0
  61. package/dist/cjs/types/api.types.d.ts +21 -6
  62. package/dist/cjs/types/api.types.js +21 -6
  63. package/dist/cjs/types/config.types.d.ts +22 -84
  64. package/dist/cjs/types/emitter.types.d.ts +11 -0
  65. package/dist/cjs/types/emitter.types.js +8 -0
  66. package/dist/cjs/types/event.types.d.ts +8 -11
  67. package/dist/cjs/types/index.d.ts +3 -1
  68. package/dist/cjs/types/index.js +3 -1
  69. package/dist/cjs/types/queue.types.d.ts +1 -0
  70. package/dist/cjs/types/session.types.d.ts +0 -64
  71. package/dist/cjs/types/state.types.d.ts +1 -0
  72. package/dist/cjs/types/test-bridge.types.d.ts +38 -0
  73. package/dist/cjs/types/validation-error.types.d.ts +7 -0
  74. package/dist/cjs/types/validation-error.types.js +11 -1
  75. package/dist/cjs/types/window.types.d.ts +1 -8
  76. package/dist/cjs/utils/data/uuid.utils.d.ts +1 -1
  77. package/dist/cjs/utils/data/uuid.utils.js +7 -5
  78. package/dist/cjs/utils/emitter.utils.d.ts +8 -0
  79. package/dist/cjs/utils/emitter.utils.js +33 -0
  80. package/dist/cjs/utils/index.d.ts +1 -0
  81. package/dist/cjs/utils/index.js +1 -0
  82. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +10 -51
  83. package/dist/cjs/utils/logging/debug-logger.utils.js +36 -127
  84. package/dist/cjs/utils/network/fetch-with-timeout.utils.d.ts +4 -0
  85. package/dist/cjs/utils/network/fetch-with-timeout.utils.js +25 -0
  86. package/dist/cjs/utils/network/index.d.ts +1 -0
  87. package/dist/cjs/utils/network/index.js +1 -0
  88. package/dist/cjs/utils/network/url.utils.js +2 -42
  89. package/dist/cjs/utils/security/sanitize.utils.d.ts +1 -8
  90. package/dist/cjs/utils/security/sanitize.utils.js +7 -41
  91. package/dist/cjs/utils/validations/config-validations.utils.d.ts +7 -0
  92. package/dist/cjs/utils/validations/config-validations.utils.js +77 -22
  93. package/dist/esm/api.d.ts +33 -19
  94. package/dist/esm/api.js +105 -118
  95. package/dist/esm/app.constants.d.ts +80 -1
  96. package/dist/esm/app.constants.js +89 -1
  97. package/dist/esm/app.d.ts +29 -44
  98. package/dist/esm/app.js +115 -213
  99. package/dist/esm/app.types.d.ts +2 -7
  100. package/dist/esm/app.types.js +1 -7
  101. package/dist/esm/constants/api.constants.js +10 -4
  102. package/dist/esm/constants/config.constants.d.ts +75 -0
  103. package/dist/esm/constants/config.constants.js +174 -0
  104. package/dist/esm/constants/error.constants.d.ts +29 -0
  105. package/dist/esm/constants/error.constants.js +47 -0
  106. package/dist/esm/constants/index.d.ts +3 -6
  107. package/dist/esm/constants/index.js +3 -6
  108. package/dist/esm/constants/performance.constants.d.ts +28 -0
  109. package/dist/esm/constants/performance.constants.js +40 -0
  110. package/dist/esm/handlers/click.handler.d.ts +1 -0
  111. package/dist/esm/handlers/click.handler.js +30 -49
  112. package/dist/esm/handlers/error.handler.d.ts +11 -6
  113. package/dist/esm/handlers/error.handler.js +91 -51
  114. package/dist/esm/handlers/page-view.handler.js +38 -29
  115. package/dist/esm/handlers/performance.handler.d.ts +3 -0
  116. package/dist/esm/handlers/performance.handler.js +71 -32
  117. package/dist/esm/handlers/scroll.handler.d.ts +15 -0
  118. package/dist/esm/handlers/scroll.handler.js +106 -32
  119. package/dist/esm/handlers/session.handler.d.ts +6 -20
  120. package/dist/esm/handlers/session.handler.js +38 -326
  121. package/dist/esm/integrations/google-analytics.integration.d.ts +0 -1
  122. package/dist/esm/integrations/google-analytics.integration.js +27 -98
  123. package/dist/esm/listeners/input-listener-managers.d.ts +18 -9
  124. package/dist/esm/listeners/input-listener-managers.js +23 -32
  125. package/dist/esm/listeners/touch-listener-manager.d.ts +1 -3
  126. package/dist/esm/listeners/touch-listener-manager.js +1 -23
  127. package/dist/esm/listeners/visibility-listener-manager.d.ts +1 -4
  128. package/dist/esm/listeners/visibility-listener-manager.js +6 -42
  129. package/dist/esm/managers/api.manager.d.ts +13 -3
  130. package/dist/esm/managers/api.manager.js +34 -3
  131. package/dist/esm/managers/config.manager.d.ts +53 -3
  132. package/dist/esm/managers/config.manager.js +133 -64
  133. package/dist/esm/managers/event.manager.d.ts +57 -36
  134. package/dist/esm/managers/event.manager.js +268 -419
  135. package/dist/esm/managers/sender.manager.d.ts +40 -22
  136. package/dist/esm/managers/sender.manager.js +201 -199
  137. package/dist/esm/managers/session.manager.d.ts +80 -66
  138. package/dist/esm/managers/session.manager.js +269 -524
  139. package/dist/esm/managers/state.manager.d.ts +33 -0
  140. package/dist/esm/managers/state.manager.js +78 -6
  141. package/dist/esm/managers/storage.manager.d.ts +26 -2
  142. package/dist/esm/managers/storage.manager.js +66 -33
  143. package/dist/esm/managers/tags.manager.d.ts +31 -7
  144. package/dist/esm/managers/tags.manager.js +124 -242
  145. package/dist/esm/managers/user.manager.d.ts +14 -5
  146. package/dist/esm/managers/user.manager.js +17 -9
  147. package/dist/esm/public-api.d.ts +10 -1
  148. package/dist/esm/public-api.js +14 -1
  149. package/dist/esm/test-bridge.d.ts +48 -0
  150. package/dist/esm/test-bridge.js +106 -0
  151. package/dist/esm/types/api.types.d.ts +21 -6
  152. package/dist/esm/types/api.types.js +21 -6
  153. package/dist/esm/types/config.types.d.ts +22 -84
  154. package/dist/esm/types/emitter.types.d.ts +11 -0
  155. package/dist/esm/types/emitter.types.js +5 -0
  156. package/dist/esm/types/event.types.d.ts +8 -11
  157. package/dist/esm/types/index.d.ts +3 -1
  158. package/dist/esm/types/index.js +3 -1
  159. package/dist/esm/types/queue.types.d.ts +1 -0
  160. package/dist/esm/types/session.types.d.ts +0 -64
  161. package/dist/esm/types/state.types.d.ts +1 -0
  162. package/dist/esm/types/test-bridge.types.d.ts +38 -0
  163. package/dist/esm/types/validation-error.types.d.ts +7 -0
  164. package/dist/esm/types/validation-error.types.js +9 -0
  165. package/dist/esm/types/window.types.d.ts +1 -8
  166. package/dist/esm/utils/data/uuid.utils.d.ts +1 -1
  167. package/dist/esm/utils/data/uuid.utils.js +7 -5
  168. package/dist/esm/utils/emitter.utils.d.ts +8 -0
  169. package/dist/esm/utils/emitter.utils.js +29 -0
  170. package/dist/esm/utils/index.d.ts +1 -0
  171. package/dist/esm/utils/index.js +1 -0
  172. package/dist/esm/utils/logging/debug-logger.utils.d.ts +10 -51
  173. package/dist/esm/utils/logging/debug-logger.utils.js +36 -127
  174. package/dist/esm/utils/network/fetch-with-timeout.utils.d.ts +4 -0
  175. package/dist/esm/utils/network/fetch-with-timeout.utils.js +22 -0
  176. package/dist/esm/utils/network/index.d.ts +1 -0
  177. package/dist/esm/utils/network/index.js +1 -0
  178. package/dist/esm/utils/network/url.utils.js +2 -42
  179. package/dist/esm/utils/security/sanitize.utils.d.ts +1 -8
  180. package/dist/esm/utils/security/sanitize.utils.js +6 -39
  181. package/dist/esm/utils/validations/config-validations.utils.d.ts +7 -0
  182. package/dist/esm/utils/validations/config-validations.utils.js +76 -22
  183. package/package.json +23 -16
  184. package/dist/browser/web-vitals-CCnqwnC8.mjs +0 -198
  185. package/dist/cjs/constants/browser.constants.d.ts +0 -3
  186. package/dist/cjs/constants/browser.constants.js +0 -41
  187. package/dist/cjs/constants/initialization.constants.d.ts +0 -40
  188. package/dist/cjs/constants/initialization.constants.js +0 -48
  189. package/dist/cjs/constants/limits.constants.d.ts +0 -25
  190. package/dist/cjs/constants/limits.constants.js +0 -40
  191. package/dist/cjs/constants/security.constants.d.ts +0 -1
  192. package/dist/cjs/constants/security.constants.js +0 -12
  193. package/dist/cjs/constants/timing.constants.d.ts +0 -22
  194. package/dist/cjs/constants/timing.constants.js +0 -34
  195. package/dist/cjs/constants/validation.constants.d.ts +0 -13
  196. package/dist/cjs/constants/validation.constants.js +0 -31
  197. package/dist/cjs/handlers/network.handler.d.ts +0 -16
  198. package/dist/cjs/handlers/network.handler.js +0 -136
  199. package/dist/cjs/managers/cross-tab-session.manager.d.ts +0 -170
  200. package/dist/cjs/managers/cross-tab-session.manager.js +0 -730
  201. package/dist/cjs/managers/sampling.manager.d.ts +0 -8
  202. package/dist/cjs/managers/sampling.manager.js +0 -53
  203. package/dist/cjs/managers/session-recovery.manager.d.ts +0 -65
  204. package/dist/cjs/managers/session-recovery.manager.js +0 -237
  205. package/dist/cjs/types/web-vitals.types.d.ts +0 -6
  206. package/dist/esm/constants/browser.constants.d.ts +0 -3
  207. package/dist/esm/constants/browser.constants.js +0 -38
  208. package/dist/esm/constants/initialization.constants.d.ts +0 -40
  209. package/dist/esm/constants/initialization.constants.js +0 -45
  210. package/dist/esm/constants/limits.constants.d.ts +0 -25
  211. package/dist/esm/constants/limits.constants.js +0 -37
  212. package/dist/esm/constants/security.constants.d.ts +0 -1
  213. package/dist/esm/constants/security.constants.js +0 -9
  214. package/dist/esm/constants/timing.constants.d.ts +0 -22
  215. package/dist/esm/constants/timing.constants.js +0 -31
  216. package/dist/esm/constants/validation.constants.d.ts +0 -13
  217. package/dist/esm/constants/validation.constants.js +0 -28
  218. package/dist/esm/handlers/network.handler.d.ts +0 -16
  219. package/dist/esm/handlers/network.handler.js +0 -132
  220. package/dist/esm/managers/cross-tab-session.manager.d.ts +0 -170
  221. package/dist/esm/managers/cross-tab-session.manager.js +0 -726
  222. package/dist/esm/managers/sampling.manager.d.ts +0 -8
  223. package/dist/esm/managers/sampling.manager.js +0 -49
  224. package/dist/esm/managers/session-recovery.manager.d.ts +0 -65
  225. package/dist/esm/managers/session-recovery.manager.js +0 -233
  226. package/dist/esm/types/web-vitals.types.d.ts +0 -6
  227. /package/dist/cjs/types/{web-vitals.types.js → test-bridge.types.js} +0 -0
  228. /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 lastAsyncSend;
10
- private lastSyncSend;
11
+ private retryCount;
12
+ private isRetrying;
11
13
  constructor(storeManager: StorageManager);
12
- sendEventsQueueAsync(body: BaseEventsQueueDto): Promise<boolean>;
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
- * Sends recovered events without re-deduplication since they were already processed
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
- private sendRecoveredEvents;
29
+ recoverPersistedEvents(callbacks?: SendCallbacks): Promise<void>;
21
30
  /**
22
- * Schedules retry for recovered events using the specific recovery method
31
+ * Persist events for recovery in case of failure
23
32
  */
24
- private scheduleRetryForRecoveredEvents;
25
- private canSendAsync;
26
- private canSendSync;
27
- private sendQueueAsync;
28
- private sendQueueSync;
29
- private sendQueue;
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 logQueue;
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, RETRY_BACKOFF_INITIAL, RETRY_BACKOFF_MAX, RATE_LIMIT_INTERVAL, EVENT_EXPIRY_HOURS, SYNC_XHR_TIMEOUT_MS, } from '../constants';
2
- import { SpecialProjectId, Mode } from '../types';
3
- import { debugLog } from '../utils/logging';
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.lastAsyncSend = 0;
11
- this.lastSyncSend = 0;
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
- async sendEventsQueueAsync(body) {
17
- return this.executeSend(body, () => this.sendQueueAsync(body));
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
- return this.executeSendSync(body, () => this.sendQueueSync(body));
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
- sendEventsQueue(body) {
23
- return this.executeSendSync(body, () => this.sendQueue(body));
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
- recoverPersistedEvents() {
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 recoveryBody = this.createRecoveryBody(persistedData);
33
- const success = this.sendRecoveredEvents(recoveryBody);
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
- debugLog.warn('SenderManager', 'Failed to recover persisted events, scheduling retry', {
43
- eventsCount: persistedData.events.length,
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
- * Sends recovered events without re-deduplication since they were already processed
88
+ * Persist events for recovery in case of failure
58
89
  */
59
- sendRecoveredEvents(body) {
60
- return this.executeSendSync(body, () => this.sendQueue(body));
90
+ persistEventsForRecovery(body) {
91
+ return this.persistEvents(body);
61
92
  }
62
93
  /**
63
- * Schedules retry for recovered events using the specific recovery method
94
+ * Legacy method for backward compatibility
95
+ * @deprecated Use sendEventsQueue instead
64
96
  */
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;
97
+ async sendEventsQueueAsync(body) {
98
+ return this.sendEventsQueue(body);
77
99
  }
78
- canSendSync() {
79
- return Date.now() - this.lastSyncSend >= RATE_LIMIT_INTERVAL;
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 sendQueueAsync(body) {
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 fetch(url, {
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
- const isCorsError = errorMessage.includes('CORS') || errorMessage.includes('NotSameOrigin') || errorMessage.includes('blocked');
100
- debugLog.error('SenderManager', 'Failed to send events async', {
120
+ debugLog.error('SenderManager', 'Send request failed', {
101
121
  error: errorMessage,
102
- isCorsError,
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
- sendQueueSync(body) {
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
- return xhr.status >= 200 && xhr.status < 300;
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
- const isCorsError = errorMessage.includes('CORS') || errorMessage.includes('NotSameOrigin') || errorMessage.includes('blocked');
139
- debugLog.error('SenderManager', 'Sync XHR failed', {
178
+ debugLog.warn('SenderManager', 'Sync XHR error', {
140
179
  error: errorMessage,
141
- isCorsError,
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 useLocalServer = this.get('config').id === SpecialProjectId.HttpLocal;
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
- const persistedDataString = this.storeManager.getItem(this.queueStorageKey);
159
- return persistedDataString ? JSON.parse(persistedDataString) : null;
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
- return ageInHours < EVENT_EXPIRY_HOURS;
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
- logQueue(queue) {
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.storeManager.setItem(this.queueStorageKey, JSON.stringify(persistedData));
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.error('SenderManager', 'Failed to persist events', { error });
239
+ debugLog.warn('SenderManager', 'Failed to persist events', { error });
240
+ return false;
195
241
  }
196
242
  }
197
243
  clearPersistedEvents() {
198
- this.storeManager.removeItem(this.queueStorageKey);
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.retryDelay = RETRY_BACKOFF_INITIAL;
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.retryTimeoutId = window.setTimeout(() => {
209
- this.retryTimeoutId = null;
210
- this.sendEventsQueue(body);
211
- }, this.retryDelay);
212
- this.retryDelay = Math.min(this.retryDelay * 2, RETRY_BACKOFF_MAX);
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
- debugLog.info('SenderManager', `🌐 Sending events to server (async)`, {
227
- eventsCount: body.events.length,
228
- sessionId: body.session_id,
229
- userId: body.user_id,
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
- return success;
250
- }
251
- catch {
252
- this.handleSendFailure(body);
253
- return false;
254
- }
255
- }
256
- executeSendSync(body, sendFn) {
257
- if (this.shouldSkipSend()) {
258
- this.logQueue(body);
259
- return true;
260
- }
261
- if (!this.canSendSync()) {
262
- debugLog.info('SenderManager', `⏱️ Rate limited - skipping sync send`, {
263
- eventsCount: body.events.length,
264
- timeSinceLastSend: Date.now() - this.lastSyncSend,
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
- else {
286
- debugLog.warn('SenderManager', 'Failed to send events', {
287
- eventsCount: body.events.length,
288
- method: 'sync',
289
- });
290
- this.handleSendFailure(body);
291
+ finally {
292
+ this.isRetrying = false;
291
293
  }
292
- return success;
293
- }
294
- catch {
295
- debugLog.info('SenderManager', `💥 Exception during event sending`, {
296
- eventsCount: body.events.length,
297
- method: 'sync',
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 { id, mode } = this.get('config');
305
- const specialModes = [Mode.QA, Mode.DEBUG];
306
- if (id === SpecialProjectId.HttpSkip) {
307
- return true;
308
- }
309
- return !!mode && specialModes.includes(mode) && id !== SpecialProjectId.HttpLocal;
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
- if (typeof navigator.sendBeacon !== 'function') {
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) {