Skip to content

Commit d11acde

Browse files
committed
feat: implement global error handling and reporting system
- Added ErrorReportingChannels to IPC types for error reporting. - Introduced ErrorHandler class for managing error reporting, including severity levels and context. - Registered error reporting IPC handlers to capture and process errors from the renderer process. - Integrated error handling into the application lifecycle, including a dialog for reporting issues. - Added utility functions for retrying operations with configurable options. - Enhanced existing utilities with a sleep function for delay handling.
1 parent 9d273a6 commit d11acde

File tree

7 files changed

+864
-61
lines changed

7 files changed

+864
-61
lines changed

src/common/types/ipc.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,18 @@ export type DialogChannels = {
175175
"dialog:showSaveDialog": ChannelDefinition<Electron.SaveDialogOptions, Electron.SaveDialogReturnValue>;
176176
};
177177

178+
export type ErrorReportingChannels = {
179+
"error:report": ChannelDefinition<
180+
{
181+
error: string;
182+
stack?: string;
183+
context?: Record<string, unknown>;
184+
severity?: "low" | "medium" | "high" | "critical";
185+
},
186+
void
187+
>;
188+
};
189+
178190
export type MainSettingsChannels = {
179191
"mainSettings:get": ChannelDefinition<void, MainSettingsType>;
180192
"mainSettings:update": ChannelDefinition<Partial<MainSettingsType>, void>;
@@ -189,6 +201,7 @@ export type IPCChannels = DatabaseChannels &
189201
ExplorerMenuChannels &
190202
ReaderChannels &
191203
DialogChannels &
204+
ErrorReportingChannels &
192205
MainSettingsChannels;
193206

194207
export type MainToRendererChannels = {

src/electron/ipc/errorReporting.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getErrorHandler } from "../util/errorHandler";
2+
import { ipc } from "./utils";
3+
4+
/**
5+
* Register error reporting IPC handlers
6+
*/
7+
export const registerErrorReportingHandlers = () => {
8+
const errorHandler = getErrorHandler();
9+
10+
ipc.handle("error:report", (event, args) => {
11+
args.severity ??= "medium";
12+
errorHandler.handleError(
13+
args.error,
14+
args.severity,
15+
{
16+
source: "Renderer Process",
17+
windowId: event.sender.id,
18+
metadata: args.context,
19+
},
20+
["high", "critical"].includes(args.severity),
21+
);
22+
});
23+
};

src/electron/main.ts

Lines changed: 82 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from "node:fs";
22
import * as remote from "@electron/remote/main";
33
import { app, BrowserWindow, Menu, type MenuItemConstructorOptions, shell } from "electron";
44
import { log } from "./util";
5+
import { getErrorHandler } from "./util/errorHandler";
56

67
remote.initialize();
78

@@ -10,6 +11,7 @@ if (require("electron-squirrel-startup")) app.quit();
1011
import { DatabaseService } from "./db";
1112
import { setupDatabaseHandlers } from "./ipc/database";
1213
import { registerDialogHandlers } from "./ipc/dialog";
14+
import { registerErrorReportingHandlers } from "./ipc/errorReporting";
1315
import { registerExplorerHandlers } from "./ipc/explorer";
1416
import { registerFSHandlers } from "./ipc/fs";
1517
import { registerUpdateHandlers } from "./ipc/update";
@@ -22,6 +24,15 @@ if (handleSquirrelEvent()) {
2224
app.quit();
2325
}
2426

27+
// initialize global error handler early
28+
const errorHandler = getErrorHandler({
29+
showDialogs: true,
30+
logToFile: true,
31+
collectSystemInfo: true,
32+
maxReports: 50,
33+
enableCrashReporting: true,
34+
});
35+
2536
const db = new DatabaseService();
2637

2738
// when manga reader opened from context menu "open with manga reader"
@@ -59,69 +70,81 @@ if (app.isPackaged) {
5970
}
6071

6172
app.on("ready", async () => {
62-
// checkForJSONMigration depends on app ready to use dialog
63-
checkForJSONMigration(db);
64-
/**
65-
* enables basic shortcut keys such as copy, paste, reload, etc.
66-
*/
67-
const template: MenuItemConstructorOptions[] = [
68-
{
69-
label: "Edit",
70-
submenu: [
71-
{ role: "undo" },
72-
{ role: "redo" },
73-
{ role: "cut" },
74-
{ role: "copy" },
75-
{ role: "paste" },
76-
{ role: "pasteAndMatchStyle" },
77-
{ role: "selectAll" },
78-
],
79-
},
80-
{
81-
label: "View",
82-
submenu: [
83-
{ role: "reload" },
84-
{ role: "forceReload" },
85-
{ role: "toggleDevTools" },
86-
{ type: "separator" },
87-
],
88-
},
89-
{
90-
label: "Others",
91-
submenu: [
92-
{
93-
role: "help",
94-
accelerator: "F1",
95-
click: () => shell.openExternal("https://github.com/mienaiyami/yomikiru"),
96-
},
97-
{
98-
label: "New Window",
99-
accelerator: process.platform === "darwin" ? "Cmd+N" : "Ctrl+N",
100-
click: () => WindowManager.createWindow(),
101-
},
102-
{
103-
label: "Close",
104-
accelerator: process.platform === "darwin" ? "Cmd+W" : "Ctrl+W",
105-
click: (_, window) => window?.close(),
106-
},
107-
],
108-
},
109-
];
110-
const menu = Menu.buildFromTemplate(template);
111-
Menu.setApplicationMenu(menu);
73+
try {
74+
// checkForJSONMigration depends on app ready to use dialog
75+
checkForJSONMigration(db);
76+
/**
77+
* enables basic shortcut keys such as copy, paste, reload, etc.
78+
*/
79+
const template: MenuItemConstructorOptions[] = [
80+
{
81+
label: "Edit",
82+
submenu: [
83+
{ role: "undo" },
84+
{ role: "redo" },
85+
{ role: "cut" },
86+
{ role: "copy" },
87+
{ role: "paste" },
88+
{ role: "pasteAndMatchStyle" },
89+
{ role: "selectAll" },
90+
],
91+
},
92+
{
93+
label: "View",
94+
submenu: [
95+
{ role: "reload" },
96+
{ role: "forceReload" },
97+
{ role: "toggleDevTools" },
98+
{ type: "separator" },
99+
],
100+
},
101+
{
102+
label: "Others",
103+
submenu: [
104+
{
105+
role: "help",
106+
accelerator: "F1",
107+
click: () => shell.openExternal("https://github.com/mienaiyami/yomikiru"),
108+
},
109+
{
110+
label: "New Window",
111+
accelerator: process.platform === "darwin" ? "Cmd+N" : "Ctrl+N",
112+
click: () => WindowManager.createWindow(),
113+
},
114+
{
115+
label: "Close",
116+
accelerator: process.platform === "darwin" ? "Cmd+W" : "Ctrl+W",
117+
click: (_, window) => window?.close(),
118+
},
119+
{
120+
label: "Report Issue",
121+
click: () => errorHandler.showIssueReportDialog(),
122+
},
123+
],
124+
},
125+
];
126+
const menu = Menu.buildFromTemplate(template);
127+
Menu.setApplicationMenu(menu);
112128

113-
await db.initialize();
114-
setupDatabaseHandlers(db);
129+
await db.initialize();
130+
setupDatabaseHandlers(db);
115131

116-
WindowManager.registerListeners();
132+
WindowManager.registerListeners();
117133

118-
registerExplorerHandlers();
119-
registerFSHandlers();
120-
registerDialogHandlers();
134+
registerExplorerHandlers();
135+
registerFSHandlers();
136+
registerDialogHandlers();
137+
registerErrorReportingHandlers();
121138

122-
WindowManager.createWindow(openFolderOnLaunch);
123-
// need to be after window is created
124-
registerUpdateHandlers();
139+
WindowManager.createWindow(openFolderOnLaunch);
140+
// need to be after window is created
141+
registerUpdateHandlers();
142+
} catch (error) {
143+
errorHandler.handleError(error as Error, "critical", {
144+
source: "App Ready Handler",
145+
action: "Initialize application",
146+
});
147+
}
125148
});
126149

127150
app.on("before-quit", () => {

src/electron/updater.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as electronDl from "electron-dl";
99
import fetch from "electron-fetch";
1010
import logger from "electron-log";
1111
import * as semver from "semver";
12-
import { IS_PORTABLE } from "./util";
12+
import { IS_PORTABLE, sleep } from "./util";
1313

1414
declare const DOWNLOAD_PROGRESS_WEBPACK_ENTRY: string;
1515

@@ -26,7 +26,7 @@ const DOWNLOAD_LINK = `${RELEASES_PAGE}/download`;
2626

2727
const checkForAnnouncements = async () => {
2828
try {
29-
await new Promise((resolve) => setTimeout(resolve, 5000));
29+
await sleep(5000);
3030
const raw = await fetch(ANNOUNCEMENTS_URL)
3131
.then((data) => data.text())
3232
.then((data) => data.split("\n").filter((e) => e !== ""));

0 commit comments

Comments
 (0)