@tracelog/lib 0.0.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +58 -24
  2. package/dist/browser/tracelog.js +1934 -3226
  3. package/dist/cjs/api.d.ts +33 -19
  4. package/dist/cjs/api.js +111 -156
  5. package/dist/cjs/app.constants.d.ts +80 -1
  6. package/dist/cjs/app.constants.js +90 -3
  7. package/dist/cjs/app.d.ts +29 -44
  8. package/dist/cjs/app.js +114 -212
  9. package/dist/cjs/app.types.d.ts +2 -7
  10. package/dist/cjs/app.types.js +10 -21
  11. package/dist/cjs/constants/api.constants.js +11 -5
  12. package/dist/cjs/constants/config.constants.d.ts +75 -0
  13. package/dist/cjs/constants/config.constants.js +178 -0
  14. package/dist/cjs/constants/error.constants.d.ts +29 -0
  15. package/dist/cjs/constants/error.constants.js +50 -0
  16. package/dist/cjs/constants/index.d.ts +3 -6
  17. package/dist/cjs/constants/index.js +3 -6
  18. package/dist/cjs/constants/performance.constants.d.ts +28 -0
  19. package/dist/cjs/constants/performance.constants.js +43 -0
  20. package/dist/cjs/handlers/click.handler.d.ts +1 -0
  21. package/dist/cjs/handlers/click.handler.js +30 -49
  22. package/dist/cjs/handlers/error.handler.d.ts +11 -6
  23. package/dist/cjs/handlers/error.handler.js +91 -51
  24. package/dist/cjs/handlers/page-view.handler.js +38 -29
  25. package/dist/cjs/handlers/performance.handler.d.ts +3 -0
  26. package/dist/cjs/handlers/performance.handler.js +76 -37
  27. package/dist/cjs/handlers/scroll.handler.d.ts +15 -0
  28. package/dist/cjs/handlers/scroll.handler.js +105 -31
  29. package/dist/cjs/handlers/session.handler.d.ts +6 -20
  30. package/dist/cjs/handlers/session.handler.js +38 -326
  31. package/dist/cjs/integrations/google-analytics.integration.d.ts +0 -1
  32. package/dist/cjs/integrations/google-analytics.integration.js +27 -98
  33. package/dist/cjs/listeners/input-listener-managers.d.ts +18 -9
  34. package/dist/cjs/listeners/input-listener-managers.js +24 -33
  35. package/dist/cjs/listeners/touch-listener-manager.d.ts +1 -3
  36. package/dist/cjs/listeners/touch-listener-manager.js +1 -23
  37. package/dist/cjs/listeners/visibility-listener-manager.d.ts +1 -4
  38. package/dist/cjs/listeners/visibility-listener-manager.js +6 -42
  39. package/dist/cjs/managers/api.manager.d.ts +13 -3
  40. package/dist/cjs/managers/api.manager.js +35 -5
  41. package/dist/cjs/managers/config.manager.d.ts +53 -3
  42. package/dist/cjs/managers/config.manager.js +131 -62
  43. package/dist/cjs/managers/event.manager.d.ts +57 -36
  44. package/dist/cjs/managers/event.manager.js +266 -417
  45. package/dist/cjs/managers/sender.manager.d.ts +40 -22
  46. package/dist/cjs/managers/sender.manager.js +200 -198
  47. package/dist/cjs/managers/session.manager.d.ts +80 -66
  48. package/dist/cjs/managers/session.manager.js +267 -522
  49. package/dist/cjs/managers/state.manager.d.ts +33 -0
  50. package/dist/cjs/managers/state.manager.js +79 -6
  51. package/dist/cjs/managers/storage.manager.d.ts +26 -2
  52. package/dist/cjs/managers/storage.manager.js +67 -34
  53. package/dist/cjs/managers/tags.manager.d.ts +31 -7
  54. package/dist/cjs/managers/tags.manager.js +123 -241
  55. package/dist/cjs/managers/user.manager.d.ts +14 -5
  56. package/dist/cjs/managers/user.manager.js +17 -9
  57. package/dist/cjs/public-api.d.ts +10 -1
  58. package/dist/cjs/public-api.js +18 -24
  59. package/dist/cjs/test-bridge.d.ts +48 -0
  60. package/dist/cjs/test-bridge.js +110 -0
  61. package/dist/cjs/types/api.types.d.ts +21 -6
  62. package/dist/cjs/types/api.types.js +21 -6
  63. package/dist/cjs/types/config.types.d.ts +22 -84
  64. package/dist/cjs/types/emitter.types.d.ts +11 -0
  65. package/dist/cjs/types/emitter.types.js +8 -0
  66. package/dist/cjs/types/event.types.d.ts +8 -11
  67. package/dist/cjs/types/index.d.ts +3 -1
  68. package/dist/cjs/types/index.js +3 -1
  69. package/dist/cjs/types/queue.types.d.ts +1 -0
  70. package/dist/cjs/types/session.types.d.ts +0 -64
  71. package/dist/cjs/types/state.types.d.ts +1 -0
  72. package/dist/cjs/types/test-bridge.types.d.ts +38 -0
  73. package/dist/cjs/types/validation-error.types.d.ts +7 -0
  74. package/dist/cjs/types/validation-error.types.js +11 -1
  75. package/dist/cjs/types/window.types.d.ts +1 -8
  76. package/dist/cjs/utils/data/uuid.utils.d.ts +1 -1
  77. package/dist/cjs/utils/data/uuid.utils.js +7 -5
  78. package/dist/cjs/utils/emitter.utils.d.ts +8 -0
  79. package/dist/cjs/utils/emitter.utils.js +33 -0
  80. package/dist/cjs/utils/index.d.ts +1 -0
  81. package/dist/cjs/utils/index.js +1 -0
  82. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +10 -51
  83. package/dist/cjs/utils/logging/debug-logger.utils.js +36 -127
  84. package/dist/cjs/utils/network/fetch-with-timeout.utils.d.ts +4 -0
  85. package/dist/cjs/utils/network/fetch-with-timeout.utils.js +25 -0
  86. package/dist/cjs/utils/network/index.d.ts +1 -0
  87. package/dist/cjs/utils/network/index.js +1 -0
  88. package/dist/cjs/utils/network/url.utils.js +2 -42
  89. package/dist/cjs/utils/security/sanitize.utils.d.ts +1 -8
  90. package/dist/cjs/utils/security/sanitize.utils.js +7 -41
  91. package/dist/cjs/utils/validations/config-validations.utils.d.ts +7 -0
  92. package/dist/cjs/utils/validations/config-validations.utils.js +77 -22
  93. package/dist/esm/api.d.ts +33 -19
  94. package/dist/esm/api.js +105 -118
  95. package/dist/esm/app.constants.d.ts +80 -1
  96. package/dist/esm/app.constants.js +89 -1
  97. package/dist/esm/app.d.ts +29 -44
  98. package/dist/esm/app.js +115 -213
  99. package/dist/esm/app.types.d.ts +2 -7
  100. package/dist/esm/app.types.js +1 -7
  101. package/dist/esm/constants/api.constants.js +10 -4
  102. package/dist/esm/constants/config.constants.d.ts +75 -0
  103. package/dist/esm/constants/config.constants.js +174 -0
  104. package/dist/esm/constants/error.constants.d.ts +29 -0
  105. package/dist/esm/constants/error.constants.js +47 -0
  106. package/dist/esm/constants/index.d.ts +3 -6
  107. package/dist/esm/constants/index.js +3 -6
  108. package/dist/esm/constants/performance.constants.d.ts +28 -0
  109. package/dist/esm/constants/performance.constants.js +40 -0
  110. package/dist/esm/handlers/click.handler.d.ts +1 -0
  111. package/dist/esm/handlers/click.handler.js +30 -49
  112. package/dist/esm/handlers/error.handler.d.ts +11 -6
  113. package/dist/esm/handlers/error.handler.js +91 -51
  114. package/dist/esm/handlers/page-view.handler.js +38 -29
  115. package/dist/esm/handlers/performance.handler.d.ts +3 -0
  116. package/dist/esm/handlers/performance.handler.js +71 -32
  117. package/dist/esm/handlers/scroll.handler.d.ts +15 -0
  118. package/dist/esm/handlers/scroll.handler.js +106 -32
  119. package/dist/esm/handlers/session.handler.d.ts +6 -20
  120. package/dist/esm/handlers/session.handler.js +38 -326
  121. package/dist/esm/integrations/google-analytics.integration.d.ts +0 -1
  122. package/dist/esm/integrations/google-analytics.integration.js +27 -98
  123. package/dist/esm/listeners/input-listener-managers.d.ts +18 -9
  124. package/dist/esm/listeners/input-listener-managers.js +23 -32
  125. package/dist/esm/listeners/touch-listener-manager.d.ts +1 -3
  126. package/dist/esm/listeners/touch-listener-manager.js +1 -23
  127. package/dist/esm/listeners/visibility-listener-manager.d.ts +1 -4
  128. package/dist/esm/listeners/visibility-listener-manager.js +6 -42
  129. package/dist/esm/managers/api.manager.d.ts +13 -3
  130. package/dist/esm/managers/api.manager.js +34 -3
  131. package/dist/esm/managers/config.manager.d.ts +53 -3
  132. package/dist/esm/managers/config.manager.js +133 -64
  133. package/dist/esm/managers/event.manager.d.ts +57 -36
  134. package/dist/esm/managers/event.manager.js +268 -419
  135. package/dist/esm/managers/sender.manager.d.ts +40 -22
  136. package/dist/esm/managers/sender.manager.js +201 -199
  137. package/dist/esm/managers/session.manager.d.ts +80 -66
  138. package/dist/esm/managers/session.manager.js +269 -524
  139. package/dist/esm/managers/state.manager.d.ts +33 -0
  140. package/dist/esm/managers/state.manager.js +78 -6
  141. package/dist/esm/managers/storage.manager.d.ts +26 -2
  142. package/dist/esm/managers/storage.manager.js +66 -33
  143. package/dist/esm/managers/tags.manager.d.ts +31 -7
  144. package/dist/esm/managers/tags.manager.js +124 -242
  145. package/dist/esm/managers/user.manager.d.ts +14 -5
  146. package/dist/esm/managers/user.manager.js +17 -9
  147. package/dist/esm/public-api.d.ts +10 -1
  148. package/dist/esm/public-api.js +14 -1
  149. package/dist/esm/test-bridge.d.ts +48 -0
  150. package/dist/esm/test-bridge.js +106 -0
  151. package/dist/esm/types/api.types.d.ts +21 -6
  152. package/dist/esm/types/api.types.js +21 -6
  153. package/dist/esm/types/config.types.d.ts +22 -84
  154. package/dist/esm/types/emitter.types.d.ts +11 -0
  155. package/dist/esm/types/emitter.types.js +5 -0
  156. package/dist/esm/types/event.types.d.ts +8 -11
  157. package/dist/esm/types/index.d.ts +3 -1
  158. package/dist/esm/types/index.js +3 -1
  159. package/dist/esm/types/queue.types.d.ts +1 -0
  160. package/dist/esm/types/session.types.d.ts +0 -64
  161. package/dist/esm/types/state.types.d.ts +1 -0
  162. package/dist/esm/types/test-bridge.types.d.ts +38 -0
  163. package/dist/esm/types/validation-error.types.d.ts +7 -0
  164. package/dist/esm/types/validation-error.types.js +9 -0
  165. package/dist/esm/types/window.types.d.ts +1 -8
  166. package/dist/esm/utils/data/uuid.utils.d.ts +1 -1
  167. package/dist/esm/utils/data/uuid.utils.js +7 -5
  168. package/dist/esm/utils/emitter.utils.d.ts +8 -0
  169. package/dist/esm/utils/emitter.utils.js +29 -0
  170. package/dist/esm/utils/index.d.ts +1 -0
  171. package/dist/esm/utils/index.js +1 -0
  172. package/dist/esm/utils/logging/debug-logger.utils.d.ts +10 -51
  173. package/dist/esm/utils/logging/debug-logger.utils.js +36 -127
  174. package/dist/esm/utils/network/fetch-with-timeout.utils.d.ts +4 -0
  175. package/dist/esm/utils/network/fetch-with-timeout.utils.js +22 -0
  176. package/dist/esm/utils/network/index.d.ts +1 -0
  177. package/dist/esm/utils/network/index.js +1 -0
  178. package/dist/esm/utils/network/url.utils.js +2 -42
  179. package/dist/esm/utils/security/sanitize.utils.d.ts +1 -8
  180. package/dist/esm/utils/security/sanitize.utils.js +6 -39
  181. package/dist/esm/utils/validations/config-validations.utils.d.ts +7 -0
  182. package/dist/esm/utils/validations/config-validations.utils.js +76 -22
  183. package/package.json +23 -16
  184. package/dist/browser/web-vitals-CCnqwnC8.mjs +0 -198
  185. package/dist/cjs/constants/browser.constants.d.ts +0 -3
  186. package/dist/cjs/constants/browser.constants.js +0 -41
  187. package/dist/cjs/constants/initialization.constants.d.ts +0 -40
  188. package/dist/cjs/constants/initialization.constants.js +0 -48
  189. package/dist/cjs/constants/limits.constants.d.ts +0 -25
  190. package/dist/cjs/constants/limits.constants.js +0 -40
  191. package/dist/cjs/constants/security.constants.d.ts +0 -1
  192. package/dist/cjs/constants/security.constants.js +0 -12
  193. package/dist/cjs/constants/timing.constants.d.ts +0 -22
  194. package/dist/cjs/constants/timing.constants.js +0 -34
  195. package/dist/cjs/constants/validation.constants.d.ts +0 -13
  196. package/dist/cjs/constants/validation.constants.js +0 -31
  197. package/dist/cjs/handlers/network.handler.d.ts +0 -16
  198. package/dist/cjs/handlers/network.handler.js +0 -136
  199. package/dist/cjs/managers/cross-tab-session.manager.d.ts +0 -170
  200. package/dist/cjs/managers/cross-tab-session.manager.js +0 -730
  201. package/dist/cjs/managers/sampling.manager.d.ts +0 -8
  202. package/dist/cjs/managers/sampling.manager.js +0 -53
  203. package/dist/cjs/managers/session-recovery.manager.d.ts +0 -65
  204. package/dist/cjs/managers/session-recovery.manager.js +0 -237
  205. package/dist/cjs/types/web-vitals.types.d.ts +0 -6
  206. package/dist/esm/constants/browser.constants.d.ts +0 -3
  207. package/dist/esm/constants/browser.constants.js +0 -38
  208. package/dist/esm/constants/initialization.constants.d.ts +0 -40
  209. package/dist/esm/constants/initialization.constants.js +0 -45
  210. package/dist/esm/constants/limits.constants.d.ts +0 -25
  211. package/dist/esm/constants/limits.constants.js +0 -37
  212. package/dist/esm/constants/security.constants.d.ts +0 -1
  213. package/dist/esm/constants/security.constants.js +0 -9
  214. package/dist/esm/constants/timing.constants.d.ts +0 -22
  215. package/dist/esm/constants/timing.constants.js +0 -31
  216. package/dist/esm/constants/validation.constants.d.ts +0 -13
  217. package/dist/esm/constants/validation.constants.js +0 -28
  218. package/dist/esm/handlers/network.handler.d.ts +0 -16
  219. package/dist/esm/handlers/network.handler.js +0 -132
  220. package/dist/esm/managers/cross-tab-session.manager.d.ts +0 -170
  221. package/dist/esm/managers/cross-tab-session.manager.js +0 -726
  222. package/dist/esm/managers/sampling.manager.d.ts +0 -8
  223. package/dist/esm/managers/sampling.manager.js +0 -49
  224. package/dist/esm/managers/session-recovery.manager.d.ts +0 -65
  225. package/dist/esm/managers/session-recovery.manager.js +0 -233
  226. package/dist/esm/types/web-vitals.types.d.ts +0 -6
  227. /package/dist/cjs/types/{web-vitals.types.js → test-bridge.types.js} +0 -0
  228. /package/dist/esm/types/{web-vitals.types.js → test-bridge.types.js} +0 -0
@@ -1,93 +1,133 @@
1
1
  import { StateManager } from '../managers/state.manager';
2
2
  import { ErrorType, EventType } from '../types';
3
+ import { PII_PATTERNS, MAX_ERROR_MESSAGE_LENGTH, ERROR_SUPPRESSION_WINDOW_MS, MAX_TRACKED_ERRORS, } from '../constants/error.constants';
3
4
  import { debugLog } from '../utils/logging';
5
+ /**
6
+ * Simplified error handler for tracking JavaScript errors and unhandled promise rejections
7
+ * Includes PII sanitization and sampling support
8
+ */
4
9
  export class ErrorHandler extends StateManager {
5
10
  constructor(eventManager) {
6
11
  super();
7
- this.piiPatterns = [
8
- /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
9
- /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
10
- /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,
11
- /\b[A-Z]{2}\d{2}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,
12
- ];
12
+ this.recentErrors = new Map();
13
13
  this.handleError = (event) => {
14
- const config = this.get('config');
15
- if (!this.shouldSample(config?.errorSampling ?? 0.1)) {
16
- debugLog.debug('ErrorHandler', `Error not sampled, skipping (errorSampling: ${config?.errorSampling})`, {
17
- errorSampling: config?.errorSampling,
18
- });
14
+ if (!this.shouldSample()) {
19
15
  return;
20
16
  }
21
- debugLog.warn('ErrorHandler', `JavaScript error captured: ${event.message} (filename: ${event.filename}, lineno: ${event.lineno})`, {
22
- message: event.message,
17
+ const sanitizedMessage = this.sanitize(event.message || 'Unknown error');
18
+ if (this.shouldSuppressError(ErrorType.JS_ERROR, sanitizedMessage)) {
19
+ return;
20
+ }
21
+ debugLog.warn('ErrorHandler', 'JS error captured', {
22
+ message: sanitizedMessage,
23
23
  filename: event.filename,
24
- lineno: event.lineno,
24
+ line: event.lineno,
25
25
  });
26
26
  this.eventManager.track({
27
27
  type: EventType.ERROR,
28
28
  error_data: {
29
29
  type: ErrorType.JS_ERROR,
30
- message: this.sanitizeText(event.message || 'Unknown error'),
30
+ message: sanitizedMessage,
31
+ ...(event.filename && { filename: event.filename }),
32
+ ...(event.lineno && { line: event.lineno }),
33
+ ...(event.colno && { column: event.colno }),
31
34
  },
32
35
  });
33
36
  };
34
- this.handleUnhandledRejection = (event) => {
35
- const config = this.get('config');
36
- if (!this.shouldSample(config?.errorSampling ?? 0.1)) {
37
- debugLog.debug('ErrorHandler', 'Promise rejection not sampled, skipping', {
38
- errorSampling: config?.errorSampling,
39
- });
37
+ this.handleRejection = (event) => {
38
+ if (!this.shouldSample()) {
40
39
  return;
41
40
  }
42
- debugLog.warn('ErrorHandler', `Unhandled promise rejection captured (reason: ${typeof event.reason})`, {
43
- reason: typeof event.reason,
44
- });
45
- let reason = 'Unknown rejection';
46
- if (event.reason) {
47
- if (typeof event.reason === 'string') {
48
- reason = event.reason;
49
- }
50
- else if (event.reason instanceof Error) {
51
- reason = event.reason.message || event.reason.toString();
52
- }
53
- else {
54
- reason = String(event.reason);
55
- }
41
+ const message = this.extractRejectionMessage(event.reason);
42
+ const sanitizedMessage = this.sanitize(message);
43
+ if (this.shouldSuppressError(ErrorType.PROMISE_REJECTION, sanitizedMessage)) {
44
+ return;
56
45
  }
46
+ debugLog.warn('ErrorHandler', 'Promise rejection captured', { message: sanitizedMessage });
57
47
  this.eventManager.track({
58
48
  type: EventType.ERROR,
59
49
  error_data: {
60
50
  type: ErrorType.PROMISE_REJECTION,
61
- message: this.sanitizeText(reason),
51
+ message: sanitizedMessage,
62
52
  },
63
53
  });
64
54
  };
65
55
  this.eventManager = eventManager;
66
56
  }
67
57
  startTracking() {
68
- debugLog.debug('ErrorHandler', 'Starting error tracking');
69
- this.setupErrorListener();
70
- this.setupUnhandledRejectionListener();
58
+ window.addEventListener('error', this.handleError);
59
+ window.addEventListener('unhandledrejection', this.handleRejection);
71
60
  }
72
61
  stopTracking() {
73
- debugLog.debug('ErrorHandler', 'Stopping error tracking');
74
62
  window.removeEventListener('error', this.handleError);
75
- window.removeEventListener('unhandledrejection', this.handleUnhandledRejection);
63
+ window.removeEventListener('unhandledrejection', this.handleRejection);
64
+ this.recentErrors.clear();
76
65
  }
77
- setupErrorListener() {
78
- window.addEventListener('error', this.handleError);
66
+ shouldSample() {
67
+ const config = this.get('config');
68
+ const samplingRate = config?.errorSampling ?? 0.1;
69
+ return Math.random() < samplingRate;
79
70
  }
80
- setupUnhandledRejectionListener() {
81
- window.addEventListener('unhandledrejection', this.handleUnhandledRejection);
71
+ extractRejectionMessage(reason) {
72
+ if (!reason)
73
+ return 'Unknown rejection';
74
+ if (typeof reason === 'string')
75
+ return reason;
76
+ if (reason instanceof Error) {
77
+ return reason.stack ?? reason.message ?? reason.toString();
78
+ }
79
+ // Handle objects with message property
80
+ if (typeof reason === 'object' && 'message' in reason) {
81
+ return String(reason.message);
82
+ }
83
+ // Try to stringify objects
84
+ try {
85
+ return JSON.stringify(reason);
86
+ }
87
+ catch {
88
+ return String(reason);
89
+ }
82
90
  }
83
- sanitizeText(text) {
84
- let sanitized = text;
85
- for (const pattern of this.piiPatterns) {
86
- sanitized = sanitized.replace(pattern, '[REDACTED]');
91
+ sanitize(text) {
92
+ let sanitized = text.length > MAX_ERROR_MESSAGE_LENGTH ? text.slice(0, MAX_ERROR_MESSAGE_LENGTH) + '...' : text;
93
+ for (const pattern of PII_PATTERNS) {
94
+ // Create new regex instance to avoid global flag state issues
95
+ const regex = new RegExp(pattern.source, pattern.flags);
96
+ sanitized = sanitized.replace(regex, '[REDACTED]');
87
97
  }
88
98
  return sanitized;
89
99
  }
90
- shouldSample(rate) {
91
- return Math.random() < rate;
100
+ shouldSuppressError(type, message) {
101
+ const now = Date.now();
102
+ const key = `${type}:${message}`;
103
+ const lastSeenAt = this.recentErrors.get(key);
104
+ if (lastSeenAt && now - lastSeenAt < ERROR_SUPPRESSION_WINDOW_MS) {
105
+ this.recentErrors.set(key, now);
106
+ return true;
107
+ }
108
+ this.recentErrors.set(key, now);
109
+ if (this.recentErrors.size > MAX_TRACKED_ERRORS) {
110
+ this.pruneOldErrors();
111
+ }
112
+ return false;
113
+ }
114
+ pruneOldErrors() {
115
+ const now = Date.now();
116
+ for (const [key, timestamp] of this.recentErrors.entries()) {
117
+ if (now - timestamp > ERROR_SUPPRESSION_WINDOW_MS) {
118
+ this.recentErrors.delete(key);
119
+ }
120
+ }
121
+ if (this.recentErrors.size <= MAX_TRACKED_ERRORS) {
122
+ return;
123
+ }
124
+ const entries = Array.from(this.recentErrors.entries()).sort((a, b) => a[1] - b[1]);
125
+ const excess = this.recentErrors.size - MAX_TRACKED_ERRORS;
126
+ for (let index = 0; index < excess; index += 1) {
127
+ const entry = entries[index];
128
+ if (entry) {
129
+ this.recentErrors.delete(entry[0]);
130
+ }
131
+ }
92
132
  }
93
133
  }
@@ -5,21 +5,23 @@ import { debugLog } from '../utils/logging';
5
5
  export class PageViewHandler extends StateManager {
6
6
  constructor(eventManager, onTrack) {
7
7
  super();
8
- this.trackCurrentPage = () => {
8
+ this.trackCurrentPage = async () => {
9
9
  const rawUrl = window.location.href;
10
10
  const normalizedUrl = normalizeUrl(rawUrl, this.get('config').sensitiveQueryParams);
11
- if (this.get('pageUrl') !== normalizedUrl) {
12
- const fromUrl = this.get('pageUrl');
13
- debugLog.debug('PageViewHandler', 'Page navigation detected', { from: fromUrl, to: normalizedUrl });
14
- this.set('pageUrl', normalizedUrl);
15
- this.eventManager.track({
16
- type: EventType.PAGE_VIEW,
17
- page_url: this.get('pageUrl'),
18
- from_page_url: fromUrl,
19
- ...(this.extractPageViewData() && { page_view: this.extractPageViewData() }),
20
- });
21
- this.onTrack();
11
+ if (this.get('pageUrl') === normalizedUrl) {
12
+ return;
22
13
  }
14
+ this.onTrack();
15
+ const fromUrl = this.get('pageUrl');
16
+ debugLog.debug('PageViewHandler', 'Page navigation detected', { from: fromUrl, to: normalizedUrl });
17
+ this.set('pageUrl', normalizedUrl);
18
+ const pageViewData = this.extractPageViewData();
19
+ this.eventManager.track({
20
+ type: EventType.PAGE_VIEW,
21
+ page_url: this.get('pageUrl'),
22
+ from_page_url: fromUrl,
23
+ ...(pageViewData && { page_view: pageViewData }),
24
+ });
23
25
  };
24
26
  this.eventManager = eventManager;
25
27
  this.onTrack = onTrack;
@@ -27,16 +29,15 @@ export class PageViewHandler extends StateManager {
27
29
  startTracking() {
28
30
  debugLog.debug('PageViewHandler', 'Starting page view tracking');
29
31
  this.trackInitialPageView();
30
- this.trackCurrentPage();
31
- window.addEventListener('popstate', this.trackCurrentPage);
32
- window.addEventListener('hashchange', this.trackCurrentPage);
32
+ window.addEventListener('popstate', this.trackCurrentPage, true);
33
+ window.addEventListener('hashchange', this.trackCurrentPage, true);
33
34
  this.patchHistory('pushState');
34
35
  this.patchHistory('replaceState');
35
36
  }
36
37
  stopTracking() {
37
38
  debugLog.debug('PageViewHandler', 'Stopping page view tracking');
38
- window.removeEventListener('popstate', this.trackCurrentPage);
39
- window.removeEventListener('hashchange', this.trackCurrentPage);
39
+ window.removeEventListener('popstate', this.trackCurrentPage, true);
40
+ window.removeEventListener('hashchange', this.trackCurrentPage, true);
40
41
  if (this.originalPushState) {
41
42
  window.history.pushState = this.originalPushState;
42
43
  }
@@ -45,35 +46,43 @@ export class PageViewHandler extends StateManager {
45
46
  }
46
47
  }
47
48
  patchHistory(method) {
49
+ const original = window.history[method];
48
50
  if (method === 'pushState' && !this.originalPushState) {
49
- this.originalPushState = window.history.pushState;
51
+ this.originalPushState = original;
50
52
  }
51
53
  else if (method === 'replaceState' && !this.originalReplaceState) {
52
- this.originalReplaceState = window.history.replaceState;
54
+ this.originalReplaceState = original;
53
55
  }
54
- const original = window.history[method];
55
56
  window.history[method] = (...args) => {
56
57
  original.apply(window.history, args);
57
58
  this.trackCurrentPage();
58
59
  };
59
60
  }
60
61
  trackInitialPageView() {
62
+ const normalizedUrl = normalizeUrl(window.location.href, this.get('config').sensitiveQueryParams);
63
+ const pageViewData = this.extractPageViewData();
61
64
  this.eventManager.track({
62
65
  type: EventType.PAGE_VIEW,
63
- page_url: this.get('pageUrl'),
64
- ...(this.extractPageViewData() && { page_view: this.extractPageViewData() }),
66
+ page_url: normalizedUrl,
67
+ ...(pageViewData && { page_view: pageViewData }),
65
68
  });
66
69
  this.onTrack();
67
70
  }
68
71
  extractPageViewData() {
69
- const location = window.location;
72
+ const { pathname, search, hash } = window.location;
73
+ const { referrer } = document;
74
+ const { title } = document;
75
+ // Early return if no meaningful data
76
+ if (!referrer && !title && !pathname && !search && !hash) {
77
+ return undefined;
78
+ }
70
79
  const data = {
71
- ...(document.referrer && { referrer: document.referrer }),
72
- ...(document.title && { title: document.title }),
73
- ...(location.pathname && { pathname: location.pathname }),
74
- ...(location.search && { search: location.search }),
75
- ...(location.hash && { hash: location.hash }),
80
+ ...(referrer && { referrer }),
81
+ ...(title && { title }),
82
+ ...(pathname && { pathname }),
83
+ ...(search && { search }),
84
+ ...(hash && { hash }),
76
85
  };
77
- return Object.values(data).some((value) => !!value) ? data : undefined;
86
+ return data;
78
87
  }
79
88
  }
@@ -5,6 +5,7 @@ export declare class PerformanceHandler extends StateManager {
5
5
  private readonly reportedByNav;
6
6
  private readonly observers;
7
7
  private lastLongTaskSentAt;
8
+ private readonly vitalThresholds;
8
9
  constructor(eventManager: EventManager);
9
10
  startTracking(): Promise<void>;
10
11
  stopTracking(): void;
@@ -15,5 +16,7 @@ export declare class PerformanceHandler extends StateManager {
15
16
  private sendVital;
16
17
  private trackWebVital;
17
18
  private getNavigationId;
19
+ private isObserverSupported;
18
20
  private safeObserve;
21
+ private shouldSendVital;
19
22
  }
@@ -1,7 +1,7 @@
1
1
  import { StateManager } from '../managers/state.manager';
2
2
  import { EventType } from '../types';
3
- import { LONG_TASK_THROTTLE_MS } from '../constants';
4
- import { PRECISION_FOUR_DECIMALS, PRECISION_TWO_DECIMALS } from '../constants';
3
+ import { LONG_TASK_THROTTLE_MS, PRECISION_TWO_DECIMALS } from '../constants';
4
+ import { WEB_VITALS_THRESHOLDS } from '../constants/performance.constants';
5
5
  import { debugLog } from '../utils/logging';
6
6
  export class PerformanceHandler extends StateManager {
7
7
  constructor(eventManager) {
@@ -9,16 +9,14 @@ export class PerformanceHandler extends StateManager {
9
9
  this.reportedByNav = new Map();
10
10
  this.observers = [];
11
11
  this.lastLongTaskSentAt = 0;
12
+ this.vitalThresholds = WEB_VITALS_THRESHOLDS;
12
13
  this.eventManager = eventManager;
13
14
  }
14
15
  async startTracking() {
15
- debugLog.debug('PerformanceHandler', 'Starting performance tracking');
16
16
  await this.initWebVitals();
17
17
  this.observeLongTasks();
18
- this.reportTTFB();
19
18
  }
20
19
  stopTracking() {
21
- debugLog.debug('PerformanceHandler', 'Stopping performance tracking', { observersCount: this.observers.length });
22
20
  this.observers.forEach((obs, index) => {
23
21
  try {
24
22
  obs.disconnect();
@@ -32,10 +30,6 @@ export class PerformanceHandler extends StateManager {
32
30
  });
33
31
  this.observers.length = 0;
34
32
  this.reportedByNav.clear();
35
- debugLog.debug('PerformanceHandler', 'Performance tracking cleanup completed', {
36
- remainingObservers: this.observers.length,
37
- clearedNavReports: true,
38
- });
39
33
  }
40
34
  observeWebVitalsFallback() {
41
35
  // TTFB - should be captured immediately as it's available from navigation timing
@@ -51,7 +45,14 @@ export class PerformanceHandler extends StateManager {
51
45
  }, { type: 'largest-contentful-paint', buffered: true }, true);
52
46
  // CLS (layout-shift)
53
47
  let clsValue = 0;
48
+ let currentNavId = this.getNavigationId();
54
49
  this.safeObserve('layout-shift', (list) => {
50
+ const navId = this.getNavigationId();
51
+ // Reset CLS on navigation change
52
+ if (navId !== currentNavId) {
53
+ clsValue = 0;
54
+ currentNavId = navId;
55
+ }
55
56
  const entries = list.getEntries();
56
57
  for (const entry of entries) {
57
58
  if (entry.hadRecentInput === true) {
@@ -60,7 +61,7 @@ export class PerformanceHandler extends StateManager {
60
61
  const value = typeof entry.value === 'number' ? entry.value : 0;
61
62
  clsValue += value;
62
63
  }
63
- this.sendVital({ type: 'CLS', value: Number(clsValue.toFixed(PRECISION_FOUR_DECIMALS)) });
64
+ this.sendVital({ type: 'CLS', value: Number(clsValue.toFixed(PRECISION_TWO_DECIMALS)) });
64
65
  }, { type: 'layout-shift', buffered: true });
65
66
  // FCP
66
67
  this.safeObserve('paint', (list) => {
@@ -107,7 +108,6 @@ export class PerformanceHandler extends StateManager {
107
108
  try {
108
109
  const nav = performance.getEntriesByType('navigation')[0];
109
110
  if (!nav) {
110
- debugLog.debug('PerformanceHandler', 'Navigation timing not available for TTFB');
111
111
  return;
112
112
  }
113
113
  const ttfb = nav.responseStart;
@@ -119,9 +119,6 @@ export class PerformanceHandler extends StateManager {
119
119
  if (typeof ttfb === 'number' && Number.isFinite(ttfb)) {
120
120
  this.sendVital({ type: 'TTFB', value: Number(ttfb.toFixed(PRECISION_TWO_DECIMALS)) });
121
121
  }
122
- else {
123
- debugLog.debug('PerformanceHandler', 'TTFB value is not a valid number', { ttfb });
124
- }
125
122
  }
126
123
  catch (error) {
127
124
  debugLog.warn('PerformanceHandler', 'Failed to report TTFB', {
@@ -136,29 +133,38 @@ export class PerformanceHandler extends StateManager {
136
133
  const duration = Number(entry.duration.toFixed(PRECISION_TWO_DECIMALS));
137
134
  const now = Date.now();
138
135
  if (now - this.lastLongTaskSentAt >= LONG_TASK_THROTTLE_MS) {
139
- this.trackWebVital('LONG_TASK', duration);
136
+ if (this.shouldSendVital('LONG_TASK', duration)) {
137
+ this.trackWebVital('LONG_TASK', duration);
138
+ }
140
139
  this.lastLongTaskSentAt = now;
141
140
  }
142
141
  }
143
142
  }, { type: 'longtask', buffered: true });
144
143
  }
145
144
  sendVital(sample) {
145
+ if (!this.shouldSendVital(sample.type, sample.value)) {
146
+ return;
147
+ }
146
148
  const navId = this.getNavigationId();
147
- const key = `${sample.type}`;
149
+ // Check for duplicates if we have a navigation ID
148
150
  if (navId) {
149
- if (!this.reportedByNav.has(navId)) {
150
- this.reportedByNav.set(navId, new Set());
151
- }
152
- const sent = this.reportedByNav.get(navId);
153
- if (sent.has(key)) {
151
+ const reportedForNav = this.reportedByNav.get(navId);
152
+ const isDuplicate = reportedForNav?.has(sample.type);
153
+ if (isDuplicate) {
154
154
  return;
155
155
  }
156
- sent.add(key);
156
+ // Initialize or update reported vitals for this navigation
157
+ if (!reportedForNav) {
158
+ this.reportedByNav.set(navId, new Set([sample.type]));
159
+ }
160
+ else {
161
+ reportedForNav.add(sample.type);
162
+ }
157
163
  }
158
164
  this.trackWebVital(sample.type, sample.value);
159
165
  }
160
166
  trackWebVital(type, value) {
161
- if (typeof value !== 'number' || !Number.isFinite(value)) {
167
+ if (!Number.isFinite(value)) {
162
168
  debugLog.warn('PerformanceHandler', 'Invalid web vital value', { type, value });
163
169
  return;
164
170
  }
@@ -176,7 +182,10 @@ export class PerformanceHandler extends StateManager {
176
182
  if (!nav) {
177
183
  return null;
178
184
  }
179
- return `${Math.round(nav.startTime)}_${window.location.pathname}`;
185
+ // Use more precise timestamp and add random component to prevent collisions
186
+ const timestamp = nav.startTime || performance.now();
187
+ const random = Math.random().toString(36).substr(2, 5);
188
+ return `${timestamp.toFixed(2)}_${window.location.pathname}_${random}`;
180
189
  }
181
190
  catch (error) {
182
191
  debugLog.warn('PerformanceHandler', 'Failed to get navigation ID', {
@@ -185,34 +194,64 @@ export class PerformanceHandler extends StateManager {
185
194
  return null;
186
195
  }
187
196
  }
197
+ isObserverSupported(type) {
198
+ if (typeof PerformanceObserver === 'undefined')
199
+ return false;
200
+ const supported = PerformanceObserver.supportedEntryTypes;
201
+ return !supported || supported.includes(type);
202
+ }
188
203
  safeObserve(type, cb, options, once = false) {
189
204
  try {
190
- if (typeof PerformanceObserver === 'undefined')
191
- return;
192
- const supported = PerformanceObserver.supportedEntryTypes;
193
- if (supported && !supported.includes(type))
194
- return;
205
+ if (!this.isObserverSupported(type)) {
206
+ return false;
207
+ }
195
208
  const obs = new PerformanceObserver((list, observer) => {
196
- cb(list, observer);
209
+ try {
210
+ cb(list, observer);
211
+ }
212
+ catch (callbackError) {
213
+ debugLog.warn('PerformanceHandler', 'Observer callback failed', {
214
+ type,
215
+ error: callbackError instanceof Error ? callbackError.message : 'Unknown error',
216
+ });
217
+ }
197
218
  if (once) {
198
219
  try {
199
220
  observer.disconnect();
200
221
  }
201
222
  catch {
202
- // Intentionally ignored
223
+ // Disconnect errors are safe to ignore
203
224
  }
204
225
  }
205
226
  });
206
- obs.observe((options ?? { type, buffered: true }));
227
+ obs.observe(options ?? { type, buffered: true });
207
228
  if (!once) {
208
229
  this.observers.push(obs);
209
230
  }
231
+ return true;
210
232
  }
211
233
  catch (error) {
212
234
  debugLog.warn('PerformanceHandler', 'Failed to create performance observer', {
213
235
  type,
214
236
  error: error instanceof Error ? error.message : 'Unknown error',
215
237
  });
238
+ return false;
239
+ }
240
+ }
241
+ shouldSendVital(type, value) {
242
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
243
+ debugLog.warn('PerformanceHandler', 'Invalid web vital value', { type, value });
244
+ return false;
245
+ }
246
+ const threshold = this.vitalThresholds[type];
247
+ if (typeof threshold === 'number' && value <= threshold) {
248
+ debugLog.debug('PerformanceHandler', 'Web vital below threshold, skipping', {
249
+ type,
250
+ value,
251
+ threshold,
252
+ });
253
+ return false;
216
254
  }
255
+ return true;
217
256
  }
218
257
  }
@@ -3,10 +3,25 @@ import { StateManager } from '../managers/state.manager';
3
3
  export declare class ScrollHandler extends StateManager {
4
4
  private readonly eventManager;
5
5
  private readonly containers;
6
+ private limitWarningLogged;
7
+ private minDepthChange;
8
+ private minIntervalMs;
9
+ private maxEventsPerSession;
6
10
  constructor(eventManager: EventManager);
7
11
  startTracking(): void;
8
12
  stopTracking(): void;
9
13
  private setupScrollContainer;
14
+ private processScrollEvent;
15
+ private shouldEmitScrollEvent;
16
+ private hasReachedSessionLimit;
17
+ private hasElapsedMinimumInterval;
18
+ private hasSignificantDepthChange;
19
+ private logLimitOnce;
20
+ private applyConfigOverrides;
21
+ private isWindowScrollable;
22
+ private clearContainerTimer;
23
+ private getScrollDirection;
24
+ private calculateScrollDepth;
10
25
  private calculateScrollData;
11
26
  private getScrollTop;
12
27
  private getViewportHeight;