Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,7 @@
"dependencies": {
"@radix-ui/react-dropdown-menu": "catalog:",
"@repo/app-trpc": "workspace:*",
"@sentry-internal/browser-utils": "^10.49.0",
"@sentry-internal/feedback": "^10.49.0",
"@sentry-internal/replay": "^10.49.0",
"@sentry-internal/replay-canvas": "^10.49.0",
"@sentry/browser": "^10.49.0",
"@sentry/core": "catalog:",
"@sentry/electron": "^7.11.0",
"@sentry/node": "^10.49.0",
"@tanstack/query-core": "^5.99.1",
"@tanstack/react-query": "catalog:",
"@trpc/client": "catalog:",
Expand Down
45 changes: 39 additions & 6 deletions apps/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as Sentry from "@sentry/electron/main";
import {
app,
BrowserWindow,
Expand All @@ -8,7 +9,11 @@ import {
shell,
} from "electron";
import contextMenu from "electron-context-menu";
import { IpcChannels, type SystemThemeVariant } from "../shared/ipc";
import {
IpcChannels,
type RendererErrorPayload,
type SystemThemeVariant,
} from "../shared/ipc";
import { openAppOrigin } from "./app-url";
import { beginSignIn } from "./auth-flow";
import {
Expand All @@ -20,7 +25,7 @@ import {
import { getBuildInfo } from "./build-info";
import { buildApplicationMenu } from "./menu";
import { getRuntimeConfig } from "./runtime-config";
import { getSentryInitOptions, initSentry } from "./sentry";
import { initSentry } from "./sentry";
import {
getSettings,
onSettingsChanged,
Expand Down Expand Up @@ -55,6 +60,37 @@ function rendererDevServerOrigin(): string | null {
}
}

function isRendererErrorPayload(value: unknown): value is RendererErrorPayload {
if (typeof value !== "object" || value === null) {
return false;
}
const candidate = value as Partial<RendererErrorPayload>;
return (
(candidate.kind === "error" || candidate.kind === "unhandledrejection") &&
typeof candidate.message === "string"
);
}

function forwardRendererErrorToSentry(payload: unknown): void {
if (!isRendererErrorPayload(payload)) {
return;
}
// The renderer-side @sentry/electron/renderer SDK silently fails to register
// a client (v10 carrier shape). Bridge renderer errors through the working
// main-side SDK instead, preserving the renderer stack so debug-id-paired
// sourcemaps can still symbolicate it.
const error = new Error(payload.message);
error.name =
payload.kind === "unhandledrejection" ? "UnhandledRejection" : "Error";
if (payload.stack) {
error.stack = payload.stack;
}
Sentry.captureException(error, {
tags: { bundle: "renderer", rendererKind: payload.kind },
extra: { source: payload.source, url: payload.url },
});
}

function openAllowedExternalUrl(url: string): void {
try {
const parsed = new URL(url);
Expand Down Expand Up @@ -132,10 +168,6 @@ function registerIpcHandlers(): void {
event.returnValue = getBuildInfo();
});

ipcMain.on(IpcChannels.getSentryInitOptionsSync, (event) => {
event.returnValue = getSentryInitOptions();
});

ipcMain.on(IpcChannels.getSettingsSync, (event) => {
event.returnValue = getSettings();
});
Expand Down Expand Up @@ -170,6 +202,7 @@ function registerIpcHandlers(): void {
ipcMain.on(IpcChannels.rendererError, (_event, payload: unknown) => {
// eslint-disable-next-line no-console
console.error("[renderer]", payload);
forwardRendererErrorToSentry(payload);
});

ipcMain.handle(IpcChannels.openWindow, async (_event, kind: unknown) => {
Expand Down
10 changes: 0 additions & 10 deletions apps/desktop/src/preload/preload.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// Installs the @sentry/electron IPC bridge on the contextBridge so the
// renderer-side SDK routes events through main via `sentry-ipc:` instead of
// fetching the ingest URL directly (which the renderer CSP blocks). Pair:
// src/main/sentry.ts (main init) and src/renderer/src/main.ts (renderer init).
import "@sentry/electron/preload";
import { contextBridge, type IpcRendererEvent, ipcRenderer } from "electron";
import type { AcceleratorName } from "../shared/accelerators";
import {
Expand All @@ -11,7 +6,6 @@ import {
IpcChannels,
type LightfastBridge,
type RuntimeConfigSnapshot,
type SentryInitSnapshot,
type SettingsSnapshot,
type SystemThemeVariant,
type UpdaterStatusSnapshot,
Expand All @@ -21,9 +15,6 @@ import {
const buildInfo = ipcRenderer.sendSync(
IpcChannels.getBuildInfoSync
) as BuildInfoSnapshot;
const sentryInit = ipcRenderer.sendSync(
IpcChannels.getSentryInitOptionsSync
) as SentryInitSnapshot;
const updaterStatus = ipcRenderer.sendSync(
IpcChannels.updaterStatusSync
) as UpdaterStatusSnapshot;
Expand Down Expand Up @@ -52,7 +43,6 @@ const bridge: LightfastBridge = {
},
},
buildInfo,
sentryInit,
platform: process.platform,
getSystemThemeVariant: () =>
ipcRenderer.invoke(IpcChannels.getSystemThemeVariant),
Expand Down
21 changes: 6 additions & 15 deletions apps/desktop/src/renderer/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
// @sentry/electron/renderer routes events through `sentry-ipc:` (CSP-bypass
// scheme registered by @sentry/electron/main + bridged by the
// `@sentry/electron/preload` import in src/preload/preload.ts). The plain
// @sentry/browser SDK fetches the ingest URL directly, which the renderer
// CSP blocks — events silently drop.
import * as Sentry from "@sentry/electron/renderer";
import "./react/entry";
import {
ACCELERATORS,
Expand All @@ -23,22 +17,19 @@ declare global {
}
}

// Renderer errors are forwarded over IPC to main, which calls
// Sentry.captureException via the working `@sentry/electron/main` SDK. The
// renderer-side `@sentry/electron/renderer` path was broken — `Sentry.init`
// silently failed to register a client in the v10 carrier — so events never
// reached the IPC transport regardless of CSP setup.
installErrorBoundary(window.lightfastBridge.reportError);

const { buildInfo, platform, sentryInit } = window.lightfastBridge;
const { buildInfo, platform } = window.lightfastBridge;
const formatPlatform: FormatPlatform =
platform === "darwin" || platform === "linux" || platform === "win32"
? platform
: "linux";

if (sentryInit.enabled) {
Sentry.init({
dsn: sentryInit.dsn,
release: sentryInit.release,
environment: sentryInit.environment,
});
}

document.documentElement.dataset.platform = platform;
document.documentElement.dataset.windowKind = window.codexWindowType;
document.documentElement.dataset.buildFlavor = buildInfo.buildFlavor;
Expand Down
9 changes: 0 additions & 9 deletions apps/desktop/src/shared/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export const IpcChannels = {
openApp: channel("open-app"),
openWindow: channel("open-window"),
getBuildInfoSync: channel("get-build-info-sync"),
getSentryInitOptionsSync: channel("get-sentry-init-options-sync"),
rendererError: channel("renderer-error"),
updaterCheck: channel("updater-check"),
updaterInstall: channel("updater-install"),
Expand Down Expand Up @@ -53,13 +52,6 @@ export interface BuildInfoSnapshot {
version: string;
}

export interface SentryInitSnapshot {
dsn: string;
enabled: boolean;
environment: string;
release: string;
}

export interface RendererErrorPayload {
kind: "error" | "unhandledrejection";
message: string;
Expand Down Expand Up @@ -127,7 +119,6 @@ export interface LightfastBridge {
openWindow: (kind: WindowKind) => Promise<void>;
platform: Platform;
reportError: (payload: RendererErrorPayload) => void;
sentryInit: SentryInitSnapshot;
settings: SettingsSnapshot;
updater: {
status: UpdaterStatusSnapshot;
Expand Down
31 changes: 5 additions & 26 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading