backsplash-app
Version:
An AI powered wallpaper app.
167 lines (142 loc) • 5.23 kB
text/typescript
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 },
});
};