-
Notifications
You must be signed in to change notification settings - Fork 597
refactor: Modularize Electron main process into single-responsibility components #704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6158236
refactor: Modularize Electron main process into single-responsibility…
Shironex d7ad87b
fix: Correct __dirname paths for Vite bundled electron modules
Shironex 0b4e957
refactor: Simplify tsx path lookup and remove redundant try-catch
Shironex 2de3ae6
fix: Address CodeRabbit security and robustness review comments
Shironex 99de781
fix: Apply titleBarStyle only on macOS
Shironex File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /** | ||
| * Electron main process constants | ||
| * | ||
| * Centralized configuration for window sizing, ports, and file names. | ||
| */ | ||
|
|
||
| // ============================================ | ||
| // Window sizing constants for kanban layout | ||
| // ============================================ | ||
| // Calculation: 4 columns × 280px + 3 gaps × 20px + 40px padding = 1220px board content | ||
| // With sidebar expanded (288px): 1220 + 288 = 1508px | ||
| // Minimum window dimensions - reduced to allow smaller windows since kanban now supports horizontal scrolling | ||
| export const MIN_WIDTH_COLLAPSED = 600; // Reduced - horizontal scrolling handles overflow | ||
| export const MIN_HEIGHT = 500; // Reduced to allow more flexibility | ||
| export const DEFAULT_WIDTH = 1600; | ||
| export const DEFAULT_HEIGHT = 950; | ||
|
|
||
| // ============================================ | ||
| // Port defaults | ||
| // ============================================ | ||
| // Default ports (can be overridden via env) - will be dynamically assigned if these are in use | ||
| // When launched via root init.mjs we pass: | ||
| // - PORT (backend) | ||
| // - TEST_PORT (vite dev server / static) | ||
| export const DEFAULT_SERVER_PORT = parseInt(process.env.PORT || '3008', 10); | ||
| export const DEFAULT_STATIC_PORT = parseInt(process.env.TEST_PORT || '3007', 10); | ||
|
|
||
| // ============================================ | ||
| // File names for userData storage | ||
| // ============================================ | ||
| export const API_KEY_FILENAME = '.api-key'; | ||
| export const WINDOW_BOUNDS_FILENAME = 'window-bounds.json'; | ||
|
|
||
| // ============================================ | ||
| // Window bounds interface | ||
| // ============================================ | ||
| // Matches @automaker/types WindowBounds | ||
| export interface WindowBounds { | ||
| x: number; | ||
| y: number; | ||
| width: number; | ||
| height: number; | ||
| isMaximized: boolean; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /** | ||
| * Electron main process modules | ||
| * | ||
| * Re-exports for convenient importing. | ||
| */ | ||
|
|
||
| // Constants and types | ||
| export * from './constants'; | ||
| export { state } from './state'; | ||
|
|
||
| // Utilities | ||
| export { isPortAvailable, findAvailablePort } from './utils/port-manager'; | ||
| export { getIconPath } from './utils/icon-manager'; | ||
|
|
||
| // Security | ||
| export { ensureApiKey, getApiKey } from './security/api-key-manager'; | ||
|
|
||
| // Windows | ||
| export { | ||
| loadWindowBounds, | ||
| saveWindowBounds, | ||
| validateBounds, | ||
| scheduleSaveWindowBounds, | ||
| } from './windows/window-bounds'; | ||
| export { createWindow } from './windows/main-window'; | ||
|
|
||
| // Server | ||
| export { startStaticServer, stopStaticServer } from './server/static-server'; | ||
| export { startServer, waitForServer, stopServer } from './server/backend-server'; | ||
|
|
||
| // IPC | ||
| export { IPC_CHANNELS, registerAllHandlers } from './ipc'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /** | ||
| * App IPC handlers | ||
| * | ||
| * Handles app-related operations like getting paths, version info, and quitting. | ||
| */ | ||
|
|
||
| import { ipcMain, app } from 'electron'; | ||
| import { createLogger } from '@automaker/utils/logger'; | ||
| import { IPC_CHANNELS } from './channels'; | ||
|
|
||
| const logger = createLogger('AppHandlers'); | ||
|
|
||
| /** | ||
| * Register app IPC handlers | ||
| */ | ||
| export function registerAppHandlers(): void { | ||
| // Get app path | ||
| ipcMain.handle(IPC_CHANNELS.APP.GET_PATH, async (_, name: Parameters<typeof app.getPath>[0]) => { | ||
| return app.getPath(name); | ||
| }); | ||
|
|
||
| // Get app version | ||
| ipcMain.handle(IPC_CHANNELS.APP.GET_VERSION, async () => { | ||
| return app.getVersion(); | ||
| }); | ||
|
|
||
| // Check if app is packaged | ||
| ipcMain.handle(IPC_CHANNELS.APP.IS_PACKAGED, async () => { | ||
| return app.isPackaged; | ||
| }); | ||
|
|
||
| // Quit the application | ||
| ipcMain.handle(IPC_CHANNELS.APP.QUIT, () => { | ||
| logger.info('Quitting application via IPC request'); | ||
| app.quit(); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| /** | ||
| * Auth IPC handlers | ||
| * | ||
| * Handles authentication-related operations. | ||
| */ | ||
|
|
||
| import { ipcMain } from 'electron'; | ||
| import { IPC_CHANNELS } from './channels'; | ||
| import { state } from '../state'; | ||
|
|
||
| /** | ||
| * Register auth IPC handlers | ||
| */ | ||
| export function registerAuthHandlers(): void { | ||
| // Get API key for authentication | ||
| // Returns null in external server mode to trigger session-based auth | ||
| ipcMain.handle(IPC_CHANNELS.AUTH.GET_API_KEY, () => { | ||
| if (state.isExternalServerMode) { | ||
| return null; | ||
| } | ||
| return state.apiKey; | ||
| }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // Check if running in external server mode (Docker API) | ||
| // Used by renderer to determine auth flow | ||
| ipcMain.handle(IPC_CHANNELS.AUTH.IS_EXTERNAL_SERVER_MODE, () => { | ||
| return state.isExternalServerMode; | ||
| }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /** | ||
| * IPC channel constants | ||
| * | ||
| * Single source of truth for all IPC channel names. | ||
| * Used by both main process handlers and preload script. | ||
| */ | ||
|
|
||
| export const IPC_CHANNELS = { | ||
| DIALOG: { | ||
| OPEN_DIRECTORY: 'dialog:openDirectory', | ||
| OPEN_FILE: 'dialog:openFile', | ||
| SAVE_FILE: 'dialog:saveFile', | ||
| }, | ||
| SHELL: { | ||
| OPEN_EXTERNAL: 'shell:openExternal', | ||
| OPEN_PATH: 'shell:openPath', | ||
| OPEN_IN_EDITOR: 'shell:openInEditor', | ||
| }, | ||
| APP: { | ||
| GET_PATH: 'app:getPath', | ||
| GET_VERSION: 'app:getVersion', | ||
| IS_PACKAGED: 'app:isPackaged', | ||
| QUIT: 'app:quit', | ||
| }, | ||
| AUTH: { | ||
| GET_API_KEY: 'auth:getApiKey', | ||
| IS_EXTERNAL_SERVER_MODE: 'auth:isExternalServerMode', | ||
| }, | ||
| WINDOW: { | ||
| UPDATE_MIN_WIDTH: 'window:updateMinWidth', | ||
| }, | ||
| SERVER: { | ||
| GET_URL: 'server:getUrl', | ||
| }, | ||
| PING: 'ping', | ||
| } as const; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| /** | ||
| * Dialog IPC handlers | ||
| * | ||
| * Handles native file dialog operations. | ||
| */ | ||
|
|
||
| import { ipcMain, dialog } from 'electron'; | ||
| import { isPathAllowed, getAllowedRootDirectory } from '@automaker/platform'; | ||
| import { IPC_CHANNELS } from './channels'; | ||
| import { state } from '../state'; | ||
|
|
||
| /** | ||
| * Register dialog IPC handlers | ||
| */ | ||
| export function registerDialogHandlers(): void { | ||
| // Open directory dialog | ||
| ipcMain.handle(IPC_CHANNELS.DIALOG.OPEN_DIRECTORY, async () => { | ||
| if (!state.mainWindow) { | ||
| return { canceled: true, filePaths: [] }; | ||
| } | ||
| const result = await dialog.showOpenDialog(state.mainWindow, { | ||
| properties: ['openDirectory', 'createDirectory'], | ||
| }); | ||
|
|
||
| // Validate selected path against ALLOWED_ROOT_DIRECTORY if configured | ||
| if (!result.canceled && result.filePaths.length > 0) { | ||
| const selectedPath = result.filePaths[0]; | ||
| if (!isPathAllowed(selectedPath)) { | ||
| const allowedRoot = getAllowedRootDirectory(); | ||
| const errorMessage = allowedRoot | ||
| ? `The selected directory is not allowed. Please select a directory within: ${allowedRoot}` | ||
| : 'The selected directory is not allowed.'; | ||
|
|
||
| await dialog.showErrorBox('Directory Not Allowed', errorMessage); | ||
|
|
||
| return { canceled: true, filePaths: [] }; | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| }); | ||
|
|
||
| // Open file dialog | ||
| ipcMain.handle(IPC_CHANNELS.DIALOG.OPEN_FILE, async (_, options = {}) => { | ||
| if (!state.mainWindow) { | ||
| return { canceled: true, filePaths: [] }; | ||
| } | ||
| const result = await dialog.showOpenDialog(state.mainWindow, { | ||
| properties: ['openFile'], | ||
| ...options, | ||
| }); | ||
| return result; | ||
| }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Save file dialog | ||
| ipcMain.handle(IPC_CHANNELS.DIALOG.SAVE_FILE, async (_, options = {}) => { | ||
| if (!state.mainWindow) { | ||
| return { canceled: true, filePath: undefined }; | ||
| } | ||
| const result = await dialog.showSaveDialog(state.mainWindow, options); | ||
| return result; | ||
| }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /** | ||
| * IPC handlers aggregator | ||
| * | ||
| * Registers all IPC handlers in one place. | ||
| */ | ||
|
|
||
| import { registerDialogHandlers } from './dialog-handlers'; | ||
| import { registerShellHandlers } from './shell-handlers'; | ||
| import { registerAppHandlers } from './app-handlers'; | ||
| import { registerAuthHandlers } from './auth-handlers'; | ||
| import { registerWindowHandlers } from './window-handlers'; | ||
| import { registerServerHandlers } from './server-handlers'; | ||
|
|
||
| export { IPC_CHANNELS } from './channels'; | ||
|
|
||
| /** | ||
| * Register all IPC handlers | ||
| */ | ||
| export function registerAllHandlers(): void { | ||
| registerDialogHandlers(); | ||
| registerShellHandlers(); | ||
| registerAppHandlers(); | ||
| registerAuthHandlers(); | ||
| registerWindowHandlers(); | ||
| registerServerHandlers(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| /** | ||
| * Server IPC handlers | ||
| * | ||
| * Handles server-related operations. | ||
| */ | ||
|
|
||
| import { ipcMain } from 'electron'; | ||
| import { IPC_CHANNELS } from './channels'; | ||
| import { state } from '../state'; | ||
|
|
||
| /** | ||
| * Register server IPC handlers | ||
| */ | ||
| export function registerServerHandlers(): void { | ||
| // Get server URL for HTTP client | ||
| ipcMain.handle(IPC_CHANNELS.SERVER.GET_URL, async () => { | ||
| return `http://localhost:${state.serverPort}`; | ||
| }); | ||
|
|
||
| // Ping - for connection check | ||
| ipcMain.handle(IPC_CHANNELS.PING, async () => { | ||
| return 'pong'; | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| /** | ||
| * Shell IPC handlers | ||
| * | ||
| * Handles shell operations like opening external links and files. | ||
| */ | ||
|
|
||
| import { ipcMain, shell } from 'electron'; | ||
| import { IPC_CHANNELS } from './channels'; | ||
|
|
||
| /** | ||
| * Register shell IPC handlers | ||
| */ | ||
| export function registerShellHandlers(): void { | ||
| // Open external URL | ||
| ipcMain.handle(IPC_CHANNELS.SHELL.OPEN_EXTERNAL, async (_, url: string) => { | ||
| try { | ||
| await shell.openExternal(url); | ||
| return { success: true }; | ||
| } catch (error) { | ||
| return { success: false, error: (error as Error).message }; | ||
| } | ||
| }); | ||
|
|
||
| // Open file path | ||
| ipcMain.handle(IPC_CHANNELS.SHELL.OPEN_PATH, async (_, filePath: string) => { | ||
| try { | ||
| await shell.openPath(filePath); | ||
| return { success: true }; | ||
| } catch (error) { | ||
| return { success: false, error: (error as Error).message }; | ||
| } | ||
| }); | ||
|
|
||
| // Open file in editor (VS Code, etc.) with optional line/column | ||
| ipcMain.handle( | ||
| IPC_CHANNELS.SHELL.OPEN_IN_EDITOR, | ||
| async (_, filePath: string, line?: number, column?: number) => { | ||
| try { | ||
| // Build VS Code URL scheme: vscode://file/path:line:column | ||
| // This works on all platforms where VS Code is installed | ||
| // URL encode the path to handle special characters (spaces, brackets, etc.) | ||
| // Handle both Unix (/) and Windows (\) path separators | ||
| const normalizedPath = filePath.replace(/\\/g, '/'); | ||
| const encodedPath = normalizedPath.startsWith('/') | ||
| ? '/' + normalizedPath.slice(1).split('/').map(encodeURIComponent).join('/') | ||
| : normalizedPath.split('/').map(encodeURIComponent).join('/'); | ||
| let url = `vscode://file${encodedPath}`; | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| if (line !== undefined && line > 0) { | ||
| url += `:${line}`; | ||
| if (column !== undefined && column > 0) { | ||
| url += `:${column}`; | ||
| } | ||
| } | ||
| await shell.openExternal(url); | ||
| return { success: true }; | ||
| } catch (error) { | ||
| return { success: false, error: (error as Error).message }; | ||
| } | ||
| } | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| /** | ||
| * Window IPC handlers | ||
| * | ||
| * Handles window management operations. | ||
| */ | ||
|
|
||
| import { ipcMain } from 'electron'; | ||
| import { IPC_CHANNELS } from './channels'; | ||
| import { MIN_WIDTH_COLLAPSED, MIN_HEIGHT } from '../constants'; | ||
| import { state } from '../state'; | ||
|
|
||
| /** | ||
| * Register window IPC handlers | ||
| */ | ||
| export function registerWindowHandlers(): void { | ||
| // Update minimum width based on sidebar state | ||
| // Now uses a fixed small minimum since horizontal scrolling handles overflow | ||
| ipcMain.handle(IPC_CHANNELS.WINDOW.UPDATE_MIN_WIDTH, (_, _sidebarExpanded: boolean) => { | ||
| if (!state.mainWindow || state.mainWindow.isDestroyed()) return; | ||
|
|
||
| // Always use the smaller minimum width - horizontal scrolling handles any overflow | ||
| state.mainWindow.setMinimumSize(MIN_WIDTH_COLLAPSED, MIN_HEIGHT); | ||
| }); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.