diff --git a/packages/app-desktop/ElectronAppWrapper.ts b/packages/app-desktop/ElectronAppWrapper.ts index 97253f3553..d0f7245789 100644 --- a/packages/app-desktop/ElectronAppWrapper.ts +++ b/packages/app-desktop/ElectronAppWrapper.ts @@ -75,6 +75,8 @@ export default class ElectronAppWrapper { // Assumes that the renderer process may be in an invalid state and so cannot // be accessed. public async handleAppFailure(errorMessage: string, canIgnore: boolean, isTesting?: boolean) { + await bridge().captureException(new Error(errorMessage)); + const buttons = []; buttons.push(_('Quit')); const exitIndex = 0; diff --git a/packages/app-desktop/app.ts b/packages/app-desktop/app.ts index bd1bb5d11d..2979c6ee8c 100644 --- a/packages/app-desktop/app.ts +++ b/packages/app-desktop/app.ts @@ -130,7 +130,7 @@ class Application extends BaseApplication { } if (action.type === 'SETTING_UPDATE_ONE' && action.key === 'autoUploadCrashDumps') { - bridge().autoUploadCrashDumps = action.value; + bridge().setAutoUploadCrashDumps(action.value); } if (action.type === 'SETTING_UPDATE_ONE' && action.key === 'style.editor.fontFamily' || action.type === 'SETTING_UPDATE_ALL') { @@ -410,9 +410,7 @@ class Application extends BaseApplication { argv = await super.start(argv); - bridge().autoUploadCrashDumps = Setting.value('autoUploadCrashDumps'); - - // this.crashDetectionHandler(); + bridge().setAutoUploadCrashDumps(Setting.value('autoUploadCrashDumps')); await this.applySettingsSideEffects(); diff --git a/packages/app-desktop/bridge.ts b/packages/app-desktop/bridge.ts index dff9e50060..fcca7a2da6 100644 --- a/packages/app-desktop/bridge.ts +++ b/packages/app-desktop/bridge.ts @@ -2,8 +2,11 @@ import ElectronAppWrapper from './ElectronAppWrapper'; import shim from '@joplin/lib/shim'; import { _, setLocale } from '@joplin/lib/locale'; import { BrowserWindow, nativeTheme, nativeImage } from 'electron'; -const { dirname, toSystemSlashes } = require('@joplin/lib/path-utils'); import * as Sentry from '@sentry/electron/main'; +import { writeFileSync } from 'fs'; +import { homedir } from 'os'; +import { msleep } from '@joplin/utils/time'; +const { dirname, toSystemSlashes } = require('@joplin/lib/path-utils'); interface LastSelectedPath { file: string; @@ -32,7 +35,20 @@ export class Bridge { Sentry.init({ dsn: 'https://cceec550871b1e8a10fee4c7a28d5cf2@o4506576757522432.ingest.sentry.io/4506594281783296', - beforeSend: event => this.autoUploadCrashDumps_ ? event : null, + beforeSend: event => { + try { + const date = (new Date()).toISOString().replace(/[:-]/g, '').split('.')[0]; + writeFileSync(`${homedir()}/joplin_crash_dump_${date}.json`, JSON.stringify(event, null, '\t'), 'utf-8'); + } catch (error) { + // Ignore the error since we can't handle it here + } + + if (!this.autoUploadCrashDumps_) { + return null; + } else { + return event; + } + }, }); } @@ -44,11 +60,14 @@ export class Bridge { return !this.electronApp().electronApp().isPackaged; } - public get autoUploadCrashDumps() { - return this.autoUploadCrashDumps_; + public async captureException(error: any) { + Sentry.captureException(error); + // We wait to give the "beforeSend" event handler time to process the crash dump and write + // it to file. + await msleep(10); } - public set autoUploadCrashDumps(v: boolean) { + public setAutoUploadCrashDumps(v: boolean) { this.autoUploadCrashDumps_ = v; }