How is webContents.send detected by ipcRenderer.on which is inside preload.js?

1.3k Views Asked by At

I am a totally new user to stack overflow and this is my first ever question.

I have been researching ways to make Electron applications more secure and came across the following discussion about contextBridge and preload.js. With contextIsolation = true, is it possible to use ipcRenderer?

I have copied the code of interest below. My questions are with regards to when the Main process sends a message back to the Renderer process.

  1. The Main process sends a message back to the Renderer via win.webContents.send("fromMain", responseObj);

  2. In the Renderer process there is no active listener (no ipcRenderer.on because that is in the preload.js). We only have window.api.receive() but this is not a listener itself?

  3. So, how does window.api.receive() know when to run?

EDIT/RESOLVED: Please see my own answer posted below, thanks.

main.js

ipcMain.on("toMain", (event, args) => {
  fs.readFile("path/to/file", (error, data) => {
    // Do something with file contents

    // Send result back to renderer process
    win.webContents.send("fromMain", responseObj);
  });
});

preload.js

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

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "api", {
        send: (channel, data) => {
            // whitelist channels
            let validChannels = ["toMain"];
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, data);
            }
        },
        receive: (channel, func) => {
            let validChannels = ["fromMain"];
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) => func(...args));
            }
        }
    }
);

renderer.js

window.api.receive("fromMain", (data) => {
            console.log(`Received ${data} from main process`);
        });
window.api.send("toMain", "some data");
1

There are 1 best solutions below

0
Blobby123 On

My own answer

After much head scratching, I realised the answer to my question was perhaps quite trivial.

Here is how I believe the Main process is able to send messages back to the Renderer process (with reference to the code in the original question):

  1. The Main process creates a new browser window and loads the preload.js script. The send and receive APIs are now ready to be used by the Renderer process.

  2. On starting the Renderer process, the renderer.js script simply runs for the first time and executes window.api.receive(), regardless of whether the Main process has sent a message or not. Consequently, the receive function defined in the preload.js is executed, which activates the ipcRenderer.on listener.

  3. In short, the ipcRenderer.on is always listening from when the renderer.js was first loaded.

In conclusion, I simply didn't follow the flow of code execution properly when looking at the scripts.