Skip to content

Commit 09a7bb8

Browse files
Tim020claude
andcommitted
Migrate electron frontend from JavaScript to TypeScript (#1032)
Converts all 5 source files to TypeScript using the same conventions as the recent client migration: strict null checks, NodeNext modules, and @typescript-eslint v8. Adds a tsc compilation step (output to dist/) since the Electron main process needs real .js files unlike the Vite-built client. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 91957cf commit 09a7bb8

12 files changed

Lines changed: 747 additions & 512 deletions

.github/workflows/nodelint.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,15 @@ jobs:
4141
node-version: 24
4242
- run: npm ci
4343
- run: npm run ci-lint
44+
run-typecheck-electron:
45+
runs-on: ubuntu-latest
46+
defaults:
47+
run:
48+
working-directory: ./electron
49+
steps:
50+
- uses: actions/checkout@v4
51+
- uses: actions/setup-node@v4
52+
with:
53+
node-version: 24
54+
- run: npm ci
55+
- run: npm run typecheck

electron/eslint.config.mjs

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,64 @@ import js from '@eslint/js';
22
import globals from 'globals';
33
import prettierConfig from 'eslint-config-prettier';
44
import prettierPlugin from 'eslint-plugin-prettier';
5+
import tsPlugin from '@typescript-eslint/eslint-plugin';
6+
import tsParser from '@typescript-eslint/parser';
7+
8+
const sharedRules = {
9+
'prettier/prettier': 'error',
10+
...prettierConfig.rules,
11+
'max-len': 'off',
12+
'no-plusplus': 'off',
13+
};
14+
15+
const tsRules = {
16+
...sharedRules,
17+
'@typescript-eslint/no-explicit-any': 'off',
18+
'@typescript-eslint/no-unused-vars': 'warn',
19+
// TypeScript handles undefined-variable checks; no-undef causes false positives
20+
// on TS-specific constructs (namespaces, declare global, etc.)
21+
'no-undef': 'off',
22+
};
523

624
export default [
725
{
8-
ignores: [
9-
'**/node_modules/**',
10-
'**/out/**',
11-
'**/dist/**',
12-
'*.backup',
13-
],
26+
ignores: ['**/node_modules/**', '**/out/**', '**/dist/**', '*.backup'],
1427
},
1528
js.configs.recommended,
29+
// ESM TypeScript files (main process + services)
1630
{
17-
files: ['**/*.js'],
31+
files: ['**/*.ts'],
1832
plugins: {
1933
prettier: prettierPlugin,
34+
'@typescript-eslint': tsPlugin,
2035
},
2136
languageOptions: {
37+
parser: tsParser,
2238
ecmaVersion: 2022,
2339
sourceType: 'module',
2440
globals: {
2541
...globals.node,
2642
...globals.es2021,
2743
},
2844
},
29-
rules: {
30-
// Prettier integration - runs Prettier as an ESLint rule
31-
'prettier/prettier': 'error',
32-
33-
// Disable formatting rules that conflict with Prettier
34-
...prettierConfig.rules,
35-
36-
// Let Prettier handle line length (via printWidth config)
37-
'max-len': 'off',
38-
39-
// Custom linting rules (non-formatting)
40-
'no-unused-vars': 'warn',
41-
'no-plusplus': 'off',
42-
},
45+
rules: tsRules,
4346
},
44-
// CommonJS files (preload scripts must be CJS when sandbox is enabled)
47+
// CommonJS TypeScript files (preload must be CJS when sandbox is enabled)
4548
{
46-
files: ['**/*.cjs'],
49+
files: ['**/*.cts'],
4750
plugins: {
4851
prettier: prettierPlugin,
52+
'@typescript-eslint': tsPlugin,
4953
},
5054
languageOptions: {
55+
parser: tsParser,
5156
ecmaVersion: 2022,
5257
sourceType: 'commonjs',
5358
globals: {
5459
...globals.node,
5560
...globals.es2021,
5661
},
5762
},
58-
rules: {
59-
'prettier/prettier': 'error',
60-
...prettierConfig.rules,
61-
'max-len': 'off',
62-
'no-unused-vars': 'warn',
63-
'no-plusplus': 'off',
64-
},
63+
rules: tsRules,
6564
},
6665
];

electron/main.js renamed to electron/main.ts

Lines changed: 12 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
/**
2-
* Electron Main Process
3-
*
4-
* Handles window creation, IPC communication, and app lifecycle.
5-
*/
6-
71
import { app, BrowserWindow, ipcMain } from 'electron';
82
import path from 'path';
93
import url from 'url';
@@ -13,25 +7,16 @@ import { fileURLToPath } from 'url';
137
const __filename = fileURLToPath(import.meta.url);
148
const __dirname = path.dirname(__filename);
159

16-
// Services
1710
import ConnectionManager from './services/ConnectionManager.js';
1811
import VersionChecker from './services/VersionChecker.js';
1912
import MDNSDiscovery from './services/MDNSDiscovery.js';
2013

21-
// Determine if running in development mode
2214
const isDev = process.argv.includes('--dev') || process.env.NODE_ENV === 'development';
2315

24-
// Keep a global reference of the window object
25-
let mainWindow;
26-
27-
// Initialize services
28-
let connectionManager;
16+
let connectionManager: ConnectionManager;
2917

30-
/**
31-
* Create the main browser window
32-
*/
33-
function createWindow() {
34-
mainWindow = new BrowserWindow({
18+
function createWindow(): void {
19+
const win = new BrowserWindow({
3520
width: 1200,
3621
height: 800,
3722
minWidth: 800,
@@ -42,24 +27,18 @@ function createWindow() {
4227
contextIsolation: true,
4328
sandbox: true,
4429
},
45-
show: false, // Don't show until ready
30+
show: false,
4631
});
4732

48-
// Load the appropriate URL
4933
if (isDev) {
50-
// Development: Load from Vite dev server
51-
mainWindow.loadURL('http://localhost:5173');
52-
53-
// Open DevTools in development
54-
mainWindow.webContents.openDevTools();
34+
win.loadURL('http://localhost:5173');
35+
win.webContents.openDevTools();
5536
} else {
56-
// Production: Load built files from client/dist-electron (Electron-specific build)
57-
// In packaged app, extraResource files are in process.resourcesPath
5837
const staticPath = app.isPackaged
5938
? path.join(process.resourcesPath, 'dist-electron')
6039
: path.join(__dirname, '../client/dist-electron');
6140

62-
mainWindow.loadURL(
41+
win.loadURL(
6342
url.format({
6443
pathname: path.join(staticPath, 'index.html'),
6544
protocol: 'file:',
@@ -68,29 +47,19 @@ function createWindow() {
6847
);
6948
}
7049

71-
// Show window when ready to avoid flicker
72-
mainWindow.once('ready-to-show', () => {
73-
mainWindow.show();
50+
win.once('ready-to-show', () => {
51+
win.show();
7452
});
7553

7654
// Enable DevTools with keyboard shortcut (Cmd/Ctrl+Shift+I) in production
77-
mainWindow.webContents.on('before-input-event', (event, input) => {
55+
win.webContents.on('before-input-event', (event, input) => {
7856
if (input.key === 'I' && input.shift && (input.meta || input.control)) {
79-
mainWindow.webContents.toggleDevTools();
57+
win.webContents.toggleDevTools();
8058
}
8159
});
82-
83-
// Emitted when the window is closed
84-
mainWindow.on('closed', () => {
85-
mainWindow = null;
86-
});
8760
}
8861

89-
/**
90-
* Register IPC handlers for renderer communication
91-
*/
92-
function registerIPCHandlers() {
93-
// Connection management
62+
function registerIPCHandlers(): void {
9463
ipcMain.handle('connections:getAll', async () => {
9564
return connectionManager.getAllConnections();
9665
});
@@ -119,13 +88,11 @@ function registerIPCHandlers() {
11988
return connectionManager.clearActiveConnection();
12089
});
12190

122-
// Server URL synchronous getter (for platform layer)
12391
ipcMain.on('server:getURLSync', (event) => {
12492
const activeConnection = connectionManager.getActiveConnection();
12593
event.returnValue = activeConnection ? activeConnection.url : null;
12694
});
12795

128-
// Storage operations (for platform layer)
12996
ipcMain.handle('storage:get', async (event, key) => {
13097
return connectionManager.getStorageItem(key);
13198
});
@@ -142,18 +109,15 @@ function registerIPCHandlers() {
142109
return connectionManager.clearStorage();
143110
});
144111

145-
// App information
146112
ipcMain.handle('app:getVersion', async () => {
147113
return app.getVersion();
148114
});
149115

150-
// Version checking
151116
ipcMain.handle('version:check', async (event, serverUrl) => {
152117
const clientVersion = app.getVersion();
153118
return VersionChecker.checkVersion(serverUrl, clientVersion);
154119
});
155120

156-
// mDNS discovery
157121
ipcMain.handle('mdns:discover', async (event, timeout = 5000) => {
158122
return MDNSDiscovery.discoverServers(timeout);
159123
});
@@ -164,33 +128,24 @@ function registerIPCHandlers() {
164128
});
165129
}
166130

167-
// App lifecycle events
168131
app.whenReady().then(() => {
169-
// Initialize services
170132
connectionManager = new ConnectionManager();
171-
172-
// Register IPC handlers
173133
registerIPCHandlers();
174-
175-
// Create window
176134
createWindow();
177135

178136
app.on('activate', () => {
179-
// On macOS, re-create window when dock icon is clicked and no windows open
180137
if (BrowserWindow.getAllWindows().length === 0) {
181138
createWindow();
182139
}
183140
});
184141
});
185142

186-
// Quit when all windows are closed (except on macOS)
187143
app.on('window-all-closed', () => {
188144
if (process.platform !== 'darwin') {
189145
app.quit();
190146
}
191147
});
192148

193-
// Handle app errors
194149
process.on('uncaughtException', (error) => {
195150
console.error('Uncaught exception:', error);
196151
});

0 commit comments

Comments
 (0)