Electron & IPC
Main Process Initialization
The Electron main process (electron/main.ts) initializes in this order:
- Install log interceptor (500-line ring buffer for diagnostics)
- Set platform-specific flags (macOS ScreenCaptureKit, Linux PulseAudio loopback)
- Create caption window controller
- Register trusted windows (main + caption)
- Create desktop source and local runtime controllers
- Request single-instance lock (quit if another instance is running)
- On
app.whenReady():- Start HTTP server on port 23456 (Volcengine proxy)
- Attach API server to the HTTP server
- Create main window
- Create system tray
- Register global shortcuts
- Set up auto-updater (if supported)
- Register all IPC handlers
IPC Modules
| Module | Purpose | Trust Required |
|---|---|---|
appIpc.ts | Window controls, auto-launch, file picker | Partial (sensitive ops only) |
captionIpc.ts | Caption window text, style, draggable, position | No |
safeStorageIpc.ts | Encrypt/decrypt secrets via OS keychain | Yes |
updaterIpc.ts | Check, download, install updates | Yes (download/install) |
diagnosticsIpc.ts | Export redacted diagnostics JSON | Yes |
apiIpc.ts | Bridge: Main ↔ Renderer for API data | No |
localRuntimeIpc.ts | whisper.cpp binary/model management | Yes |
Trusted Window Verification
ipcSecurity.ts maintains a list of trusted BrowserWindow providers. assertTrustedSender(event, channel) checks that event.sender.id matches one of the registered windows' webContents.id.
Only the main window and caption window are registered as trusted.
IPC Patterns
Main → Renderer (Request/Response)
Used by the API bridge (apiIpc.ts):
Main process (apiServer receives HTTP request)
→ webContents.send('api-get-sessions', { limit, offset })
→ Renderer (useApiIpcResponder) processes request
→ ipcRenderer.send('api-respond-sessions', data)
→ Main process resolves pending Promise
→ HTTP response sent to clientThe pattern uses a 5-second timeout with fallback empty data to avoid hanging.
Renderer → Main (One-Way)
Session lifecycle notifications:
Renderer (sessionStore detects session start/end)
→ ipcRenderer.send('api-notify-session-start', { sessionId })
→ Main process (apiIpc.ts) receives notification
→ apiBroadcast broadcasts to WebSocket clientsRenderer → Main (Invoke)
Standard Electron invoke pattern for most operations:
Renderer: await window.electronAPI.getAppVersion()
→ ipcMain.handle('get-app-version', () => app.getVersion())Preload Bridge
electron/preload.ts exposes a single electronAPI object via contextBridge.exposeInMainWorld. This is the only way the Renderer communicates with the Main process.
The full API surface is defined in shared/electronApi.ts as the ElectronAPI TypeScript interface, ensuring type safety across the process boundary.
Window Configuration
Main Window
- 1200×800 default, 800×600 minimum
frame: false(custom title bar)contextIsolation: true,nodeIntegration: falsebackgroundThrottling: false(keeps recording active when minimized)- CSP injected via
session.webRequest.onHeadersReceived - Navigation restricted to localhost:5173, file:, and devtools: URLs
Caption Window
- Separate
BrowserWindowfor floating subtitle overlay transparent: true(Linux uses semi-transparent black)alwaysOnTop: true,skipTaskbar: true- Click-through when not interacting (
setIgnoreMouseEventswithforward: true) - Mouse position polling (100ms) to detect hover for temporary interaction
focusabletoggled based on interaction state