diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index d191da9f6..0b5758609 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -1,7 +1,10 @@ import { CustomHead } from "@/next/components/Head"; import { setupI18n } from "@/next/i18n"; import log from "@/next/log"; -import { logStartupBanner } from "@/next/log-web"; +import { + logStartupBanner, + logUnhandledErrorsAndRejections, +} from "@/next/log-web"; import { AppUpdateInfo } from "@/next/types/ipc"; import { APPS, @@ -147,9 +150,13 @@ export default function App({ Component, pageProps }: AppProps) { setupI18n().finally(() => setIsI18nReady(true)); const userId = (getData(LS_KEYS.USER) as User)?.id; logStartupBanner(APPS.PHOTOS, userId); + logUnhandledErrorsAndRejections(true); HTTPService.setHeaders({ "X-Client-Package": CLIENT_PACKAGE_NAMES.get(APPS.PHOTOS), }); + return () => { + logUnhandledErrorsAndRejections(false); + }; }, []); useEffect(() => { diff --git a/web/packages/next/log-web.ts b/web/packages/next/log-web.ts index 093a2065c..f319118ce 100644 --- a/web/packages/next/log-web.ts +++ b/web/packages/next/log-web.ts @@ -18,6 +18,33 @@ export const logStartupBanner = (appId: string, userId?: number) => { log.info(`Starting ente-${appIdL}-web ${buildId}uid ${userId ?? 0}`); }; +/** + * Attach handlers to log any unhandled exceptions and promise rejections. + * + * @param attach If true, attach handlers, and if false, remove them. This + * allows us to use this in a React hook that cleans up after itself. + */ +export const logUnhandledErrorsAndRejections = (attach: boolean) => { + const handleError = (event: ErrorEvent) => { + log.error("Unhandled error", event.error); + }; + + const handleUnhandledRejection = (event: PromiseRejectionEvent) => { + log.error("Unhandled promise rejection", event.reason); + }; + + if (attach) { + window.addEventListener("error", handleError); + window.addEventListener("unhandledrejection", handleUnhandledRejection); + } else { + window.removeEventListener("error", handleError); + window.removeEventListener( + "unhandledrejection", + handleUnhandledRejection, + ); + } +}; + interface LogEntry { timestamp: number; logLine: string;