Use napi_create_threadsafe_function for cross-thread callbacks to JavaScript
When calling JavaScript callbacks from native C++ threads, use napi_create_threadsafe_function to create a threadsafe function that marshals the call from the native thread to the Node.js event loop. This allows safe asynchronous communication between native code running on different threads and JavaScript.
Use Napi::ObjectWrap to wrap C++ classes for JavaScript
Use Napi::ObjectWrap<ClassName> to create a wrapper class that exposes C++ functionality to JavaScript. Implement DefineClass to register methods, set up a constructor, and export to JavaScript via exports.Set().
napi_release_threadsafe_function with napi_tsfn_release on shutdown
Call napi_release_threadsafe_function(tsfn_, napi_tsfn_release) in the destructor to properly clean up the threadsafe function. Use napi_tsfn_abort only when aborting without waiting for pending work.
napi_tsfn_blocking mode waits for threadsafe function to become available
When calling napi_call_threadsafe_function, pass napi_tsfn_blocking as the mode to wait for the function to become available if the queue is full. This is safer than non-blocking modes for critical callbacks.
Persistent references keep objects alive across event loop cycles
Use Napi::ObjectReference with Napi::Persistent to create persistent references to JavaScript objects and functions that need to survive beyond the current native function call. These must be manually reset when no longer needed.
Use EventEmitter pattern in JavaScript wrapper for native addon callbacks
Wrap native addon callback events in a JavaScript EventEmitter class. Listen to native addon events and re-emit them as EventEmitter events. This provides a familiar Node.js interface for consuming native callbacks.
Check process.platform before loading platform-specific native addons
In the JavaScript wrapper for platform-specific native addons, check process.platform to ensure the addon is only loaded on supported platforms. Throw an error if loaded on an unsupported platform.
Thread-safe function pattern for Swift callbacks to JavaScript
Use napi_create_threadsafe_function to safely bridge callbacks from Swift running on native threads to the Node.js event loop. The pattern involves: creating a CallbackData struct to pass event type and payload between threads, using a lambda-generated Objective-C block as the Swift callback, and calling napi_call_threadsafe_function with napi_tsfn_blocking mode. In the callback handler, retrieve the registered JavaScript function from a stored callbacks object and call it with the payload. Always release the threadsafe function in the destructor using napi_release_threadsafe_function with napi_tsfn_release (or napi_tsfn_abort in the destroy method).
ipcRenderer.send method signature and behavior
ipcRenderer.send(channel, ...args) sends an asynchronous message to the main process via channel along with arguments. Arguments are serialized with the Structured Clone Algorithm like window.postMessage, so prototype chains are not included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception. Non-standard JavaScript types such as DOM objects or special Electron objects will throw an exception. DOM objects such as ImageBitmap, File, DOMMatrix cannot be sent over Electron's IPC. The main process handles it by listening for channel with the ipcMain module.
ipcRenderer.send vs ipcRenderer.invoke
If you want to receive a single response from the main process, like the result of a method call, consider using ipcRenderer.invoke instead of ipcRenderer.send.
ipcRenderer.invoke method signature and return value
ipcRenderer.invoke(channel, ...args) sends a message to the main process via channel and expects a result asynchronously. Returns Promise<any> that resolves with the response from the main process. Arguments are serialized with the Structured Clone Algorithm like window.postMessage, so prototype chains are not included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception.
ipcRenderer.invoke requires ipcMain.handle on main process
The main process should listen for the channel in ipcRenderer.invoke using ipcMain.handle().
ipcRenderer.invoke example
// Renderer process
ipcRenderer.invoke('some-name', someArgument).then((result) => {
// ...
})
// Main process
ipcMain.handle('some-name', async (event, someArgument) => {
const result = await doSomeWork(someArgument)
return result
})
This example shows how to use ipcRenderer.invoke to send a message from the renderer and receive a response from the main process.
ipcRenderer.invoke with MessagePort
If you need to transfer a MessagePort to the main process, use ipcRenderer.postMessage instead of ipcRenderer.invoke.
ipcRenderer.invoke non-standard type restrictions
Non-standard JavaScript types such as DOM objects or special Electron objects cannot be sent via ipcRenderer.invoke and will throw an exception. DOM objects such as ImageBitmap, File, DOMMatrix cannot be sent over Electron's IPC.
ipcRenderer.invoke error handling
If the handler in the main process throws an error, the promise returned by invoke will reject. However, the Error object in the renderer process will not be the same as the one thrown in the main process.
ipcRenderer.sendSync method signature and return value
ipcRenderer.sendSync(channel, ...args) sends a message to the main process via channel and expects a result synchronously. Returns any - the value sent back by the ipcMain handler. Arguments are serialized with the Structured Clone Algorithm like window.postMessage, so prototype chains are not included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception.
ipcRenderer.sendSync main process handling
The main process handles ipcRenderer.sendSync by listening for channel with ipcMain module and replying by setting event.returnValue.
Avoid synchronous IPC - sendSync blocks renderer process
Sending a synchronous message with ipcRenderer.sendSync will block the whole renderer process until the reply is received. Use this method only as a last resort. It is much better to use the asynchronous version, invoke().
ipcRenderer.postMessage method signature
ipcRenderer.postMessage(channel, message, [transfer]) sends a message to the main process, optionally transferring ownership of zero or more MessagePort objects. Channel is a string, message is any type, and transfer is an optional array of MessagePort objects.
ipcRenderer.postMessage MessagePort handling
The transferred MessagePort objects will be available in the main process as MessagePortMain objects by accessing the ports property of the emitted event.
ipcRenderer.postMessage example
// Renderer process
const { port1, port2 } = new MessageChannel()
ipcRenderer.postMessage('port', { message: 'hello' }, [port1])
// Main process
ipcMain.on('port', (e, msg) => {
const [port] = e.ports
// ...
})
This example shows how to transfer a MessagePort from the renderer to the main process using ipcRenderer.postMessage.
IpcRendererEvent object structure
IpcRendererEvent extends Event and has two properties: sender (an IpcRenderer instance that emitted the event originally) and ports (a list of MessagePorts that were transferred with the message).
IpcRendererEvent sender property
The sender property of IpcRendererEvent is an IpcRenderer instance that emitted the event originally.
IpcRendererEvent ports property
The ports property of IpcRendererEvent is a list of MessagePorts that were transferred with the message.