Files
webmail/app/global-error.tsx
T
Linus Rath cba1109444 feat: report browser and server errors to an error tracker the admin sets
Admins had logs and nothing else: client errors only reached the
browser console. An admin can now set a Sentry-protocol address (DSN)
for Bugsink, GlitchTip or Sentry under System > Monitoring, or with
`ERROR_REPORTING_DSN`, `ERROR_REPORTING_DSN_FILE`,
`ERROR_REPORTING_SAMPLE_RATE` and `ERROR_REPORTING_ENVIRONMENT`.
Bulwark Lite reads the same from `errorReporting` in policy.json.
Everything stays off while no address is set.

- browsers report errors from the error boundaries, window.onerror,
  unhandled rejections and failed JMAP requests (method and error type
  only), with route and JMAP method breadcrumbs and the chunks' debug IDs
- the server reports what Next hands to onRequestError and what routes
  log with logger.error
- every report is scrubbed first: quoted text, addresses, URLs, file
  names, tokens and the rest after the first line are removed, and the
  server scrubs what browsers post to /api/errors again
- reports are deduplicated and rate-limited per session and process
- errors are counted per day for "errors today" on the admin page
- startup messages and audit entries go through the logger, so
  `LOG_FORMAT=json` covers them
2026-11-01 02:50:19 +01:00

63 lines
2.2 KiB
TypeScript

"use client";
import { useEffect } from "react";
import { AlertTriangle, RefreshCw } from "@/components/icons";
import { fetchConfig } from "@/hooks/use-config";
import { captureException } from "@/lib/error-reporting/client";
import "./globals.css";
/**
* Global error boundary for the root layout.
*
* IMPORTANT: Strings in this file CANNOT be translated.
* This global error boundary renders outside the root layout and has no access
* to providers (including next-intl). This is a Next.js limitation for
* catastrophic error handling. These English strings only appear during
* critical failures when the entire app crashes.
*
* The component must render its own <html> and <body> tags as it replaces
* the root layout entirely.
*/
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error("Global error:", error);
// Waits for the config when the app crashed before it arrived; sent only
// when error reporting is configured.
captureException(error, "global-error", false);
void fetchConfig().catch(() => undefined);
}, [error]);
return (
<html lang="en">
<body className="bg-gray-50 dark:bg-gray-900">
<div className="min-h-screen flex items-center justify-center">
<div className="text-center max-w-md px-4">
<div className="w-20 h-20 mx-auto mb-6 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<AlertTriangle className="w-10 h-10 text-red-600 dark:text-red-400" />
</div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2">
Something went wrong
</h1>
<p className="text-gray-600 dark:text-gray-400 mb-6">
An unexpected error occurred. Please try again.
</p>
<button
onClick={reset}
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<RefreshCw className="w-4 h-4 me-2" />
Try again
</button>
</div>
</div>
</body>
</html>
);
}