MCRM
MCRM

Reputation: 11

Electron secondary window not receiving IPC messages

In my typescript which is attached to the HTML being run by my app, I am creating a new window for my settings. From what I can tell, the preloaded script is also being loaded onto the new window once it's opened, but the window isn't receiving IPC messages from the main script.

Here is the preloaded script:

const { contextBridge, ipcRenderer } = require("electron");

console.log("preloaded!");

contextBridge.exposeInMainWorld("api", {
  send: (channel, data) => {
    let validChannels = ["toMain", "select-dirs", "toSettings", "fromSettings"];
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, data);
    }
  },
  receive: (channel, func) => {
    let validChannels = ["fromMain", "toSettings", "fromSettings"];
    if (validChannels.includes(channel)) {
      ipcRenderer.on(channel, (event, ...args) => func(...args));
    }
  },
});

And here is the typescript file that I've attached to the second window's HTML.

(<any>window).api.receive("toSettings", (data: any) => {
  console.log(data);
})

var closeButton: HTMLButtonElement;

var settings = "";
var settignsDir = "";

document.onreadystatechange = () => {
  if (document.readyState == "interactive") {
    (<any>window).api.send("fromSettings", "ready")
    closeButton = <HTMLButtonElement>document.getElementById("closeButton");

    closeButton.addEventListener("click", () => {
      (<any>window).api.send("toMain", "refresh");
      self.close();
    });
  }
};

I'm using the same preloaded script for my renderer and using the same receiving code, it's working just fine. And from the second window's typescript, I can properly send IPC messages to the main process. But I can't receive any messages on the second window. I'm thinking I need to re-preload the file directly to the second window via the features array in window.open(). Oh and here's the code that is opening the settings window.

window.open(
          "./html/settings.html",
          "_blank",
          "top=200,left=600,frame=false,nodeIntegration=no"
        );

According to the Electron documentation, you can also include a preload in the third-string but I can't figure out how as the documentation fails to have an example and I can't find one anywhere.

In response to Kdau:

Here is the requested code:

(<any>window).api.receive("fromSettings", (data: any) => {
  (<any>window).api.send("toSettings", "WHAT!");
})

I was mainly using it to see if the settings or child window as you called it was receiving the message. If you could clarify what you mean by address to the child window because I thought that the preload script should be automatically parsing, "ok so this channel needs to go to this receiver".

I would like to point out that in the code snippet that you included returns this error: Argument of type '({ url }: HandlerDetails) => { frame: boolean; webPreferences: { nodeIntegration: boolean; preload: string; }; } | undefined' is not assignable to parameter of type '(details: HandlerDetails) => { action: "deny"; } | { action: "allow"; overrideBrowserWindowOptions?: BrowserWindowConstructorOptions | undefined; }'. Type '{ frame: boolean; webPreferences: { nodeIntegration: boolean; preload: string; }; } | undefined' is not assignable to type '{ action: "deny"; } | { action: "allow"; overrideBrowserWindowOptions?: BrowserWindowConstructorOptions | undefined; }'. Type 'undefined' is not assignable to type '{ action: "deny"; } | { action: "allow"; overrideBrowserWindowOptions?: BrowserWindowConstructorOptions | undefined; }'.

I don't know what to do with this.

Upvotes: 0

Views: 553

Answers (1)

kdau
kdau

Reputation: 2099

While you can pass a preload in the third argument, the more flexible and reliable approach is to use setWindowOpenHandler. So, just after constructing your main window (assuming the variable is mainWindow):

mainWindow.webContents.setWindowOpenHandler(({ url }) => {
  if (url === './html/settings.html') {
    return {
      action: 'allow',
      overrideBrowserWindowOptions: {
        frame: false,
        webPreferences: {
          nodeIntegration: false,
          preload: 'my-child-window-preload-script.js'
        }
      }
    }
  }
})

However, most (all??) options are inherited from the parent window anyway, and I would imagine that your preload has been inherited and that is why you can send messages from the child window.

As for not receiving messages in the child window, the messages aren't being addressed to that window's webContents by your code. Sending to the main window's webContents won't work. Unfortunately, the IPC in Electron isn't a broadcast system where every process receives every message filtered only by channel. The channel feature sits on top of process-based targeting. Each render process can only send messages to the main process, and the main process must address one specific render process at a time when sending.

So, to communicate between two render processes, use the main process as a relay. In the main process, to get the webContents of a window that was opened through window.open, you can listen for a message from that process (such as one you send as soon as it loads) and/or call webContents.getAllWebContents() and iterate through to find the right one.

Upvotes: 0

Related Questions