Reputation: 344
In my App I have two windows: mainWindow
and actionWindow
. On my mainWindow I use the ipcRenderer.on
listener to receive as message from the main process when the actionWindow is closed. The message however doesn't come through.
The mainWindow
is used to control actions that take place on the actionWindow
(e.g. navigate to an URL, remotely close the window, ...). I want to give the user the power to move and close the actionWindow
manually as well, which is why its title bar is visible and usable.
I expose ipcRenderer.invoke
for two-way communication and ipcRenderer.on
to the mainWindow's renderer via contextBridge in a preload file.
This is what the code looks like (based on vite-electron-builder template)
main process
const mainWindow = new BrowserWindow({
show: false, // Use 'ready-to-show' event to show window
webPreferences: {
nativeWindowOpen: true,
webviewTag: false,
preload: join(__dirname, "../../preload/dist/index.cjs"),
},
});
const actionWindow = new BrowserWindow({
// some props
})
actionWindow.on("close", () => {
console.log("window closed")
mainWindow.webContents.send("closed", { message: "window closed" });
});
preload
contextBridge.exposeInMainWorld("ipcRenderer", {
invoke: ipcRenderer.invoke,
on: ipcRenderer.on,
});
renderer (mainWindow)
window.ipcRenderer.on("closed", () => {
console.log("message received")
// do something
});
I know for a fact that
invoke
works and the actions it fires on the main process are executed on the actionWindow as supposed + the response also comes back to the renderer.close
listener on the actionWindow works since I can see the log window closed
in my consolemessage received
doesn't appear in my dev tools consoleTo me this means that either
mainWindow.webContents.send
doesn't work -> the message is never sentwindow.ipcRenderer.on
doesn't work -> the message never reaches its destinationSo either my code is buggy or Electron has recently put some restrictions on one of these methods which I'm not aware of.
Any ideas? If there is a smarter way to do this than IPC I'm also open to that.
Upvotes: 6
Views: 1483
Reputation: 344
Ok after hours of searching, trying and suffering I (almost accidentaly) found a solution to my problem. It really seems to be the case that electron simply doesn't do anything anymore when you call the on
method from your renderer.
Studying the docs about contextBridge again I saw that the way I exposed invoke
and on
to the renderer, was considered bad code. The safer way to do this is expose a function for EVERY ipc channel you want to use. In my case using TypeScript it looks like this:
preload
contextBridge.exposeInMainWorld("ipcRenderer", {
invokeOpen: async (optionsString: string) => {
await ipcRenderer.invoke("open", optionsString);
},
onClose: (callback: () => void) => {
ipcRenderer.on("closed", callback);
},
removeOnClose: (callback: () => void) => {
ipcRenderer.removeListener("closed", callback);
},
});
renderer(mainWindow)
window.ipcRenderer.onClose(() => {
// do sth
});
window.ipcRenderer.invokeOpen(JSON.stringify(someData)).then(() => {
// do sth when response came back
});
NOTE: To prevent memory leaks by creating listeners on every render of the mainWindow you also have to use a cleanup function which is provided with removeOnClose
(see preload). How to use this function differs depending on the frontend framework. Using React it looks like this:
const doSth= () => {
console.log("doing something")
...
};
useEffect(() => {
window.ipcRenderer.onClose(doSth);
return () => {
window.ipcRenderer.removeOnClose(doSth);
};
}, []);
Not only is this a safer solution, it actually suddenly works :O Using the cleanup function we also take care of leaks.
Upvotes: 4