IPC Communication (Inter-Process Communication)
Electron applications consist of a main process and one or more renderer processes. These processes run in isolation and cannot directly access each other’s memory. Inter-Process Communication (IPC) is the mechanism that allows them to communicate.
Trufos uses Electron’s IPC modules (ipcMain and ipcRenderer) along with a preload script for secure and structured communication.
Preload Script
- File: src/main/preload.ts
- Purpose: The preload script runs in a privileged context that has access to both the renderer’s windowobject and Node.js APIs. It acts as a bridge, selectively exposing main process functionalities to the renderer process in a secure way usingcontextBridge.exposeInMainWorld.
- Implementation: // src/main/preload.ts import { contextBridge, ipcRenderer } from 'electron'; const electronHandler = { ipcRenderer: { send: ipcRenderer.send.bind(ipcRenderer), // ... other ipcRenderer methods like on, once, invoke, removeListener }, }; contextBridge.exposeInMainWorld('electron', electronHandler); export type ElectronHandler = typeof electronHandler;This exposes an electronobject on the globalwindowin the renderer process (window.electron).
- Type Definitions: src/renderer/preload.d.tsprovides TypeScript definitions forwindow.electronfor type safety in the renderer code.
Communication Flow: Renderer to Main (Request/Response)
For most operations initiated by the UI (renderer) that require main process capabilities (e.g., file system access, network requests), a request/response pattern using ipcRenderer.invoke and ipcMain.handle is used.
- Renderer (RendererEventService):- src/renderer/services/event/renderer-event-service.ts
- Provides an abstraction layer for sending requests to the main process.
- Uses a helper createEventMethodwhich wrapswindow.electron.ipcRenderer.invoke(channel, ...args).
- This invokecall sends a message to the specifiedchanneland returns a Promise that resolves with the main process’s response.
- If the main process returns an Errorobject,RendererEventServicethrows it as aMainProcessError.
 
- Main (MainEventService):- src/main/event/main-event-service.ts
- Initializes itself by iterating over its methods and registering handlers for each using ipcMain.handle(channel, handler). Thechannelname is derived from the method name.
- The handlerfunction is an async function that typically calls another service in the main process (e.g.,PersistenceService,HttpService).
- A wrapWithErrorHandlerutility ensures that any exceptions thrown by the actual service methods are caught and returned asErrorobjects to the renderer, whichRendererEventServicethen re-throws.
 
Example: Fetching app version
- Renderer (RendererEventService.getAppVersion()):// Simplified async getAppVersion() { const result = await window.electron.ipcRenderer.invoke('getAppVersion'); if (result instanceof Error) throw new MainProcessError(result.message); return result; }
- Main (MainEventService.getAppVersion()):// Simplified, registered via ipcMain.handle('getAppVersion', ...) async getAppVersion() { return app.getVersion(); // electron.app }
Communication Flow: Main to Renderer (Events)
For events initiated by the main process or for continuous data streams, webContents.send (main) and ipcRenderer.on (renderer) are used.
- Main Process: - Can send messages to a specific renderer process (window) using mainWindow.webContents.send(channel, ...args).
- Example: In src/main/main.ts, before closing the window:mainWindow?.webContents.send('before-close');
 
- Can send messages to a specific renderer process (window) using 
- Renderer Process (RendererEventService):- Listens for these messages using window.electron.ipcRenderer.on(channel, listener).
- Example: The RendererEventServiceitself can act as an event emitter or forward these events.// In RendererEventService on(event: 'before-close', listener: () => void) { window.electron.ipcRenderer.on(event, listener); return this; }The collectionStore.tslistens for the'before-close'event to save any pending request changes.
 
- Listens for these messages using 
Streaming Data (IPC Push Stream)
For streaming large data, like request/response bodies, Trufos uses a custom IPC stream implementation.
- Main Process (src/main/event/stream-events.ts):- Handles 'stream-open'IPC call: Creates afs.ReadStreamfor a file or fromPersistenceService.loadTextBodyOfRequest.
- Assigns a unique ID to the stream.
- Listens to data,end, anderrorevents on the Node.js stream and sends corresponding IPC messages ('stream-data','stream-end','stream-error') to the renderer, tagged with the stream ID.
- Handles 'stream-close'IPC call to close the server-side stream.
 
- Handles 
- Renderer Process (src/renderer/lib/ipc-stream.ts):- IpcPushStreamclass:- open(filePathOrRequest): Sends- 'stream-open'to main and gets a stream ID.
- Listens for 'stream-data','stream-end','stream-error'IPC messages from main, filtered by stream ID, and emits corresponding events on theIpcPushStreaminstance.
- close(): Sends- 'stream-close'to main.
- collect(stream): Utility to aggregate all data from a stream into a single string.
 
 
This setup allows efficient streaming of potentially large data from main to renderer without overwhelming the IPC channel with single large messages.
Channels Used
The primary channels are implicitly defined by the method names in IEventService (src/shim/event-service.ts) and used by MainEventService and RendererEventService. Additional channels for streaming:
- stream-open(renderer to main, invoke)
- stream-close(renderer to main, on)
- stream-data(main to renderer)
- stream-end(main to renderer)
- stream-error(main to renderer) Logging channel:
- log(renderer to main, for forwarding renderer logs) Application lifecycle:
- before-close(main to renderer)
- ready-to-close(renderer to main)