如何在 Electron 中正确使用 preload.js
我正在尝试在我的 renderer
进程中使用 Node 模块(在本例中为 fs
),如下所示:
I'm trying to use Node modules (in this example, fs
) in my renderer
processes, like this:
// main_window.js
const fs = require('fs')
function action() {
console.log(fs)
}
注意:当我按下 main_window
中的按钮时,会调用 action
函数.
Note: The action
function gets called when I press a button in my main_window
.
但这会报错:
Uncaught ReferenceError: require is not defined
at main_window.js:1
我可以通过将这些行添加到我的 main.js
初始化main_window
时:
I can solve this issue, as suggested by this accepted answer, by adding these lines to my main.js
when initializing the main_window
:
// main.js
main_window = new BrowserWindow({
width: 650,
height: 550,
webPreferences: {
nodeIntegration: true
}
})
但是,根据docs,这不是最好的做法,我应该创建一个 preload.js
文件并在那里加载这些 Node 模块,然后在我的所有 <代码>渲染器进程.像这样:
But, according to the docs, this isn't the best thing to do, and I should instead, create a preload.js
file and load these Node modules there and then use it in all of my renderer
processes. Like this:
main.js
:
main_window = new BrowserWindow({
width: 650,
height: 550,
webPreferences: {
preload: path.join(app.getAppPath(), 'preload.js')
}
})
preload.js
:
const fs = require('fs')
window.test = function() {
console.log(fs)
}
main_window.js
:
function action() {
window.test()
}
而且它有效!
现在我的问题是,我应该在 preload.js
中编写我的 renderer
进程的大部分代码是否违反直觉(因为仅在 preload.js
我可以访问 Node 模块)然后只调用每个 renderer.js
文件中的函数(例如这里的 main_window.js
)?我在这里不明白什么?
Now my question is, isn't it counter-intuitive that I should write most of the code of my renderer
processes in preload.js
(Because only in preload.js
I have access to Node modules) and then merely call the functions in each renderer.js
file (for example here, main_window.js
)? What am I not understanding here?
推荐答案
编辑
正如另一位用户所问,让我在下面解释我的答案.
Edit
As another user asked, let me explain my answer below.
在 Electron 中使用 preload.js
的正确方法是在您的应用可能需要 require
的任何模块周围公开列入白名单的包装器.
The proper way to use the preload.js
in Electron is to expose whitelisted wrappers around any module your app may need to require
.
安全方面,在 preload.js
中暴露 require
或通过 require
调用检索到的任何内容都是危险的(请参阅 我在这里的评论了解更多原因).如果您的应用加载远程内容(很多人都会这样做),则尤其如此.
Security-wise, it's dangerous to expose require
, or anything you retrieve through the require
call in your preload.js
(see my comment here for more explanation why). This is especially true if your app loads remote content, which many do.
为了正确地做事,您需要在 <代码>BrowserWindow 如下所述.设置这些选项会强制您的电子应用程序通过 IPC(进程间通信)进行通信,并将两个环境相互隔离.像这样设置您的应用程序可以让您验证后端中可能是 require
的任何模块,而客户端不会对其进行篡改.
In order to do things right, you need to enable a lot of options on your BrowserWindow
as I detail below. Setting these options forces your electron app to communicate via IPC (inter-process communication) and isolates the two environments from each other. Setting up your app like this allows you to validate anything that may be a require
'd module in your backend, which is free from the client tampering with it.
您将在下面找到一个简短示例,说明我所说的内容以及它在您的应用中的外观.如果你刚开始,我可能会建议使用 secure-electron-template
(我是其中的作者)在构建电子应用程序时从一开始就包含了所有这些安全最佳实践.
Below, you will find a brief example of what I speak about and how it can look in your app. If you are starting fresh, I might suggest using secure-electron-template
(which I am the author of) that has all of these security best-practices baked in from the get go when building an electron app.
这个页面也有很好的信息关于使用 preload.js 制作安全应用时所需的架构.
This page also has good information on the architecture that's required when using the preload.js to make secure apps.
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>
相关文章