电子require()未定义


107

我正在为自己的目的创建Electron应用程序。我的问题是,当我在HTML页面中使用节点函数时,它引发以下错误:

未定义'require()'。

有没有办法在我所有的HTML页面中使用Node功能?如果可以的话,请给我举个例子,或者提供一个链接。以下是我要在HTML页面中尝试使用的变量:

  var app = require('electron').remote; 
  var dialog = app.dialog;
  var fs = require('fs');

这些是我在Electron的所有HTML窗口中使用的值。


Answers:


285

从版本5开始,默认值nodeIntegration从true更改为false。您可以在创建浏览器窗口时启用它:

app.on('ready', () => {
    mainWindow = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true
        }
    });
});

由于安全原因,由于Electron的最新版本将nodeIntegration缺省设置为false,因此推荐使用哪种方式访问​​节点模块?没有nodeIntegration,有没有办法与主进程通信?
Paulo Henrique

28
nodeIntegration: true仅当您在应用程序上执行某些不受信任的远程代码时,@ PauloHenrique 才存在安全风险。例如,假设您的应用程序打开了第三方网页。这将带来安全风险,因为第三方网页将有权访问节点运行时,并且可以在用户的​​文件系统上运行某些恶意代码。在这种情况下,设置是有意义的nodeIntegration: false。如果您的应用程序不显示任何远程内容,或仅显示受信任的内容,则可以进行设置nodeIntegration: true
xyres

1
这让我发疯。我的应用程序不会显示任何错误,也不会运行我的代码。正是当我使用try catch块拦截错误时,才最终将我带到这里。
Heriberto Juarez,

2
@PauloHenrique-如果您要遵循并创建一个安全的应用程序(遵循安全性最佳实践),请按照我在此评论中描述的设置进行操作: github.com/electron/electron/issues/9920#issuecomment-575839738
Zac

33

出于安全原因,您应该保留nodeIntegration: false并使用预加载脚本,以通过window变量将需要的Node / Electron API暴露给渲染器进程(视图)。从电子文档

预加载脚本继续可以访问require和其他Node.js功能


main.js

const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: path.join(app.getAppPath(), 'preload.js')
  }
})

preload.js

const { remote } = require('electron');

let currWindow = remote.BrowserWindow.getFocusedWindow();

window.closeCurrentWindow = function(){
  currWindow.close();
}

renderer.js

let closebtn = document.getElementById('closebtn');

closebtn.addEventListener('click', (e) => {
  e.preventDefault();
  window.closeCurrentWindow();
});

1
如果您像我一样是电子新手:渲染器文件通常以经典方式包含在html中:<script src="./renderer.js"></script>
MrAn3

22

我希望这个答案能引起大家的注意,因为这里的大多数答案都会在您的电子应用程序中留下很大的安全漏洞。实际上,这个答案实际上就是您应该使用的答案require()在电子应用程序中。(只有一个新的电子API使其在v7中更加干净)。

我使用最新的电子api在github上写了详细的解释/解决方案require(),但是我将在这里简要解释为什么您应该使用预加载脚本,contextBridge和ipc。

问题

电子应用程序很棒,因为我们可以使用节点,但是这种能力是一把双刃剑。如果我们不小心的话,我们会给某人通过我们的应用程序访问节点的权限,并且由于该节点的行为不当,可能会损坏您的计算机或删除您的操作系统文件(我想还有其他事情)。

正如@raddevus在评论中提到的那样,在加载远程内容时这是必需的。如果您的电子应用完全离线 / 本地运行,那么打开电源就可以了。但是,我仍然会选择继续为使用您的应用程序的意外/恶意用户提供保障,并防止您机器上可能安装的任何可能的恶意软件与您的电子应用程序进行交互并使用攻击媒介(这非常少见) ,但可能会发生)!nodeIntegration:truenodeIntegration:falsenodeIntegration:true

问题是什么样的

当您(以下任何一项)时,就会出现此问题:

  1. nodeIntegration:true启用
  2. 使用remote模块

所有这些问题使您可以从渲染器进程不间断地访问节点。如果渲染器进程被劫持,则可以认为所有丢失。

我们的解决方案是

解决方案是不给渲染器直接访问节点(即require()),而是给我们的电子主进程访问require,并且在我们的渲染器进程需要使用任何时间时require,将请求封送给主进程。

在Electron的最新版本(7+)中,此方法的工作方式是在渲染器端设置ipcRenderer绑定,在主端设置ipcMain绑定。在ipcMain绑定中,我们设置了使用模块we的侦听器方法require()。这很好,因为我们的主要流程可以满足require所有需求。

我们使用contextBridge将ipcRenderer绑定传递给我们的应用程序代码(以便使用),因此,当我们的应用程序需要require在main中使用d模块时,它会通过IPC(进程间通信)发送一条消息,并且主进程运行一些代码,然后我们将结果发送回一条消息。

大致来说,这就是您想要做的。

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>

免责声明

我是的作者secure-electron-template,该模板是构建电子应用程序的安全模板。我关心这个主题,并且已经为此工作了几个星期(在这个时间点上)。


我是新的ElectronJS开发人员,并且正在通过PluralSite教程工作,该教程不再运行,因为节点集成设置已更改。您的帖子非常好,我还将阅读与您相关的Github帖子和相关的官方Electron安全文档。完全正确地设置节点集成(这样应用程序将能够正常运行并确保安全性)有很多活动内容(尤其是对于新手而言)。以下是Electron docs的句子:“至关重要的是,您不要在加载远程内容的任何渲染器(BrowserWindow,BrowserView或<webview>)启用Node.js集成 ”(我的重点)
raddevus

我假设相反的情况是正确的……“如果BrowserWindow不加载远程内容,则可以安全地包含Node集成”。如果这句话是正确的,那么您可能需要稍微改变一下帖子以强调这一点,因为在我的情况下,我有两个属于该类别的应用程序,不需要删除节点集成。谢谢你的帮助。
raddevus

1
@raddevus谢谢,我希望模板可以帮助您构建安全的电子应用程序(如果您选择使用它)!是的,您的强调是正确的。但是,我要说的是,禁用功能nodeIntegration可以防止用户在使用该应用程序时意外或有意对自己造成伤害,这是一种额外的保护措施,以防某些恶意软件附着在您的电子过程中并且能够在知道此向量已打开的情况下执行XSS(令人难以置信)罕见,但这就是我的大脑去的地方!
扎克

1
@raddevus谢谢,我正在更新我的帖子以反映您的评论。
扎克

9

您正在使用nodeIntegration: falseBrowserWindow初始化吗?如果是这样,请将其设置为true(默认值为true)。

并在HTML中包含您的外部脚本,而不是这样<script> src="./index.js" </script>

<script>
   require('./index.js')
</script>

我正在与此脱机使用pdf js。所以当我使用nodeIntegration:true时,将到达PDFJS.getDocument不是函数错误。当pdfjs完全加载时,如何在我的html页面中设置nodeIntegration:true
Mari Selvan

你看过这个例子吗?您也许可以通过导入软件包var pdfjsLib = require('pdfjs-dist')并以这种方式使用它。
RoyalBingBong17年

您为什么建议使用require代替<script src="..."></script>?这也有一个悬而未决的问题在这里
bluenote10

@ bluenote10 Webpack回答了这个问题:很难说出脚本所依赖的内容,必须管理依赖关系的顺序,并且仍然会下载并执行不必要的代码。
haykam '19

7

首先,@ Sathiraumesh解决方案使您的电子应用程序面临巨大的安全问题。想象您的应用程序向中添加了一些额外的功能messenger.com,例如,当您有未读消息时,工具栏的图标将更改或闪烁。因此,在您的main.js文件中,您将像这样创建新的BrowserWindow(注意,我故意拼写了messenger.com):

app.on('ready', () => {
    const mainWindow = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true
        }
    });
    mainWindow.loadURL(`https://messengre.com`);
});

如果messengre.com是一个恶意网站,想要损害您的计算机怎么办?如果设置nodeIntegration: true此站点可以访问本地文件系统,并且可以执行以下操作:

require('child_process').exec('rm -r ~/');

您的主目录不见了。

解决方案
仅公开您需要的内容,而不公开所有内容。这是通过使用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>

现在可怕的messengre.com无法删除您的整个文件系统。


-1

最后,使它生效。将此代码添加到HTML文档的Script Element中。

抱歉,回复晚了。我使用下面的代码来完成此操作。

window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;

并使用nodeRequire而不是require

它工作正常。


请分享您的HTML页面代码。
维杰

-1

您必须在webPreferences中启用nodeIntegration才能使用它。见下文,

const { BrowserWindow } = require('electron')
let win = new BrowserWindow({
  webPreferences: {
    nodeIntegration: true
  }
})
win.show()

电子版本5.0(存储库中的公告)的api发生了重大变化。在最新版本中,nodeIntegration默认情况下设置为false

文档由于Electron的Node.js集成,在DOM中插入了一些额外的符号,例如模块,导出,需要。这会给某些库带来问题,因为它们想插入相同名称的符号。要解决此问题,可以在Electron中关闭节点集成:

但是,如果您想保留使用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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.