简体   繁体   English

电子 require() 未定义

[英]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,
        }
    });
});

Edit 2022编辑 2022


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 历史的大型文章,它的安全性为影响不同框架版本中如何处理安全性的变化提供了额外的上下文(以及最好的方法是什么)。

Original answer原始答案


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 的方法。

The problem问题

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 keep nodeIntegration: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 the nodeIntegration:true attack vector (incredibly rare, but could happen)!但是,我仍然会选择保留nodeIntegration:false来保护意外/恶意用户使用您的应用程序,并防止任何可能安装在您机器上的恶意软件与您的电子应用程序交互并使用nodeIntegration:true攻击向量(非常罕见,但可能发生)!

What does the problem look like问题是什么样的

This problem manifests when you (any one of the below):当您(以下任何一项)时,就会出现此问题:

  1. Have nodeIntegration:true enabled启用nodeIntegration:true
  2. Use the remote module使用remote模块

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.如果您的渲染器进程被劫持,您可以认为一切都丢失了。

What our solution is我们的解决方案是什么

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>

Disclaimer免责声明

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 功能


Example例子

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 中删除)

Recommendation推荐

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...

You could use Browserify to fix your issue, had it been a web page or a website. 您可以使用Browserify来修复您的问题,如果它是网页或网站。 But since you are using Electron to render html, you just need to set node integration to true under web preference: like shown here . 但是,由于您使用的电子呈现HTML,你只需要节点整合设置为true,在网络偏好:喜欢出在这里

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM