@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.
- package/README.md +58 -24
- package/dist/browser/tracelog.js +1934 -3226
- package/dist/cjs/api.d.ts +33 -19
- package/dist/cjs/api.js +111 -156
- package/dist/cjs/app.constants.d.ts +80 -1
- package/dist/cjs/app.constants.js +90 -3
- package/dist/cjs/app.d.ts +29 -44
- package/dist/cjs/app.js +114 -212
- package/dist/cjs/app.types.d.ts +2 -7
- package/dist/cjs/app.types.js +10 -21
- package/dist/cjs/constants/api.constants.js +11 -5
- package/dist/cjs/constants/config.constants.d.ts +75 -0
- package/dist/cjs/constants/config.constants.js +178 -0
- package/dist/cjs/constants/error.constants.d.ts +29 -0
- package/dist/cjs/constants/error.constants.js +50 -0
- package/dist/cjs/constants/index.d.ts +3 -6
- package/dist/cjs/constants/index.js +3 -6
- package/dist/cjs/constants/performance.constants.d.ts +28 -0
- package/dist/cjs/constants/performance.constants.js +43 -0
- package/dist/cjs/handlers/click.handler.d.ts +1 -0
- package/dist/cjs/handlers/click.handler.js +30 -49
- package/dist/cjs/handlers/error.handler.d.ts +11 -6
- package/dist/cjs/handlers/error.handler.js +91 -51
- package/dist/cjs/handlers/page-view.handler.js +38 -29
- package/dist/cjs/handlers/performance.handler.d.ts +3 -0
- package/dist/cjs/handlers/performance.handler.js +76 -37
- package/dist/cjs/handlers/scroll.handler.d.ts +15 -0
- package/dist/cjs/handlers/scroll.handler.js +105 -31
- package/dist/cjs/handlers/session.handler.d.ts +6 -20
- package/dist/cjs/handlers/session.handler.js +38 -326
- package/dist/cjs/integrations/google-analytics.integration.d.ts +0 -1
- package/dist/cjs/integrations/google-analytics.integration.js +27 -98
- package/dist/cjs/listeners/input-listener-managers.d.ts +18 -9
- package/dist/cjs/listeners/input-listener-managers.js +24 -33
- package/dist/cjs/listeners/touch-listener-manager.d.ts +1 -3
- package/dist/cjs/listeners/touch-listener-manager.js +1 -23
- package/dist/cjs/listeners/visibility-listener-manager.d.ts +1 -4
- package/dist/cjs/listeners/visibility-listener-manager.js +6 -42
- package/dist/cjs/managers/api.manager.d.ts +13 -3
- package/dist/cjs/managers/api.manager.js +35 -5
- package/dist/cjs/managers/config.manager.d.ts +53 -3
- package/dist/cjs/managers/config.manager.js +131 -62
- package/dist/cjs/managers/event.manager.d.ts +57 -36
- package/dist/cjs/managers/event.manager.js +266 -417
- package/dist/cjs/managers/sender.manager.d.ts +40 -22
- package/dist/cjs/managers/sender.manager.js +200 -198
- package/dist/cjs/managers/session.manager.d.ts +80 -66
- package/dist/cjs/managers/session.manager.js +267 -522
- package/dist/cjs/managers/state.manager.d.ts +33 -0
- package/dist/cjs/managers/state.manager.js +79 -6
- package/dist/cjs/managers/storage.manager.d.ts +26 -2
- package/dist/cjs/managers/storage.manager.js +67 -34
- package/dist/cjs/managers/tags.manager.d.ts +31 -7
- package/dist/cjs/managers/tags.manager.js +123 -241
- package/dist/cjs/managers/user.manager.d.ts +14 -5
- package/dist/cjs/managers/user.manager.js +17 -9
- package/dist/cjs/public-api.d.ts +10 -1
- package/dist/cjs/public-api.js +18 -24
- package/dist/cjs/test-bridge.d.ts +48 -0
- package/dist/cjs/test-bridge.js +110 -0
- package/dist/cjs/types/api.types.d.ts +21 -6
- package/dist/cjs/types/api.types.js +21 -6
- package/dist/cjs/types/config.types.d.ts +22 -84
- package/dist/cjs/types/emitter.types.d.ts +11 -0
- package/dist/cjs/types/emitter.types.js +8 -0
- package/dist/cjs/types/event.types.d.ts +8 -11
- package/dist/cjs/types/index.d.ts +3 -1
- package/dist/cjs/types/index.js +3 -1
- package/dist/cjs/types/queue.types.d.ts +1 -0
- package/dist/cjs/types/session.types.d.ts +0 -64
- package/dist/cjs/types/state.types.d.ts +1 -0
- package/dist/cjs/types/test-bridge.types.d.ts +38 -0
- package/dist/cjs/types/validation-error.types.d.ts +7 -0
- package/dist/cjs/types/validation-error.types.js +11 -1
- package/dist/cjs/types/window.types.d.ts +1 -8
- package/dist/cjs/utils/data/uuid.utils.d.ts +1 -1
- package/dist/cjs/utils/data/uuid.utils.js +7 -5
- package/dist/cjs/utils/emitter.utils.d.ts +8 -0
- package/dist/cjs/utils/emitter.utils.js +33 -0
- package/dist/cjs/utils/index.d.ts +1 -0
- package/dist/cjs/utils/index.js +1 -0
- package/dist/cjs/utils/logging/debug-logger.utils.d.ts +10 -51
- package/dist/cjs/utils/logging/debug-logger.utils.js +36 -127
- package/dist/cjs/utils/network/fetch-with-timeout.utils.d.ts +4 -0
- package/dist/cjs/utils/network/fetch-with-timeout.utils.js +25 -0
- package/dist/cjs/utils/network/index.d.ts +1 -0
- package/dist/cjs/utils/network/index.js +1 -0
- package/dist/cjs/utils/network/url.utils.js +2 -42
- package/dist/cjs/utils/security/sanitize.utils.d.ts +1 -8
- package/dist/cjs/utils/security/sanitize.utils.js +7 -41
- package/dist/cjs/utils/validations/config-validations.utils.d.ts +7 -0
- package/dist/cjs/utils/validations/config-validations.utils.js +77 -22
- package/dist/esm/api.d.ts +33 -19
- package/dist/esm/api.js +105 -118
- package/dist/esm/app.constants.d.ts +80 -1
- package/dist/esm/app.constants.js +89 -1
- package/dist/esm/app.d.ts +29 -44
- package/dist/esm/app.js +115 -213
- package/dist/esm/app.types.d.ts +2 -7
- package/dist/esm/app.types.js +1 -7
- package/dist/esm/constants/api.constants.js +10 -4
- package/dist/esm/constants/config.constants.d.ts +75 -0
- package/dist/esm/constants/config.constants.js +174 -0
- package/dist/esm/constants/error.constants.d.ts +29 -0
- package/dist/esm/constants/error.constants.js +47 -0
- package/dist/esm/constants/index.d.ts +3 -6
- package/dist/esm/constants/index.js +3 -6
- package/dist/esm/constants/performance.constants.d.ts +28 -0
- package/dist/esm/constants/performance.constants.js +40 -0
- package/dist/esm/handlers/click.handler.d.ts +1 -0
- package/dist/esm/handlers/click.handler.js +30 -49
- package/dist/esm/handlers/error.handler.d.ts +11 -6
- package/dist/esm/handlers/error.handler.js +91 -51
- package/dist/esm/handlers/page-view.handler.js +38 -29
- package/dist/esm/handlers/performance.handler.d.ts +3 -0
- package/dist/esm/handlers/performance.handler.js +71 -32
- package/dist/esm/handlers/scroll.handler.d.ts +15 -0
- package/dist/esm/handlers/scroll.handler.js +106 -32
- package/dist/esm/handlers/session.handler.d.ts +6 -20
- package/dist/esm/handlers/session.handler.js +38 -326
- package/dist/esm/integrations/google-analytics.integration.d.ts +0 -1
- package/dist/esm/integrations/google-analytics.integration.js +27 -98
- package/dist/esm/listeners/input-listener-managers.d.ts +18 -9
- package/dist/esm/listeners/input-listener-managers.js +23 -32
- package/dist/esm/listeners/touch-listener-manager.d.ts +1 -3
- package/dist/esm/listeners/touch-listener-manager.js +1 -23
- package/dist/esm/listeners/visibility-listener-manager.d.ts +1 -4
- package/dist/esm/listeners/visibility-listener-manager.js +6 -42
- package/dist/esm/managers/api.manager.d.ts +13 -3
- package/dist/esm/managers/api.manager.js +34 -3
- package/dist/esm/managers/config.manager.d.ts +53 -3
- package/dist/esm/managers/config.manager.js +133 -64
- package/dist/esm/managers/event.manager.d.ts +57 -36
- package/dist/esm/managers/event.manager.js +268 -419
- package/dist/esm/managers/sender.manager.d.ts +40 -22
- package/dist/esm/managers/sender.manager.js +201 -199
- package/dist/esm/managers/session.manager.d.ts +80 -66
- package/dist/esm/managers/session.manager.js +269 -524
- package/dist/esm/managers/state.manager.d.ts +33 -0
- package/dist/esm/managers/state.manager.js +78 -6
- package/dist/esm/managers/storage.manager.d.ts +26 -2
- package/dist/esm/managers/storage.manager.js +66 -33
- package/dist/esm/managers/tags.manager.d.ts +31 -7
- package/dist/esm/managers/tags.manager.js +124 -242
- package/dist/esm/managers/user.manager.d.ts +14 -5
- package/dist/esm/managers/user.manager.js +17 -9
- package/dist/esm/public-api.d.ts +10 -1
- package/dist/esm/public-api.js +14 -1
- package/dist/esm/test-bridge.d.ts +48 -0
- package/dist/esm/test-bridge.js +106 -0
- package/dist/esm/types/api.types.d.ts +21 -6
- package/dist/esm/types/api.types.js +21 -6
- package/dist/esm/types/config.types.d.ts +22 -84
- package/dist/esm/types/emitter.types.d.ts +11 -0
- package/dist/esm/types/emitter.types.js +5 -0
- package/dist/esm/types/event.types.d.ts +8 -11
- package/dist/esm/types/index.d.ts +3 -1
- package/dist/esm/types/index.js +3 -1
- package/dist/esm/types/queue.types.d.ts +1 -0
- package/dist/esm/types/session.types.d.ts +0 -64
- package/dist/esm/types/state.types.d.ts +1 -0
- package/dist/esm/types/test-bridge.types.d.ts +38 -0
- package/dist/esm/types/validation-error.types.d.ts +7 -0
- package/dist/esm/types/validation-error.types.js +9 -0
- package/dist/esm/types/window.types.d.ts +1 -8
- package/dist/esm/utils/data/uuid.utils.d.ts +1 -1
- package/dist/esm/utils/data/uuid.utils.js +7 -5
- package/dist/esm/utils/emitter.utils.d.ts +8 -0
- package/dist/esm/utils/emitter.utils.js +29 -0
- package/dist/esm/utils/index.d.ts +1 -0
- package/dist/esm/utils/index.js +1 -0
- package/dist/esm/utils/logging/debug-logger.utils.d.ts +10 -51
- package/dist/esm/utils/logging/debug-logger.utils.js +36 -127
- package/dist/esm/utils/network/fetch-with-timeout.utils.d.ts +4 -0
- package/dist/esm/utils/network/fetch-with-timeout.utils.js +22 -0
- package/dist/esm/utils/network/index.d.ts +1 -0
- package/dist/esm/utils/network/index.js +1 -0
- package/dist/esm/utils/network/url.utils.js +2 -42
- package/dist/esm/utils/security/sanitize.utils.d.ts +1 -8
- package/dist/esm/utils/security/sanitize.utils.js +6 -39
- package/dist/esm/utils/validations/config-validations.utils.d.ts +7 -0
- package/dist/esm/utils/validations/config-validations.utils.js +76 -22
- package/package.json +23 -16
- package/dist/browser/web-vitals-CCnqwnC8.mjs +0 -198
- package/dist/cjs/constants/browser.constants.d.ts +0 -3
- package/dist/cjs/constants/browser.constants.js +0 -41
- package/dist/cjs/constants/initialization.constants.d.ts +0 -40
- package/dist/cjs/constants/initialization.constants.js +0 -48
- package/dist/cjs/constants/limits.constants.d.ts +0 -25
- package/dist/cjs/constants/limits.constants.js +0 -40
- package/dist/cjs/constants/security.constants.d.ts +0 -1
- package/dist/cjs/constants/security.constants.js +0 -12
- package/dist/cjs/constants/timing.constants.d.ts +0 -22
- package/dist/cjs/constants/timing.constants.js +0 -34
- package/dist/cjs/constants/validation.constants.d.ts +0 -13
- package/dist/cjs/constants/validation.constants.js +0 -31
- package/dist/cjs/handlers/network.handler.d.ts +0 -16
- package/dist/cjs/handlers/network.handler.js +0 -136
- package/dist/cjs/managers/cross-tab-session.manager.d.ts +0 -170
- package/dist/cjs/managers/cross-tab-session.manager.js +0 -730
- package/dist/cjs/managers/sampling.manager.d.ts +0 -8
- package/dist/cjs/managers/sampling.manager.js +0 -53
- package/dist/cjs/managers/session-recovery.manager.d.ts +0 -65
- package/dist/cjs/managers/session-recovery.manager.js +0 -237
- package/dist/cjs/types/web-vitals.types.d.ts +0 -6
- package/dist/esm/constants/browser.constants.d.ts +0 -3
- package/dist/esm/constants/browser.constants.js +0 -38
- package/dist/esm/constants/initialization.constants.d.ts +0 -40
- package/dist/esm/constants/initialization.constants.js +0 -45
- package/dist/esm/constants/limits.constants.d.ts +0 -25
- package/dist/esm/constants/limits.constants.js +0 -37
- package/dist/esm/constants/security.constants.d.ts +0 -1
- package/dist/esm/constants/security.constants.js +0 -9
- package/dist/esm/constants/timing.constants.d.ts +0 -22
- package/dist/esm/constants/timing.constants.js +0 -31
- package/dist/esm/constants/validation.constants.d.ts +0 -13
- package/dist/esm/constants/validation.constants.js +0 -28
- package/dist/esm/handlers/network.handler.d.ts +0 -16
- package/dist/esm/handlers/network.handler.js +0 -132
- package/dist/esm/managers/cross-tab-session.manager.d.ts +0 -170
- package/dist/esm/managers/cross-tab-session.manager.js +0 -726
- package/dist/esm/managers/sampling.manager.d.ts +0 -8
- package/dist/esm/managers/sampling.manager.js +0 -49
- package/dist/esm/managers/session-recovery.manager.d.ts +0 -65
- package/dist/esm/managers/session-recovery.manager.js +0 -233
- package/dist/esm/types/web-vitals.types.d.ts +0 -6
- /package/dist/cjs/types/{web-vitals.types.js → test-bridge.types.js} +0 -0
- /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.
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
35
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
51
|
+
message: sanitizedMessage,
|
|
62
52
|
},
|
|
63
53
|
});
|
|
64
54
|
};
|
|
65
55
|
this.eventManager = eventManager;
|
|
66
56
|
}
|
|
67
57
|
startTracking() {
|
|
68
|
-
|
|
69
|
-
this.
|
|
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.
|
|
63
|
+
window.removeEventListener('unhandledrejection', this.handleRejection);
|
|
64
|
+
this.recentErrors.clear();
|
|
76
65
|
}
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
shouldSample() {
|
|
67
|
+
const config = this.get('config');
|
|
68
|
+
const samplingRate = config?.errorSampling ?? 0.1;
|
|
69
|
+
return Math.random() < samplingRate;
|
|
79
70
|
}
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
let sanitized = text;
|
|
85
|
-
for (const pattern of
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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')
|
|
12
|
-
|
|
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('
|
|
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 =
|
|
51
|
+
this.originalPushState = original;
|
|
50
52
|
}
|
|
51
53
|
else if (method === 'replaceState' && !this.originalReplaceState) {
|
|
52
|
-
this.originalReplaceState =
|
|
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:
|
|
64
|
-
...(
|
|
66
|
+
page_url: normalizedUrl,
|
|
67
|
+
...(pageViewData && { page_view: pageViewData }),
|
|
65
68
|
});
|
|
66
69
|
this.onTrack();
|
|
67
70
|
}
|
|
68
71
|
extractPageViewData() {
|
|
69
|
-
const
|
|
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
|
-
...(
|
|
72
|
-
...(
|
|
73
|
-
...(
|
|
74
|
-
...(
|
|
75
|
-
...(
|
|
80
|
+
...(referrer && { referrer }),
|
|
81
|
+
...(title && { title }),
|
|
82
|
+
...(pathname && { pathname }),
|
|
83
|
+
...(search && { search }),
|
|
84
|
+
...(hash && { hash }),
|
|
76
85
|
};
|
|
77
|
-
return
|
|
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 {
|
|
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(
|
|
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.
|
|
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
|
-
|
|
149
|
+
// Check for duplicates if we have a navigation ID
|
|
148
150
|
if (navId) {
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
191
|
-
return;
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
223
|
+
// Disconnect errors are safe to ignore
|
|
203
224
|
}
|
|
204
225
|
}
|
|
205
226
|
});
|
|
206
|
-
obs.observe(
|
|
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;
|