Electron require () не определен


111

Я создаю приложение Electron для своих собственных целей. Моя проблема в том, что когда я использую функции узла внутри своей HTML-страницы, возникает ошибка:

'require ()' не определен.

Есть ли способ использовать функции узла на всех моих HTML-страницах? Если возможно, дайте мне пример, как это сделать, или дайте ссылку. Вот переменные, которые я пытаюсь использовать на своей HTML-странице:

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

и это значения, которые я использую во всех своих окнах HTML в Electron.


Ответы:


296

Начиная с версии 5, значение по умолчанию для nodeIntegrationизменено с true на false. Вы можете включить его при создании окна браузера:

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

Поскольку в последней версии Electron по умолчанию для nodeIntegration установлено значение false из соображений безопасности, какой способ доступа к модулям узлов рекомендуется использовать? Есть ли способ общаться с основным процессом без nodeIntegration?
Пауло Энрике

29
@PauloHenrique nodeIntegration: trueпредставляет собой угрозу безопасности только тогда, когда вы выполняете ненадежный удаленный код в своем приложении. Например, предположим, что ваше приложение открывает стороннюю веб-страницу. Это было бы угрозой безопасности, потому что сторонняя веб-страница будет иметь доступ к среде выполнения узла и может запускать вредоносный код в файловой системе вашего пользователя. В таком случае имеет смысл установить nodeIntegration: false. Если ваше приложение не отображает какой-либо удаленный контент или отображает только доверенный контент, тогда настройка nodeIntegration: trueв порядке.
xyres

1
Это сводило меня с ума. Мое приложение просто не показывало ошибок и не запускало мой код. Это было тогда, когда я использовал блок try catch, чтобы перехватить ошибку, которая наконец привела меня сюда.
Эриберто Хуарес

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

35

По соображениям безопасности вы должны сохранить 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

23

Я надеюсь, что этот ответ привлечет некоторое внимание, потому что подавляющее большинство ответов здесь оставляют большие дыры в безопасности в вашем электронном приложении. Фактически, этот ответ - это то, что вы должны делать, чтобы использовать require()в своих электронных приложениях. (Есть только новый электронный API, который делает его немного чище в версии 7).

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

Эта проблема

Приложения Electron великолепны, потому что мы можем использовать узел, но эта сила - палка о двух концах. Если мы не будем осторожны, мы дадим кому-то доступ к узлу через наше приложение, и с помощью узла злоумышленник может повредить вашу машину или удалить файлы вашей операционной системы (среди прочего, я полагаю).

Как отметил @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 не загружает удаленный контент, можно безопасно включить интеграцию узла». Если это предложение верно, вы можете немного изменить свои сообщения, чтобы подчеркнуть этот момент, потому что в моем случае у меня есть два приложения, которые попадают в эту категорию, и мне не нужно удалять интеграцию узлов. Спасибо за вашу помощь.
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-документ Script Element.

Извините за поздний ответ. Для этого я использую приведенный ниже код.

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()

Произошли критические изменения api в электронном 5.0 ( Объявление в репозитории ). В последних версиях для nodeIntegration по умолчанию установлено значение false .

Документы Из-за интеграции Electron с Node.js в DOM вставлены некоторые дополнительные символы, такие как module, exports, require. Это вызывает проблемы для некоторых библиотек, поскольку они хотят вставить символы с одинаковыми именами. Чтобы решить эту проблему, вы можете отключить интеграцию узлов в Electron:

Но если вы хотите сохранить возможность использования API-интерфейсов Node.js и Electron, вам необходимо переименовать символы на странице, прежде чем включать другие библиотеки:

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