'With contextIsolation = true, is it possible to use ipcRenderer?
Here's my setup:
Step 1. Create a preload.js file with the code:
window.ipcRenderer = require('electron').ipcRenderer;
Step 2. Preload this file in your main.js via webPreferences:
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
preload: __dirname + '/preload.js'
}
});
Step 3. In a renderer:
console.log(window.ipcRenderer); // Works!
Now following Electron's security guide, I wish to turn contextIsolation=true: https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content
Step 2bis.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
preload: __dirname + '/preload.js'
}
});
Step 3bis. In a renderer:
console.log(window.ipcRenderer); // undefined
Question: can I use ipcRenderer when contextIsolation=true?
Solution 1:[1]
2022 edit
For those that would like additional context of understanding the IPC renderer, I've published a post that explains this concept, as well as related security concerns that revolve around using the IPC renderer.
new answer
You can follow the setup outlined here. This setup is being used in secure-electron-template Essentially, this is what you can do:
main.js
const {
app,
BrowserWindow,
ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "preload.js") // use a preload script
}
});
// Load app
win.loadFile(path.join(__dirname, "dist/index.html"));
// rest of code..
}
app.on("ready", createWindow);
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));
}
}
}
);
index.html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<script>
window.api.receive("fromMain", (data) => {
console.log(`Received ${data} from main process`);
});
window.api.send("toMain", "some data");
</script>
</body>
</html>
original
You are still able to use ipcRenderer in a renderer process with contextIsolation set to true. The contextBridge is what you want to use, although there is a current bug that is preventing from you calling ipcRenderer.on in a renderer process; all you can do is send from the renderer process to the main process.
This code is taken from secure-electron-template a template for Electron built with security in mind. (I am the author)
preload.js
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld(
"electron",
{
ipcRenderer: ipcRenderer
}
);
main.js
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
nodeIntegrationInWorker: false,
nodeIntegrationInSubFrames: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, "preload.js")
}
});
}
some renderer.js file
window.electron.ipcRenderer
Solution 2:[2]
Notice this sentence in the middle of the description of context isolation. It's easy to miss.
The Electron API will only be available in the
preloadscript and not the loaded page.
Looks like the answer is no.
Solution 3:[3]
Please check this. It works for me. I am using CRA & Electron.
preload.js
const { contextBridge, ipcRenderer } = require('electron');
const MESSAGE_TYPES = ipcRenderer.sendSync('GET_MESSAGE_TYPES');
require = null;
class SafeIpcRenderer { ... }
const registerMessages = () => {
const safeIpcRenderer = new SafeIpcRenderer(Object.values(MESSAGE_TYPES));
contextBridge.exposeInMainWorld('ELECTRON', {
sendMessage: safeIpcRenderer.send,
onReceiveMessage: safeIpcRenderer.on,
MESSAGE_TYPES,
});
};
registerMessages();
main.js
const registerPreloadImports = require('./src/client/preloadUtils');
// Required if sandbox flag is set to true. Non-electron modules cannot be directly imported in preload script.
// For more info please check https://www.electronjs.org/docs/api/sandbox-option
registerPreloadImports();
let mainWindow = new BrowserWindow({
// Web preferences for mainWindow
webPreferences: {
preload: path.join(__dirname, 'src/client/preload.js'),
contextIsolation: true, // TODO: Remove it once it's enabled by default (from Electron v12)
disableBlinkFeatures: 'Auxclick',
sandbox: true,
// https://www.electronjs.org/docs/api/sandbox-option#status
enableRemoteModule: false,
},
});
preloadUtils.js
const { ipcMain } = require('electron');
const MESSAGE_TYPES = require('../utils/messageTypes');
const registerPreloadImports = () => {
ipcMain.on(MESSAGE_TYPES.GET_MESSAGE_TYPES, (event, message) => {
event.returnValue = MESSAGE_TYPES;
});
};
module.exports = registerPreloadImports;
messageTypes.js
module.exports = {
DNS_ONLINE_STATUS: 'dns-online-status',
APP_ONLINE_STATUS: 'online-status',
ONLINE_MODEL_SYNC: 'online-model-sync',
APP_ONLINE: 'app-online',
INITIAL_DATA_SYNC: 'INITIAL_DATA_SYNC',
GET_MESSAGE_TYPES: 'GET_MESSAGE_TYPES',
};
actions.js (renderer)
const { MESSAGE_TYPES, sendMessage } = window.ELECTRON || {};
if (!MESSAGE_TYPES) return;
const actions = {
[MESSAGE_TYPES.INITIAL_DATA_SYNC]: (event, initialSync) => {
console.log(MESSAGE_TYPES.INITIAL_DATA_SYNC, initialSync);
},
[MESSAGE_TYPES.ONLINE_MODEL_SYNC]: (event, message) => {
console.log(MESSAGE_TYPES.ONLINE_MODEL_SYNC, message);
},
[MESSAGE_TYPES.APP_ONLINE]: (event, isOnline) => {
console.log(MESSAGE_TYPES.APP_ONLINE, isOnline);
},
};
const registerActions = () => {
const { onReceiveMessage } = window.ELECTRON;
Object.keys(actions).forEach((messageType) => {
onReceiveMessage(messageType, actions[messageType]);
});
};
registerActions();
package.json
{
"dependencies": {
"cross-env": "7.0.2",
"deepmerge": "4.2.2",
"electron-is-dev": "1.2.0",
"electron-log": "4.2.2",
"electron-updater": "4.3.1",
"sequelize-cli": "6.2.0",
"sequelize": "6.3.3",
"sqlite3": "5.0.0",
"umzug": "2.3.0",
"uuid": "8.2.0"
},
"devDependencies": {
"concurrently": "5.2.0",
"electron": "9.1.0",
"electron-builder": "22.7.0",
"spectron": "11.1.0",
"wait-on": "5.1.0",
"xvfb-maybe": "0.2.1"
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | pushkin |
| Solution 3 |
