src/preload
The preload is the only code that runs with both ipcRenderer and renderer access. It is part of the
renderer-to-main trust boundary. Main-process rules apply here.
-
index.tsmaps eachOpenBotDesktopApimethod to one IPC channel and exposes the result withcontextBridge.exposeInMainWorld. Add no business rules here. -
Decode each value that main sends before the renderer can read it. Put decoders in the
*-decoding.tsfile for their domain, not inindex.ts. Keep the preloadFromMaindecoders apart from the mainFromHostdecoders. They protect different boundaries. -
Build a group whose methods pass straight through with
bridgeGroup(IPC_ENDPOINTS.group, decoders). Pass the group directly, with no explicit type arguments, so the decoder map is checked against every endpoint. A request's decoder decodes its result; an event's decoder decodes its payload, ordropInvalid(decode)drops a value that answers null (for a deep-link id). A group is bridged whole or written by hand; do not add a direct call for one endpoint of a bridged group. A bridged method forwards only its first argument, so main still sees at most one payload. It also forwards an argument that a no-payload method gets. A scoped endpoint (scopedRequest,scopedQuery) is sent asAgentIpcRequest: the server is the argument after the payload, or the selected server, read at call time. Do not pass a bridged method by reference as an event handler, such asonClick={api.update.check}: Electron cannot copy the DOM event, and the call rejects. -
Only
attachmentImportsand the untypedbrowserInputare written by hand. Invoke throughinvokeAgentForServer, oripcRenderer.invokefor the untyped endpoint. Subscribe throughlistenand decode the raw value in the handler. Do not callipcRenderer.ondirectly. -
Invoke only request endpoints from
IPC_ENDPOINTS, and subscribe only to event endpoints.src/main/ipc-channel-coverage.test.tsreads these sources and fails on an unused or extra channel. -
Do not expose
ipcRenderer, Electron objects, or Node APIs to the page.team-webrtc.tsgives the hidden WebRTC window only oneMessagePort. -
Add a method in the same change as its channel, handler, and preview mock. See IPC binding rules.
-
Keep the sandbox and context isolation. The browser tabs' preload (
browser-page.ts) has no IPC and exposes no world: a tab page gets nowindow.openbot. It only runs one function in the page world, andscripts/verify-preload-bundle.tschecks both. -
scripts/verify-preload-bundle.tsruns the built bundle and compareswindow.openbotwithIPC_ENDPOINTSat the places thatIPC_GROUP_PATHSgives. A method written by hand needs an entry inHAND_WRITTEN_METHODS. CI runs it after the build incheck:desktop:static.
Run one test file: bun run test:desktop -- src/preload/<name>.test.ts. For a channel change, also
run bun run test:desktop -- src/main/ipc-channel-coverage.test.ts.