[英]Electron require() is not defined
I'm creating an Electron app for my own purpose.我正在为自己的目的创建一个 Electron 应用程序。 My problem is when I'm using node functions inside my HTML page it throws an error of:
我的问题是当我在我的 HTML 页面中使用节点函数时,它会引发以下错误:
'require()' is not defined.
'require()' 未定义。
Is there any way to use Node functionalities in all my HTML pages?有没有办法在我的所有 HTML 页面中使用节点功能? If it is possible please give me an example of how to do this or provide a link.
如果可能的话,请给我一个如何做到这一点的例子或提供一个链接。 Here are the variables I'm trying to use in my HTML page:
以下是我试图在我的 HTML 页面中使用的变量:
var app = require('electron').remote;
var dialog = app.dialog;
var fs = require('fs');
and these are the values I'm using in all my HTML windows within Electron.这些是我在 Electron 中的所有 HTML 窗口中使用的值。
As of version 5, the default for nodeIntegration
changed from true to false.从版本 5 开始,
nodeIntegration
的默认值从 true 更改为 false。 You can enable it when creating the Browser Window:您可以在创建浏览器窗口时启用它:
app.on('ready', () => {
mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
}
});
});
I've published a larger post on the history of Electron and it's security that provides additional context on the changes that affect how security was approached in different framework versions (and what's the best approach to take).我发表了一篇关于 Electron 历史的大型文章,它的安全性为影响不同框架版本中如何处理安全性的变化提供了额外的上下文(以及最好的方法是什么)。
I hope this answer gets some attention, because a large majority of answers here leave large security holes in your electron app.我希望这个答案能引起注意,因为这里的大多数答案都会在您的电子应用程序中留下很大的安全漏洞。 In fact this answer is essentially what you should be doing to use
require()
in your electron apps.事实上,这个答案本质上就是你在电子应用程序中使用
require()
应该做的事情。 (There is just a new electron API that makes it a little bit cleaner in v7). (只有一个新的电子 API 使它在 v7 中更简洁一些)。
I wrote a detailed explanation/solution in github using the most current electron apis of how you can require()
something, but I'll explain briefly here why you should follow an approach using a preload script, contextBridge and ipc.我使用最新的电子 api 在 github 中编写了详细的解释/解决方案,说明如何使用
require()
某些东西,但我将在这里简要解释为什么你应该遵循使用预加载脚本、contextBridge 和 ipc 的方法。
Electron apps are great because we get to use node, but this power is a double-edged sword. Electron 应用程序很棒,因为我们可以使用节点,但这种力量是一把双刃剑。 If we are not careful, we give someone access to node through our app, and with node a bad actor can corrupt your machine or delete your operating system files (among other things, I imagine).
如果我们不小心,我们会通过我们的应用程序让某人访问节点,并且使用节点的不良行为者可能会损坏您的机器或删除您的操作系统文件(除其他外,我想)。
As brought up by @raddevus in a comment, this is necessary when loading remote content.
正如@raddevus 在评论中提到的,在加载远程内容时这是必要的。 If your electron app is entirely offline / local , then you are probably okay simply turning on
nodeIntegration:true
.如果您的电子应用程序完全离线/本地,那么您可能只需打开
nodeIntegration:true
就可以了。 I still would, however, opt to keepnodeIntegration:false
to act as a safeguard for accidental/malicious users using your app, and prevent any possible malware that might ever get installed on your machine from interacting with your electron app and using thenodeIntegration:true
attack vector (incredibly rare, but could happen)!但是,我仍然会选择保留
nodeIntegration:false
来保护意外/恶意用户使用您的应用程序,并防止任何可能安装在您机器上的恶意软件与您的电子应用程序交互并使用nodeIntegration:true
攻击向量(非常罕见,但可能发生)!
This problem manifests when you (any one of the below):当您(以下任何一项)时,就会出现此问题:
All of these problems give uninterrupted access to node from your renderer process.所有这些问题都使您可以从渲染器进程不间断地访问节点。 If your renderer process is ever hijacked, you can consider all is lost.
如果您的渲染器进程被劫持,您可以认为一切都丢失了。
The solution is to not give the renderer direct access to node (ie. require()
), but to give our electron main process access to require
, and anytime our renderer process needs to use require
, marshal a request to the main process.解决方案是不让渲染器直接访问节点(即
require()
),而是让我们的电子主进程访问require
,并且任何时候我们的渲染器进程需要使用require
,将请求编组到主进程。
The way this works in the latest versions (7+) of Electron is on the renderer side we set up ipcRenderer bindings, and on the main side we set up ipcMain bindings.这在 Electron 的最新版本(7+)中的工作方式是在渲染器端我们设置ipcRenderer绑定,在主端我们设置ipcMain绑定。 In the ipcMain bindings we set up listener methods that use modules we
require()
.在 ipcMain 绑定中,我们设置了使用我们
require()
模块的侦听器方法。 This is fine and well because our main process can require
all it wants.这很好,因为我们的主要过程可能
require
它想要的一切。
We use the contextBridge to pass the ipcRenderer bindings to our app code (to use), and so when our app needs to use the require
d modules in main, it sends a message via IPC (inter-process-communication) and the main process runs some code, and we then send a message back with our result.我们使用contextBridge将 ipcRenderer 绑定传递给我们的应用程序代码(使用),因此当我们的应用程序需要在 main 中使用
require
d 模块时,它通过 IPC(进程间通信)和主进程发送消息运行一些代码,然后我们用我们的结果发回一条消息。
Roughly , here's what you want to do.粗略地说,这就是你想要做的。
main.js 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 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索引.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>
I'm the author of secure-electron-template
, a secure template to build electron apps.我是
secure-electron-template
的作者,这是一个用于构建电子应用程序的安全模板。 I care about this topic, and have been working on this for a few weeks (at this point in time).我关心这个话题,并且已经为此工作了几个星期(在这个时间点)。
For security reasons, you should keep nodeIntegration: false
and use a preload script to expose just what you need from Node/Electron API to the renderer process (view) via window variable.出于安全原因,您应该保留
nodeIntegration: false
并使用预加载脚本通过窗口变量将 Node/Electron API 所需的内容公开给渲染器进程(视图)。 From the Electron docs :来自电子文档:
Preload scripts continue to have access to
require
and other Node.js features预加载脚本继续可以访问
require
和其他 Node.js 功能
main.js main.js
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(app.getAppPath(), 'preload.js')
}
})
preload.js preload.js
const { remote } = require('electron');
let currWindow = remote.BrowserWindow.getFocusedWindow();
window.closeCurrentWindow = function(){
currWindow.close();
}
renderer.js渲染器.js
let closebtn = document.getElementById('closebtn');
closebtn.addEventListener('click', (e) => {
e.preventDefault();
window.closeCurrentWindow();
});
First off, @Sathiraumesh solution leaves your electron application with huge security issue.首先,@Sathiraumesh 解决方案给您的电子应用程序留下了巨大的安全问题。 Imagine that your app is adding some extra features to
messenger.com
, for example toolbar's icon will change or blink when you've have unread message.想象一下,您的应用正在向
messenger.com
添加一些额外功能,例如,当您有未读消息时,工具栏的图标会发生变化或闪烁。 So in your main.js
file, you create new BrowserWindow like so (notice I intentionally misspelled messenger.com):所以在你的
main.js
文件中,你像这样创建新的 BrowserWindow(注意我故意拼错了 messenger.com):
app.on('ready', () => {
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true
}
});
mainWindow.loadURL(`https://messengre.com`);
});
What if messengre.com
is a malicious website, that wants to harm your computer.如果
messengre.com
是一个想要损害您的计算机的恶意网站怎么办。 If you set nodeIntegration: true
this site has access to your local file system and can execute this:如果你设置
nodeIntegration: true
这个站点可以访问你的本地文件系统并且可以执行这个:
require('child_process').exec('rm -r ~/');
And your home directory is gone.你的主目录不见了。
Solution解决方案
Expose only what you need, instead of everything.只展示你需要的东西,而不是一切。 This is achived by preloading javascript code with
require
statements.这是通过使用
require
语句预加载 javascript 代码来实现的。
// main.js
app.on('ready', () => {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: `${__dirname}/preload.js`
}
});
mainWindow.loadURL(`https://messengre.com`);
});
// preload.js
window.ipcRenderer = require('electron').ipcRenderer;
// index.html
<script>
window.ipcRenderer.send('channel', data);
</script>
Now awful messengre.com
cannot delete your entire file system.现在糟糕
messengre.com
无法删除您的整个文件系统。
It looks like Electron's security evolved like this ( source ).看起来 Electron 的安全性是这样发展的(来源)。
Electron 1 nodeIntegration defaults to true Electron 1 nodeIntegration默认为 true
Renderer has full access to Node API -- huge security risks if Renderer loads remote code. Renderer 拥有对 Node API 的完全访问权限——如果 Renderer 加载远程代码,将会带来巨大的安全风险。
Electron 5 nodeIntegration defaults to false Electron 5 nodeIntegration默认为 false
When set to false, a preload script is used to expose specific API to Renderer.当设置为 false 时,预加载脚本用于向渲染器公开特定的 API。 (The preload script always has access to Node APIs regardless of the value of nodeIntegration )
(无论nodeIntegration的值如何,预加载脚本始终可以访问节点 API)
//preload.js
window.api = {
deleteFile: f => require('fs').unlink(f)
}
Electron 5 contextIsolation defaults to true (actually still defaults to false in Electron 11) Electron 5 contextIsolation默认为 true(实际上在 Electron 11 中仍默认为 false)
This causes preload script to run in a separate context.这会导致预加载脚本在单独的上下文中运行。 You can no longer do
window.api = ...
.你不能再做
window.api = ...
。 You now have to do:你现在必须做:
//preload.js
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('api', {
deleteFile: f => require('fs').unlink(f)
})
Electron 6 require()
ing node builtins in sandboxed renderers no longer implicitly loads the remote version Electron 6
require()
沙盒渲染器中的内置节点不再隐式加载远程版本
If Renderer has sandbox
set to true, you have to do:如果 Renderer 将
sandbox
设置为 true,则必须执行以下操作:
//preload.js
const { contextBridge, remote } = require('electron')
contextBridge.exposeInMainWorld('api', {
deleteFile: f => remote.require('fs').unlink(f)
})
Electron 10 enableRemoteModule default to false ( remote module deprecated in Electron 12) Electron 10 enableRemoteModule默认为 false(在 Electron 12 中不推荐使用远程模块)
The remote
module is used when you need to access Node APIs from a sandboxed Renderer (as in above example);当您需要从沙盒渲染器访问节点 API 时使用
remote
模块(如上例所示); or when you need to access Electron APIs that are available only to the Main process (such as dialog, menu).或者当您需要访问仅可用于主进程(例如对话框、菜单)的 Electron API 时。 Without
remote
, you'll need to write explicit IPC handlers like follows.如果没有
remote
,您将需要编写如下的显式 IPC 处理程序。
//preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('api', {
displayMessage: text => ipcRenderer.invoke("displayMessage", text)
})
//main.js
const { ipcMain, dialog } = require('electron')
ipcMain.handle("displayMessage", text => dialog.showMessageBox(text))
Electron 10 deprecate nodeIntegration flag (removed in Electron 12) Electron 10弃用nodeIntegration标志(在 Electron 12 中删除)
Always set {nodeIntegration: false, contextIsolation: true, enableRemoteModule: false}
.始终设置
{nodeIntegration: false, contextIsolation: true, enableRemoteModule: false}
。
For max security, set {sandbox: true}
.为了获得最大的安全性,请设置
{sandbox: true}
。 Your preload script will have to use IPC to call the Main process to do everything .您的预加载脚本必须使用 IPC 来调用 Main 进程来完成所有操作。
If sandbox
is false, your preload script can access Node API directly, as in require('fs').readFile
.如果
sandbox
为 false,您的预加载脚本可以直接访问 Node API,如require('fs').readFile
。 You're secure as long as you don't this:只要你不这样做,你就是安全的:
//bad
contextBridge.exposeInMainWorld('api', {
readFile: require('fs').readFile
})
Are you using nodeIntegration: false
while BrowserWindow initialization?您在 BrowserWindow 初始化时使用
nodeIntegration: false
吗? If so, set it to true
(defaults value is true
).如果是这样,请将其设置为
true
(默认值为true
)。
And include your external scripts in the HTML like this (not as <script> src="./index.js" </script>
):并像这样在 HTML 中包含您的外部脚本(而不是
<script> src="./index.js" </script>
):
<script>
require('./index.js')
</script>
All I wanted to do was to require a js file in my html page because of the tutorial I was following.由于我正在遵循教程,我想做的只是在我的 html 页面中需要一个 js 文件。 However, I intend to use remote modules so security was paramount.
但是,我打算使用远程模块,因此安全性至关重要。 I modified Michael's answer up there so I'm posting, purely for those who spent hours looking for a secure alternative to 'require' like me.
我在那里修改了迈克尔的答案,所以我发布了,纯粹是为了那些像我一样花费数小时寻找“要求”的安全替代方案的人。 If the code is incorrect, feel free to point it out.
如果代码不正确,请随时指出。
main.js main.js
const electron = require('electron');
const app=electron.app;
const BrowserWindow=electron.BrowserWindow;
const ipcMain=electron.ipcMain;
const path=require('path');
const url=require('url');
let win;
function createWindow(){
win=new BrowserWindow({
webPreferences:{
contextIsolation: true,
preload: path.join(__dirname, "preload.js")
}
});
win.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true
}));
win.on('close', function(){
win=null
});
}
app.on('ready', createWindow);
preload.js preload.js
const electron=require('electron');
const contextBridge=electron.contextBridge;
contextBridge.exposeInMainWorld(
"api", {
loadscript(filename){
require(filename);
}
}
);
index.html索引.html
<!DOCTYPE html>
<html>
<head>
<title>Hello World App</title>
</head>
<body>
<h1>Hello World</h1>
<button id="btn">Click</button>
</body>
<script>
window.api.loadscript('./index.js');
</script>
</html>
index.js index.js
const btn = document.getElementById('btn');
btn.addEventListener('click', function(){
console.log('button clicked');
});
I am especially curious to know if this still presents a security risk.我特别想知道这是否仍然存在安全风险。 Thanks.
谢谢。
You have to enable the nodeIntegration in
webPreferences to use it.
您必须在webPreferences中启用
nodeIntegration才能使用它。 see below,
见下文,
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({
webPreferences: {
nodeIntegration: true
}
})
win.show()
There was a breaking api changes in electron 5.0( Announcement on Repository ).电子 5.0 中有一个重大的 api 更改( 关于存储库的公告)。 In recent versions
nodeIntegration is by default set to
false .
在最近的版本中 nodeIntegration默认设置为
false 。
Docs Due to the Node.js integration of Electron, there are some extra symbols inserted into the DOM like module, exports, require.
Docs由于 Electron 的 Node.js 集成,DOM 中插入了一些额外的符号,如模块、导出、要求。 This causes problems for some libraries since they want to insert the symbols with the same names.To solve this, you can turn off node integration in Electron:
这会导致一些库出现问题,因为他们想要插入具有相同名称的符号。要解决这个问题,您可以在 Electron 中关闭节点集成:
But if you want to keep the abilities to use Node.js and Electron APIs, you have to rename the symbols in the page before including other libraries:但是如果你想保留使用 Node.js 和 Electron API 的能力,你必须在包含其他库之前重命名页面中的符号:
<head>
<script>
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
</script>
<script type="text/javascript" src="jquery.js"></script>
</head>
If you just don't care about any security issues and want to have require being interpreted correctly by JavaScript on the browser window, then have an extra flag on the main.js code:如果您只是不关心任何安全问题并希望在浏览器窗口上被 JavaScript 正确解释,那么在main.js代码上添加一个额外的标志:
webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: true, nodeIntegrationInSubFrames: true, enableRemoteModule: true, contextIsolation: false //required flag } //rest of the code...
Finally, I made it work.Add this code to your HTML document Script Element.最后,我成功了。将此代码添加到您的 HTML 文档脚本元素中。
Sorry for the late Reply.I use the below code to do this thing.抱歉回复晚了。我使用下面的代码来做这件事。
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
And use nodeRequire
instead of using require
.并使用
nodeRequire
而不是使用require
。
It works Fine.它工作正常。
you have to add the require.js dependency. 你必须添加require.js依赖项。
http://requirejs.org/docs/download.html http://requirejs.org/docs/download.html
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.