@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,583 @@
1
+ import { DEFAULT_MOTION_THRESHOLD, DEFAULT_SESSION_TIMEOUT_MS, DEFAULT_THROTTLE_DELAY_MS, DEFAULT_VISIBILITY_TIMEOUT_MS, } from '../constants';
2
+ import { DeviceType, EventType } from '../types';
3
+ import { generateUUID, getDeviceType } from '../utils';
4
+ import { debugLog } from '../utils/logging';
5
+ import { ActivityListenerManager, KeyboardListenerManager, MouseListenerManager, TouchListenerManager, UnloadListenerManager, VisibilityListenerManager, } from '../listeners';
6
+ import { StateManager } from './state.manager';
7
+ import { SessionRecoveryManager } from './session-recovery.manager';
8
+ export class SessionManager extends StateManager {
9
+ constructor(onActivity, onInactivity, eventManager, storageManager, sessionEndConfig) {
10
+ super();
11
+ this.eventManager = null;
12
+ this.storageManager = null;
13
+ this.listenerManagers = [];
14
+ // Recovery manager
15
+ this.recoveryManager = null;
16
+ this.isSessionActive = false;
17
+ this.lastActivityTime = 0;
18
+ this.inactivityTimer = null;
19
+ this.sessionStartTime = 0;
20
+ this.throttleTimeout = null;
21
+ // Track visibility change timeout for proper cleanup
22
+ this.visibilityChangeTimeout = null;
23
+ // Session End Management
24
+ this.pendingSessionEnd = false;
25
+ this.sessionEndPromise = null;
26
+ this.sessionEndLock = Promise.resolve({
27
+ success: true,
28
+ reason: 'manual_stop',
29
+ timestamp: Date.now(),
30
+ eventsFlushed: 0,
31
+ method: 'async',
32
+ });
33
+ this.cleanupHandlers = [];
34
+ this.sessionEndReason = null;
35
+ this.sessionEndPriority = {
36
+ page_unload: 4,
37
+ manual_stop: 3,
38
+ orphaned_cleanup: 2,
39
+ inactivity: 1,
40
+ tab_closed: 0,
41
+ };
42
+ this.sessionEndStats = {
43
+ totalSessionEnds: 0,
44
+ successfulEnds: 0,
45
+ failedEnds: 0,
46
+ duplicatePrevented: 0,
47
+ reasonCounts: {
48
+ inactivity: 0,
49
+ page_unload: 0,
50
+ manual_stop: 0,
51
+ orphaned_cleanup: 0,
52
+ tab_closed: 0,
53
+ },
54
+ };
55
+ // Session health monitoring
56
+ this.sessionHealth = {
57
+ recoveryAttempts: 0,
58
+ sessionTimeouts: 0,
59
+ crossTabConflicts: 0,
60
+ lastHealthCheck: Date.now(),
61
+ };
62
+ this.handleActivity = () => {
63
+ const now = Date.now();
64
+ if (now - this.lastActivityTime < this.config.throttleDelay) {
65
+ return;
66
+ }
67
+ this.lastActivityTime = now;
68
+ if (this.isSessionActive) {
69
+ // Always call onActivity to update cross-tab session activity
70
+ this.onActivity();
71
+ this.resetInactivityTimer();
72
+ }
73
+ else {
74
+ if (this.throttleTimeout) {
75
+ clearTimeout(this.throttleTimeout);
76
+ this.throttleTimeout = null;
77
+ }
78
+ this.throttleTimeout = window.setTimeout(() => {
79
+ this.onActivity();
80
+ this.throttleTimeout = null;
81
+ }, 100);
82
+ }
83
+ };
84
+ this.handleInactivity = () => {
85
+ // Track session timeout for health monitoring
86
+ this.trackSessionHealth('timeout');
87
+ this.onInactivity();
88
+ };
89
+ this.handleVisibilityChange = () => {
90
+ if (document.hidden) {
91
+ if (this.isSessionActive) {
92
+ if (this.inactivityTimer) {
93
+ clearTimeout(this.inactivityTimer);
94
+ this.inactivityTimer = null;
95
+ }
96
+ this.inactivityTimer = window.setTimeout(this.handleInactivity, this.config.visibilityTimeout);
97
+ }
98
+ }
99
+ else {
100
+ this.handleActivity();
101
+ }
102
+ };
103
+ this.resetInactivityTimer = () => {
104
+ if (this.inactivityTimer) {
105
+ clearTimeout(this.inactivityTimer);
106
+ this.inactivityTimer = null;
107
+ }
108
+ if (this.isSessionActive) {
109
+ this.inactivityTimer = window.setTimeout(() => {
110
+ this.handleInactivity();
111
+ }, this.config.timeout);
112
+ }
113
+ };
114
+ this.config = {
115
+ throttleDelay: DEFAULT_THROTTLE_DELAY_MS,
116
+ visibilityTimeout: DEFAULT_VISIBILITY_TIMEOUT_MS,
117
+ motionThreshold: DEFAULT_MOTION_THRESHOLD,
118
+ timeout: this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT_MS,
119
+ };
120
+ this.sessionEndConfig = {
121
+ enablePageUnloadHandlers: true,
122
+ syncTimeoutMs: 1000,
123
+ maxRetries: 2,
124
+ debugMode: false,
125
+ ...sessionEndConfig,
126
+ };
127
+ this.onActivity = onActivity;
128
+ this.onInactivity = onInactivity;
129
+ this.eventManager = eventManager ?? null;
130
+ this.storageManager = storageManager ?? null;
131
+ this.deviceCapabilities = this.detectDeviceCapabilities();
132
+ this.initializeRecoveryManager();
133
+ this.initializeListenerManagers();
134
+ this.setupAllListeners();
135
+ if (this.sessionEndConfig.enablePageUnloadHandlers) {
136
+ this.setupPageUnloadHandlers();
137
+ }
138
+ debugLog.debug('SessionManager', 'SessionManager initialized', {
139
+ sessionTimeout: this.config.timeout,
140
+ deviceCapabilities: this.deviceCapabilities,
141
+ unloadHandlersEnabled: this.sessionEndConfig.enablePageUnloadHandlers,
142
+ });
143
+ }
144
+ /**
145
+ * Initialize recovery manager
146
+ */
147
+ initializeRecoveryManager() {
148
+ if (!this.storageManager)
149
+ return;
150
+ const projectId = this.get('config')?.id;
151
+ if (!projectId)
152
+ return;
153
+ try {
154
+ // Initialize session recovery manager (always enabled)
155
+ this.recoveryManager = new SessionRecoveryManager(this.storageManager, projectId, this.eventManager ?? undefined);
156
+ debugLog.debug('SessionManager', 'Recovery manager initialized', { projectId });
157
+ }
158
+ catch (error) {
159
+ debugLog.error('SessionManager', 'Failed to initialize recovery manager', { error, projectId });
160
+ }
161
+ }
162
+ /**
163
+ * Store session context for recovery
164
+ */
165
+ storeSessionContextForRecovery() {
166
+ if (!this.recoveryManager)
167
+ return;
168
+ const sessionId = this.get('sessionId');
169
+ if (!sessionId)
170
+ return;
171
+ const sessionContext = {
172
+ sessionId,
173
+ startTime: this.sessionStartTime,
174
+ lastActivity: this.lastActivityTime,
175
+ tabCount: 1, // This will be updated by cross-tab manager
176
+ recoveryAttempts: 0,
177
+ metadata: {
178
+ userAgent: navigator.userAgent,
179
+ pageUrl: this.get('pageUrl'),
180
+ },
181
+ };
182
+ this.recoveryManager.storeSessionContextForRecovery(sessionContext);
183
+ }
184
+ startSession() {
185
+ const now = Date.now();
186
+ // Attempt session recovery first
187
+ let sessionId = '';
188
+ let wasRecovered = false;
189
+ if (this.recoveryManager?.hasRecoverableSession()) {
190
+ const recoveryResult = this.recoveryManager.attemptSessionRecovery();
191
+ if (recoveryResult.recovered && recoveryResult.recoveredSessionId) {
192
+ sessionId = recoveryResult.recoveredSessionId;
193
+ wasRecovered = true;
194
+ // Track session recovery for health monitoring
195
+ this.trackSessionHealth('recovery');
196
+ // Update session timing from recovery context
197
+ if (recoveryResult.context) {
198
+ this.sessionStartTime = recoveryResult.context.startTime;
199
+ this.lastActivityTime = now;
200
+ }
201
+ else {
202
+ this.sessionStartTime = now;
203
+ this.lastActivityTime = now;
204
+ }
205
+ debugLog.info('SessionManager', 'Session successfully recovered', {
206
+ sessionId,
207
+ recoveryAttempts: this.sessionHealth.recoveryAttempts,
208
+ });
209
+ }
210
+ }
211
+ // If no recovery, create new session
212
+ if (!wasRecovered) {
213
+ sessionId = generateUUID();
214
+ this.sessionStartTime = now;
215
+ this.lastActivityTime = now;
216
+ debugLog.info('SessionManager', 'New session started', { sessionId });
217
+ }
218
+ this.isSessionActive = true;
219
+ this.resetInactivityTimer();
220
+ // Store session context for future recovery
221
+ this.storeSessionContextForRecovery();
222
+ return { sessionId, recovered: wasRecovered };
223
+ }
224
+ endSession() {
225
+ if (this.sessionStartTime === 0) {
226
+ return 0;
227
+ }
228
+ const durationMs = Date.now() - this.sessionStartTime;
229
+ this.sessionStartTime = 0;
230
+ this.isSessionActive = false;
231
+ if (this.inactivityTimer) {
232
+ clearTimeout(this.inactivityTimer);
233
+ this.inactivityTimer = null;
234
+ }
235
+ return durationMs;
236
+ }
237
+ destroy() {
238
+ this.clearTimers();
239
+ this.cleanupAllListeners();
240
+ this.resetState();
241
+ this.cleanupHandlers.forEach((cleanup) => cleanup());
242
+ this.cleanupHandlers = [];
243
+ this.pendingSessionEnd = false;
244
+ this.sessionEndPromise = null;
245
+ this.sessionEndLock = Promise.resolve({
246
+ success: true,
247
+ reason: 'manual_stop',
248
+ timestamp: Date.now(),
249
+ eventsFlushed: 0,
250
+ method: 'async',
251
+ });
252
+ if (this.recoveryManager) {
253
+ this.recoveryManager.cleanupOldRecoveryAttempts();
254
+ this.recoveryManager = null;
255
+ }
256
+ }
257
+ detectDeviceCapabilities() {
258
+ const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
259
+ const hasMouse = window.matchMedia('(pointer: fine)').matches;
260
+ const hasKeyboard = !window.matchMedia('(pointer: coarse)').matches;
261
+ const isMobile = getDeviceType() === DeviceType.Mobile;
262
+ return { hasTouch, hasMouse, hasKeyboard, isMobile };
263
+ }
264
+ initializeListenerManagers() {
265
+ this.listenerManagers.push(new ActivityListenerManager(this.handleActivity));
266
+ if (this.deviceCapabilities.hasTouch) {
267
+ this.listenerManagers.push(new TouchListenerManager(this.handleActivity, this.config.motionThreshold));
268
+ }
269
+ if (this.deviceCapabilities.hasMouse) {
270
+ this.listenerManagers.push(new MouseListenerManager(this.handleActivity));
271
+ }
272
+ if (this.deviceCapabilities.hasKeyboard) {
273
+ this.listenerManagers.push(new KeyboardListenerManager(this.handleActivity));
274
+ }
275
+ this.listenerManagers.push(new VisibilityListenerManager(this.handleActivity, this.handleVisibilityChange, this.deviceCapabilities.isMobile));
276
+ this.listenerManagers.push(new UnloadListenerManager(this.handleInactivity));
277
+ }
278
+ setupAllListeners() {
279
+ this.listenerManagers.forEach((manager) => manager.setup());
280
+ }
281
+ cleanupAllListeners() {
282
+ this.listenerManagers.forEach((manager) => manager.cleanup());
283
+ }
284
+ clearTimers() {
285
+ if (this.inactivityTimer) {
286
+ clearTimeout(this.inactivityTimer);
287
+ this.inactivityTimer = null;
288
+ }
289
+ if (this.throttleTimeout) {
290
+ clearTimeout(this.throttleTimeout);
291
+ this.throttleTimeout = null;
292
+ }
293
+ }
294
+ resetState() {
295
+ this.isSessionActive = false;
296
+ this.lastActivityTime = 0;
297
+ this.sessionStartTime = 0;
298
+ }
299
+ clearInactivityTimer() {
300
+ if (this.inactivityTimer) {
301
+ clearTimeout(this.inactivityTimer);
302
+ this.inactivityTimer = null;
303
+ }
304
+ }
305
+ shouldProceedWithSessionEnd(reason) {
306
+ return !this.sessionEndReason || this.sessionEndPriority[reason] > this.sessionEndPriority[this.sessionEndReason];
307
+ }
308
+ async waitForCompletion() {
309
+ if (this.sessionEndPromise) {
310
+ return await this.sessionEndPromise;
311
+ }
312
+ return {
313
+ success: false,
314
+ reason: 'inactivity',
315
+ timestamp: Date.now(),
316
+ eventsFlushed: 0,
317
+ method: 'async',
318
+ };
319
+ }
320
+ async endSessionManaged(reason) {
321
+ return (this.sessionEndLock = this.sessionEndLock.then(async () => {
322
+ this.sessionEndStats.totalSessionEnds++;
323
+ this.sessionEndStats.reasonCounts[reason]++;
324
+ if (this.pendingSessionEnd) {
325
+ this.sessionEndStats.duplicatePrevented++;
326
+ debugLog.debug('SessionManager', 'Session end already pending, waiting for completion', { reason });
327
+ return this.waitForCompletion();
328
+ }
329
+ if (!this.shouldProceedWithSessionEnd(reason)) {
330
+ if (this.sessionEndConfig.debugMode) {
331
+ debugLog.debug('SessionManager', `Session end skipped due to lower priority. Current: ${this.sessionEndReason}, Requested: ${reason}`);
332
+ }
333
+ return {
334
+ success: false,
335
+ reason,
336
+ timestamp: Date.now(),
337
+ eventsFlushed: 0,
338
+ method: 'async',
339
+ };
340
+ }
341
+ this.sessionEndReason = reason;
342
+ this.pendingSessionEnd = true;
343
+ this.sessionEndPromise = this.performSessionEnd(reason, 'async');
344
+ try {
345
+ const result = await this.sessionEndPromise;
346
+ return result;
347
+ }
348
+ finally {
349
+ this.pendingSessionEnd = false;
350
+ this.sessionEndPromise = null;
351
+ this.sessionEndReason = null;
352
+ }
353
+ }));
354
+ }
355
+ endSessionSafely(reason, options) {
356
+ const shouldUseSync = options?.forceSync ?? (options?.allowSync && ['page_unload', 'tab_closed'].includes(reason));
357
+ if (shouldUseSync) {
358
+ return this.endSessionManagedSync(reason);
359
+ }
360
+ return this.endSessionManaged(reason);
361
+ }
362
+ isPendingSessionEnd() {
363
+ return this.pendingSessionEnd;
364
+ }
365
+ /**
366
+ * Track session health events for monitoring and diagnostics
367
+ */
368
+ trackSessionHealth(event) {
369
+ const now = Date.now();
370
+ // Update health counters
371
+ switch (event) {
372
+ case 'recovery':
373
+ this.sessionHealth.recoveryAttempts++;
374
+ break;
375
+ case 'timeout':
376
+ this.sessionHealth.sessionTimeouts++;
377
+ break;
378
+ case 'conflict':
379
+ this.sessionHealth.crossTabConflicts++;
380
+ break;
381
+ }
382
+ this.sessionHealth.lastHealthCheck = now;
383
+ // Send health degradation event if recovery attempts are high
384
+ if (this.sessionHealth.recoveryAttempts > 3 && this.eventManager) {
385
+ this.eventManager.track({
386
+ type: EventType.CUSTOM,
387
+ custom_event: {
388
+ name: 'session_health_degraded',
389
+ metadata: {
390
+ ...this.sessionHealth,
391
+ event_trigger: event,
392
+ },
393
+ },
394
+ });
395
+ if (this.sessionEndConfig.debugMode) {
396
+ debugLog.warn('SessionManager', `Session health degraded: ${this.sessionHealth.recoveryAttempts} recovery attempts`);
397
+ }
398
+ }
399
+ if (this.sessionEndConfig.debugMode) {
400
+ debugLog.debug('SessionManager', `Session health event tracked: ${event}`);
401
+ }
402
+ }
403
+ async performSessionEnd(reason, method) {
404
+ const timestamp = Date.now();
405
+ let eventsFlushed = 0;
406
+ try {
407
+ debugLog.info('SessionManager', 'Starting session end', { method, reason, timestamp });
408
+ if (this.eventManager) {
409
+ this.eventManager.track({
410
+ type: EventType.SESSION_END,
411
+ session_end_reason: reason,
412
+ });
413
+ eventsFlushed = this.eventManager.getQueueLength();
414
+ const flushResult = await this.eventManager.flushImmediately();
415
+ this.cleanupSession();
416
+ const result = {
417
+ success: flushResult,
418
+ reason,
419
+ timestamp,
420
+ eventsFlushed,
421
+ method,
422
+ };
423
+ if (flushResult) {
424
+ this.sessionEndStats.successfulEnds++;
425
+ }
426
+ else {
427
+ this.sessionEndStats.failedEnds++;
428
+ }
429
+ return result;
430
+ }
431
+ this.cleanupSession();
432
+ const result = {
433
+ success: true,
434
+ reason,
435
+ timestamp,
436
+ eventsFlushed: 0,
437
+ method,
438
+ };
439
+ this.sessionEndStats.successfulEnds++;
440
+ return result;
441
+ }
442
+ catch (error) {
443
+ this.sessionEndStats.failedEnds++;
444
+ debugLog.error('SessionManager', 'Session end failed', { error, reason, method });
445
+ this.cleanupSession();
446
+ return {
447
+ success: false,
448
+ reason,
449
+ timestamp,
450
+ eventsFlushed,
451
+ method,
452
+ };
453
+ }
454
+ }
455
+ cleanupSession() {
456
+ this.endSession();
457
+ this.clearTimers();
458
+ this.set('sessionId', null);
459
+ this.set('hasStartSession', false);
460
+ }
461
+ endSessionManagedSync(reason) {
462
+ this.sessionEndStats.totalSessionEnds++;
463
+ this.sessionEndStats.reasonCounts[reason]++;
464
+ if (this.pendingSessionEnd) {
465
+ this.sessionEndStats.duplicatePrevented++;
466
+ debugLog.warn('SessionManager', 'Sync session end called while async end pending', { reason });
467
+ }
468
+ if (!this.shouldProceedWithSessionEnd(reason)) {
469
+ if (this.sessionEndConfig.debugMode) {
470
+ debugLog.debug('SessionManager', `Sync session end skipped due to lower priority. Current: ${this.sessionEndReason}, Requested: ${reason}`);
471
+ }
472
+ return {
473
+ success: false,
474
+ reason,
475
+ timestamp: Date.now(),
476
+ eventsFlushed: 0,
477
+ method: 'sync',
478
+ };
479
+ }
480
+ this.sessionEndReason = reason;
481
+ this.pendingSessionEnd = true;
482
+ try {
483
+ return this.performSessionEndSync(reason);
484
+ }
485
+ finally {
486
+ this.pendingSessionEnd = false;
487
+ this.sessionEndPromise = null;
488
+ this.sessionEndReason = null;
489
+ }
490
+ }
491
+ performSessionEndSync(reason) {
492
+ const timestamp = Date.now();
493
+ let eventsFlushed = 0;
494
+ try {
495
+ if (this.eventManager) {
496
+ this.eventManager.track({
497
+ type: EventType.SESSION_END,
498
+ session_end_reason: reason,
499
+ });
500
+ eventsFlushed = this.eventManager.getQueueLength();
501
+ const success = this.eventManager.flushImmediatelySync();
502
+ this.cleanupSession();
503
+ const result = {
504
+ success,
505
+ reason,
506
+ timestamp,
507
+ eventsFlushed,
508
+ method: 'sync',
509
+ };
510
+ if (success) {
511
+ this.sessionEndStats.successfulEnds++;
512
+ }
513
+ else {
514
+ this.sessionEndStats.failedEnds++;
515
+ }
516
+ return result;
517
+ }
518
+ this.cleanupSession();
519
+ const result = {
520
+ success: true,
521
+ reason,
522
+ timestamp,
523
+ eventsFlushed: 0,
524
+ method: 'sync',
525
+ };
526
+ this.sessionEndStats.successfulEnds++;
527
+ return result;
528
+ }
529
+ catch (error) {
530
+ this.sessionEndStats.failedEnds++;
531
+ this.cleanupSession();
532
+ debugLog.error('SessionManager', 'Sync session end failed', { error, reason });
533
+ return {
534
+ success: false,
535
+ reason,
536
+ timestamp,
537
+ eventsFlushed,
538
+ method: 'sync',
539
+ };
540
+ }
541
+ }
542
+ setupPageUnloadHandlers() {
543
+ let unloadHandled = false;
544
+ const handlePageUnload = () => {
545
+ if (unloadHandled || !this.get('sessionId')) {
546
+ return;
547
+ }
548
+ unloadHandled = true;
549
+ this.clearInactivityTimer();
550
+ this.endSessionSafely('page_unload', { forceSync: true });
551
+ };
552
+ // Primary handler for modern browsers
553
+ const beforeUnloadHandler = () => {
554
+ handlePageUnload();
555
+ };
556
+ // Fallback for older browsers and mobile Safari
557
+ const pageHideHandler = (event) => {
558
+ if (!event.persisted) {
559
+ handlePageUnload();
560
+ }
561
+ };
562
+ // Delayed handler for visibility changes (gives time for page transitions)
563
+ const visibilityChangeHandler = () => {
564
+ if (document.visibilityState === 'hidden' && this.get('sessionId') && !unloadHandled) {
565
+ this.visibilityChangeTimeout = window.setTimeout(() => {
566
+ if (document.visibilityState === 'hidden' && this.get('sessionId') && !unloadHandled) {
567
+ handlePageUnload();
568
+ }
569
+ this.visibilityChangeTimeout = null;
570
+ }, 1000);
571
+ }
572
+ };
573
+ window.addEventListener('beforeunload', beforeUnloadHandler);
574
+ window.addEventListener('pagehide', pageHideHandler);
575
+ document.addEventListener('visibilitychange', visibilityChangeHandler);
576
+ this.cleanupHandlers.push(() => window.removeEventListener('beforeunload', beforeUnloadHandler), () => window.removeEventListener('pagehide', pageHideHandler), () => document.removeEventListener('visibilitychange', visibilityChangeHandler), () => {
577
+ if (this.visibilityChangeTimeout) {
578
+ clearTimeout(this.visibilityChangeTimeout);
579
+ this.visibilityChangeTimeout = null;
580
+ }
581
+ });
582
+ }
583
+ }
@@ -0,0 +1,5 @@
1
+ import { State } from '../types';
2
+ export declare abstract class StateManager {
3
+ protected get<T extends keyof State>(key: T): State[T];
4
+ protected set<T extends keyof State>(key: T, value: State[T]): void;
5
+ }
@@ -0,0 +1,19 @@
1
+ import { debugLog } from '../utils/logging';
2
+ const globalState = {};
3
+ export class StateManager {
4
+ get(key) {
5
+ return globalState[key];
6
+ }
7
+ set(key, value) {
8
+ const oldValue = globalState[key];
9
+ globalState[key] = value;
10
+ // Log critical state changes
11
+ if (key === 'sessionId' || key === 'config' || key === 'hasStartSession') {
12
+ debugLog.debug('StateManager', 'Critical state updated', {
13
+ key,
14
+ oldValue: key === 'config' ? !!oldValue : oldValue,
15
+ newValue: key === 'config' ? !!value : value,
16
+ });
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ export declare class StorageManager {
2
+ private readonly storage;
3
+ private readonly fallbackStorage;
4
+ private storageAvailable;
5
+ constructor();
6
+ getItem(key: string): string | null;
7
+ setItem(key: string, value: string): void;
8
+ removeItem(key: string): void;
9
+ private init;
10
+ }
@@ -0,0 +1,77 @@
1
+ import { debugLog } from '../utils/logging';
2
+ export class StorageManager {
3
+ constructor() {
4
+ this.storage = null;
5
+ this.fallbackStorage = new Map();
6
+ this.storageAvailable = false;
7
+ this.storage = this.init();
8
+ this.storageAvailable = this.storage !== null;
9
+ if (!this.storageAvailable) {
10
+ debugLog.warn('StorageManager', 'localStorage not available, using memory fallback');
11
+ }
12
+ }
13
+ getItem(key) {
14
+ if (!this.storageAvailable) {
15
+ return this.fallbackStorage.get(key) ?? null;
16
+ }
17
+ try {
18
+ if (this.storage) {
19
+ return this.storage.getItem(key);
20
+ }
21
+ return this.fallbackStorage.get(key) ?? null;
22
+ }
23
+ catch (error) {
24
+ debugLog.warn('StorageManager', 'Storage getItem failed, using memory fallback', { key, error });
25
+ this.storageAvailable = false;
26
+ return this.fallbackStorage.get(key) ?? null;
27
+ }
28
+ }
29
+ setItem(key, value) {
30
+ if (!this.storageAvailable) {
31
+ this.fallbackStorage.set(key, value);
32
+ return;
33
+ }
34
+ try {
35
+ if (this.storage) {
36
+ this.storage.setItem(key, value);
37
+ return;
38
+ }
39
+ this.fallbackStorage.set(key, value);
40
+ }
41
+ catch (error) {
42
+ debugLog.warn('StorageManager', 'Storage setItem failed, using memory fallback', { key, error });
43
+ this.storageAvailable = false;
44
+ this.fallbackStorage.set(key, value);
45
+ }
46
+ }
47
+ removeItem(key) {
48
+ if (!this.storageAvailable) {
49
+ this.fallbackStorage.delete(key);
50
+ return;
51
+ }
52
+ try {
53
+ if (this.storage) {
54
+ this.storage.removeItem(key);
55
+ return;
56
+ }
57
+ this.fallbackStorage.delete(key);
58
+ }
59
+ catch (error) {
60
+ debugLog.warn('StorageManager', 'Storage removeItem failed, using memory fallback', { key, error });
61
+ this.storageAvailable = false;
62
+ this.fallbackStorage.delete(key);
63
+ }
64
+ }
65
+ init() {
66
+ try {
67
+ const test = '__storage_test__';
68
+ const storage = window['localStorage'];
69
+ storage.setItem(test, test);
70
+ storage.removeItem(test);
71
+ return storage;
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ }