feat: track errors on all platforms to ensure stability across all environments - #1882
Conversation
There was a problem hiding this comment.
Pull request overview
This PR wires up error/crash reporting across Jetstream’s non-web surfaces (Desktop renderer + Electron main process, browser extension pages, and Canvas) using per-app public DSNs, and adds user-controlled opt-out that propagates immediately. It also tightens/clarifies deviceId handling (desktop deep link compatibility + API request logging context) and ensures Canvas CSP allows the error tracker’s ingest origin when configured.
Changes:
- Add per-platform DSN env vars and initialize the error tracker in the extension (app + popup), Canvas, and the Electron main process (via renderer→main IPC).
- Add
crashReportingEnabledpreference with UI controls (extension settings + desktop menu checkbox) and live opt-out propagation (IPC event). - Improve deviceId flow: make desktop deep-link
deviceIdoptional for backwards compatibility, and enrich API request-scoped logging context with resolved deviceId; update Canvas CSPconnect-srcdynamically from DSN.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| libs/desktop-types/src/lib/desktop-app.types.ts | Adds IPC channel + API surface for crash reporting config and opt-out change notifications; adds crashReportingEnabled preference. |
| apps/landing/hooks/desktop-auth.hooks.ts | Documents deep-link parameter backwards compatibility behavior for desktop auth. |
| apps/jetstream-web-extension/src/utils/extension.types.ts | Extends stored options to include crashReportingEnabled. |
| apps/jetstream-web-extension/src/utils/extension.store.ts | Normalizes persisted extension options to default crash reporting to enabled. |
| apps/jetstream-web-extension/src/pages/popup/Popup.tsx | Initializes error tracking in the popup page and applies opt-out based on settings. |
| apps/jetstream-web-extension/src/pages/additional-settings/AdditionalSettings.tsx | Adds a UI toggle to opt out of crash reporting. |
| apps/jetstream-web-extension/src/hooks/useExtensionSettings.ts | Persists partial option updates without clobbering other option fields; adds crash reporting setters. |
| apps/jetstream-web-extension/src/environments/environment.ts | Switches extension DSN env var to NX_PUBLIC_SENTRY_DSN_EXTENSION. |
| apps/jetstream-web-extension/src/environments/environment.staging.ts | Switches extension DSN env var to NX_PUBLIC_SENTRY_DSN_EXTENSION. |
| apps/jetstream-web-extension/src/environments/environment.prod.ts | Switches extension DSN env var to NX_PUBLIC_SENTRY_DSN_EXTENSION. |
| apps/jetstream-web-extension/src/core/AppInitializer.tsx | Initializes error tracking for the main extension app, applies opt-out, and sets user context. |
| apps/jetstream-desktop/src/services/menu.service.ts | Adds desktop menu checkbox to toggle crash reporting and broadcasts changes to renderers. |
| apps/jetstream-desktop/src/services/ipc.service.ts | Adds IPC handler to configure the main-process crash reporter; makes deep-link deviceId optional. |
| apps/jetstream-desktop/src/preload.ts | Exposes crash reporting IPC methods/events to the renderer. |
| apps/jetstream-desktop/src/config/error-tracker.ts | Implements Electron main-process error tracker init/opt-out/capture helpers. |
| apps/jetstream-desktop/src/config/auto-updater.ts | Captures auto-updater errors into the main-process error tracker. |
| apps/jetstream-desktop/project.json | Marks @sentry/node as external for the desktop build bundling configuration. |
| apps/jetstream-desktop-client/src/environments/environment.ts | Wires desktop renderer DSN from NX_PUBLIC_SENTRY_DSN_DESKTOP. |
| apps/jetstream-desktop-client/src/environments/environment.prod.ts | Wires desktop renderer DSN from NX_PUBLIC_SENTRY_DSN_DESKTOP. |
| apps/jetstream-desktop-client/src/app/components/core/AppInitializer.tsx | Configures main-process crash reporting from the renderer and reacts to opt-out changes. |
| apps/jetstream-desktop-client/src/app/components/core/AppDesktopState.ts | Defaults desktop preferences to crash reporting enabled when unavailable. |
| apps/jetstream-canvas/vite.config.mts | Inlines Canvas DSN at build time (no envPrefix) for runtime access. |
| apps/jetstream-canvas/src/environments/environment.ts | Uses NX_PUBLIC_SENTRY_DSN_CANVAS for Canvas error tracking DSN. |
| apps/jetstream-canvas/src/environments/environment.staging.ts | Uses NX_PUBLIC_SENTRY_DSN_CANVAS for Canvas error tracking DSN. |
| apps/jetstream-canvas/src/environments/environment.prod.ts | Uses NX_PUBLIC_SENTRY_DSN_CANVAS for Canvas error tracking DSN. |
| apps/jetstream-canvas/src/app/core/AppInitializer.tsx | Initializes error tracking in Canvas and best-effort sets user context from signed request. |
| apps/api/src/app/services/external-auth.service.ts | Documents/standardizes deviceId resolution and enriches request-scoped logger context with deviceId. |
| apps/api/src/app/routes/canvas.routes.ts | Adds DSN-derived ingest origin to Canvas CSP connect-src when configured. |
| .env.example | Adds per-platform public DSN env vars for desktop/extension/canvas. |
fbc2ca0 to
0080677
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
apps/jetstream-web-extension/src/pages/popup/Popup.tsx:54
- Crash reporting opt-out is applied after initErrorTracker() runs. Since initErrorTracker() bails out when opted out, this ordering can initialize the tracker even when the user has disabled crash reporting. Seed opt-out first (and allow enabling later to initialize on demand).
useEffect(() => {
initErrorTracker({
dsn: environment.sentryDsn,
environment: environment.production ? 'production' : 'development',
version: getBrowserExtensionVersion(),
});
}, []);
useEffect(() => {
setErrorTrackerOptOut(crashReportingEnabled === false);
}, [crashReportingEnabled]);
apps/jetstream-web-extension/src/core/AppInitializer.tsx:89
- The error tracker is initialized before applying the user's crash-reporting preference. Because initErrorTracker() returns early when opted out, calling setErrorTrackerOptOut() first avoids initializing telemetry for users who have disabled it, and still allows later opt-in to initialize on demand.
useEffect(() => {
initErrorTracker({
dsn: environment.sentryDsn,
environment: environment.production ? 'production' : 'development',
version: getBrowserExtensionVersion(),
});
}, []);
// Honor the "Send crash reports to Jetstream" extension setting (runs after init so it can toggle at runtime).
useEffect(() => {
setErrorTrackerOptOut(options.crashReportingEnabled === false);
}, [options.crashReportingEnabled]);
apps/jetstream-desktop-client/src/app/components/core/AppInitializer.tsx:133
- The renderer initializes initErrorTracker() before it reads the persisted crash-reporting preference from the main process. Since initErrorTracker() skips initialization when opted out, seed setErrorTrackerOptOut() first and only then call initErrorTracker(), so users who opt out don’t initialize telemetry during startup.
// Hand the (build-time-baked) DSN to the main process for main-process crash reporting, and seed
// the renderer's opt-out from the persisted "Send crash reports to Jetstream" menu preference.
useEffect(() => {
if (!window.electronAPI) {
return;
}
window.electronAPI.configureCrashReporter(environment.sentryDsn).catch((ex) => {
logger.error('[CRASH REPORTER] Error configuring main process crash reporter', ex);
});
window.electronAPI
.getPreferences()
.then((preferences) => setErrorTrackerOptOut(preferences.crashReportingEnabled === false))
.catch((ex) => {
logger.error('[CRASH REPORTER] Error reading crash reporting preference', ex);
});
}, []);
The desktop already knows its own deviceId — it generates the login URL from `dataService.getAppData()` and passes that same value to `verifyAuthToken`. The callback URL only echoes it back, so requiring it as a literal added no security while forcing the browser relay page to keep the deviceId in the deep-link URL. Accept `deviceId` when present and still assert it matches, but make it optional. The per-login `token` nonce (generated in the same closure) remains required and is what proves the callback belongs to this login attempt. The landing page still sends the parameter for older desktop builds; it can be dropped once those have aged out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…vironments Wire the error tracker into the desktop app (renderer + main process), the browser extension (app pages + popup), and the Salesforce canvas app, each reporting to its own Better Stack project via a separate public DSN. - Desktop: renderer hands the build-time DSN to the main process over IPC, which initializes @sentry/node. Opt out from the "Send crash reports to Jetstream" menu checkbox; the persisted preference is the single source of truth (read on every event) and a change re-reads preferences in the renderer so the settings page can never write a stale value back over it. - Extension: one `useExtensionErrorTracker` hook shared by the app pages and the popup, driven by a new "Send crash reports to Jetstream" setting. - Canvas: reports user context from the signed request. The API allow-lists the ingest origin in the canvas CSP `connect-src` (validated in `ENV` at boot so a malformed DSN fails loudly instead of silently blocking reports in an iframe). In every case the opt-out is applied before init, so nothing is reported while the preference is still loading, and re-enabling starts reporting without a restart. `fileReplacements` are not applied by the @nx/vite build, so `environment.ts` ships in every build for canvas and the desktop renderer - `production` now derives from `import.meta.env.PROD` so error reports are not all tagged `development`. The identifiers that production has always shipped (the desktop `name` used for the local database, the canvas `serverUrl`) are deliberately left alone. Also: `crashReportingEnabled` defaults to true in the preferences schema rather than relying on an "absence means enabled" convention, and `saveAuthResponseToAppData` takes the server-shaped `UserProfileUi` it actually persists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0080677 to
b2c49cb
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
apps/jetstream-canvas/vite.config.mts:36
definecurrently usesJSON.stringify(process.env.NX_PUBLIC_SENTRY_DSN_CANVAS ?? null), which will inline the literal string "null" when the env var is unset. That makesenvironment.sentryDsntruthy andinitErrorTrackerwill attempt to initialize with an invalid DSN instead of treating it as not configured.
// Canvas vite has no `envPrefix`, so inline the DSN explicitly (mirrors the amplitude handling above).
'import.meta.env.NX_PUBLIC_SENTRY_DSN_CANVAS': JSON.stringify(process.env.NX_PUBLIC_SENTRY_DSN_CANVAS ?? null),
'globalThis.__IS_CANVAS_APP__': true,
8ce2590 to
bcef7bf
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.
Suppressed comments (2)
apps/jetstream-desktop-client/src/app/components/core/useDesktopErrorTracker.ts:26
window.electronAPI?.configureCrashReporter(...)is optionally invoked, but.catch(...)is still called on the result. WhenelectronAPIis undefined (e.g. browser dev), this will throwTypeError: Cannot read properties of undefined (reading 'catch')on mount.
useEffect(() => {
window.electronAPI?.configureCrashReporter(environment.sentryDsn).catch((ex) => {
logger.error('[CRASH REPORTER] Error configuring main process crash reporter', ex);
});
apps/jetstream-desktop-client/src/app/components/core/useDesktopErrorTracker.ts:40
- The crash-reporting toggle listener closes over
preferencesand writes{ ...preferences, crashReportingEnabled }. If preferences change between renders (e.g. other settings updated) and the IPC event arrives before this effect re-subscribes, this can overwrite newer preference fields with stale values. Prefer a functional state update so the latest preferences are always merged.
useEffect(
() => window.electronAPI?.onCrashReportingChanged((crashReportingEnabled) => setPreferences({ ...preferences, crashReportingEnabled })),
[preferences, setPreferences],
);
…iling boot `z.url().optional()` rejects an empty string, and both an unset CI secret and the `NX_PUBLIC_SENTRY_DSN_CANVAS=''` line in `.env.example` produce one — so the API exited at startup with an env parse error rather than simply disabling canvas error reporting. Coerce empty to undefined; a non-empty but malformed DSN still fails the boot-time parse, which is the point of validating it there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bcef7bf to
61c7840
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.
Suppressed comments (1)
apps/jetstream-desktop-client/src/app/components/core/useDesktopErrorTracker.ts:26
window.electronAPI?.configureCrashReporter(...)can evaluate toundefinedwhen running outside Electron (browser dev), but.catch(...)is still invoked on the result. That will throwCannot read properties of undefined (reading 'catch')and can break the initializer in non-Electron contexts.
useEffect(() => {
window.electronAPI?.configureCrashReporter(environment.sentryDsn).catch((ex) => {
logger.error('[CRASH REPORTER] Error configuring main process crash reporter', ex);
});
}, []);
No description provided.