/** @jsxImportSource react */ import { Component, useMemo, useState, type ErrorInfo, type ReactNode } from "react"; import { backendRequest, requestElectrobunRestart } from "./backend-rpc"; declare global { interface Window { __gloomRenderFatalError?: (error: unknown, details?: string, source?: string) => void; } } interface DesktopFatalScreenProps { title?: string; error: unknown; details?: string; source: string; } interface ElectrobunErrorBoundaryState { hasError: boolean; error: unknown; details?: string; } function formatFatalError(error: unknown, details?: string): string { const message = error instanceof Error ? error.stack ?? error.message : String(error); return [message, details].filter((value): value is string => Boolean(value)).join("\n"); } export function DesktopFatalScreen({ title = "Gloomberb crashed", error, details, source, }: DesktopFatalScreenProps) { const [status, setStatus] = useState(null); const errorText = useMemo(() => formatFatalError(error, details), [details, error]); const reloadWindow = () => { window.location.reload(); }; const restartApp = () => { setStatus("Restart requested..."); try { requestElectrobunRestart({ source, reason: errorText.slice(0, 240), }); window.setTimeout(() => { setStatus("If the app stays open, quit it and open it again."); }, 2500); } catch (restartError) { setStatus(`Restart request failed: ${formatFatalError(restartError)}`); } }; const copyError = async () => { try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(errorText); } else { await backendRequest("host.copyText", { text: errorText }); } setStatus("Error copied."); } catch (copyError) { setStatus(`Copy failed: ${formatFatalError(copyError)}`); } }; return (

{title}

{status &&
{status}
}
{errorText}
); } export class ElectrobunErrorBoundary extends Component< { children: ReactNode }, ElectrobunErrorBoundaryState > { override state: ElectrobunErrorBoundaryState = { hasError: false, error: null }; static getDerivedStateFromError(error: unknown): ElectrobunErrorBoundaryState { return { hasError: true, error }; } override componentDidCatch(error: unknown, errorInfo: ErrorInfo): void { console.error("[desktop-recovery] renderer error boundary", error, errorInfo.componentStack); this.setState({ hasError: true, error, details: errorInfo.componentStack ?? undefined, }); } override render(): ReactNode { if (this.state.hasError) { return ( ); } return this.props.children; } }