IPC between window and main
The window cannot touch files, the network or the database. It asks main through IPC channels, each a named request with an answer.
The pieces
| Piece | Where | What it is |
|---|---|---|
| Channel names | src/shared/ipc-channels.ts | IPC_CHANNELS: one constant per channel, such as ABOUT_INFO: 'about:info'. |
| A copy of the names | src/preload/index.ts | The same IPC_CHANNELS, written out again: a sandboxed preload cannot import other files. Keep both lists the same. |
| The method | src/preload/index.ts | A method on window.electronAPI that calls ipcRenderer.invoke(channel, …), and its line in the ElectronAPI type. |
| The handler | src/main/ipc.ts or src/main/ipc/*Ipc.ts | ipcMain.handle(channel, handler) inside a register…Ipc(ipcMain) function. |
| The types | src/shared/contracts/ | What goes in and what comes back, shared by all three. |
The guard
src/main/ipc.ts wraps Electron's ipcMain with guardedIpcMain (src/main/ipc/guard.ts) before any handler is
registered:
handlechecks that the sender is the app's own page (the builtindex.htmlor the app scheme; the Vite server in development). Any other frame, an iframe, or a page that navigated away gets the error "The request did not come from the app window." and the handler never runs.on,once,addListener,prependListenerandhandleOncethrow: they would carry no sender check. Usehandlefor every channel.
Inside the handler, treat every argument as untrusted: parse it with a zod schema and return a fixed error for anything that does not fit.
Adding a channel
- Add the name to
IPC_CHANNELSinsrc/shared/ipc-channels.ts, and the same line to the copy insrc/preload/index.ts. - Add the types to
src/shared/contracts/if the call carries more than a string. - In
src/preload/index.ts, add a method to theelectronAPIobject and its line to theElectronAPItype. - In main, add
ipcMain.handle(IPC_CHANNELS.YOUR_CHANNEL, handler)to the rightregister…Ipcfunction. Parse the arguments; never return a secret. - Call it from a service in
src/renderer/services/, not from a component. - Test the handler in
tests/unit(call the function the handler uses), and the path through the window in an e2e test if a person can see the result.
Example in the code: the Help reader's help:tree, help:page and help:search in src/main/ipc/aboutIpc.ts.
Streams
The assistant's answer comes back in pieces: the window sends a request on llm:stream-chat, and main sends chunks
back to that window until the turn ends (src/main/ipc/agentIpc.ts, src/renderer/services/llm/chatService.ts).