@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.
Files changed (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +217 -0
  3. package/dist/browser/tracelog.js +4040 -0
  4. package/dist/browser/web-vitals-CCnqwnC8.mjs +198 -0
  5. package/dist/cjs/api.d.ts +46 -0
  6. package/dist/cjs/api.js +224 -0
  7. package/dist/cjs/app.constants.d.ts +1 -0
  8. package/dist/cjs/app.constants.js +5 -0
  9. package/dist/cjs/app.d.ts +59 -0
  10. package/dist/cjs/app.js +272 -0
  11. package/dist/cjs/app.types.d.ts +6 -0
  12. package/dist/cjs/app.types.js +22 -0
  13. package/dist/cjs/constants/api.constants.d.ts +4 -0
  14. package/dist/cjs/constants/api.constants.js +18 -0
  15. package/dist/cjs/constants/browser.constants.d.ts +3 -0
  16. package/dist/cjs/constants/browser.constants.js +41 -0
  17. package/dist/cjs/constants/index.d.ts +8 -0
  18. package/dist/cjs/constants/index.js +24 -0
  19. package/dist/cjs/constants/initialization.constants.d.ts +40 -0
  20. package/dist/cjs/constants/initialization.constants.js +48 -0
  21. package/dist/cjs/constants/limits.constants.d.ts +25 -0
  22. package/dist/cjs/constants/limits.constants.js +40 -0
  23. package/dist/cjs/constants/security.constants.d.ts +1 -0
  24. package/dist/cjs/constants/security.constants.js +12 -0
  25. package/dist/cjs/constants/storage.constants.d.ts +9 -0
  26. package/dist/cjs/constants/storage.constants.js +22 -0
  27. package/dist/cjs/constants/timing.constants.d.ts +22 -0
  28. package/dist/cjs/constants/timing.constants.js +34 -0
  29. package/dist/cjs/constants/validation.constants.d.ts +13 -0
  30. package/dist/cjs/constants/validation.constants.js +31 -0
  31. package/dist/cjs/handlers/click.handler.d.ts +17 -0
  32. package/dist/cjs/handlers/click.handler.js +199 -0
  33. package/dist/cjs/handlers/error.handler.d.ts +15 -0
  34. package/dist/cjs/handlers/error.handler.js +97 -0
  35. package/dist/cjs/handlers/network.handler.d.ts +16 -0
  36. package/dist/cjs/handlers/network.handler.js +136 -0
  37. package/dist/cjs/handlers/page-view.handler.d.ts +15 -0
  38. package/dist/cjs/handlers/page-view.handler.js +83 -0
  39. package/dist/cjs/handlers/performance.handler.d.ts +19 -0
  40. package/dist/cjs/handlers/performance.handler.js +255 -0
  41. package/dist/cjs/handlers/scroll.handler.d.ts +16 -0
  42. package/dist/cjs/handlers/scroll.handler.js +138 -0
  43. package/dist/cjs/handlers/session.handler.d.ts +29 -0
  44. package/dist/cjs/handlers/session.handler.js +357 -0
  45. package/dist/cjs/integrations/google-analytics.integration.d.ts +18 -0
  46. package/dist/cjs/integrations/google-analytics.integration.js +159 -0
  47. package/dist/cjs/listeners/activity-listener-manager.d.ts +8 -0
  48. package/dist/cjs/listeners/activity-listener-manager.js +32 -0
  49. package/dist/cjs/listeners/index.d.ts +6 -0
  50. package/dist/cjs/listeners/index.js +14 -0
  51. package/dist/cjs/listeners/input-listener-managers.d.ts +15 -0
  52. package/dist/cjs/listeners/input-listener-managers.js +58 -0
  53. package/dist/cjs/listeners/listeners.types.d.ts +4 -0
  54. package/dist/cjs/listeners/listeners.types.js +2 -0
  55. package/dist/cjs/listeners/touch-listener-manager.d.ts +10 -0
  56. package/dist/cjs/listeners/touch-listener-manager.js +56 -0
  57. package/dist/cjs/listeners/unload-listener-manager.d.ts +8 -0
  58. package/dist/cjs/listeners/unload-listener-manager.js +30 -0
  59. package/dist/cjs/listeners/visibility-listener-manager.d.ts +12 -0
  60. package/dist/cjs/listeners/visibility-listener-manager.js +83 -0
  61. package/dist/cjs/managers/api.manager.d.ts +3 -0
  62. package/dist/cjs/managers/api.manager.js +14 -0
  63. package/dist/cjs/managers/config.manager.d.ts +7 -0
  64. package/dist/cjs/managers/config.manager.js +94 -0
  65. package/dist/cjs/managers/cross-tab-session.manager.d.ts +170 -0
  66. package/dist/cjs/managers/cross-tab-session.manager.js +730 -0
  67. package/dist/cjs/managers/event.manager.d.ts +61 -0
  68. package/dist/cjs/managers/event.manager.js +508 -0
  69. package/dist/cjs/managers/sampling.manager.d.ts +8 -0
  70. package/dist/cjs/managers/sampling.manager.js +53 -0
  71. package/dist/cjs/managers/sender.manager.d.ts +46 -0
  72. package/dist/cjs/managers/sender.manager.js +304 -0
  73. package/dist/cjs/managers/session-recovery.manager.d.ts +65 -0
  74. package/dist/cjs/managers/session-recovery.manager.js +237 -0
  75. package/dist/cjs/managers/session.manager.d.ts +72 -0
  76. package/dist/cjs/managers/session.manager.js +587 -0
  77. package/dist/cjs/managers/state.manager.d.ts +5 -0
  78. package/dist/cjs/managers/state.manager.js +23 -0
  79. package/dist/cjs/managers/storage.manager.d.ts +10 -0
  80. package/dist/cjs/managers/storage.manager.js +81 -0
  81. package/dist/cjs/managers/tags.manager.d.ts +12 -0
  82. package/dist/cjs/managers/tags.manager.js +289 -0
  83. package/dist/cjs/managers/user.manager.d.ts +7 -0
  84. package/dist/cjs/managers/user.manager.js +22 -0
  85. package/dist/cjs/public-api.d.ts +1 -0
  86. package/dist/cjs/public-api.js +37 -0
  87. package/dist/cjs/types/api.types.d.ts +21 -0
  88. package/dist/cjs/types/api.types.js +25 -0
  89. package/dist/cjs/types/common.types.d.ts +1 -0
  90. package/dist/cjs/types/common.types.js +2 -0
  91. package/dist/cjs/types/config.types.d.ts +104 -0
  92. package/dist/cjs/types/config.types.js +2 -0
  93. package/dist/cjs/types/device.types.d.ts +6 -0
  94. package/dist/cjs/types/device.types.js +10 -0
  95. package/dist/cjs/types/event.types.d.ts +104 -0
  96. package/dist/cjs/types/event.types.js +25 -0
  97. package/dist/cjs/types/index.d.ts +13 -0
  98. package/dist/cjs/types/index.js +29 -0
  99. package/dist/cjs/types/log.types.d.ts +4 -0
  100. package/dist/cjs/types/log.types.js +2 -0
  101. package/dist/cjs/types/mode.types.d.ts +7 -0
  102. package/dist/cjs/types/mode.types.js +11 -0
  103. package/dist/cjs/types/queue.types.d.ts +23 -0
  104. package/dist/cjs/types/queue.types.js +2 -0
  105. package/dist/cjs/types/session.types.d.ts +65 -0
  106. package/dist/cjs/types/session.types.js +2 -0
  107. package/dist/cjs/types/state.types.d.ts +12 -0
  108. package/dist/cjs/types/state.types.js +2 -0
  109. package/dist/cjs/types/tag.types.d.ts +43 -0
  110. package/dist/cjs/types/tag.types.js +31 -0
  111. package/dist/cjs/types/validation-error.types.d.ts +42 -0
  112. package/dist/cjs/types/validation-error.types.js +68 -0
  113. package/dist/cjs/types/web-vitals.types.d.ts +6 -0
  114. package/dist/cjs/types/web-vitals.types.js +2 -0
  115. package/dist/cjs/types/window.types.d.ts +17 -0
  116. package/dist/cjs/types/window.types.js +2 -0
  117. package/dist/cjs/utils/browser/device-detector.utils.d.ts +6 -0
  118. package/dist/cjs/utils/browser/device-detector.utils.js +71 -0
  119. package/dist/cjs/utils/browser/index.d.ts +2 -0
  120. package/dist/cjs/utils/browser/index.js +18 -0
  121. package/dist/cjs/utils/browser/utm-params.utils.d.ts +6 -0
  122. package/dist/cjs/utils/browser/utm-params.utils.js +37 -0
  123. package/dist/cjs/utils/data/index.d.ts +1 -0
  124. package/dist/cjs/utils/data/index.js +17 -0
  125. package/dist/cjs/utils/data/uuid.utils.d.ts +5 -0
  126. package/dist/cjs/utils/data/uuid.utils.js +18 -0
  127. package/dist/cjs/utils/index.d.ts +6 -0
  128. package/dist/cjs/utils/index.js +22 -0
  129. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +56 -0
  130. package/dist/cjs/utils/logging/debug-logger.utils.js +139 -0
  131. package/dist/cjs/utils/logging/index.d.ts +1 -0
  132. package/dist/cjs/utils/logging/index.js +5 -0
  133. package/dist/cjs/utils/network/index.d.ts +1 -0
  134. package/dist/cjs/utils/network/index.js +17 -0
  135. package/dist/cjs/utils/network/url.utils.d.ts +20 -0
  136. package/dist/cjs/utils/network/url.utils.js +172 -0
  137. package/dist/cjs/utils/security/index.d.ts +1 -0
  138. package/dist/cjs/utils/security/index.js +17 -0
  139. package/dist/cjs/utils/security/sanitize.utils.d.ts +32 -0
  140. package/dist/cjs/utils/security/sanitize.utils.js +319 -0
  141. package/dist/cjs/utils/validations/config-validations.utils.d.ts +42 -0
  142. package/dist/cjs/utils/validations/config-validations.utils.js +297 -0
  143. package/dist/cjs/utils/validations/event-validations.utils.d.ts +12 -0
  144. package/dist/cjs/utils/validations/event-validations.utils.js +30 -0
  145. package/dist/cjs/utils/validations/index.d.ts +5 -0
  146. package/dist/cjs/utils/validations/index.js +21 -0
  147. package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +22 -0
  148. package/dist/cjs/utils/validations/metadata-validations.utils.js +115 -0
  149. package/dist/cjs/utils/validations/type-guards.utils.d.ts +6 -0
  150. package/dist/cjs/utils/validations/type-guards.utils.js +31 -0
  151. package/dist/cjs/utils/validations/url-validations.utils.d.ts +15 -0
  152. package/dist/cjs/utils/validations/url-validations.utils.js +47 -0
  153. package/dist/esm/api.d.ts +46 -0
  154. package/dist/esm/api.js +183 -0
  155. package/dist/esm/app.constants.d.ts +1 -0
  156. package/dist/esm/app.constants.js +1 -0
  157. package/dist/esm/app.d.ts +59 -0
  158. package/dist/esm/app.js +268 -0
  159. package/dist/esm/app.types.d.ts +6 -0
  160. package/dist/esm/app.types.js +6 -0
  161. package/dist/esm/constants/api.constants.d.ts +4 -0
  162. package/dist/esm/constants/api.constants.js +14 -0
  163. package/dist/esm/constants/browser.constants.d.ts +3 -0
  164. package/dist/esm/constants/browser.constants.js +38 -0
  165. package/dist/esm/constants/index.d.ts +8 -0
  166. package/dist/esm/constants/index.js +8 -0
  167. package/dist/esm/constants/initialization.constants.d.ts +40 -0
  168. package/dist/esm/constants/initialization.constants.js +45 -0
  169. package/dist/esm/constants/limits.constants.d.ts +25 -0
  170. package/dist/esm/constants/limits.constants.js +37 -0
  171. package/dist/esm/constants/security.constants.d.ts +1 -0
  172. package/dist/esm/constants/security.constants.js +9 -0
  173. package/dist/esm/constants/storage.constants.d.ts +9 -0
  174. package/dist/esm/constants/storage.constants.js +11 -0
  175. package/dist/esm/constants/timing.constants.d.ts +22 -0
  176. package/dist/esm/constants/timing.constants.js +31 -0
  177. package/dist/esm/constants/validation.constants.d.ts +13 -0
  178. package/dist/esm/constants/validation.constants.js +28 -0
  179. package/dist/esm/handlers/click.handler.d.ts +17 -0
  180. package/dist/esm/handlers/click.handler.js +195 -0
  181. package/dist/esm/handlers/error.handler.d.ts +15 -0
  182. package/dist/esm/handlers/error.handler.js +93 -0
  183. package/dist/esm/handlers/network.handler.d.ts +16 -0
  184. package/dist/esm/handlers/network.handler.js +132 -0
  185. package/dist/esm/handlers/page-view.handler.d.ts +15 -0
  186. package/dist/esm/handlers/page-view.handler.js +79 -0
  187. package/dist/esm/handlers/performance.handler.d.ts +19 -0
  188. package/dist/esm/handlers/performance.handler.js +218 -0
  189. package/dist/esm/handlers/scroll.handler.d.ts +16 -0
  190. package/dist/esm/handlers/scroll.handler.js +134 -0
  191. package/dist/esm/handlers/session.handler.d.ts +29 -0
  192. package/dist/esm/handlers/session.handler.js +353 -0
  193. package/dist/esm/integrations/google-analytics.integration.d.ts +18 -0
  194. package/dist/esm/integrations/google-analytics.integration.js +155 -0
  195. package/dist/esm/listeners/activity-listener-manager.d.ts +8 -0
  196. package/dist/esm/listeners/activity-listener-manager.js +28 -0
  197. package/dist/esm/listeners/index.d.ts +6 -0
  198. package/dist/esm/listeners/index.js +5 -0
  199. package/dist/esm/listeners/input-listener-managers.d.ts +15 -0
  200. package/dist/esm/listeners/input-listener-managers.js +53 -0
  201. package/dist/esm/listeners/listeners.types.d.ts +4 -0
  202. package/dist/esm/listeners/listeners.types.js +1 -0
  203. package/dist/esm/listeners/touch-listener-manager.d.ts +10 -0
  204. package/dist/esm/listeners/touch-listener-manager.js +52 -0
  205. package/dist/esm/listeners/unload-listener-manager.d.ts +8 -0
  206. package/dist/esm/listeners/unload-listener-manager.js +26 -0
  207. package/dist/esm/listeners/visibility-listener-manager.d.ts +12 -0
  208. package/dist/esm/listeners/visibility-listener-manager.js +79 -0
  209. package/dist/esm/managers/api.manager.d.ts +3 -0
  210. package/dist/esm/managers/api.manager.js +10 -0
  211. package/dist/esm/managers/config.manager.d.ts +7 -0
  212. package/dist/esm/managers/config.manager.js +90 -0
  213. package/dist/esm/managers/cross-tab-session.manager.d.ts +170 -0
  214. package/dist/esm/managers/cross-tab-session.manager.js +726 -0
  215. package/dist/esm/managers/event.manager.d.ts +61 -0
  216. package/dist/esm/managers/event.manager.js +504 -0
  217. package/dist/esm/managers/sampling.manager.d.ts +8 -0
  218. package/dist/esm/managers/sampling.manager.js +49 -0
  219. package/dist/esm/managers/sender.manager.d.ts +46 -0
  220. package/dist/esm/managers/sender.manager.js +300 -0
  221. package/dist/esm/managers/session-recovery.manager.d.ts +65 -0
  222. package/dist/esm/managers/session-recovery.manager.js +233 -0
  223. package/dist/esm/managers/session.manager.d.ts +72 -0
  224. package/dist/esm/managers/session.manager.js +583 -0
  225. package/dist/esm/managers/state.manager.d.ts +5 -0
  226. package/dist/esm/managers/state.manager.js +19 -0
  227. package/dist/esm/managers/storage.manager.d.ts +10 -0
  228. package/dist/esm/managers/storage.manager.js +77 -0
  229. package/dist/esm/managers/tags.manager.d.ts +12 -0
  230. package/dist/esm/managers/tags.manager.js +285 -0
  231. package/dist/esm/managers/user.manager.d.ts +7 -0
  232. package/dist/esm/managers/user.manager.js +18 -0
  233. package/dist/esm/public-api.d.ts +1 -0
  234. package/dist/esm/public-api.js +1 -0
  235. package/dist/esm/types/api.types.d.ts +21 -0
  236. package/dist/esm/types/api.types.js +22 -0
  237. package/dist/esm/types/common.types.d.ts +1 -0
  238. package/dist/esm/types/common.types.js +1 -0
  239. package/dist/esm/types/config.types.d.ts +104 -0
  240. package/dist/esm/types/config.types.js +1 -0
  241. package/dist/esm/types/device.types.d.ts +6 -0
  242. package/dist/esm/types/device.types.js +7 -0
  243. package/dist/esm/types/event.types.d.ts +104 -0
  244. package/dist/esm/types/event.types.js +22 -0
  245. package/dist/esm/types/index.d.ts +13 -0
  246. package/dist/esm/types/index.js +13 -0
  247. package/dist/esm/types/log.types.d.ts +4 -0
  248. package/dist/esm/types/log.types.js +1 -0
  249. package/dist/esm/types/mode.types.d.ts +7 -0
  250. package/dist/esm/types/mode.types.js +8 -0
  251. package/dist/esm/types/queue.types.d.ts +23 -0
  252. package/dist/esm/types/queue.types.js +1 -0
  253. package/dist/esm/types/session.types.d.ts +65 -0
  254. package/dist/esm/types/session.types.js +1 -0
  255. package/dist/esm/types/state.types.d.ts +12 -0
  256. package/dist/esm/types/state.types.js +1 -0
  257. package/dist/esm/types/tag.types.d.ts +43 -0
  258. package/dist/esm/types/tag.types.js +28 -0
  259. package/dist/esm/types/validation-error.types.d.ts +42 -0
  260. package/dist/esm/types/validation-error.types.js +59 -0
  261. package/dist/esm/types/web-vitals.types.d.ts +6 -0
  262. package/dist/esm/types/web-vitals.types.js +1 -0
  263. package/dist/esm/types/window.types.d.ts +17 -0
  264. package/dist/esm/types/window.types.js +1 -0
  265. package/dist/esm/utils/browser/device-detector.utils.d.ts +6 -0
  266. package/dist/esm/utils/browser/device-detector.utils.js +67 -0
  267. package/dist/esm/utils/browser/index.d.ts +2 -0
  268. package/dist/esm/utils/browser/index.js +2 -0
  269. package/dist/esm/utils/browser/utm-params.utils.d.ts +6 -0
  270. package/dist/esm/utils/browser/utm-params.utils.js +33 -0
  271. package/dist/esm/utils/data/index.d.ts +1 -0
  272. package/dist/esm/utils/data/index.js +1 -0
  273. package/dist/esm/utils/data/uuid.utils.d.ts +5 -0
  274. package/dist/esm/utils/data/uuid.utils.js +14 -0
  275. package/dist/esm/utils/index.d.ts +6 -0
  276. package/dist/esm/utils/index.js +6 -0
  277. package/dist/esm/utils/logging/debug-logger.utils.d.ts +56 -0
  278. package/dist/esm/utils/logging/debug-logger.utils.js +136 -0
  279. package/dist/esm/utils/logging/index.d.ts +1 -0
  280. package/dist/esm/utils/logging/index.js +1 -0
  281. package/dist/esm/utils/network/index.d.ts +1 -0
  282. package/dist/esm/utils/network/index.js +1 -0
  283. package/dist/esm/utils/network/url.utils.d.ts +20 -0
  284. package/dist/esm/utils/network/url.utils.js +166 -0
  285. package/dist/esm/utils/security/index.d.ts +1 -0
  286. package/dist/esm/utils/security/index.js +1 -0
  287. package/dist/esm/utils/security/sanitize.utils.d.ts +32 -0
  288. package/dist/esm/utils/security/sanitize.utils.js +311 -0
  289. package/dist/esm/utils/validations/config-validations.utils.d.ts +42 -0
  290. package/dist/esm/utils/validations/config-validations.utils.js +289 -0
  291. package/dist/esm/utils/validations/event-validations.utils.d.ts +12 -0
  292. package/dist/esm/utils/validations/event-validations.utils.js +26 -0
  293. package/dist/esm/utils/validations/index.d.ts +5 -0
  294. package/dist/esm/utils/validations/index.js +5 -0
  295. package/dist/esm/utils/validations/metadata-validations.utils.d.ts +22 -0
  296. package/dist/esm/utils/validations/metadata-validations.utils.js +110 -0
  297. package/dist/esm/utils/validations/type-guards.utils.d.ts +6 -0
  298. package/dist/esm/utils/validations/type-guards.utils.js +27 -0
  299. package/dist/esm/utils/validations/url-validations.utils.d.ts +15 -0
  300. package/dist/esm/utils/validations/url-validations.utils.js +42 -0
  301. 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
+ }