@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,730 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CrossTabSessionManager = void 0;
4
+ const constants_1 = require("../constants");
5
+ const storage_constants_1 = require("../constants/storage.constants");
6
+ const utils_1 = require("../utils");
7
+ const logging_1 = require("../utils/logging");
8
+ const state_manager_1 = require("./state.manager");
9
+ class CrossTabSessionManager extends state_manager_1.StateManager {
10
+ constructor(storageManager, projectId, config, callbacks) {
11
+ super();
12
+ this.callbacks = callbacks;
13
+ this.leaderTabId = null;
14
+ this.isTabLeader = false;
15
+ this.heartbeatInterval = null;
16
+ this.electionTimeout = null;
17
+ this.cleanupTimeout = null;
18
+ this.sessionEnded = false;
19
+ // Additional timeout tracking for proper cleanup
20
+ this.fallbackLeadershipTimeout = null;
21
+ this.electionDelayTimeout = null;
22
+ this.tabInfoCleanupTimeout = null;
23
+ this.closingAnnouncementTimeout = null;
24
+ this.leaderHealthCheckInterval = null;
25
+ this.lastHeartbeatSent = 0;
26
+ this.storageManager = storageManager;
27
+ this.projectId = projectId;
28
+ this.tabId = (0, utils_1.generateUUID)();
29
+ this.config = {
30
+ tabHeartbeatIntervalMs: constants_1.TAB_HEARTBEAT_INTERVAL_MS,
31
+ tabElectionTimeoutMs: constants_1.TAB_ELECTION_TIMEOUT_MS,
32
+ debugMode: (this.get('config')?.mode === 'qa' || this.get('config')?.mode === 'debug') ?? false,
33
+ ...config,
34
+ };
35
+ this.tabInfo = {
36
+ id: this.tabId,
37
+ lastHeartbeat: Date.now(),
38
+ isLeader: false,
39
+ sessionId: '',
40
+ startTime: Date.now(),
41
+ };
42
+ this.broadcastChannel = this.initializeBroadcastChannel();
43
+ this.initialize();
44
+ }
45
+ /**
46
+ * Initialize BroadcastChannel if supported
47
+ */
48
+ initializeBroadcastChannel() {
49
+ if (!this.isBroadcastChannelSupported()) {
50
+ return null;
51
+ }
52
+ try {
53
+ const channel = new BroadcastChannel((0, storage_constants_1.BROADCAST_CHANNEL_NAME)(this.projectId));
54
+ this.setupBroadcastListeners(channel);
55
+ return channel;
56
+ }
57
+ catch (error) {
58
+ if (this.config.debugMode) {
59
+ logging_1.debugLog.warn('CrossTabSession', 'Failed to initialize BroadcastChannel', { error });
60
+ }
61
+ return null;
62
+ }
63
+ }
64
+ /**
65
+ * Initialize the cross-tab session manager
66
+ */
67
+ initialize() {
68
+ if (!this.broadcastChannel) {
69
+ // Fallback to single-tab behavior when BroadcastChannel is not available
70
+ this.becomeLeader(); // This will create a session and set up the tab as leader
71
+ return;
72
+ }
73
+ // Check for existing session
74
+ const existingSession = this.getStoredSessionContext();
75
+ if (existingSession) {
76
+ // Try to join existing session
77
+ this.tryJoinExistingSession(existingSession);
78
+ }
79
+ else {
80
+ // Start new session leadership election
81
+ this.startLeaderElection();
82
+ }
83
+ // Start heartbeat
84
+ this.startHeartbeat();
85
+ // Fallback mechanism: only in multi-tab scenarios (when BroadcastChannel is available)
86
+ if (this.broadcastChannel) {
87
+ this.setupLeadershipFallback();
88
+ }
89
+ }
90
+ /**
91
+ * Check if this tab should be the session leader
92
+ */
93
+ tryJoinExistingSession(sessionContext) {
94
+ if (this.config.debugMode) {
95
+ logging_1.debugLog.debug('CrossTabSession', `Attempting to join existing session: ${sessionContext.sessionId}`);
96
+ }
97
+ // Set session info
98
+ this.tabInfo.sessionId = sessionContext.sessionId;
99
+ // Request leadership status from other tabs
100
+ this.requestLeadershipStatus();
101
+ // Update session context with new tab
102
+ sessionContext.tabCount += 1;
103
+ sessionContext.lastActivity = Date.now();
104
+ this.storeSessionContext(sessionContext);
105
+ // Notify activity callback
106
+ if (this.callbacks?.onTabActivity) {
107
+ this.callbacks.onTabActivity();
108
+ }
109
+ }
110
+ /**
111
+ * Request leadership status from other tabs
112
+ */
113
+ requestLeadershipStatus() {
114
+ if (!this.broadcastChannel)
115
+ return;
116
+ // Clear any existing election timeout
117
+ if (this.electionTimeout) {
118
+ clearTimeout(this.electionTimeout);
119
+ this.electionTimeout = null;
120
+ }
121
+ const message = {
122
+ type: 'election_request',
123
+ tabId: this.tabId,
124
+ sessionId: this.tabInfo.sessionId,
125
+ timestamp: Date.now(),
126
+ };
127
+ this.broadcastChannel.postMessage(message);
128
+ // Set timeout for election with additional random delay to prevent race conditions
129
+ const randomDelay = Math.floor(Math.random() * 500); // 0-500ms random delay
130
+ this.electionTimeout = window.setTimeout(() => {
131
+ // No response means we become the leader
132
+ if (!this.isTabLeader) {
133
+ this.becomeLeader();
134
+ }
135
+ }, this.config.tabElectionTimeoutMs + randomDelay);
136
+ }
137
+ /**
138
+ * Start leader election process with debouncing to prevent excessive elections
139
+ */
140
+ startLeaderElection() {
141
+ // Prevent multiple concurrent elections
142
+ if (this.electionTimeout) {
143
+ if (this.config.debugMode) {
144
+ logging_1.debugLog.debug('CrossTabSession', 'Leader election already in progress, skipping');
145
+ }
146
+ return;
147
+ }
148
+ if (this.config.debugMode) {
149
+ logging_1.debugLog.debug('CrossTabSession', 'Starting leader election');
150
+ }
151
+ // Add randomized delay to prevent thundering herd (optimized for performance tests)
152
+ const randomDelay = Math.floor(Math.random() * 50) + 10; // 10-60ms delay (reduced for better performance)
153
+ this.electionTimeout = window.setTimeout(() => {
154
+ this.electionTimeout = null;
155
+ this.requestLeadershipStatus();
156
+ }, randomDelay);
157
+ }
158
+ /**
159
+ * Become the session leader
160
+ */
161
+ becomeLeader() {
162
+ // Double-check we're not already a leader (race condition protection)
163
+ if (this.isTabLeader) {
164
+ return;
165
+ }
166
+ this.isTabLeader = true;
167
+ this.tabInfo.isLeader = true;
168
+ this.leaderTabId = this.tabId;
169
+ if (this.config.debugMode) {
170
+ logging_1.debugLog.debug('CrossTabSession', `Tab ${this.tabId} became session leader`);
171
+ }
172
+ // Clear any existing election timeout
173
+ if (this.electionTimeout) {
174
+ clearTimeout(this.electionTimeout);
175
+ this.electionTimeout = null;
176
+ }
177
+ // Start new session if we don't have one
178
+ if (!this.tabInfo.sessionId) {
179
+ const sessionId = (0, utils_1.generateUUID)();
180
+ this.tabInfo.sessionId = sessionId;
181
+ const sessionContext = {
182
+ sessionId,
183
+ startTime: Date.now(),
184
+ lastActivity: Date.now(),
185
+ tabCount: 1,
186
+ recoveryAttempts: 0,
187
+ };
188
+ this.storeSessionContext(sessionContext);
189
+ // Notify session start
190
+ if (this.callbacks?.onSessionStart) {
191
+ this.callbacks.onSessionStart(sessionId);
192
+ }
193
+ // Announce new session to other tabs
194
+ this.announceSessionStart(sessionId);
195
+ }
196
+ else {
197
+ // Update existing session context
198
+ const sessionContext = this.getStoredSessionContext();
199
+ if (sessionContext) {
200
+ sessionContext.lastActivity = Date.now();
201
+ this.storeSessionContext(sessionContext);
202
+ }
203
+ }
204
+ // Store tab info
205
+ this.storeTabInfo();
206
+ // Send immediate leadership announcement to ensure other tabs know
207
+ this.announceLeadership();
208
+ }
209
+ /**
210
+ * Announce session start to other tabs
211
+ */
212
+ announceSessionStart(sessionId) {
213
+ if (!this.broadcastChannel)
214
+ return;
215
+ const message = {
216
+ type: 'session_start',
217
+ tabId: this.tabId,
218
+ sessionId,
219
+ timestamp: Date.now(),
220
+ };
221
+ this.broadcastChannel.postMessage(message);
222
+ }
223
+ /**
224
+ * Announce leadership to other tabs
225
+ */
226
+ announceLeadership() {
227
+ if (!this.broadcastChannel || !this.tabInfo.sessionId)
228
+ return;
229
+ const message = {
230
+ type: 'election_response',
231
+ tabId: this.tabId,
232
+ sessionId: this.tabInfo.sessionId,
233
+ timestamp: Date.now(),
234
+ data: { isLeader: true },
235
+ };
236
+ this.broadcastChannel.postMessage(message);
237
+ }
238
+ /**
239
+ * Setup fallback mechanism to ensure a leader is always elected
240
+ */
241
+ setupLeadershipFallback() {
242
+ // Shorter fallback delay to ensure it works within test timeouts
243
+ const fallbackDelay = this.config.tabElectionTimeoutMs + 1500; // Election timeout + 1.5s buffer
244
+ this.fallbackLeadershipTimeout = window.setTimeout(() => {
245
+ // Check if we need to force leadership
246
+ if (!this.isTabLeader && !this.leaderTabId) {
247
+ // If we have a session but no leader, become leader
248
+ if (this.tabInfo.sessionId) {
249
+ if (this.config.debugMode) {
250
+ logging_1.debugLog.warn('CrossTabSession', `No leader detected after ${fallbackDelay}ms, forcing leadership for tab ${this.tabId}`);
251
+ }
252
+ this.becomeLeader();
253
+ }
254
+ else {
255
+ // If we don't even have a session, start a new one
256
+ if (this.config.debugMode) {
257
+ logging_1.debugLog.warn('CrossTabSession', `No session or leader detected after ${fallbackDelay}ms, starting new session for tab ${this.tabId}`);
258
+ }
259
+ this.becomeLeader();
260
+ }
261
+ }
262
+ this.fallbackLeadershipTimeout = null;
263
+ }, fallbackDelay);
264
+ // Additional periodic check for leader health
265
+ this.leaderHealthCheckInterval = window.setInterval(() => {
266
+ if (!this.sessionEnded && this.leaderTabId && !this.isTabLeader) {
267
+ // Check if leader is still responsive by checking last activity
268
+ const sessionContext = this.getStoredSessionContext();
269
+ if (sessionContext) {
270
+ const timeSinceLastActivity = Date.now() - sessionContext.lastActivity;
271
+ const maxInactiveTime = this.config.tabHeartbeatIntervalMs * 3; // 3 heartbeat intervals
272
+ if (timeSinceLastActivity > maxInactiveTime) {
273
+ if (this.config.debugMode) {
274
+ logging_1.debugLog.warn('CrossTabSession', `Leader tab appears inactive (${timeSinceLastActivity}ms), attempting to become leader`);
275
+ }
276
+ this.leaderTabId = null;
277
+ this.startLeaderElection();
278
+ }
279
+ }
280
+ }
281
+ }, this.config.tabHeartbeatIntervalMs * 2); // Check every 2 heartbeat intervals
282
+ // Clean up the health check interval when session ends
283
+ const originalEndSession = this.endSession.bind(this);
284
+ this.endSession = (reason) => {
285
+ if (this.leaderHealthCheckInterval) {
286
+ clearInterval(this.leaderHealthCheckInterval);
287
+ this.leaderHealthCheckInterval = null;
288
+ }
289
+ originalEndSession(reason);
290
+ };
291
+ }
292
+ /**
293
+ * Setup BroadcastChannel event listeners
294
+ */
295
+ setupBroadcastListeners(channel) {
296
+ channel.addEventListener('message', (event) => {
297
+ const message = event.data;
298
+ if (message.tabId === this.tabId) {
299
+ return;
300
+ }
301
+ this.handleCrossTabMessage(message);
302
+ });
303
+ }
304
+ /**
305
+ * Handle cross-tab messages
306
+ */
307
+ handleCrossTabMessage(message) {
308
+ if (this.config.debugMode) {
309
+ logging_1.debugLog.debug('CrossTabSession', `Received cross-tab message: ${message.type} from ${message.tabId}`);
310
+ }
311
+ switch (message.type) {
312
+ case 'heartbeat':
313
+ this.handleHeartbeatMessage(message);
314
+ break;
315
+ case 'session_start':
316
+ this.handleSessionStartMessage(message);
317
+ break;
318
+ case 'session_end':
319
+ this.handleSessionEndMessage(message);
320
+ break;
321
+ case 'tab_closing':
322
+ this.handleTabClosingMessage(message);
323
+ break;
324
+ case 'election_request':
325
+ this.handleElectionRequest(message);
326
+ break;
327
+ case 'election_response':
328
+ this.handleElectionResponse(message);
329
+ break;
330
+ }
331
+ }
332
+ /**
333
+ * Handle heartbeat message from another tab
334
+ */
335
+ handleHeartbeatMessage(message) {
336
+ // Update session activity if this tab has the same session
337
+ if (message.sessionId === this.tabInfo.sessionId) {
338
+ const sessionContext = this.getStoredSessionContext();
339
+ if (sessionContext) {
340
+ sessionContext.lastActivity = Date.now();
341
+ this.storeSessionContext(sessionContext);
342
+ // Notify activity callback
343
+ if (this.callbacks?.onTabActivity) {
344
+ this.callbacks.onTabActivity();
345
+ }
346
+ }
347
+ }
348
+ }
349
+ /**
350
+ * Handle session start message from another tab
351
+ */
352
+ handleSessionStartMessage(message) {
353
+ if (!message.sessionId)
354
+ return;
355
+ // Join the session if we don't have one
356
+ if (!this.tabInfo.sessionId) {
357
+ this.tabInfo.sessionId = message.sessionId;
358
+ this.storeTabInfo();
359
+ // Update session context
360
+ const sessionContext = this.getStoredSessionContext();
361
+ if (sessionContext) {
362
+ sessionContext.tabCount += 1;
363
+ this.storeSessionContext(sessionContext);
364
+ }
365
+ }
366
+ }
367
+ /**
368
+ * Handle session end message from another tab
369
+ */
370
+ handleSessionEndMessage(message) {
371
+ // Ignore if this tab is the leader
372
+ if (this.isTabLeader) {
373
+ if (this.config.debugMode) {
374
+ logging_1.debugLog.debug('CrossTabSession', `Ignoring session end message from ${message.tabId} (this tab is leader)`);
375
+ }
376
+ return;
377
+ }
378
+ // Verify the message is from the current leader
379
+ if (!this.leaderTabId || message.tabId !== this.leaderTabId) {
380
+ if (this.config.debugMode) {
381
+ const extra = this.leaderTabId ? `; leader is ${this.leaderTabId}` : '';
382
+ logging_1.debugLog.debug('CrossTabSession', `Ignoring session end message from ${message.tabId}${extra}`);
383
+ }
384
+ return;
385
+ }
386
+ this.tabInfo.sessionId = '';
387
+ this.storeTabInfo();
388
+ this.leaderTabId = null;
389
+ const sessionContext = this.getStoredSessionContext();
390
+ // Start a new session if none exists
391
+ if (!sessionContext) {
392
+ if (this.broadcastChannel) {
393
+ this.startLeaderElection();
394
+ }
395
+ else {
396
+ this.becomeLeader();
397
+ }
398
+ }
399
+ }
400
+ /**
401
+ * Handle tab closing message from another tab
402
+ */
403
+ handleTabClosingMessage(message) {
404
+ const sessionContext = this.getStoredSessionContext();
405
+ if (sessionContext && message.sessionId === sessionContext.sessionId) {
406
+ // Decrease tab count with minimum of 1 (current tab)
407
+ const oldCount = sessionContext.tabCount;
408
+ sessionContext.tabCount = Math.max(1, sessionContext.tabCount - 1);
409
+ sessionContext.lastActivity = Date.now();
410
+ this.storeSessionContext(sessionContext);
411
+ if (this.config.debugMode) {
412
+ logging_1.debugLog.debug('CrossTabSession', `Tab count updated from ${oldCount} to ${sessionContext.tabCount} after tab ${message.tabId} closed`);
413
+ }
414
+ // If the closing tab was the leader, handle leadership transition
415
+ const wasLeader = message.data?.isLeader ?? message.tabId === this.leaderTabId;
416
+ if (wasLeader && !this.isTabLeader) {
417
+ if (this.config.debugMode) {
418
+ logging_1.debugLog.debug('CrossTabSession', `Leader tab ${message.tabId} closed, starting leader election`);
419
+ }
420
+ this.leaderTabId = null;
421
+ // Add a small delay to ensure other tabs have processed the closing message
422
+ this.electionDelayTimeout = window.setTimeout(() => {
423
+ this.startLeaderElection();
424
+ this.electionDelayTimeout = null;
425
+ }, 200);
426
+ }
427
+ }
428
+ }
429
+ /**
430
+ * Handle election request from another tab
431
+ */
432
+ handleElectionRequest(_message) {
433
+ if (this.isTabLeader) {
434
+ // Respond that we're the leader
435
+ const response = {
436
+ type: 'election_response',
437
+ tabId: this.tabId,
438
+ sessionId: this.tabInfo.sessionId,
439
+ timestamp: Date.now(),
440
+ data: { isLeader: true },
441
+ };
442
+ if (this.broadcastChannel) {
443
+ this.broadcastChannel.postMessage(response);
444
+ }
445
+ }
446
+ }
447
+ /**
448
+ * Handle election response from another tab
449
+ */
450
+ handleElectionResponse(message) {
451
+ if (message.data?.isLeader) {
452
+ // Another tab is already the leader - only accept if we're not already a leader
453
+ if (!this.isTabLeader) {
454
+ this.isTabLeader = false;
455
+ this.tabInfo.isLeader = false;
456
+ this.leaderTabId = message.tabId;
457
+ if (this.config.debugMode) {
458
+ logging_1.debugLog.debug('CrossTabSession', `Acknowledging tab ${message.tabId} as leader`);
459
+ }
460
+ // Clear election timeout
461
+ if (this.electionTimeout) {
462
+ clearTimeout(this.electionTimeout);
463
+ this.electionTimeout = null;
464
+ }
465
+ // Join their session
466
+ if (message.sessionId) {
467
+ this.tabInfo.sessionId = message.sessionId;
468
+ this.storeTabInfo();
469
+ }
470
+ }
471
+ else if (this.config.debugMode) {
472
+ // We're already a leader, log potential conflict
473
+ logging_1.debugLog.warn('CrossTabSession', `Received leadership claim from ${message.tabId} but this tab is already leader`);
474
+ // Notify conflict callback
475
+ if (this.callbacks?.onCrossTabConflict) {
476
+ this.callbacks.onCrossTabConflict();
477
+ }
478
+ }
479
+ }
480
+ }
481
+ /**
482
+ * Start heartbeat to keep session active
483
+ */
484
+ startHeartbeat() {
485
+ if (this.heartbeatInterval) {
486
+ clearInterval(this.heartbeatInterval);
487
+ }
488
+ this.heartbeatInterval = window.setInterval(() => {
489
+ this.sendHeartbeat();
490
+ this.updateTabInfo();
491
+ }, this.config.tabHeartbeatIntervalMs);
492
+ }
493
+ /**
494
+ * Send heartbeat to other tabs with rate limiting to prevent flooding
495
+ */
496
+ sendHeartbeat() {
497
+ if (!this.broadcastChannel || !this.tabInfo.sessionId)
498
+ return;
499
+ // Rate limit heartbeats - only send if we're the leader or haven't sent recently
500
+ const now = Date.now();
501
+ const lastHeartbeat = this.lastHeartbeatSent ?? 0;
502
+ const minHeartbeatInterval = this.config.tabHeartbeatIntervalMs * 0.8; // 80% of interval
503
+ if (!this.isTabLeader && now - lastHeartbeat < minHeartbeatInterval) {
504
+ return; // Skip heartbeat to reduce noise
505
+ }
506
+ const message = {
507
+ type: 'heartbeat',
508
+ tabId: this.tabId,
509
+ sessionId: this.tabInfo.sessionId,
510
+ timestamp: now,
511
+ };
512
+ this.broadcastChannel.postMessage(message);
513
+ this.lastHeartbeatSent = now;
514
+ }
515
+ /**
516
+ * Update tab info with current timestamp
517
+ */
518
+ updateTabInfo() {
519
+ this.tabInfo.lastHeartbeat = Date.now();
520
+ this.storeTabInfo();
521
+ }
522
+ /**
523
+ * End session and notify other tabs
524
+ */
525
+ endSession(reason) {
526
+ if (this.sessionEnded) {
527
+ return;
528
+ }
529
+ this.sessionEnded = true;
530
+ if (this.config.debugMode) {
531
+ logging_1.debugLog.debug('CrossTabSession', `Ending cross-tab session: ${reason} (tab: ${this.tabId}, isLeader: ${this.isTabLeader})`);
532
+ }
533
+ // Announce tab closing with current state
534
+ this.announceTabClosing();
535
+ // If this is the leader, announce session end to remaining tabs
536
+ if (this.isTabLeader && reason !== 'manual_stop') {
537
+ this.announceSessionEnd(reason);
538
+ }
539
+ // Give time for messages to be sent before cleanup
540
+ this.tabInfoCleanupTimeout = window.setTimeout(() => {
541
+ this.clearTabInfo();
542
+ this.tabInfoCleanupTimeout = null;
543
+ }, 150);
544
+ }
545
+ /**
546
+ * Announce tab is closing to other tabs
547
+ */
548
+ announceTabClosing() {
549
+ if (!this.broadcastChannel || !this.tabInfo.sessionId)
550
+ return;
551
+ const message = {
552
+ type: 'tab_closing',
553
+ tabId: this.tabId,
554
+ sessionId: this.tabInfo.sessionId,
555
+ timestamp: Date.now(),
556
+ data: { isLeader: this.isTabLeader },
557
+ };
558
+ this.broadcastChannel.postMessage(message);
559
+ // Give other tabs time to process the message before we close
560
+ this.closingAnnouncementTimeout = window.setTimeout(() => {
561
+ if (this.config.debugMode) {
562
+ logging_1.debugLog.debug('CrossTabSession', `Tab ${this.tabId} closing announcement sent`);
563
+ }
564
+ this.closingAnnouncementTimeout = null;
565
+ }, 100);
566
+ }
567
+ /**
568
+ * Announce session end to other tabs
569
+ */
570
+ announceSessionEnd(reason) {
571
+ if (!this.broadcastChannel)
572
+ return;
573
+ const message = {
574
+ type: 'session_end',
575
+ tabId: this.tabId,
576
+ sessionId: this.tabInfo.sessionId,
577
+ timestamp: Date.now(),
578
+ data: { reason },
579
+ };
580
+ this.broadcastChannel.postMessage(message);
581
+ }
582
+ /**
583
+ * Get current session ID
584
+ */
585
+ getSessionId() {
586
+ return this.tabInfo.sessionId;
587
+ }
588
+ /**
589
+ * Get current tab ID
590
+ */
591
+ getTabId() {
592
+ return this.tabId;
593
+ }
594
+ /**
595
+ * Check if this tab is the session leader
596
+ */
597
+ isLeader() {
598
+ return this.isTabLeader;
599
+ }
600
+ /**
601
+ * Get current session context from storage
602
+ */
603
+ getStoredSessionContext() {
604
+ try {
605
+ const stored = this.storageManager.getItem((0, storage_constants_1.CROSS_TAB_SESSION_KEY)(this.projectId));
606
+ return stored ? JSON.parse(stored) : null;
607
+ }
608
+ catch (error) {
609
+ if (this.config.debugMode) {
610
+ logging_1.debugLog.warn('CrossTabSession', 'Failed to parse stored session context', { error });
611
+ }
612
+ return null;
613
+ }
614
+ }
615
+ /**
616
+ * Store session context to localStorage
617
+ */
618
+ storeSessionContext(context) {
619
+ try {
620
+ this.storageManager.setItem((0, storage_constants_1.CROSS_TAB_SESSION_KEY)(this.projectId), JSON.stringify(context));
621
+ }
622
+ catch (error) {
623
+ if (this.config.debugMode) {
624
+ logging_1.debugLog.warn('CrossTabSession', 'Failed to store session context', { error });
625
+ }
626
+ }
627
+ }
628
+ /**
629
+ * Clear stored session context
630
+ */
631
+ clearStoredSessionContext() {
632
+ this.storageManager.removeItem((0, storage_constants_1.CROSS_TAB_SESSION_KEY)(this.projectId));
633
+ }
634
+ /**
635
+ * Store tab info to localStorage
636
+ */
637
+ storeTabInfo() {
638
+ try {
639
+ this.storageManager.setItem((0, storage_constants_1.TAB_SPECIFIC_INFO_KEY)(this.projectId, this.tabId), JSON.stringify(this.tabInfo));
640
+ }
641
+ catch (error) {
642
+ if (this.config.debugMode) {
643
+ logging_1.debugLog.warn('CrossTabSession', 'Failed to store tab info', { error });
644
+ }
645
+ }
646
+ }
647
+ /**
648
+ * Clear tab info from localStorage
649
+ */
650
+ clearTabInfo() {
651
+ this.storageManager.removeItem((0, storage_constants_1.TAB_SPECIFIC_INFO_KEY)(this.projectId, this.tabId));
652
+ }
653
+ /**
654
+ * Check if BroadcastChannel is supported
655
+ */
656
+ isBroadcastChannelSupported() {
657
+ return typeof window !== 'undefined' && 'BroadcastChannel' in window;
658
+ }
659
+ /**
660
+ * Get session timeout considering cross-tab activity
661
+ */
662
+ getEffectiveSessionTimeout() {
663
+ const sessionContext = this.getStoredSessionContext();
664
+ if (!sessionContext) {
665
+ return this.get('config')?.sessionTimeout ?? constants_1.DEFAULT_SESSION_TIMEOUT_MS;
666
+ }
667
+ const now = Date.now();
668
+ const timeSinceLastActivity = now - sessionContext.lastActivity;
669
+ const sessionTimeout = this.get('config')?.sessionTimeout ?? constants_1.DEFAULT_SESSION_TIMEOUT_MS;
670
+ return Math.max(0, sessionTimeout - timeSinceLastActivity);
671
+ }
672
+ /**
673
+ * Update session activity from any tab
674
+ */
675
+ updateSessionActivity() {
676
+ const sessionContext = this.getStoredSessionContext();
677
+ if (sessionContext) {
678
+ sessionContext.lastActivity = Date.now();
679
+ this.storeSessionContext(sessionContext);
680
+ }
681
+ // Send heartbeat to notify other tabs
682
+ this.sendHeartbeat();
683
+ }
684
+ /**
685
+ * Cleanup resources
686
+ */
687
+ destroy() {
688
+ // Clear intervals and timeouts
689
+ if (this.heartbeatInterval) {
690
+ clearInterval(this.heartbeatInterval);
691
+ this.heartbeatInterval = null;
692
+ }
693
+ if (this.electionTimeout) {
694
+ clearTimeout(this.electionTimeout);
695
+ this.electionTimeout = null;
696
+ }
697
+ if (this.cleanupTimeout) {
698
+ clearTimeout(this.cleanupTimeout);
699
+ this.cleanupTimeout = null;
700
+ }
701
+ // Clear additional timeouts
702
+ if (this.fallbackLeadershipTimeout) {
703
+ clearTimeout(this.fallbackLeadershipTimeout);
704
+ this.fallbackLeadershipTimeout = null;
705
+ }
706
+ if (this.electionDelayTimeout) {
707
+ clearTimeout(this.electionDelayTimeout);
708
+ this.electionDelayTimeout = null;
709
+ }
710
+ if (this.tabInfoCleanupTimeout) {
711
+ clearTimeout(this.tabInfoCleanupTimeout);
712
+ this.tabInfoCleanupTimeout = null;
713
+ }
714
+ if (this.closingAnnouncementTimeout) {
715
+ clearTimeout(this.closingAnnouncementTimeout);
716
+ this.closingAnnouncementTimeout = null;
717
+ }
718
+ if (this.leaderHealthCheckInterval) {
719
+ clearInterval(this.leaderHealthCheckInterval);
720
+ this.leaderHealthCheckInterval = null;
721
+ }
722
+ // End session and cleanup
723
+ this.endSession('manual_stop');
724
+ // Close BroadcastChannel
725
+ if (this.broadcastChannel) {
726
+ this.broadcastChannel.close();
727
+ }
728
+ }
729
+ }
730
+ exports.CrossTabSessionManager = CrossTabSessionManager;