diff --git a/js/src/lib/sentry.ts b/js/src/lib/sentry.ts index 284a35c..41b6812 100644 --- a/js/src/lib/sentry.ts +++ b/js/src/lib/sentry.ts @@ -1,28 +1,41 @@ import * as Sentry from "@sentry/electron/renderer"; import { appConfig } from "@/config/env"; -import { installErrorSinks } from "@/lib/errors"; +import { installErrorSinks, reportError } from "@/lib/errors"; /** - * Initialise Sentry for a renderer process. No-ops when `sentryDsn` is empty - * so dev builds and unconfigured envs stay quiet. + * Initialise renderer error handling. When a DSN is configured, Sentry's + * own global handlers capture uncaught exceptions and unhandled promise + * rejections. Otherwise we install a minimal fallback so stray rejections + * aren't silent in dev / unconfigured envs. */ export function initSentryRenderer(): void { - if (!appConfig.sentryDsn) return; + if (appConfig.sentryDsn) { + Sentry.init({ + dsn: appConfig.sentryDsn, + tracesSampleRate: 0, + }); - Sentry.init({ - dsn: appConfig.sentryDsn, - tracesSampleRate: 0, + installErrorSinks({ + capture: (err, context) => + Sentry.captureException(err, { extra: context }), + breadcrumb: (err, context) => + Sentry.addBreadcrumb({ + category: "error", + level: "error", + message: err instanceof Error ? err.message : String(err), + data: context, + }), + }); + return; + } + + window.addEventListener("unhandledrejection", (event) => { + reportError(event.reason, { scope: "unhandledrejection" }); }); - - installErrorSinks({ - capture: (err, context) => - Sentry.captureException(err, { extra: context }), - breadcrumb: (err, context) => - Sentry.addBreadcrumb({ - category: "error", - level: "error", - message: err instanceof Error ? err.message : String(err), - data: context, - }), + window.addEventListener("error", (event) => { + reportError(event.error ?? new Error(event.message), { + scope: "window.error", + }); }); } + diff --git a/js/src/main/sentry.ts b/js/src/main/sentry.ts index 8b724ed..ce8aa8c 100644 --- a/js/src/main/sentry.ts +++ b/js/src/main/sentry.ts @@ -1,30 +1,39 @@ import { app } from "electron"; import * as Sentry from "@sentry/electron/main"; import { appConfig } from "@/config/env"; -import { installErrorSinks } from "@/lib/errors"; +import { installErrorSinks, reportError } from "@/lib/errors"; /** - * Initialise Sentry for the main process. Captures uncaught exceptions from - * the Node side and the crash reporter. Safe to call before `app.whenReady`. + * Initialise main-process error handling. Sentry owns uncaught-exception + * capture when a DSN is configured; otherwise we install a minimal Node + * fallback so rejections aren't silent. Safe to call before `app.whenReady`. */ export function initSentryMain(): void { - if (!appConfig.sentryDsn) return; + if (appConfig.sentryDsn) { + Sentry.init({ + dsn: appConfig.sentryDsn, + tracesSampleRate: 0, + release: app.getVersion(), + }); - Sentry.init({ - dsn: appConfig.sentryDsn, - tracesSampleRate: 0, - release: app.getVersion(), + installErrorSinks({ + capture: (err, context) => + Sentry.captureException(err, { extra: context }), + breadcrumb: (err, context) => + Sentry.addBreadcrumb({ + category: "error", + level: "error", + message: err instanceof Error ? err.message : String(err), + data: context, + }), + }); + return; + } + + process.on("uncaughtException", (err) => { + reportError(err, { scope: "uncaughtException" }); }); - - installErrorSinks({ - capture: (err, context) => - Sentry.captureException(err, { extra: context }), - breadcrumb: (err, context) => - Sentry.addBreadcrumb({ - category: "error", - level: "error", - message: err instanceof Error ? err.message : String(err), - data: context, - }), + process.on("unhandledRejection", (reason) => { + reportError(reason, { scope: "unhandledRejection" }); }); }