2

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");
Blobby123
  • 51
  • 6
  • If I'm not mistaken, context bridge is essentially attaching a property that has the send and recieve methods onto the window api, send calls `ipcRenderer.send` and recieve calls `ipcRenderer.on`. The whole point of this is to make electron more secure since setting `contextIsolation: false` makes the electron api accessible from the renderer process meaning if malicous code were to get into your program, then the code can do whatever electron can to that user's machine, which won't end well. – Ameer Jul 28 '21 at 23:57
  • @Ameer Thanks Ameer. I understand the purpose of contextBridge and what it is doing in the above code (i.e. by exposing only the node APIs we need). I had a deeper think about it last night and I think I've answered my own question :D I will edit the original post to include my answer for anyone in the future. Thanks – Blobby123 Jul 29 '21 at 10:25

1 Answers1

3

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.

Blobby123
  • 51
  • 6