diff --git a/node_modules b/node_modules index 79759e6687..268dc4db23 160000 --- a/node_modules +++ b/node_modules @@ -1 +1 @@ -Subproject commit 79759e66871bd68516a7c6481a3bd160442e2786 +Subproject commit 268dc4db2323ec5af3e9ebc57971213badb3bf9f diff --git a/package.json b/package.json index 4689667717..6be85c4dac 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "luxon": "^3.6.1", "react": "18.3.1", "react-dom": "18.3.1", + "stacktrace-js": "2.0.2", "throttle-debounce": "5.0.2" } } diff --git a/src/components/Error.jsx b/src/components/Error.jsx index 92bd91b5b0..bdb62d8441 100644 --- a/src/components/Error.jsx +++ b/src/components/Error.jsx @@ -17,6 +17,7 @@ import cockpit from "cockpit"; +import StackTrace from "stacktrace-js"; import { fmt_to_fragments as fmtToFragments } from "utils"; import React, { cloneElement, useContext, useEffect } from "react"; @@ -337,30 +338,24 @@ export class ErrorBoundary extends React.Component { // Add window.onerror and window.onunhandledrejection handlers componentDidMount () { - window.onerror = (message, source, lineno, colno, _error) => { + const errorHandler = async (_error) => { error("ErrorBoundary caught an error:", _error); - this.setState({ frontendException: _error, hasError: true }); - return true; - }; - - window.onunhandledrejection = (event) => { - error("ErrorBoundary caught an error:", event.reason); - this.setState({ frontendException: event.reason, hasError: true }); + const arrayStackFrame = await StackTrace.fromError(_error); + const stack = arrayStackFrame.map(frame => frame.toString()).join("\n"); + this.setState({ + frontendException: { message: _error.message, stack }, + hasError: true + }); return true; }; + window.onerror = async (message, source, lineno, colno, _error) => errorHandler(_error); + window.onunhandledrejection = (event) => errorHandler(event.reason); } - static getDerivedStateFromError (_error) { - if (_error) { - return { - backendException: _error, - hasError: true - }; - } - } - - componentDidCatch (_error, info) { - error("ComponentDidCatch: ErrorBoundary caught an error:", _error, info); + // React Error Boundary: Catches React rendering errors synchronously + // This prevents errors from propagating and crashing the page before window.onerror fires + static getDerivedStateFromError () { + return { hasError: true }; } onCritFailBackend = (arg) => {