Electron require () nie jest zdefiniowana


111

Tworzę aplikację Electron do własnych celów. Mój problem polega na tym, że kiedy używam funkcji węzłów na mojej stronie HTML, generuje błąd:

„require ()” nie jest zdefiniowana.

Czy istnieje sposób na wykorzystanie funkcji Node na wszystkich moich stronach HTML? Jeśli to możliwe, proszę podać przykład, jak to zrobić lub podać link. Oto zmienne, których próbuję użyć na mojej stronie HTML:

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

i to są wartości, których używam we wszystkich oknach HTML w Electron.


Odpowiedzi:


296

Począwszy od wersji 5, wartość domyślna nodeIntegrationzmieniła się z true na false. Możesz ją włączyć podczas tworzenia okna przeglądarki:

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

Ponieważ ostatnia wersja Electron ma domyślnie wartość nodeIntegration na false ze względów bezpieczeństwa, jaki jest zalecany sposób uzyskiwania dostępu do modułów węzła? Czy istnieje sposób komunikacji z głównym procesem bez nodeIntegration?
Paulo Henrique

29
@PauloHenrique nodeIntegration: truestanowi zagrożenie bezpieczeństwa tylko wtedy, gdy wykonujesz niezaufany zdalny kod w swojej aplikacji. Na przykład załóżmy, że aplikacja otwiera stronę internetową innej firmy. Stanowiłoby to zagrożenie bezpieczeństwa, ponieważ strona internetowa strony trzeciej będzie miała dostęp do środowiska wykonawczego węzła i może uruchamiać złośliwy kod w systemie plików użytkownika. W takim przypadku warto ustawić nodeIntegration: false. Jeśli Twoja aplikacja nie wyświetla żadnych zdalnych treści lub wyświetla tylko zaufane treści, ustawienie nodeIntegration: truejest prawidłowe.
xyres

1
To doprowadzało mnie do szału. Moja aplikacja po prostu nie wyświetlała żadnego błędu i nie uruchamiała mojego kodu. To było wtedy, gdy użyłem bloku try catch, aby przechwycić błąd, który w końcu mnie tu sprowadził.
Heriberto Juarez

2
@PauloHenrique - Jeśli chcesz śledzić i stworzyć bezpieczną aplikację (zgodnie z najlepszymi praktykami bezpieczeństwa), postępuj zgodnie z moją konfiguracją, jak opisuję w tym komentarzu: github.com/electron/electron/issues/9920#issuecomment-575839738
Zac

35

Ze względów bezpieczeństwa powinieneś zachować nodeIntegration: falsei użyć skryptu wstępnego ładowania, aby udostępnić tylko to, czego potrzebujesz z Node / Electron API do procesu renderowania (widoku) za pośrednictwem zmiennej okna. Z dokumentów Electron :

Skrypty ładowania wstępnego nadal mają dostęp do requirei innych funkcji Node.js.


Przykład

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
Jeśli jesteś nowicjuszem w dziedzinie elektronów, tak jak ja: plik renderera jest zwykle dołączany do html w klasyczny sposób:<script src="./renderer.js"></script>
MrAn3

23

Mam nadzieję, że ta odpowiedź przyciągnie trochę uwagi, ponieważ znaczna większość odpowiedzi pozostawia duże luki w zabezpieczeniach aplikacji electron. W rzeczywistości ta odpowiedź jest zasadniczo tym, co powinieneś zrobić, aby użyć require()w swoich aplikacjach electron. (Jest tylko nowe elektroniczne API, które sprawia, że ​​jest trochę czystsze w wersji 7).

Napisałem szczegółowe wyjaśnienie / rozwiązanie na githubie, używając najbardziej aktualnych elektronicznych require()interfejsów API, jak można coś zrobić, ale wyjaśnię tutaj pokrótce, dlaczego powinieneś zastosować podejście przy użyciu skryptu wstępnego ładowania, contextBridge i ipc.

Problem

Aplikacje Electron są świetne, ponieważ używamy węzła, ale ta moc jest mieczem obosiecznym. Jeśli nie jesteśmy ostrożni, dajemy komuś dostęp do węzła za pośrednictwem naszej aplikacji, a przy użyciu węzła zły aktor może uszkodzić twój komputer lub usunąć pliki systemu operacyjnego (między innymi, jak mi się wydaje).

Jak wspomniał @raddevus w komentarzu, jest to konieczne podczas ładowania zdalnej zawartości. Jeśli Twoja aplikacja electron jest całkowicie offline / lokalna , prawdopodobnie wystarczy, że ją włączysz nodeIntegration:true. Nadal jednak zdecydowałbym się zachować nodeIntegration:falseochronę przed przypadkowymi / złośliwymi użytkownikami korzystającymi z Twojej aplikacji i zapobiegać wszelkim możliwym złośliwym programom, które mogłoby kiedykolwiek zostać zainstalowane na Twoim komputerze, przed interakcją z Twoją aplikacją Electron i wykorzystaniem nodeIntegration:truewektora ataku (niezwykle rzadkie , ale może się zdarzyć)!

Jak wygląda problem

Ten problem objawia się, gdy (którykolwiek z poniższych):

  1. zostały nodeIntegration:truewłączone
  2. Skorzystaj z remotemodułu

Wszystkie te problemy zapewniają nieprzerwany dostęp do węzła z procesu renderowania. Jeśli twój proces renderowania zostanie kiedykolwiek przejęty, możesz uznać, że wszystko jest stracone.

Jakie jest nasze rozwiązanie

Rozwiązaniem jest, aby nie dawać rendererowi bezpośredniego dostępu do węzła (tj. require()), Ale dać naszemu głównemu procesowi Electronowi dostęp do require, i za każdym razem, gdy nasz proces renderowania potrzebuje require, zorganizować żądanie do głównego procesu.

Sposób, w jaki to działa w najnowszych wersjach (7+) Electron, polega na tym, że po stronie renderera skonfigurowaliśmy wiązania ipcRenderer , a po stronie głównej skonfigurowaliśmy wiązania ipcMain . W powiązaniach ipcMain konfigurujemy metody nasłuchujące, które używają modułów my require(). Jest to w porządku i dobrze, ponieważ nasz główny proces może zrobić requirewszystko, czego chce.

Używamy contextBridge do przekazywania powiązań ipcRenderer do naszego kodu aplikacji (do użycia), a więc gdy nasza aplikacja musi użyć requiremodułów d w main, wysyła wiadomość za pośrednictwem IPC (komunikacja między procesami) i proces główny działa trochę kodu, a następnie wysyłamy wiadomość zwrotną z naszym wynikiem.

Z grubsza , oto co chcesz zrobić.

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>

Zrzeczenie się

Jestem autorem secure-electron-templatebezpiecznego szablonu do tworzenia aplikacji Electron. Zależy mi na tym temacie i pracuję nad tym od kilku tygodni (w tym momencie).


Jestem nowym programistą ElectronJS i pracowałem przez samouczek PluralSite, który już nie działa, ponieważ zmieniły się ustawienia integracji węzłów. Twój post jest bardzo dobry i czytam również powiązany z nim post na Githubie i powiązane oficjalne dokumenty bezpieczeństwa Electron. Dokładne skonfigurowanie integracji węzłów (aby aplikacje działały poprawnie i były bezpieczne) ma wiele ruchomych części (szczególnie dla początkujących). Następujące zdanie z dokumentacji firmy Electron „Najważniejsze jest, aby nie włączać integracji Node.js w żadnym rendererze (BrowserWindow, BrowserView lub <webview>), który ładuje zdalną zawartość. ” (Podkreślenie moje)
raddevus

Zakładam, że odwrotność jest prawdą ... "jeśli BrowserWindow nie ładuje zdalnej zawartości, to można bezpiecznie dołączyć integrację węzła". Jeśli to zdanie jest prawdziwe, możesz trochę zmienić swoje posty, aby podkreślić ten punkt, ponieważ w moim przypadku mam dwie aplikacje, które należą do tej kategorii i nie muszę usuwać integracji węzłów. Dzięki za pomoc.
raddevus

1
@raddevus Dziękuję, mam nadzieję, że szablon pomoże Ci w tworzeniu bezpiecznych aplikacji electron (jeśli zdecydujesz się go używać)! Tak, masz rację, jeśli chodzi o nacisk. Jednak powiem, że wyłączenie nodeIntegrationzapobiega przypadkowemu lub celowemu wyrządzeniu szkody użytkownikowi podczas korzystania z aplikacji i jest dodatkowym zabezpieczeniem na wypadek, gdyby jakieś złośliwe oprogramowanie zostało podłączone do twojego procesu elektronicznego i było w stanie wykonać XSS, wiedząc, że ten wektor był otwarty (niewiarygodnie rzadko, ale właśnie tam poszedł mój mózg)!
Zac

1
@raddevus Dziękuję, aktualizuję moje posty, aby odzwierciedlić Twój komentarz.
Zac

9

Czy używasz nodeIntegration: falsepodczas inicjalizacji BrowserWindow? Jeśli tak, ustaw go na true(wartość domyślna to true).

I dołącz swoje zewnętrzne skrypty do kodu HTML w następujący sposób (nie jako <script> src="./index.js" </script>):

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

Używam do tego pdf js offline. Więc kiedy używam nodeIntegration: true, to PDFJS.getDocument nie pojawi się błąd funkcji. Jak ustawić nodeIntegration: true na mojej stronie html, gdy pdfjs jest całkowicie załadowany.
Mari Selvan

Czy spojrzałeś na ten przykład ? Możesz po prostu zaimportować pakiet przez var pdfjsLib = require('pdfjs-dist')i używać go w ten sposób.
RoyalBingBong

Dlaczego warto używać requirezamiast <script src="..."></script>? Ma to również pytanie bez odpowiedzi tutaj .
bluenote10

@ bluenote10 Webpack odpowiada na to pytanie : trudno powiedzieć, od czego zależy skrypt, należy zarządzać kolejnością zależności, a niepotrzebny kod będzie nadal pobierany i wykonywany.
Haykam

7

Po pierwsze, rozwiązanie @Sathiraumesh pozostawia Twoją aplikację elektronową z dużym problemem bezpieczeństwa. Wyobraź sobie, że Twoja aplikacja dodaje kilka dodatkowych funkcji messenger.com, na przykład ikona paska narzędzi zmienia się lub miga, gdy masz nieprzeczytaną wiadomość. Więc w swoim main.jspliku tworzysz nowe okno BrowserWindow w ten sposób (zauważ, że celowo błędnie napisałem messenger.com):

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

Co zrobić, jeśli messengre.comjest to złośliwa witryna, która chce zaszkodzić Twojemu komputerowi. Jeśli ustawisz, że nodeIntegration: trueta witryna ma dostęp do lokalnego systemu plików i może wykonać to:

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

Twój katalog domowy zniknął.

Rozwiązanie Wyświetlaj
tylko to, czego potrzebujesz, zamiast wszystkiego. Osiąga się to poprzez wstępne ładowanie kodu JavaScript z requireinstrukcjami.

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

Teraz okropne messengre.comnie może usunąć całego systemu plików.


-1

Wreszcie udało mi się. Dodaj ten kod do elementu skryptu dokumentu HTML.

Przepraszam za spóźnioną odpowiedź, używam poniższego kodu, aby to zrobić.

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

I używaj nodeRequirezamiast używać require.

To działa dobrze.


Udostępnij swój kod strony HTML.
Vijay

-1

Aby z niego korzystać, musisz włączyć opcję nodeIntegration w webPreferences . patrz poniżej,

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

Nastąpiła przełomowa zmiana API w elektronie 5.0 ( ogłoszenie w repozytorium ). W ostatnich wersjach nodeIntegration jest domyślnie ustawiony na false .

Dokumenty Ze względu na integrację Electron z Node.js, do modułu DOM są wstawiane dodatkowe symbole, takie jak, eksport, wymaga. Powoduje to problemy dla niektórych bibliotek, ponieważ chcą wstawiać symbole o takich samych nazwach.Aby rozwiązać ten problem, możesz wyłączyć integrację węzłów w Electron:

Ale jeśli chcesz zachować możliwość korzystania z Node.js i interfejsów API Electron, musisz zmienić nazwy symboli na stronie przed dołączeniem innych bibliotek:

<head>
    <script>
        window.nodeRequire = require;
        delete window.require;
        delete window.exports;
        delete window.module;
    </script>
    <script type="text/javascript" src="jquery.js"></script>
</head>
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.