Skip to main content

Two-Way Main-Renderer IPC

note

See the Electron-Documentation for a more indepth explanation of the reasons for, and how the IPC works between the Main process, the Renderer process, and why the preload step is required.

To Share Or Not To Share

Unlike typical Fable.Remoting, the entire stack is compiled to JavaScript, so there is no requirement to define your API types in a Shared project. You can choose to do this or not.

Example

The Two-Way Main-Renderer IPC channel allows the client/renderer process to send a message to the main process, and to then receive a response asynchronously.

This is required due to the Renderer processes not having access to the Node.js API, nor the full Electron API.

A full example is provided below:

open Fable.Core.JS // Promise type

type ExampleRouting = {
SayHelloWorld: string -> Promise<Result<string, unit>>
}
warning

The API for Fable.Electron.Remoting is highly likely to change according to user preference at the beginning.

IpcMainEvent

If you are familiar with Electron, or have checked the documentation, you'll notice that there is no inherent inclusion of the IpcMainEvent argument which is usually received as the first parameter for the Main process.

This is intentional, naturally, as including it destroys the type safety of our function signatures in F#, and renders us unable to use one API type as the source of truth.

Under the hood, we use an intermediary lambda which discards the first argument on the Receiver (Main process).

important

If you want to keep and use the IpcMainEvent, just include it as the FIRST parameter of your function signature. Pass null for this value (or Unchecked.defaultof<_>, or undefined) on the renderer side.

Shared.fs
type ExampleRouting = {
SayHelloWorld: IpcMainEvent -> string -> Promise<Result<string, unit>>
}
Renderer.fs
let api =
Remoting.init
|> Remoting.buildClient<Shared.ExampleRouting>

(api.SayHelloWorld JS.undefined "hello").``then``(function
| Ok v -> console.log v
| Error _ -> console.log "Didn't say hello back :(")
|> ignore

The preload script will ignore the first argument it receives from the Renderer if it is of the type IpcMainEvent, and you will have the correct types on the receivers (Main) end.

Main.fs

let api: Shared.ExampleRouting = {
SayHelloWorld = fun (event: IpcMainEvent) (text: string) -> promise {
if text = "hello" then
return Ok <| text + " world!"
else
return Error()
}
}