Closed Seanmclem closed 2 years ago
I had a similar problem, the solution in electron in general is to never use ipc in the client app at all, but you would rather make a bridge.
To make a brief history, the core idea in general is to exclude IPC entirely for safety reasons. In my real-case app scenario I did this:
file main.ts
: you need to reference preload.ts
in the browserWindow object, like this (in my case I had webpack, I need to check how to do that with vite, but I don't think it's a complex task, you can see an example in this repository):
const mainWindow = new BrowserWindow({
height: 600,
width: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY
},
autoHideMenuBar: true
});
preload.ts
:
import { IPC_CHANNELS } from "./ipc";
import {
contextBridge,
ipcRenderer
} from "electron";
console.log('-loading preload.js-');
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
process.once('loaded', () => {
contextBridge.exposeInMainWorld(
"api", {
send: (channel: IPC_CHANNELS, data: any) => {
console.log(`[preload.ts] -> sending command ${channel}, data=${data}`)
// whitelist channels
let validChannels = Object.values(IPC_CHANNELS);
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel: IPC_CHANNELS, func: any) => {
let validChannels = Object.values(IPC_CHANNELS);
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => {
console.log(`[preload.ts] <- sending result for ${channel}, args=${args}`)
func(...args)
});
}
}
}
);
});
file ipc/index.ts
export enum IPC_CHANNELS {
PING = 'PING'
}
Example in the client environment (renderer with react in this case):
useEffect(() => {
window.api.receive(channel, (args) => {
console.log('[useIpcChannel]:: setting message to', args);
setMessage(args)
});
}, [channel]);
Note that window.api
is what is exposed in preload.
The same as above can be applied with window.api.send
Example to handle a command from the server:
ipcMain.on(
IPC_CHANNELS.PING, (evt, args) => {
console.log('-> got request:', args);
win.webContents.send(IPC_CHANNELS.PING, args);
}
);
The core idea behind this is to have a shared file that has the IPC CHANNELS defined (through an enum in my case): in this way you can safely normalize the communication protocol between the main and the renderer process.
Hope this helps you to get an idea of how you can structure that.
Thanks @briosheje i might try that. If you know, can you elaborate on the reason why this is insecure? I only ask because the app I'm working on relies on elevated levels of security anyway. So just wondering if I really need to take these steps. What extra security are we getting? Vulnerabilities we are avoiding?
Thanks @briosheje i might try that. If you know, can you elaborate on the reason why this is insecure? I only ask because the app I'm working on relies on elevated levels of security anyway. So just wondering if I really need to take these steps. What extra security are we getting? Vulnerabilities we are avoiding?
Electron relies on two processes: main and renderer.
The main process is the node
(you can imagine it as the server) process, while the renderer process is the chromium process.
The communication between the main and the renderer usually happens through the ipc
channel and one of the main concerns was that the renderer process could easily communicate with the main through ipcRenderer
without any sort of "control", which is the reason why ipcRenderer
is not exposed to the renderer process anymore.
In fact, as you can see, in this repo there is the preload.ts
which is already warning you about this: https://github.com/maxstue/vite-reactts-electron-starter/blob/ea9638fb1592685e00cc7bcf58f70b15a60c495f/electron/preload.ts#L42
The "secure" way to implement this is to expose an API to the renderer from the preload
where only what is expected to be received from the main process is effectively send. Further validation is of course required from the main
process but still, in this way you cannot directly use ipcRenderer
(and eventually other main libraries) in the renderer process.
Thanks for explaning and helping out :)
I'm using a new version at the moment
my
App.tsx
looks like thisIf i run
yarn dev
on this, it doesn't render anything, but starts up. No terminal or dev-tools console errors. If I comment outipcRenderer.send
line, then it renders. I've made sure I have a corresponding event in Main, with no difference. What am I missing? This should work, right?...Edit1
Actually, there are a couple errors:
...Edit 2
Seems I can get it to work by changing the import in
App.tsx
toconst { ipcRenderer } = window.require('electron')
and
windowOptions.webPreferences.contextIsolation
tofalse
, in index.tsBut, I have to imagine something about both of these changes is maybe not ideal? I would be happy for a better or alternative pattern. ... unless this is fine? I don't know electron extremely well. Please advise