11// Import the dependency directly. Vite will bundle it.
22import StackTrace from 'stacktrace-js' ;
33
4- // --- Module Configuration & State ---
5- // All variables are now in the module's top-level scope.
6- let config = {
7- endpointUrl : null ,
8- service : 'not-configured' ,
9- version : 'not-configured' ,
10- traceId : 'not-configured' ,
11- context : { } ,
12- } ;
13-
14- // --- Private Functions ---
15-
164/**
17- * Handles the error, generates a stack trace, and sends the report.
18- * @param {Error|string } error - The error object or message.
5+ * Checks if a function name looks like a valid identifier, not a parser artifact.
6+ * A valid name should not contain characters like parentheses, spaces, etc.
7+ * @param {string | null | undefined } name The function name to check.
8+ * @returns {boolean }
199 */
20- function handleError ( error ) {
21- StackTrace . fromError ( error )
22- . then ( stackFrames => {
23- const payload = formatPayload ( error , stackFrames ) ;
24- sendReport ( payload ) ;
25- } )
26- . catch ( err => {
27- console . error ( 'Error while generating stacktrace:' , err ) ;
28- // Fallback: send report with no stack trace
29- const payload = formatPayload ( error , [ ] ) ;
30- sendReport ( payload ) ;
31- } ) ;
10+ function isValidFunctionName ( name ) {
11+ if ( ! name || name === '<unknown>' ) {
12+ return false ;
13+ }
14+ // This regex allows for typical JS identifiers, including object properties (e.g., object.method).
15+ // It explicitly disallows characters commonly found in parser artifacts like ')' or ' '.
16+ return / ^ [ a - z A - Z 0 - 9 _ $ . < > ] + $ / . test ( name ) ;
3217}
3318
34- /**
35- * Formats the error data into the payload for the backend.
36- * @param {Error|string } error - The error object or message.
37- * @param {Array } stackFrames - The array of stack frames from StackTrace.js.
38- * @returns {object } The formatted payload.
39- */
40- function formatPayload ( error , stackFrames ) {
41- return {
42- message : error . message || String ( error ) ,
43- serviceContext : {
44- service : config . service ,
45- version : config . version ,
46- } ,
47- traceId : config . traceId ,
48- stack_trace_frames : stackFrames . map ( sf => ( {
49- function_name : sf . functionName ,
50- file_name : sf . fileName ,
51- line_number : sf . lineNumber ,
52- column_number : sf . columnNumber ,
53- } ) ) ,
54- context : {
55- ...config . context ,
56- httpRequest : {
57- url : window . location . href ,
58- userAgent : navigator . userAgent ,
59- } ,
60- } ,
61- } ;
62- }
6319
64- /**
65- * Sends the report to the configured backend endpoint.
66- * @param {object } payload - The error report payload.
67- */
68- function sendReport ( payload ) {
69- if ( ! config . endpointUrl ) {
70- console . error ( 'Error reporter: endpointUrl is not configured.' ) ;
71- console . log ( 'Error payload:' , payload ) ; // Log to console for debugging
72- return ;
73- }
20+ const errorReporter = {
21+ config : {
22+ endpointUrl : null ,
23+ service : 'not-configured' ,
24+ version : 'not-configured' ,
25+ traceId : 'not-configured' ,
26+ context : { } ,
27+ } ,
7428
75- if ( navigator . sendBeacon ) {
76- const blob = new Blob ( [ JSON . stringify ( payload ) ] , { type : 'application/json' } ) ;
77- navigator . sendBeacon ( config . endpointUrl , blob ) ;
78- } else {
79- fetch ( config . endpointUrl , {
80- method : 'POST' ,
81- headers : { 'Content-Type' : 'application/json' } ,
82- body : JSON . stringify ( payload ) ,
83- keepalive : true ,
84- } ) . catch ( err => console . error ( 'Error sending report:' , err ) ) ;
85- }
86- }
29+ init ( userConfig ) {
30+ this . config = { ...this . config , ...userConfig } ;
8731
88- // --- Public API ---
32+ if ( typeof StackTrace === 'undefined' ) {
33+ console . error ( 'StackTrace.js is not available. Error reporting is disabled.' ) ;
34+ return ;
35+ }
8936
90- /**
91- * Initializes the error reporter and attaches global handlers.
92- * @param {object } userConfig - The configuration object.
93- */
94- function init ( userConfig ) {
95- config = { ...config , ...userConfig } ;
37+ window . onerror = ( message , source , lineno , colno , error ) => {
38+ this . handleError ( error || message ) ;
39+ return false ;
40+ } ;
9641
97- if ( typeof StackTrace === 'undefined' ) {
98- console . error ( 'StackTrace.js dependency is not available. Error reporting is disabled. ') ;
99- return ;
100- }
42+ window . addEventListener ( 'unhandledrejection' , event => {
43+ this . handleError ( event . reason || 'Unhandled promise rejection ') ;
44+ } ) ;
45+ } ,
10146
102- window . onerror = ( message , source , lineno , colno , error ) => {
103- handleError ( error || message ) ;
104- return false ;
105- } ;
47+ handleError ( error ) {
48+ StackTrace . fromError ( error )
49+ . then ( stackFrames => {
50+ const payload = this . formatPayload ( error , stackFrames ) ;
51+ this . sendReport ( payload ) ;
52+ } )
53+ . catch ( err => {
54+ console . error ( 'Error while generating stacktrace:' , err ) ;
55+ const payload = this . formatPayload ( error , [ ] ) ;
56+ this . sendReport ( payload ) ;
57+ } ) ;
58+ } ,
10659
107- window . addEventListener ( 'unhandledrejection' , event => {
108- handleError ( event . reason || 'Unhandled promise rejection' ) ;
109- } ) ;
110- }
60+ formatPayload ( error , stackFrames ) {
61+ let errorMessage = String ( error ) ;
62+ // If the error is a proper Error object, prefix the message with its type (e.g., "ReferenceError: ...").
63+ if ( error instanceof Error && error . name && error . message ) {
64+ errorMessage = `${ error . name } : ${ error . message } ` ;
65+ }
11166
112- // --- EXPLICIT GLOBAL ASSIGNMENT ---
113- // Instead of relying on a default export and Vite's 'name' property in UMD mode,
114- // we explicitly assign our public API to the window object. This is a more direct
115- // and robust method to ensure the global variable is available after bundling.
116- window . errorReporter = {
117- init,
67+ return {
68+ message : errorMessage ,
69+ serviceContext : {
70+ service : this . config . service ,
71+ version : this . config . version ,
72+ } ,
73+ traceId : this . config . traceId ,
74+ stack_trace_frames : stackFrames . map ( sf => ( {
75+ function_name : isValidFunctionName ( sf . functionName ) ? sf . functionName : null ,
76+ file_name : sf . fileName ,
77+ line_number : sf . lineNumber ,
78+ column_number : sf . columnNumber ,
79+ } ) ) ,
80+ context : {
81+ ...this . config . context ,
82+ url : window . location . href ,
83+ userAgent : navigator . userAgent ,
84+ } ,
85+ } ;
86+ } ,
87+
88+ sendReport ( payload ) {
89+ if ( ! this . config . endpointUrl ) {
90+ console . error ( 'Error reporter: endpointUrl is not configured.' ) ;
91+ console . log ( 'Error payload:' , payload ) ;
92+ return ;
93+ }
94+
95+ if ( navigator . sendBeacon ) {
96+ const blob = new Blob ( [ JSON . stringify ( payload ) ] , { type : 'application/json' } ) ;
97+ navigator . sendBeacon ( this . config . endpointUrl , blob ) ;
98+ } else {
99+ fetch ( this . config . endpointUrl , {
100+ method : 'POST' ,
101+ headers : {
102+ 'Content-Type' : 'application/json' ,
103+ 'Accept' : 'application/json' ,
104+ } ,
105+ body : JSON . stringify ( payload ) ,
106+ keepalive : true ,
107+ } ) . catch ( err => console . error ( 'Error sending report:' , err ) ) ;
108+ }
109+ }
118110} ;
111+
112+ // Directly assign to window to ensure it's globally available for UMD builds.
113+ window . errorReporter = errorReporter ;
0 commit comments