电子require()未定义

时间:2017-06-06 13:32:19

标签: javascript html node.js electron

我正在为自己的目的创建一个Electron应用程序。我的问题是,当我在HTML页面中使用节点函数时,它会抛出错误:

  

'require()'未定义。

有没有办法在我的所有HTML页面中使用Node功能?如果有可能,请举例说明如何执行此操作或提供链接。以下是我在HTML页面中尝试使用的变量:

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

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

10 个答案:

答案 0 :(得分:27)

我希望这个答案能引起关注,因为这里的绝大多数答案会在您的电子应用中留下 large 个安全漏洞。实际上,this answer本质上就是您在电子应用程序中使用require()时应该做的事情。 (只有一个新的电子API使其在v7中变得更干净了。)

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

问题

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

由@raddevus在评论中提出,在加载远程内容时,这是必需的。如果您的电子应用完全是 offline / local ,那么打开nodeIntegration:true就可以了。但是,我仍然会选择保留nodeIntegration:false来保护使用您的应用程序的意外/恶意用户,并防止可能已经安装在您的计算机上的任何恶意软件与您的电子应用程序进行交互并使用nodeIntegration:true攻击媒介(极少见,但有可能发生)!

问题是什么样的

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

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

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

我们的解决方案是什么

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

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

我们使用contextBridge将ipcRenderer绑定传递给我们的应用程序代码(以使用),因此当我们的应用程序需要在主应用程序中使用require 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(创建电子应用程序的安全模板)的作者。我关心这个主题,并且已经在这个星期上进行了几周的研究。

答案 1 :(得分:16)

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

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

答案 2 :(得分:7)

看起来 Electron 的安全性是这样演变的 (source)。

Electron 1 nodeIntegration 默认为 true

Renderer 拥有对 Node API 的完全访问权——如果 Renderer 加载远程代码,则存在巨大的安全风险。

Electron 5 nodeIntegration 默认为 false

当设置为 false 时,预加载脚本用于向渲染器公开特定的 API。 (无论 nodeIntegration 的值如何,预加载脚本始终可以访问 Node API)

//preload.js
window.api = {
    deleteFile: f => require('fs').unlink(f)
}

Electron 5 contextIsolation 默认为 true(实际上在 Electron 11 中仍然默认为 false)

这会导致预加载脚本在单独的上下文中运行。你不能再做window.api = ...。你现在必须做:

//preload.js
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('api', {
    deleteFile: f => require('fs').unlink(f)
})

Electron 6 require() 沙盒渲染器中的内置节点不再隐式加载远程版本

如果 Renderer 将 sandbox 设置为 true,则您必须:

//preload.js
const { contextBridge, remote } = require('electron')

contextBridge.exposeInMainWorld('api', {
    deleteFile: f => remote.require('fs').unlink(f)
})

Electron 10 enableRemoteModule 默认为 false(remote 模块在 Electron 12 中已弃用)

当您需要从沙盒渲染器(如上例所示)访问 Node API 时使用 remote 模块;或者当您需要访问仅适用于主进程(例如对话框、菜单)的 Electron API 时。如果没有 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 标志(在 Electron 12 中删除)

推荐

始终设置 {nodeIntegration: false, contextIsolation: true, enableRemoteModule: false}

为了最大安全性,设置 {sandbox: true}。您的预加载脚本必须使用 IPC 调用 Main 进程来完成所有事情

如果 sandbox 为 false,您的预加载脚本可以直接访问 Node API,如 require('fs').readFile。只要你不这样做,你就是安全的:

//bad
contextBridge.exposeInMainWorld('api', {
    readFile: require('fs').readFile
})

答案 3 :(得分:6)

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

并在HTML中包含您的外部脚本(不是<script> src="./index.js" </script>):

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

答案 4 :(得分:1)

最后,我做了它。将此代码添加到HTML文档脚本元素。

对于迟到的回复抱歉。我使用以下代码来做这件事。

Visibility

使用window.nodeRequire = require; delete window.require; delete window.exports; delete window.module; 代替nodeRequire

工作正常。

答案 5 :(得分:1)

首先,@ 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糟透了,无法删除整个文件系统。

答案 6 :(得分:1)

由于要遵循的教程,我要做的只是在html页面中需要一个js文件。但是,我打算使用远程模块,因此安全性至关重要。我在那儿修改了Michael的答案,所以我才发布,纯粹是为了那些像我这样花费数小时寻找“ require”的安全选择的人。如果代码不正确,请随时指出。

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

const electron=require('electron');
const contextBridge=electron.contextBridge;

contextBridge.exposeInMainWorld(
    "api", {
        loadscript(filename){
            require(filename);
        }
    }
);

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

const btn = document.getElementById('btn');
btn.addEventListener('click', function(){
    console.log('button clicked');
});

我特别想知道这是否仍然存在安全风险。谢谢。

答案 7 :(得分:0)

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

  

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

https://electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content

示例:

//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();
});

答案 8 :(得分:0)

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

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

电子5.0(Announcement on Repository)的api发生了重大变化。在最新版本中,默认将 nodeIntegration 设置为 false

  

Docs由于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>

答案 9 :(得分:-6)

您必须添加require.js依赖项。

http://requirejs.org/docs/download.html