Skip to content

Commit 059062a

Browse files
committed
refactor: upgrade to ESM and secure IPC communication
- Upgrade to ESM module system with type: module - Regenerate Electron Forge scaffolding for better ESM compatibility - Convert Vite configs from .mjs to .ts for better type safety - Implement secure IPC communication using contextBridge - Add automatic type inference for API interfaces - Fix ESLint configuration to ignore build files - Add @types/electron-squirrel-startup for proper typing - Restructure main process files for better organization - Update all renderer components to use new secure API All linting and type checks pass ✅
1 parent cee368e commit 059062a

21 files changed

Lines changed: 212 additions & 507 deletions

.yarnrc.yml

Lines changed: 0 additions & 1 deletion
This file was deleted.

dprint.json

Lines changed: 0 additions & 19 deletions
This file was deleted.

eslint.config.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ import reactHooks from "eslint-plugin-react-hooks";
66
import stylistic from "@stylistic/eslint-plugin";
77

88
export default [
9+
{
10+
// Ignore build and generated files
11+
ignores: [
12+
".vite/**/*",
13+
"out/**/*",
14+
"node_modules/**/*",
15+
"dist/**/*"
16+
],
17+
},
918
{
1019
files: ["src/**/*.{ts,tsx}"],
1120
languageOptions: {
@@ -37,7 +46,6 @@ export default [
3746

3847
// TODO: re-enable these rules
3948
"@typescript-eslint/unbound-method": "off",
40-
"@typescript-eslint/no-require-imports": "off",
4149

4250
// Disable conflicting base ESLint rules
4351
"no-unused-vars": "off",
@@ -111,9 +119,6 @@ export default [
111119
"@stylistic/jsx-max-props-per-line": ["error", { maximum: 1, when: "multiline" }],
112120
"@stylistic/jsx-first-prop-new-line": ["error", "multiline-multiprop"],
113121

114-
// Electron-specific exceptions
115-
"@typescript-eslint/no-var-requires": "off", // Allow require() in Electron main process
116-
117122
// Project-specific adjustments for Electron/React app
118123
"@typescript-eslint/no-unsafe-member-access": "off", // Electron IPC and external APIs
119124
"@typescript-eslint/no-unsafe-assignment": "off", // Electron IPC and external APIs

forge.config.js

Lines changed: 0 additions & 42 deletions
This file was deleted.

forge.config.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { ForgeConfig } from '@electron-forge/shared-types';
2+
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
3+
import { MakerZIP } from '@electron-forge/maker-zip';
4+
import { MakerDeb } from '@electron-forge/maker-deb';
5+
import { MakerRpm } from '@electron-forge/maker-rpm';
6+
import { VitePlugin } from '@electron-forge/plugin-vite';
7+
import { FusesPlugin } from '@electron-forge/plugin-fuses';
8+
import { FuseV1Options, FuseVersion } from '@electron/fuses';
9+
10+
const config: ForgeConfig = {
11+
packagerConfig: {
12+
asar: true,
13+
},
14+
rebuildConfig: {},
15+
makers: [
16+
new MakerSquirrel({}),
17+
new MakerZIP({}, ['darwin']),
18+
new MakerRpm({}),
19+
new MakerDeb({}),
20+
],
21+
plugins: [
22+
new VitePlugin({
23+
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
24+
// If you are familiar with Vite configuration, it will look really familiar.
25+
build: [
26+
{
27+
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
28+
entry: 'src/main.ts',
29+
config: 'vite.main.config.ts',
30+
target: 'main',
31+
},
32+
{
33+
entry: 'src/preload.ts',
34+
config: 'vite.preload.config.ts',
35+
target: 'preload',
36+
},
37+
],
38+
renderer: [
39+
{
40+
name: 'main_window',
41+
config: 'vite.renderer.config.ts',
42+
},
43+
],
44+
}),
45+
// Fuses are used to enable/disable various Electron functionality
46+
// at package time, before code signing the application
47+
new FusesPlugin({
48+
version: FuseVersion.V1,
49+
[FuseV1Options.RunAsNode]: false,
50+
[FuseV1Options.EnableCookieEncryption]: true,
51+
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
52+
[FuseV1Options.EnableNodeCliInspectArguments]: false,
53+
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
54+
[FuseV1Options.OnlyLoadAppFromAsar]: true,
55+
}),
56+
],
57+
publishers: [
58+
{
59+
name: "@electron-forge/publisher-github",
60+
config: {
61+
repository: {
62+
owner: "pd4d10",
63+
name: "debugtron",
64+
},
65+
},
66+
},
67+
],
68+
};
69+
70+
export default config;

forge.env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="@electron-forge/plugin-vite/forge-vite-env" />

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "debugtron",
3+
"productName": "Debugtron",
34
"version": "1.0.0-alpha.0",
45
"description": "Debug in-production Electron based App",
56
"repository": "pd4d10/debugtron",
@@ -15,7 +16,6 @@
1516
"type-check": "tsc --noEmit"
1617
},
1718
"dependencies": {
18-
"electron-redux": "^2.0.0",
1919
"electron-squirrel-startup": "^1.0.1",
2020
"redux": "^5.0.1",
2121
"registry-js": "^1.16.1",
@@ -27,15 +27,17 @@
2727
"@blueprintjs/select": "^5.3.21",
2828
"@electron-forge/cli": "^7.9.0",
2929
"@electron-forge/maker-deb": "^7.9.0",
30-
"@electron-forge/maker-dmg": "^7.9.0",
3130
"@electron-forge/maker-rpm": "^7.9.0",
3231
"@electron-forge/maker-squirrel": "^7.9.0",
3332
"@electron-forge/maker-zip": "^7.9.0",
3433
"@electron-forge/plugin-auto-unpack-natives": "^7.9.0",
34+
"@electron-forge/plugin-fuses": "^7.9.0",
3535
"@electron-forge/plugin-vite": "^7.9.0",
3636
"@electron-forge/publisher-github": "^7.9.0",
37+
"@electron/fuses": "^1.8.0",
3738
"@reduxjs/toolkit": "^2.9.0",
3839
"@stylistic/eslint-plugin": "^5.4.0",
40+
"@types/electron-squirrel-startup": "^1.0.2",
3941
"@types/ini": "^4.1.0",
4042
"@types/lodash-es": "^4.17.12",
4143
"@types/react": "^18.3.24",
@@ -50,6 +52,7 @@
5052
"@xterm/xterm": "^5.5.0",
5153
"electron": "^31.7.7",
5254
"electron-devtools-installer": "^3.2.1",
55+
"electron-redux": "^2.0.0",
5356
"electron-update-notification": "^0.1.0",
5457
"eslint": "^9.36.0",
5558
"eslint-plugin-react": "^7.37.5",
@@ -72,6 +75,5 @@
7275
"universal-analytics": "^0.5.3",
7376
"vite": "^5.4.20"
7477
},
75-
"packageManager": "yarn@1.22.22",
76-
"productName": "Debugtron"
78+
"packageManager": "yarn@1.22.22"
7779
}

src/main/main.ts renamed to src/main.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import path from "path";
2+
import { fileURLToPath } from "url";
3+
4+
const __filename = fileURLToPath(import.meta.url);
5+
const __dirname = path.dirname(__filename);
26

37
import { app, BrowserWindow, ipcMain, Menu, MenuItem, nativeImage, shell } from "electron";
48

5-
import type { AppInfo } from "../reducers/app";
9+
import type { AppInfo } from "./reducers/app";
610

7-
import { debug, debugPath, init } from "./actions";
8-
import { store } from "./store";
9-
import { setReporter, setUpdater } from "./utils";
11+
import { debug, debugPath, init } from "./main/actions";
12+
import { store } from "./main/store";
13+
import { setReporter, setUpdater } from "./main/utils";
1014

1115
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
12-
if (require("electron-squirrel-startup")) {
16+
import squirrelStartup from "electron-squirrel-startup";
17+
if (squirrelStartup) {
1318
app.quit();
1419
}
1520

@@ -22,11 +27,9 @@ const createWindow = () => {
2227
titleBarStyle: "hidden",
2328
trafficLightPosition: { x: 14, y: 14 },
2429
icon: process.platform === "linux"
25-
? nativeImage.createFromDataURL(require("../../assets/icon.png"))
30+
? nativeImage.createFromPath(path.join(__dirname, "../../assets/icon.png"))
2631
: undefined,
2732
webPreferences: {
28-
nodeIntegration: true,
29-
contextIsolation: false, // for `require`
3033
preload: path.join(__dirname, "preload.js"),
3134
},
3235
});
@@ -40,7 +43,7 @@ const createWindow = () => {
4043
}
4144

4245
if (!app.isPackaged) {
43-
mainWindow.webContents.openDevTools();
46+
mainWindow.webContents.openDevTools({ mode: "detach" });
4447
}
4548

4649
mainWindow.on("closed", () => {
@@ -109,7 +112,7 @@ if (!gotTheLock) {
109112
debugPath(path),
110113
);
111114
});
112-
ipcMain.on("open-window", (_, url: string) => {
115+
ipcMain.on("open-devtools", (_, url: string) => {
113116
const win = new BrowserWindow();
114117
// console.log(url)
115118
void win.loadURL(url);

src/preload.ts

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,61 @@
1-
// const { contextBridge, ipcRenderer } = require("electron");
1+
import { contextBridge, ipcRenderer } from "electron";
2+
// Keep electron-redux for state synchronization
3+
import { preload } from "electron-redux/preload";
24

3-
// contextBridge.exposeInMainWorld("electronAPI", {
4-
// openFile: () => ipcRenderer.invoke("openFile"),
5-
// });
5+
preload();
66

7-
require("electron-redux/preload");
7+
/**
8+
* Helper function to create IPC send methods
9+
*/
10+
function createIPCSender(channel: string) {
11+
return (...args: unknown[]) => {
12+
ipcRenderer.send(channel, ...args);
13+
};
14+
}
15+
16+
/**
17+
* Helper function to create event listeners
18+
*/
19+
function createEventListener(channel: string) {
20+
return (callback: (event: unknown, ...args: unknown[]) => void) => {
21+
const wrappedCallback = (event: unknown, ...args: unknown[]) => {
22+
callback(event, ...args);
23+
};
24+
ipcRenderer.on(channel, wrappedCallback);
25+
return () => ipcRenderer.removeListener(channel, wrappedCallback);
26+
};
27+
}
28+
29+
/**
30+
* Helper function to create utility methods
31+
*/
32+
function createUtilityMethods() {
33+
return {
34+
removeAllListeners: (channel: string) => ipcRenderer.removeAllListeners(channel),
35+
};
36+
}
37+
38+
// Define the API implementation using builders
39+
const debugtronAPI = {
40+
// App debugging operations
41+
debug: createIPCSender("debug"),
42+
debugPath: createIPCSender("debug-path"),
43+
44+
// DevTools operations
45+
openDevTools: createIPCSender("open-devtools"),
46+
47+
// General window operations
48+
openWindow: createIPCSender("open-window"),
49+
50+
// Event listeners (for future use)
51+
onSessionUpdate: createEventListener("session-updated"),
52+
53+
// Utility methods
54+
...createUtilityMethods(),
55+
} as const;
56+
57+
// Export the type for use in renderer process
58+
export type DebugTronAPI = typeof debugtronAPI;
59+
60+
// Expose safe APIs to renderer process
61+
contextBridge.exposeInMainWorld("debugtronAPI", debugtronAPI);

src/renderer/header.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const Header: FC = () => {
5858
onItemSelect={(item) => {
5959
const appInfo = appState[item.id];
6060
if (appInfo) {
61-
require("electron").ipcRenderer.send("debug", appInfo);
61+
window.debugtronAPI.debug(appInfo);
6262
}
6363
}}
6464
>
@@ -101,7 +101,7 @@ export const Header: FC = () => {
101101
text="Debug"
102102
icon="build"
103103
onClick={() => {
104-
require("electron").ipcRenderer.send("debug-path", input);
104+
window.debugtronAPI.debugPath(input);
105105
}}
106106
/>
107107
</ControlGroup>

0 commit comments

Comments
 (0)