Електрон вимагає () не визначено


108

Я створюю додаток Electron для власних цілей. Моя проблема полягає в тому, що коли я використовую функції вузлів на моїй HTML-сторінці, це видає помилку:

'require ()' не визначено.

Чи є спосіб використовувати функції Node на всіх моїх HTML-сторінках? Якщо це можливо, дайте мені приклад, як це зробити, або надайте посилання. Ось змінні, які я намагаюся використовувати на своїй HTML-сторінці:

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

і ці значення я використовую у всіх своїх вікнах HTML в Electron.


Відповіді:


287

Починаючи з версії 5, значення за замовчуванням nodeIntegrationзмінено з true на false. Ви можете ввімкнути його під час створення вікна браузера:

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

Оскільки остання версія Electron має nodeIntegration за замовчуванням значення false з міркувань безпеки, який рекомендований спосіб отримати доступ до модульних вузлів? Чи є спосіб спілкування з основним процесом без nodeIntegration?
Пауло Енріке,

28
@PauloHenrique nodeIntegration: true- це ризик для безпеки лише тоді, коли ви виконуєте якийсь ненадійний віддалений код у своїй програмі. Наприклад, припустимо, що ваша програма відкриває веб-сторінку третьої сторони. Це загрожує безпеці, оскільки стороння веб-сторінка матиме доступ до середовища виконання та може запускати зловмисний код у файловій системі вашого користувача. У такому випадку має сенс встановити nodeIntegration: false. Якщо у вашій програмі не відображається віддалений вміст або відображається лише надійний вміст, тоді налаштування nodeIntegration: trueнормально.
xyres

1
Це зводило мене з розуму. Мій додаток просто не відображав помилок і не запускав мій код. Це було тоді, коли я використав блок try try, щоб перехопити помилку, яка нарешті привела мене сюди.
Хуарес

2
@PauloHenrique - Якщо ви хочете підписатись і створити безпечний додаток (дотримуючись найкращих практик безпеки), дотримуйтесь моїх налаштувань, як я описав у цьому коментарі: github.com/electron/electron/issues/9920#issuecomment-575839738
Zac

33

З міркувань безпеки слід зберігати nodeIntegration: falseта використовувати сценарій попереднього завантаження, щоб виставити лише те, що вам потрібно, від Node / Electron API до процесу візуалізації (перегляду) за допомогою змінної вікна. З документів Electron :

Сценарії попереднього завантаження продовжують мати доступ до 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, який робить його трохи чистішим у версії 7).

Я написав детальне пояснення / рішення у github, використовуючи найновіший електронний apis того, як ви require()щось можете , але коротко тут поясню, чому слід дотримуватися підходу, використовуючи сценарій попереднього завантаження, contextBridge та ipc.

Проблема

Додатки Electron чудові, тому що ми можемо використовувати node, але ця сила - це двосічний меч. Якщо ми не будемо обережні, ми надаємо комусь доступ до node через наш додаток, а з node поганий актор може пошкодити вашу машину або видалити файли операційної системи (серед іншого, я думаю).

Як зазначив @raddevus у коментарі, це необхідно під час завантаження віддаленого вмісту. Якщо ваш електронний додаток повністю офлайн / локальний , ви, мабуть, у порядку, просто ввімкнувши nodeIntegration:true. Однак я все-таки вирішив би продовжувати nodeIntegration:falseдіяти як захист для випадкових / зловмисних користувачів, які використовують ваш додаток, і запобігати взаємодії будь-якого можливого шкідливого програмного забезпечення, яке коли-небудь може бути встановлено на вашій машині, з вашим електронним додатком та використання nodeIntegration:trueвектора атаки (неймовірно рідко , але може статися)!

Як виглядає проблема

Ця проблема проявляється, коли ви (будь-який із наведених нижче):

  1. Чи були nodeIntegration:trueвключений
  2. Використовуйте remoteмодуль

Усі ці проблеми дають безперебійний доступ до вузла з вашого процесу візуалізації. Якщо ваш процес візуалізації коли-небудь викрадений, ви можете вважати, що все втрачено.

Яке наше рішення

Рішення полягає в тому, щоб не надати рендерингу прямий доступ до вузла (тобто. require()), А надати нашому головному процесу електрон доступ requireі щоразу, коли наш процес рендерингу потрібно використовувати require, маршал запитує основний процес.

Це працює в останніх версіях (7+) Electron на стороні візуалізації, ми встановлюємо прив'язки 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безпечного шаблону для створення електронних додатків. Я дбаю про цю тему і працюю над цим вже кілька тижнів (на даний момент).


Я новий розробник ElectronJS, і я працював через навчальний посібник PluralSite, який більше не працює, оскільки налаштування інтеграції вузлів були змінені. Ваша публікація дуже гарна, і я також читаю вашу пов’язану публікацію Github та відповідні офіційні документи щодо безпеки Electron. Правильне налаштування інтеграції вузлів (щоб додатки працювали належним чином і були безпечними) має багато рухомих частин (особливо для новачків). Наступне речення з документації Electron "Найважливіше, що ви не вмикаєте інтеграцію Node.js в жодному візуалізаторі (BrowserWindow, BrowserView або <webview>), який завантажує віддалений вміст. " (Мій акцент)
raddevus

Я припускаю, що зворотне відповідає дійсності ... "якщо BrowserWindow не завантажує віддалений вміст, то безпечно включити інтеграцію Node". Якщо це речення відповідає дійсності, можливо, ви захочете трохи змінити свої публікації, щоб наголосити на цьому, оскільки в моєму випадку у мене є дві програми, які належать до цієї категорії і не потребують видалення інтеграції вузлів. Спасибі за вашу допомогу.
raddevus

1
@raddevus Дякую, сподіваюся, шаблон допоможе вам створити безпечні електронні програми (якщо ви вирішите його використовувати)! Так, ви правильно підкреслюєте. Однак я скажу, що відключення nodeIntegrationзапобігає випадковому або навмисному заподіюванню шкоди собі під час використання програми, і є додатковим захистом на випадок, якщо до вашого електронного процесу приєднано якесь шкідливе програмне забезпечення і змогло виконати XSS, знаючи, що цей вектор відкритий (неймовірно рідко, але саме туди пішов мій мозок)!
Зак

1
@raddevus Дякую, я оновлюю свої повідомлення, щоб відображати ваш коментар.
Зак

9

Ви використовуєте nodeIntegration: falseпід час ініціалізації BrowserWindow? Якщо так, встановіть для нього значення true(значення за замовчуванням true).

І включіть свої зовнішні сценарії в HTML так (не як <script> src="./index.js" </script>):

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

Я використовую pdf js в автономному режимі з цим. Тому, коли я використовую nodeIntegration: true, тоді PDFJS.getDocument не призводить до помилки функції. Як встановити nodeIntegration: true на моїй HTML-сторінці, коли pdfjs повністю завантажений.
Марі Селван

Ви розглядали цей приклад ? Можливо, ви зможете просто імпортувати пакет через var pdfjsLib = require('pdfjs-dist')і використовувати його таким чином.
RoyalBingBong

Чому ви рекомендуєте використовувати requireзамість <script src="..."></script>? Це також залишається без відповіді питання тут .
bluenote10

@ bluenote10 Webpack відповідає на це запитання : важко сказати, від чого залежить сценарій, порядком залежностей потрібно керувати, а непотрібний код все одно буде завантажений і виконаний.
haykam

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 ~/');

І ваш домашній каталог зник.

Рішення
Виставляйте замість усього лише те, що вам потрібно. Це досягається попереднім завантаженням коду javascript з requireінструкціями.

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

Вибачте за пізню відповідь. Для цього я використовую код нижче.

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

І використовувати nodeRequireзамість використання require.

Це чудово працює.


Поділіться своїм кодом сторінки HTML.
Віджай,

-1

Для його використання потрібно ввімкнути nodeIntegration у webPreferences . Дивись нижче,

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

У електроні 5.0 ( анонс на сховищі ) відбулися невдалі зміни API . В останніх версіях nodeIntegration за замовчуванням має значення false .

Документи Завдяки інтеграції Node.js Electron, деякі додаткові символи вставляються в 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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.