@tracelog/lib 0.0.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +58 -24
  2. package/dist/browser/tracelog.js +1934 -3226
  3. package/dist/cjs/api.d.ts +33 -19
  4. package/dist/cjs/api.js +111 -156
  5. package/dist/cjs/app.constants.d.ts +80 -1
  6. package/dist/cjs/app.constants.js +90 -3
  7. package/dist/cjs/app.d.ts +29 -44
  8. package/dist/cjs/app.js +114 -212
  9. package/dist/cjs/app.types.d.ts +2 -7
  10. package/dist/cjs/app.types.js +10 -21
  11. package/dist/cjs/constants/api.constants.js +11 -5
  12. package/dist/cjs/constants/config.constants.d.ts +75 -0
  13. package/dist/cjs/constants/config.constants.js +178 -0
  14. package/dist/cjs/constants/error.constants.d.ts +29 -0
  15. package/dist/cjs/constants/error.constants.js +50 -0
  16. package/dist/cjs/constants/index.d.ts +3 -6
  17. package/dist/cjs/constants/index.js +3 -6
  18. package/dist/cjs/constants/performance.constants.d.ts +28 -0
  19. package/dist/cjs/constants/performance.constants.js +43 -0
  20. package/dist/cjs/handlers/click.handler.d.ts +1 -0
  21. package/dist/cjs/handlers/click.handler.js +30 -49
  22. package/dist/cjs/handlers/error.handler.d.ts +11 -6
  23. package/dist/cjs/handlers/error.handler.js +91 -51
  24. package/dist/cjs/handlers/page-view.handler.js +38 -29
  25. package/dist/cjs/handlers/performance.handler.d.ts +3 -0
  26. package/dist/cjs/handlers/performance.handler.js +76 -37
  27. package/dist/cjs/handlers/scroll.handler.d.ts +15 -0
  28. package/dist/cjs/handlers/scroll.handler.js +105 -31
  29. package/dist/cjs/handlers/session.handler.d.ts +6 -20
  30. package/dist/cjs/handlers/session.handler.js +38 -326
  31. package/dist/cjs/integrations/google-analytics.integration.d.ts +0 -1
  32. package/dist/cjs/integrations/google-analytics.integration.js +27 -98
  33. package/dist/cjs/listeners/input-listener-managers.d.ts +18 -9
  34. package/dist/cjs/listeners/input-listener-managers.js +24 -33
  35. package/dist/cjs/listeners/touch-listener-manager.d.ts +1 -3
  36. package/dist/cjs/listeners/touch-listener-manager.js +1 -23
  37. package/dist/cjs/listeners/visibility-listener-manager.d.ts +1 -4
  38. package/dist/cjs/listeners/visibility-listener-manager.js +6 -42
  39. package/dist/cjs/managers/api.manager.d.ts +13 -3
  40. package/dist/cjs/managers/api.manager.js +35 -5
  41. package/dist/cjs/managers/config.manager.d.ts +53 -3
  42. package/dist/cjs/managers/config.manager.js +131 -62
  43. package/dist/cjs/managers/event.manager.d.ts +57 -36
  44. package/dist/cjs/managers/event.manager.js +266 -417
  45. package/dist/cjs/managers/sender.manager.d.ts +40 -22
  46. package/dist/cjs/managers/sender.manager.js +200 -198
  47. package/dist/cjs/managers/session.manager.d.ts +80 -66
  48. package/dist/cjs/managers/session.manager.js +267 -522
  49. package/dist/cjs/managers/state.manager.d.ts +33 -0
  50. package/dist/cjs/managers/state.manager.js +79 -6
  51. package/dist/cjs/managers/storage.manager.d.ts +26 -2
  52. package/dist/cjs/managers/storage.manager.js +67 -34
  53. package/dist/cjs/managers/tags.manager.d.ts +31 -7
  54. package/dist/cjs/managers/tags.manager.js +123 -241
  55. package/dist/cjs/managers/user.manager.d.ts +14 -5
  56. package/dist/cjs/managers/user.manager.js +17 -9
  57. package/dist/cjs/public-api.d.ts +10 -1
  58. package/dist/cjs/public-api.js +18 -24
  59. package/dist/cjs/test-bridge.d.ts +48 -0
  60. package/dist/cjs/test-bridge.js +110 -0
  61. package/dist/cjs/types/api.types.d.ts +21 -6
  62. package/dist/cjs/types/api.types.js +21 -6
  63. package/dist/cjs/types/config.types.d.ts +22 -84
  64. package/dist/cjs/types/emitter.types.d.ts +11 -0
  65. package/dist/cjs/types/emitter.types.js +8 -0
  66. package/dist/cjs/types/event.types.d.ts +8 -11
  67. package/dist/cjs/types/index.d.ts +3 -1
  68. package/dist/cjs/types/index.js +3 -1
  69. package/dist/cjs/types/queue.types.d.ts +1 -0
  70. package/dist/cjs/types/session.types.d.ts +0 -64
  71. package/dist/cjs/types/state.types.d.ts +1 -0
  72. package/dist/cjs/types/test-bridge.types.d.ts +38 -0
  73. package/dist/cjs/types/validation-error.types.d.ts +7 -0
  74. package/dist/cjs/types/validation-error.types.js +11 -1
  75. package/dist/cjs/types/window.types.d.ts +1 -8
  76. package/dist/cjs/utils/data/uuid.utils.d.ts +1 -1
  77. package/dist/cjs/utils/data/uuid.utils.js +7 -5
  78. package/dist/cjs/utils/emitter.utils.d.ts +8 -0
  79. package/dist/cjs/utils/emitter.utils.js +33 -0
  80. package/dist/cjs/utils/index.d.ts +1 -0
  81. package/dist/cjs/utils/index.js +1 -0
  82. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +10 -51
  83. package/dist/cjs/utils/logging/debug-logger.utils.js +36 -127
  84. package/dist/cjs/utils/network/fetch-with-timeout.utils.d.ts +4 -0
  85. package/dist/cjs/utils/network/fetch-with-timeout.utils.js +25 -0
  86. package/dist/cjs/utils/network/index.d.ts +1 -0
  87. package/dist/cjs/utils/network/index.js +1 -0
  88. package/dist/cjs/utils/network/url.utils.js +2 -42
  89. package/dist/cjs/utils/security/sanitize.utils.d.ts +1 -8
  90. package/dist/cjs/utils/security/sanitize.utils.js +7 -41
  91. package/dist/cjs/utils/validations/config-validations.utils.d.ts +7 -0
  92. package/dist/cjs/utils/validations/config-validations.utils.js +77 -22
  93. package/dist/esm/api.d.ts +33 -19
  94. package/dist/esm/api.js +105 -118
  95. package/dist/esm/app.constants.d.ts +80 -1
  96. package/dist/esm/app.constants.js +89 -1
  97. package/dist/esm/app.d.ts +29 -44
  98. package/dist/esm/app.js +115 -213
  99. package/dist/esm/app.types.d.ts +2 -7
  100. package/dist/esm/app.types.js +1 -7
  101. package/dist/esm/constants/api.constants.js +10 -4
  102. package/dist/esm/constants/config.constants.d.ts +75 -0
  103. package/dist/esm/constants/config.constants.js +174 -0
  104. package/dist/esm/constants/error.constants.d.ts +29 -0
  105. package/dist/esm/constants/error.constants.js +47 -0
  106. package/dist/esm/constants/index.d.ts +3 -6
  107. package/dist/esm/constants/index.js +3 -6
  108. package/dist/esm/constants/performance.constants.d.ts +28 -0
  109. package/dist/esm/constants/performance.constants.js +40 -0
  110. package/dist/esm/handlers/click.handler.d.ts +1 -0
  111. package/dist/esm/handlers/click.handler.js +30 -49
  112. package/dist/esm/handlers/error.handler.d.ts +11 -6
  113. package/dist/esm/handlers/error.handler.js +91 -51
  114. package/dist/esm/handlers/page-view.handler.js +38 -29
  115. package/dist/esm/handlers/performance.handler.d.ts +3 -0
  116. package/dist/esm/handlers/performance.handler.js +71 -32
  117. package/dist/esm/handlers/scroll.handler.d.ts +15 -0
  118. package/dist/esm/handlers/scroll.handler.js +106 -32
  119. package/dist/esm/handlers/session.handler.d.ts +6 -20
  120. package/dist/esm/handlers/session.handler.js +38 -326
  121. package/dist/esm/integrations/google-analytics.integration.d.ts +0 -1
  122. package/dist/esm/integrations/google-analytics.integration.js +27 -98
  123. package/dist/esm/listeners/input-listener-managers.d.ts +18 -9
  124. package/dist/esm/listeners/input-listener-managers.js +23 -32
  125. package/dist/esm/listeners/touch-listener-manager.d.ts +1 -3
  126. package/dist/esm/listeners/touch-listener-manager.js +1 -23
  127. package/dist/esm/listeners/visibility-listener-manager.d.ts +1 -4
  128. package/dist/esm/listeners/visibility-listener-manager.js +6 -42
  129. package/dist/esm/managers/api.manager.d.ts +13 -3
  130. package/dist/esm/managers/api.manager.js +34 -3
  131. package/dist/esm/managers/config.manager.d.ts +53 -3
  132. package/dist/esm/managers/config.manager.js +133 -64
  133. package/dist/esm/managers/event.manager.d.ts +57 -36
  134. package/dist/esm/managers/event.manager.js +268 -419
  135. package/dist/esm/managers/sender.manager.d.ts +40 -22
  136. package/dist/esm/managers/sender.manager.js +201 -199
  137. package/dist/esm/managers/session.manager.d.ts +80 -66
  138. package/dist/esm/managers/session.manager.js +269 -524
  139. package/dist/esm/managers/state.manager.d.ts +33 -0
  140. package/dist/esm/managers/state.manager.js +78 -6
  141. package/dist/esm/managers/storage.manager.d.ts +26 -2
  142. package/dist/esm/managers/storage.manager.js +66 -33
  143. package/dist/esm/managers/tags.manager.d.ts +31 -7
  144. package/dist/esm/managers/tags.manager.js +124 -242
  145. package/dist/esm/managers/user.manager.d.ts +14 -5
  146. package/dist/esm/managers/user.manager.js +17 -9
  147. package/dist/esm/public-api.d.ts +10 -1
  148. package/dist/esm/public-api.js +14 -1
  149. package/dist/esm/test-bridge.d.ts +48 -0
  150. package/dist/esm/test-bridge.js +106 -0
  151. package/dist/esm/types/api.types.d.ts +21 -6
  152. package/dist/esm/types/api.types.js +21 -6
  153. package/dist/esm/types/config.types.d.ts +22 -84
  154. package/dist/esm/types/emitter.types.d.ts +11 -0
  155. package/dist/esm/types/emitter.types.js +5 -0
  156. package/dist/esm/types/event.types.d.ts +8 -11
  157. package/dist/esm/types/index.d.ts +3 -1
  158. package/dist/esm/types/index.js +3 -1
  159. package/dist/esm/types/queue.types.d.ts +1 -0
  160. package/dist/esm/types/session.types.d.ts +0 -64
  161. package/dist/esm/types/state.types.d.ts +1 -0
  162. package/dist/esm/types/test-bridge.types.d.ts +38 -0
  163. package/dist/esm/types/validation-error.types.d.ts +7 -0
  164. package/dist/esm/types/validation-error.types.js +9 -0
  165. package/dist/esm/types/window.types.d.ts +1 -8
  166. package/dist/esm/utils/data/uuid.utils.d.ts +1 -1
  167. package/dist/esm/utils/data/uuid.utils.js +7 -5
  168. package/dist/esm/utils/emitter.utils.d.ts +8 -0
  169. package/dist/esm/utils/emitter.utils.js +29 -0
  170. package/dist/esm/utils/index.d.ts +1 -0
  171. package/dist/esm/utils/index.js +1 -0
  172. package/dist/esm/utils/logging/debug-logger.utils.d.ts +10 -51
  173. package/dist/esm/utils/logging/debug-logger.utils.js +36 -127
  174. package/dist/esm/utils/network/fetch-with-timeout.utils.d.ts +4 -0
  175. package/dist/esm/utils/network/fetch-with-timeout.utils.js +22 -0
  176. package/dist/esm/utils/network/index.d.ts +1 -0
  177. package/dist/esm/utils/network/index.js +1 -0
  178. package/dist/esm/utils/network/url.utils.js +2 -42
  179. package/dist/esm/utils/security/sanitize.utils.d.ts +1 -8
  180. package/dist/esm/utils/security/sanitize.utils.js +6 -39
  181. package/dist/esm/utils/validations/config-validations.utils.d.ts +7 -0
  182. package/dist/esm/utils/validations/config-validations.utils.js +76 -22
  183. package/package.json +23 -16
  184. package/dist/browser/web-vitals-CCnqwnC8.mjs +0 -198
  185. package/dist/cjs/constants/browser.constants.d.ts +0 -3
  186. package/dist/cjs/constants/browser.constants.js +0 -41
  187. package/dist/cjs/constants/initialization.constants.d.ts +0 -40
  188. package/dist/cjs/constants/initialization.constants.js +0 -48
  189. package/dist/cjs/constants/limits.constants.d.ts +0 -25
  190. package/dist/cjs/constants/limits.constants.js +0 -40
  191. package/dist/cjs/constants/security.constants.d.ts +0 -1
  192. package/dist/cjs/constants/security.constants.js +0 -12
  193. package/dist/cjs/constants/timing.constants.d.ts +0 -22
  194. package/dist/cjs/constants/timing.constants.js +0 -34
  195. package/dist/cjs/constants/validation.constants.d.ts +0 -13
  196. package/dist/cjs/constants/validation.constants.js +0 -31
  197. package/dist/cjs/handlers/network.handler.d.ts +0 -16
  198. package/dist/cjs/handlers/network.handler.js +0 -136
  199. package/dist/cjs/managers/cross-tab-session.manager.d.ts +0 -170
  200. package/dist/cjs/managers/cross-tab-session.manager.js +0 -730
  201. package/dist/cjs/managers/sampling.manager.d.ts +0 -8
  202. package/dist/cjs/managers/sampling.manager.js +0 -53
  203. package/dist/cjs/managers/session-recovery.manager.d.ts +0 -65
  204. package/dist/cjs/managers/session-recovery.manager.js +0 -237
  205. package/dist/cjs/types/web-vitals.types.d.ts +0 -6
  206. package/dist/esm/constants/browser.constants.d.ts +0 -3
  207. package/dist/esm/constants/browser.constants.js +0 -38
  208. package/dist/esm/constants/initialization.constants.d.ts +0 -40
  209. package/dist/esm/constants/initialization.constants.js +0 -45
  210. package/dist/esm/constants/limits.constants.d.ts +0 -25
  211. package/dist/esm/constants/limits.constants.js +0 -37
  212. package/dist/esm/constants/security.constants.d.ts +0 -1
  213. package/dist/esm/constants/security.constants.js +0 -9
  214. package/dist/esm/constants/timing.constants.d.ts +0 -22
  215. package/dist/esm/constants/timing.constants.js +0 -31
  216. package/dist/esm/constants/validation.constants.d.ts +0 -13
  217. package/dist/esm/constants/validation.constants.js +0 -28
  218. package/dist/esm/handlers/network.handler.d.ts +0 -16
  219. package/dist/esm/handlers/network.handler.js +0 -132
  220. package/dist/esm/managers/cross-tab-session.manager.d.ts +0 -170
  221. package/dist/esm/managers/cross-tab-session.manager.js +0 -726
  222. package/dist/esm/managers/sampling.manager.d.ts +0 -8
  223. package/dist/esm/managers/sampling.manager.js +0 -49
  224. package/dist/esm/managers/session-recovery.manager.d.ts +0 -65
  225. package/dist/esm/managers/session-recovery.manager.js +0 -233
  226. package/dist/esm/types/web-vitals.types.d.ts +0 -6
  227. /package/dist/cjs/types/{web-vitals.types.js → test-bridge.types.js} +0 -0
  228. /package/dist/esm/types/{web-vitals.types.js → test-bridge.types.js} +0 -0
@@ -3,585 +3,330 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SessionManager = void 0;
4
4
  const constants_1 = require("../constants");
5
5
  const types_1 = require("../types");
6
- const utils_1 = require("../utils");
7
6
  const logging_1 = require("../utils/logging");
8
- const listeners_1 = require("../listeners");
9
7
  const state_manager_1 = require("./state.manager");
10
- const session_recovery_manager_1 = require("./session-recovery.manager");
11
8
  class SessionManager extends state_manager_1.StateManager {
12
- constructor(onActivity, onInactivity, eventManager, storageManager, sessionEndConfig) {
9
+ constructor(storageManager, eventManager) {
13
10
  super();
14
- this.eventManager = null;
15
- this.storageManager = null;
16
- this.listenerManagers = [];
17
- // Recovery manager
18
- this.recoveryManager = null;
19
- this.isSessionActive = false;
20
- this.lastActivityTime = 0;
21
- this.inactivityTimer = null;
22
- this.sessionStartTime = 0;
23
- this.throttleTimeout = null;
24
- // Track visibility change timeout for proper cleanup
25
- this.visibilityChangeTimeout = null;
26
- // Session End Management
27
- this.pendingSessionEnd = false;
28
- this.sessionEndPromise = null;
29
- this.sessionEndLock = Promise.resolve({
30
- success: true,
31
- reason: 'manual_stop',
32
- timestamp: Date.now(),
33
- eventsFlushed: 0,
34
- method: 'async',
35
- });
36
- this.cleanupHandlers = [];
37
- this.sessionEndReason = null;
38
- this.sessionEndPriority = {
39
- page_unload: 4,
40
- manual_stop: 3,
41
- orphaned_cleanup: 2,
42
- inactivity: 1,
43
- tab_closed: 0,
44
- };
45
- this.sessionEndStats = {
46
- totalSessionEnds: 0,
47
- successfulEnds: 0,
48
- failedEnds: 0,
49
- duplicatePrevented: 0,
50
- reasonCounts: {
51
- inactivity: 0,
52
- page_unload: 0,
53
- manual_stop: 0,
54
- orphaned_cleanup: 0,
55
- tab_closed: 0,
56
- },
57
- };
58
- // Session health monitoring
59
- this.sessionHealth = {
60
- recoveryAttempts: 0,
61
- sessionTimeouts: 0,
62
- crossTabConflicts: 0,
63
- lastHealthCheck: Date.now(),
64
- };
65
- this.handleActivity = () => {
66
- const now = Date.now();
67
- if (now - this.lastActivityTime < this.config.throttleDelay) {
11
+ this.sessionTimeoutId = null;
12
+ this.broadcastChannel = null;
13
+ this.activityHandler = null;
14
+ this.visibilityChangeHandler = null;
15
+ this.beforeUnloadHandler = null;
16
+ this.isTracking = false;
17
+ this.storageManager = storageManager;
18
+ this.eventManager = eventManager;
19
+ }
20
+ /**
21
+ * Initialize cross-tab synchronization
22
+ */
23
+ initCrossTabSync() {
24
+ if (typeof BroadcastChannel === 'undefined') {
25
+ logging_1.debugLog.warn('SessionManager', 'BroadcastChannel not supported');
26
+ return;
27
+ }
28
+ const projectId = this.getProjectId();
29
+ this.broadcastChannel = new BroadcastChannel((0, constants_1.BROADCAST_CHANNEL_NAME)(projectId));
30
+ this.broadcastChannel.onmessage = (event) => {
31
+ const { action, sessionId, timestamp, projectId: messageProjectId } = event.data ?? {};
32
+ if (messageProjectId !== projectId) {
68
33
  return;
69
34
  }
70
- this.lastActivityTime = now;
71
- if (this.isSessionActive) {
72
- // Always call onActivity to update cross-tab session activity
73
- this.onActivity();
74
- this.resetInactivityTimer();
35
+ if (action === 'session_end') {
36
+ logging_1.debugLog.debug('SessionManager', 'Session end synced from another tab');
37
+ this.resetSessionState();
38
+ return;
75
39
  }
76
- else {
77
- if (this.throttleTimeout) {
78
- clearTimeout(this.throttleTimeout);
79
- this.throttleTimeout = null;
40
+ if (sessionId && typeof timestamp === 'number' && timestamp > Date.now() - 5000) {
41
+ this.set('sessionId', sessionId);
42
+ this.set('hasStartSession', true);
43
+ this.persistSession(sessionId, timestamp);
44
+ if (this.isTracking) {
45
+ this.setupSessionTimeout();
80
46
  }
81
- this.throttleTimeout = window.setTimeout(() => {
82
- this.onActivity();
83
- this.throttleTimeout = null;
84
- }, 100);
85
- }
86
- };
87
- this.handleInactivity = () => {
88
- // Track session timeout for health monitoring
89
- this.trackSessionHealth('timeout');
90
- this.onInactivity();
91
- };
92
- this.handleVisibilityChange = () => {
93
- if (document.hidden) {
94
- if (this.isSessionActive) {
95
- if (this.inactivityTimer) {
96
- clearTimeout(this.inactivityTimer);
97
- this.inactivityTimer = null;
98
- }
99
- this.inactivityTimer = window.setTimeout(this.handleInactivity, this.config.visibilityTimeout);
100
- }
101
- }
102
- else {
103
- this.handleActivity();
104
- }
105
- };
106
- this.resetInactivityTimer = () => {
107
- if (this.inactivityTimer) {
108
- clearTimeout(this.inactivityTimer);
109
- this.inactivityTimer = null;
47
+ logging_1.debugLog.debug('SessionManager', 'Session synced from another tab', { sessionId });
110
48
  }
111
- if (this.isSessionActive) {
112
- this.inactivityTimer = window.setTimeout(() => {
113
- this.handleInactivity();
114
- }, this.config.timeout);
115
- }
116
- };
117
- this.config = {
118
- throttleDelay: constants_1.DEFAULT_THROTTLE_DELAY_MS,
119
- visibilityTimeout: constants_1.DEFAULT_VISIBILITY_TIMEOUT_MS,
120
- motionThreshold: constants_1.DEFAULT_MOTION_THRESHOLD,
121
- timeout: this.get('config')?.sessionTimeout ?? constants_1.DEFAULT_SESSION_TIMEOUT_MS,
122
49
  };
123
- this.sessionEndConfig = {
124
- enablePageUnloadHandlers: true,
125
- syncTimeoutMs: 1000,
126
- maxRetries: 2,
127
- debugMode: false,
128
- ...sessionEndConfig,
129
- };
130
- this.onActivity = onActivity;
131
- this.onInactivity = onInactivity;
132
- this.eventManager = eventManager ?? null;
133
- this.storageManager = storageManager ?? null;
134
- this.deviceCapabilities = this.detectDeviceCapabilities();
135
- this.initializeRecoveryManager();
136
- this.initializeListenerManagers();
137
- this.setupAllListeners();
138
- if (this.sessionEndConfig.enablePageUnloadHandlers) {
139
- this.setupPageUnloadHandlers();
140
- }
141
- logging_1.debugLog.debug('SessionManager', 'SessionManager initialized', {
142
- sessionTimeout: this.config.timeout,
143
- deviceCapabilities: this.deviceCapabilities,
144
- unloadHandlersEnabled: this.sessionEndConfig.enablePageUnloadHandlers,
145
- });
146
50
  }
147
51
  /**
148
- * Initialize recovery manager
52
+ * Share session with other tabs
149
53
  */
150
- initializeRecoveryManager() {
151
- if (!this.storageManager)
152
- return;
153
- const projectId = this.get('config')?.id;
154
- if (!projectId)
54
+ shareSession(sessionId) {
55
+ this.broadcastChannel?.postMessage({
56
+ action: 'session_start',
57
+ projectId: this.getProjectId(),
58
+ sessionId,
59
+ timestamp: Date.now(),
60
+ });
61
+ }
62
+ broadcastSessionEnd(sessionId, reason) {
63
+ if (!sessionId) {
155
64
  return;
156
- try {
157
- // Initialize session recovery manager (always enabled)
158
- this.recoveryManager = new session_recovery_manager_1.SessionRecoveryManager(this.storageManager, projectId, this.eventManager ?? undefined);
159
- logging_1.debugLog.debug('SessionManager', 'Recovery manager initialized', { projectId });
160
- }
161
- catch (error) {
162
- logging_1.debugLog.error('SessionManager', 'Failed to initialize recovery manager', { error, projectId });
163
65
  }
66
+ this.broadcastChannel?.postMessage({
67
+ action: 'session_end',
68
+ projectId: this.getProjectId(),
69
+ sessionId,
70
+ reason,
71
+ timestamp: Date.now(),
72
+ });
164
73
  }
165
74
  /**
166
- * Store session context for recovery
75
+ * Cleanup cross-tab sync
167
76
  */
168
- storeSessionContextForRecovery() {
169
- if (!this.recoveryManager)
170
- return;
171
- const sessionId = this.get('sessionId');
172
- if (!sessionId)
173
- return;
174
- const sessionContext = {
175
- sessionId,
176
- startTime: this.sessionStartTime,
177
- lastActivity: this.lastActivityTime,
178
- tabCount: 1, // This will be updated by cross-tab manager
179
- recoveryAttempts: 0,
180
- metadata: {
181
- userAgent: navigator.userAgent,
182
- pageUrl: this.get('pageUrl'),
183
- },
184
- };
185
- this.recoveryManager.storeSessionContextForRecovery(sessionContext);
186
- }
187
- startSession() {
188
- const now = Date.now();
189
- // Attempt session recovery first
190
- let sessionId = '';
191
- let wasRecovered = false;
192
- if (this.recoveryManager?.hasRecoverableSession()) {
193
- const recoveryResult = this.recoveryManager.attemptSessionRecovery();
194
- if (recoveryResult.recovered && recoveryResult.recoveredSessionId) {
195
- sessionId = recoveryResult.recoveredSessionId;
196
- wasRecovered = true;
197
- // Track session recovery for health monitoring
198
- this.trackSessionHealth('recovery');
199
- // Update session timing from recovery context
200
- if (recoveryResult.context) {
201
- this.sessionStartTime = recoveryResult.context.startTime;
202
- this.lastActivityTime = now;
203
- }
204
- else {
205
- this.sessionStartTime = now;
206
- this.lastActivityTime = now;
207
- }
208
- logging_1.debugLog.info('SessionManager', 'Session successfully recovered', {
209
- sessionId,
210
- recoveryAttempts: this.sessionHealth.recoveryAttempts,
211
- });
212
- }
213
- }
214
- // If no recovery, create new session
215
- if (!wasRecovered) {
216
- sessionId = (0, utils_1.generateUUID)();
217
- this.sessionStartTime = now;
218
- this.lastActivityTime = now;
219
- logging_1.debugLog.info('SessionManager', 'New session started', { sessionId });
77
+ cleanupCrossTabSync() {
78
+ if (this.broadcastChannel) {
79
+ this.broadcastChannel.close();
80
+ this.broadcastChannel = null;
220
81
  }
221
- this.isSessionActive = true;
222
- this.resetInactivityTimer();
223
- // Store session context for future recovery
224
- this.storeSessionContextForRecovery();
225
- return { sessionId, recovered: wasRecovered };
226
82
  }
227
- endSession() {
228
- if (this.sessionStartTime === 0) {
229
- return 0;
83
+ /**
84
+ * Recover session from localStorage if it exists and hasn't expired
85
+ */
86
+ recoverSession() {
87
+ const storedSession = this.loadStoredSession();
88
+ if (!storedSession) {
89
+ return null;
230
90
  }
231
- const durationMs = Date.now() - this.sessionStartTime;
232
- this.sessionStartTime = 0;
233
- this.isSessionActive = false;
234
- if (this.inactivityTimer) {
235
- clearTimeout(this.inactivityTimer);
236
- this.inactivityTimer = null;
91
+ const sessionTimeout = this.get('config')?.sessionTimeout ?? constants_1.DEFAULT_SESSION_TIMEOUT;
92
+ if (Date.now() - storedSession.lastActivity > sessionTimeout) {
93
+ logging_1.debugLog.debug('SessionManager', 'Stored session expired');
94
+ this.clearStoredSession();
95
+ return null;
237
96
  }
238
- return durationMs;
97
+ logging_1.debugLog.info('SessionManager', 'Session recovered from storage', { sessionId: storedSession.id });
98
+ return storedSession.id;
239
99
  }
240
- destroy() {
241
- this.clearTimers();
242
- this.cleanupAllListeners();
243
- this.resetState();
244
- this.cleanupHandlers.forEach((cleanup) => cleanup());
245
- this.cleanupHandlers = [];
246
- this.pendingSessionEnd = false;
247
- this.sessionEndPromise = null;
248
- this.sessionEndLock = Promise.resolve({
249
- success: true,
250
- reason: 'manual_stop',
251
- timestamp: Date.now(),
252
- eventsFlushed: 0,
253
- method: 'async',
100
+ /**
101
+ * Persist session data to localStorage
102
+ */
103
+ persistSession(sessionId, lastActivity = Date.now()) {
104
+ this.saveStoredSession({
105
+ id: sessionId,
106
+ lastActivity,
254
107
  });
255
- if (this.recoveryManager) {
256
- this.recoveryManager.cleanupOldRecoveryAttempts();
257
- this.recoveryManager = null;
258
- }
259
108
  }
260
- detectDeviceCapabilities() {
261
- const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
262
- const hasMouse = window.matchMedia('(pointer: fine)').matches;
263
- const hasKeyboard = !window.matchMedia('(pointer: coarse)').matches;
264
- const isMobile = (0, utils_1.getDeviceType)() === types_1.DeviceType.Mobile;
265
- return { hasTouch, hasMouse, hasKeyboard, isMobile };
109
+ clearStoredSession() {
110
+ const storageKey = this.getSessionStorageKey();
111
+ this.storageManager.removeItem(storageKey);
266
112
  }
267
- initializeListenerManagers() {
268
- this.listenerManagers.push(new listeners_1.ActivityListenerManager(this.handleActivity));
269
- if (this.deviceCapabilities.hasTouch) {
270
- this.listenerManagers.push(new listeners_1.TouchListenerManager(this.handleActivity, this.config.motionThreshold));
113
+ loadStoredSession() {
114
+ const storageKey = this.getSessionStorageKey();
115
+ const storedData = this.storageManager.getItem(storageKey);
116
+ if (!storedData) {
117
+ return null;
271
118
  }
272
- if (this.deviceCapabilities.hasMouse) {
273
- this.listenerManagers.push(new listeners_1.MouseListenerManager(this.handleActivity));
119
+ try {
120
+ const parsed = JSON.parse(storedData);
121
+ if (!parsed.id || typeof parsed.lastActivity !== 'number') {
122
+ return null;
123
+ }
124
+ return parsed;
274
125
  }
275
- if (this.deviceCapabilities.hasKeyboard) {
276
- this.listenerManagers.push(new listeners_1.KeyboardListenerManager(this.handleActivity));
126
+ catch {
127
+ this.storageManager.removeItem(storageKey);
128
+ return null;
277
129
  }
278
- this.listenerManagers.push(new listeners_1.VisibilityListenerManager(this.handleActivity, this.handleVisibilityChange, this.deviceCapabilities.isMobile));
279
- this.listenerManagers.push(new listeners_1.UnloadListenerManager(this.handleInactivity));
280
130
  }
281
- setupAllListeners() {
282
- this.listenerManagers.forEach((manager) => manager.setup());
131
+ saveStoredSession(session) {
132
+ const storageKey = this.getSessionStorageKey();
133
+ this.storageManager.setItem(storageKey, JSON.stringify(session));
134
+ }
135
+ getSessionStorageKey() {
136
+ return (0, constants_1.SESSION_STORAGE_KEY)(this.getProjectId());
283
137
  }
284
- cleanupAllListeners() {
285
- this.listenerManagers.forEach((manager) => manager.cleanup());
138
+ getProjectId() {
139
+ return this.get('config')?.id ?? '';
286
140
  }
287
- clearTimers() {
288
- if (this.inactivityTimer) {
289
- clearTimeout(this.inactivityTimer);
290
- this.inactivityTimer = null;
141
+ /**
142
+ * Start session tracking
143
+ */
144
+ async startTracking() {
145
+ if (this.isTracking) {
146
+ logging_1.debugLog.warn('SessionManager', 'Session tracking already active');
147
+ return;
291
148
  }
292
- if (this.throttleTimeout) {
293
- clearTimeout(this.throttleTimeout);
294
- this.throttleTimeout = null;
149
+ const recoveredSessionId = this.recoverSession();
150
+ const sessionId = recoveredSessionId ?? this.generateSessionId();
151
+ const isRecovered = Boolean(recoveredSessionId);
152
+ this.isTracking = true;
153
+ try {
154
+ this.set('sessionId', sessionId);
155
+ this.persistSession(sessionId);
156
+ // Track session start event
157
+ this.eventManager.track({
158
+ type: types_1.EventType.SESSION_START,
159
+ ...(isRecovered && { session_start_recovered: true }),
160
+ });
161
+ // Initialize components
162
+ this.initCrossTabSync();
163
+ this.shareSession(sessionId);
164
+ this.setupSessionTimeout();
165
+ this.setupActivityListeners();
166
+ this.setupLifecycleListeners();
167
+ logging_1.debugLog.info('SessionManager', 'Session tracking started', { sessionId, recovered: isRecovered });
168
+ }
169
+ catch (error) {
170
+ this.isTracking = false;
171
+ this.clearSessionTimeout();
172
+ this.cleanupActivityListeners();
173
+ this.cleanupLifecycleListeners();
174
+ this.cleanupCrossTabSync();
175
+ this.set('sessionId', null);
176
+ throw error;
295
177
  }
296
178
  }
297
- resetState() {
298
- this.isSessionActive = false;
299
- this.lastActivityTime = 0;
300
- this.sessionStartTime = 0;
179
+ /**
180
+ * Generate unique session ID
181
+ */
182
+ generateSessionId() {
183
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
301
184
  }
302
- clearInactivityTimer() {
303
- if (this.inactivityTimer) {
304
- clearTimeout(this.inactivityTimer);
305
- this.inactivityTimer = null;
306
- }
185
+ /**
186
+ * Setup session timeout
187
+ */
188
+ setupSessionTimeout() {
189
+ this.clearSessionTimeout();
190
+ const sessionTimeout = this.get('config')?.sessionTimeout ?? constants_1.DEFAULT_SESSION_TIMEOUT;
191
+ this.sessionTimeoutId = setTimeout(() => {
192
+ this.endSession('inactivity');
193
+ }, sessionTimeout);
307
194
  }
308
- shouldProceedWithSessionEnd(reason) {
309
- return !this.sessionEndReason || this.sessionEndPriority[reason] > this.sessionEndPriority[this.sessionEndReason];
195
+ /**
196
+ * Reset session timeout and update activity
197
+ */
198
+ resetSessionTimeout() {
199
+ this.setupSessionTimeout();
200
+ const sessionId = this.get('sessionId');
201
+ if (sessionId) {
202
+ this.persistSession(sessionId);
203
+ }
310
204
  }
311
- async waitForCompletion() {
312
- if (this.sessionEndPromise) {
313
- return await this.sessionEndPromise;
205
+ /**
206
+ * Clear session timeout
207
+ */
208
+ clearSessionTimeout() {
209
+ if (this.sessionTimeoutId) {
210
+ clearTimeout(this.sessionTimeoutId);
211
+ this.sessionTimeoutId = null;
314
212
  }
315
- return {
316
- success: false,
317
- reason: 'inactivity',
318
- timestamp: Date.now(),
319
- eventsFlushed: 0,
320
- method: 'async',
321
- };
322
213
  }
323
- async endSessionManaged(reason) {
324
- return (this.sessionEndLock = this.sessionEndLock.then(async () => {
325
- this.sessionEndStats.totalSessionEnds++;
326
- this.sessionEndStats.reasonCounts[reason]++;
327
- if (this.pendingSessionEnd) {
328
- this.sessionEndStats.duplicatePrevented++;
329
- logging_1.debugLog.debug('SessionManager', 'Session end already pending, waiting for completion', { reason });
330
- return this.waitForCompletion();
331
- }
332
- if (!this.shouldProceedWithSessionEnd(reason)) {
333
- if (this.sessionEndConfig.debugMode) {
334
- logging_1.debugLog.debug('SessionManager', `Session end skipped due to lower priority. Current: ${this.sessionEndReason}, Requested: ${reason}`);
335
- }
336
- return {
337
- success: false,
338
- reason,
339
- timestamp: Date.now(),
340
- eventsFlushed: 0,
341
- method: 'async',
342
- };
343
- }
344
- this.sessionEndReason = reason;
345
- this.pendingSessionEnd = true;
346
- this.sessionEndPromise = this.performSessionEnd(reason, 'async');
347
- try {
348
- const result = await this.sessionEndPromise;
349
- return result;
350
- }
351
- finally {
352
- this.pendingSessionEnd = false;
353
- this.sessionEndPromise = null;
354
- this.sessionEndReason = null;
355
- }
356
- }));
214
+ /**
215
+ * Setup activity listeners to track user engagement
216
+ */
217
+ setupActivityListeners() {
218
+ this.activityHandler = () => this.resetSessionTimeout();
219
+ document.addEventListener('click', this.activityHandler, { passive: true });
220
+ document.addEventListener('keydown', this.activityHandler, { passive: true });
221
+ document.addEventListener('scroll', this.activityHandler, { passive: true });
357
222
  }
358
- endSessionSafely(reason, options) {
359
- const shouldUseSync = options?.forceSync ?? (options?.allowSync && ['page_unload', 'tab_closed'].includes(reason));
360
- if (shouldUseSync) {
361
- return this.endSessionManagedSync(reason);
223
+ /**
224
+ * Clean up activity listeners
225
+ */
226
+ cleanupActivityListeners() {
227
+ if (this.activityHandler) {
228
+ document.removeEventListener('click', this.activityHandler);
229
+ document.removeEventListener('keydown', this.activityHandler);
230
+ document.removeEventListener('scroll', this.activityHandler);
231
+ this.activityHandler = null;
362
232
  }
363
- return this.endSessionManaged(reason);
364
- }
365
- isPendingSessionEnd() {
366
- return this.pendingSessionEnd;
367
233
  }
368
234
  /**
369
- * Track session health events for monitoring and diagnostics
235
+ * Setup page lifecycle listeners (visibility and unload)
370
236
  */
371
- trackSessionHealth(event) {
372
- const now = Date.now();
373
- // Update health counters
374
- switch (event) {
375
- case 'recovery':
376
- this.sessionHealth.recoveryAttempts++;
377
- break;
378
- case 'timeout':
379
- this.sessionHealth.sessionTimeouts++;
380
- break;
381
- case 'conflict':
382
- this.sessionHealth.crossTabConflicts++;
383
- break;
237
+ setupLifecycleListeners() {
238
+ if (this.visibilityChangeHandler || this.beforeUnloadHandler) {
239
+ return;
384
240
  }
385
- this.sessionHealth.lastHealthCheck = now;
386
- // Send health degradation event if recovery attempts are high
387
- if (this.sessionHealth.recoveryAttempts > 3 && this.eventManager) {
388
- this.eventManager.track({
389
- type: types_1.EventType.CUSTOM,
390
- custom_event: {
391
- name: 'session_health_degraded',
392
- metadata: {
393
- ...this.sessionHealth,
394
- event_trigger: event,
395
- },
396
- },
397
- });
398
- if (this.sessionEndConfig.debugMode) {
399
- logging_1.debugLog.warn('SessionManager', `Session health degraded: ${this.sessionHealth.recoveryAttempts} recovery attempts`);
241
+ this.visibilityChangeHandler = () => {
242
+ if (document.hidden) {
243
+ this.clearSessionTimeout();
400
244
  }
245
+ else {
246
+ const sessionId = this.get('sessionId');
247
+ if (sessionId) {
248
+ this.setupSessionTimeout();
249
+ }
250
+ }
251
+ };
252
+ this.beforeUnloadHandler = () => {
253
+ this.eventManager.flushImmediatelySync();
254
+ };
255
+ // Handle tab visibility changes
256
+ document.addEventListener('visibilitychange', this.visibilityChangeHandler);
257
+ // Handle page unload
258
+ window.addEventListener('beforeunload', this.beforeUnloadHandler);
259
+ }
260
+ cleanupLifecycleListeners() {
261
+ if (this.visibilityChangeHandler) {
262
+ document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
263
+ this.visibilityChangeHandler = null;
401
264
  }
402
- if (this.sessionEndConfig.debugMode) {
403
- logging_1.debugLog.debug('SessionManager', `Session health event tracked: ${event}`);
265
+ if (this.beforeUnloadHandler) {
266
+ window.removeEventListener('beforeunload', this.beforeUnloadHandler);
267
+ this.beforeUnloadHandler = null;
404
268
  }
405
269
  }
406
- async performSessionEnd(reason, method) {
407
- const timestamp = Date.now();
408
- let eventsFlushed = 0;
409
- try {
410
- logging_1.debugLog.info('SessionManager', 'Starting session end', { method, reason, timestamp });
411
- if (this.eventManager) {
412
- this.eventManager.track({
413
- type: types_1.EventType.SESSION_END,
414
- session_end_reason: reason,
415
- });
416
- eventsFlushed = this.eventManager.getQueueLength();
417
- const flushResult = await this.eventManager.flushImmediately();
418
- this.cleanupSession();
419
- const result = {
420
- success: flushResult,
421
- reason,
422
- timestamp,
423
- eventsFlushed,
424
- method,
425
- };
426
- if (flushResult) {
427
- this.sessionEndStats.successfulEnds++;
428
- }
429
- else {
430
- this.sessionEndStats.failedEnds++;
431
- }
432
- return result;
433
- }
434
- this.cleanupSession();
435
- const result = {
436
- success: true,
437
- reason,
438
- timestamp,
439
- eventsFlushed: 0,
440
- method,
441
- };
442
- this.sessionEndStats.successfulEnds++;
443
- return result;
270
+ /**
271
+ * End current session
272
+ */
273
+ endSession(reason) {
274
+ const sessionId = this.get('sessionId');
275
+ if (!sessionId) {
276
+ logging_1.debugLog.warn('SessionManager', 'endSession called without active session', { reason });
277
+ this.resetSessionState();
278
+ return;
444
279
  }
445
- catch (error) {
446
- this.sessionEndStats.failedEnds++;
447
- logging_1.debugLog.error('SessionManager', 'Session end failed', { error, reason, method });
448
- this.cleanupSession();
449
- return {
450
- success: false,
451
- reason,
452
- timestamp,
453
- eventsFlushed,
454
- method,
455
- };
280
+ logging_1.debugLog.info('SessionManager', 'Ending session', { sessionId, reason });
281
+ this.eventManager.track({
282
+ type: types_1.EventType.SESSION_END,
283
+ session_end_reason: reason,
284
+ });
285
+ const finalize = () => {
286
+ this.broadcastSessionEnd(sessionId, reason);
287
+ this.resetSessionState();
288
+ };
289
+ const flushResult = this.eventManager.flushImmediatelySync();
290
+ if (flushResult) {
291
+ finalize();
292
+ return;
456
293
  }
294
+ this.eventManager
295
+ .flushImmediately()
296
+ .then(finalize)
297
+ .catch((error) => {
298
+ logging_1.debugLog.warn('SessionManager', 'Async flush failed during session end', {
299
+ error: error instanceof Error ? error.message : 'Unknown error',
300
+ });
301
+ finalize();
302
+ });
457
303
  }
458
- cleanupSession() {
459
- this.endSession();
460
- this.clearTimers();
304
+ resetSessionState() {
305
+ this.clearSessionTimeout();
306
+ this.cleanupActivityListeners();
307
+ this.cleanupLifecycleListeners();
308
+ this.cleanupCrossTabSync();
309
+ this.clearStoredSession();
461
310
  this.set('sessionId', null);
462
311
  this.set('hasStartSession', false);
312
+ this.isTracking = false;
463
313
  }
464
- endSessionManagedSync(reason) {
465
- this.sessionEndStats.totalSessionEnds++;
466
- this.sessionEndStats.reasonCounts[reason]++;
467
- if (this.pendingSessionEnd) {
468
- this.sessionEndStats.duplicatePrevented++;
469
- logging_1.debugLog.warn('SessionManager', 'Sync session end called while async end pending', { reason });
470
- }
471
- if (!this.shouldProceedWithSessionEnd(reason)) {
472
- if (this.sessionEndConfig.debugMode) {
473
- logging_1.debugLog.debug('SessionManager', `Sync session end skipped due to lower priority. Current: ${this.sessionEndReason}, Requested: ${reason}`);
474
- }
475
- return {
476
- success: false,
477
- reason,
478
- timestamp: Date.now(),
479
- eventsFlushed: 0,
480
- method: 'sync',
481
- };
482
- }
483
- this.sessionEndReason = reason;
484
- this.pendingSessionEnd = true;
485
- try {
486
- return this.performSessionEndSync(reason);
487
- }
488
- finally {
489
- this.pendingSessionEnd = false;
490
- this.sessionEndPromise = null;
491
- this.sessionEndReason = null;
492
- }
493
- }
494
- performSessionEndSync(reason) {
495
- const timestamp = Date.now();
496
- let eventsFlushed = 0;
497
- try {
498
- if (this.eventManager) {
499
- this.eventManager.track({
500
- type: types_1.EventType.SESSION_END,
501
- session_end_reason: reason,
502
- });
503
- eventsFlushed = this.eventManager.getQueueLength();
504
- const success = this.eventManager.flushImmediatelySync();
505
- this.cleanupSession();
506
- const result = {
507
- success,
508
- reason,
509
- timestamp,
510
- eventsFlushed,
511
- method: 'sync',
512
- };
513
- if (success) {
514
- this.sessionEndStats.successfulEnds++;
515
- }
516
- else {
517
- this.sessionEndStats.failedEnds++;
518
- }
519
- return result;
520
- }
521
- this.cleanupSession();
522
- const result = {
523
- success: true,
524
- reason,
525
- timestamp,
526
- eventsFlushed: 0,
527
- method: 'sync',
528
- };
529
- this.sessionEndStats.successfulEnds++;
530
- return result;
531
- }
532
- catch (error) {
533
- this.sessionEndStats.failedEnds++;
534
- this.cleanupSession();
535
- logging_1.debugLog.error('SessionManager', 'Sync session end failed', { error, reason });
536
- return {
537
- success: false,
538
- reason,
539
- timestamp,
540
- eventsFlushed,
541
- method: 'sync',
542
- };
543
- }
314
+ /**
315
+ * Stop session tracking
316
+ */
317
+ async stopTracking() {
318
+ this.endSession('manual_stop');
544
319
  }
545
- setupPageUnloadHandlers() {
546
- let unloadHandled = false;
547
- const handlePageUnload = () => {
548
- if (unloadHandled || !this.get('sessionId')) {
549
- return;
550
- }
551
- unloadHandled = true;
552
- this.clearInactivityTimer();
553
- this.endSessionSafely('page_unload', { forceSync: true });
554
- };
555
- // Primary handler for modern browsers
556
- const beforeUnloadHandler = () => {
557
- handlePageUnload();
558
- };
559
- // Fallback for older browsers and mobile Safari
560
- const pageHideHandler = (event) => {
561
- if (!event.persisted) {
562
- handlePageUnload();
563
- }
564
- };
565
- // Delayed handler for visibility changes (gives time for page transitions)
566
- const visibilityChangeHandler = () => {
567
- if (document.visibilityState === 'hidden' && this.get('sessionId') && !unloadHandled) {
568
- this.visibilityChangeTimeout = window.setTimeout(() => {
569
- if (document.visibilityState === 'hidden' && this.get('sessionId') && !unloadHandled) {
570
- handlePageUnload();
571
- }
572
- this.visibilityChangeTimeout = null;
573
- }, 1000);
574
- }
575
- };
576
- window.addEventListener('beforeunload', beforeUnloadHandler);
577
- window.addEventListener('pagehide', pageHideHandler);
578
- document.addEventListener('visibilitychange', visibilityChangeHandler);
579
- this.cleanupHandlers.push(() => window.removeEventListener('beforeunload', beforeUnloadHandler), () => window.removeEventListener('pagehide', pageHideHandler), () => document.removeEventListener('visibilitychange', visibilityChangeHandler), () => {
580
- if (this.visibilityChangeTimeout) {
581
- clearTimeout(this.visibilityChangeTimeout);
582
- this.visibilityChangeTimeout = null;
583
- }
584
- });
320
+ /**
321
+ * Clean up all resources
322
+ */
323
+ destroy() {
324
+ this.clearSessionTimeout();
325
+ this.cleanupActivityListeners();
326
+ this.cleanupCrossTabSync();
327
+ this.cleanupLifecycleListeners();
328
+ this.isTracking = false;
329
+ this.set('hasStartSession', false);
585
330
  }
586
331
  }
587
332
  exports.SessionManager = SessionManager;