UNPKG

backsplash-app

Version:
167 lines (142 loc) 5.23 kB
import toast from "react-hot-toast"; type ErrorSeverity = "low" | "medium" | "high" | "critical"; interface ErrorContext { message?: string; severity?: ErrorSeverity; context?: Record<string, unknown>; } // Track active tour state globally to prevent navigation recovery during tours let isTourActive = false; export const setTourErrorHandlingState = (active: boolean) => { isTourActive = active; console.log(`[ErrorHandler] Tour active state: ${active}`); }; // Set up global error handlers export const setupGlobalErrorHandlers = () => { // Handle unhandled promise rejections window.addEventListener("unhandledrejection", (event) => { const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason)); // Check if it's a DOM-related error if (isDOMError(error)) { handleDOMError(error); } else { handleError(error, { message: "Unhandled promise rejection", severity: "high", context: { type: "unhandledRejection" }, }); } // Prevent the default handler event.preventDefault(); }); // Handle uncaught exceptions window.addEventListener("error", (event) => { // Ignore ResizeObserver errors which are not critical if (event.message?.includes("ResizeObserver") || event.error?.message?.includes("ResizeObserver")) { return; } const error = event.error instanceof Error ? event.error : new Error(event.message || "Unknown error"); if (isDOMError(error)) { handleDOMError(error); } else { handleError(error, { message: "Uncaught exception", severity: "high", context: { type: "uncaughtException" }, }); } // Only prevent default for DOM errors to let other critical errors bubble up if (isDOMError(error)) { event.preventDefault(); } }); console.log("[ErrorHandler] Global error handlers initialized"); }; // Check if an error is DOM-related const isDOMError = (error: Error): boolean => { const errorStr = error.message?.toLowerCase() || ""; return ( errorStr.includes("dom") || errorStr.includes("node") || errorStr.includes("target") || errorStr.includes("element") || errorStr.includes("removechild") || errorStr.includes("appendchild") || errorStr.includes("insertbefore") || errorStr.includes("cannot read") || errorStr.includes("undefined is not an object") || errorStr.includes("null is not an object") || error.name === "HierarchyRequestError" || error.name === "NotFoundError" ); }; /** * Global error handler that logs errors, reports to Sentry, and shows toast messages */ export function handleError(error: Error, options: ErrorContext = {}) { const { message = "An error occurred", severity = "medium", context = {} } = options; console.error("Application Error:", error, context); // Conditionally report to Sentry if available if (!__DEV__) { // Use dynamic import to avoid bundling Sentry in development import("@sentry/electron/renderer") .then((Sentry) => { Sentry.withScope((scope) => { scope.setLevel(severity === "critical" ? "fatal" : severity === "high" ? "error" : "warning"); Object.entries(context).forEach(([key, value]) => { scope.setContext(key, value as any); // Type assertion for Sentry context }); scope.setTag("errorHandler", true); Sentry.captureException(error); }); }) .catch(() => { // Sentry not available, continue without it }); } // Show user-friendly toast const toastMessage = severity === "low" ? message : `${message}: ${error.message}`; if (severity === "critical" || severity === "high") { toast.error(toastMessage); } else { toast(toastMessage); } } /** * Helper to handle DOM-related errors which are common with React */ export const handleDOMError = (error: Error) => { // Check if we should recover navigation const recoverNavigation = () => { try { // Skip navigation recovery during tour to prevent conflicts if (isTourActive) { console.log("[ErrorHandler] Skipping navigation recovery during tour"); return false; } // If we have a previous path in history state, try to go back to it if (window.history.state?.prevPath) { console.log(`[ErrorHandler] Attempting to recover by navigating to: ${window.history.state.prevPath}`); window.history.pushState(null, "", window.history.state.prevPath); return true; } } catch (e) { console.error("[ErrorHandler] Navigation recovery failed:", e); } return false; }; if (isDOMError(error)) { const didRecover = recoverNavigation(); return handleError(error, { message: didRecover ? "UI rendering issue resolved" : "UI rendering error", severity: "medium", context: { type: "DOM", recovered: didRecover, tourActive: isTourActive }, }); } // For unknown errors, treat with higher severity return handleError(error, { message: "An unexpected error occurred", severity: "high", context: { type: "unknown", tourActive: isTourActive }, }); };