Electron IPC-Kommunikation mit echten Beispielen erklärt

Electron Interprozess-Kommunikationsdiagramm

Electron ist eines der beliebtesten Frameworks zur Erstellung plattformübergreifender Desktop-Anwendungen unter Verwendung von Webtechnologien wie HTML, CSS und JavaScript. Unter der Haube nutzt Electron eine Multi-Prozess-Architektur, bestehend aus einem Hauptprozess (der Node.js ausführt) und einem oder mehreren Renderer-Prozessen (die Chromium zur Darstellung der Benutzeroberfläche ausführen).

Aus Sicherheitsgründen isolieren moderne Electron-Anwendungen den Renderer-Prozess vom Betriebssystem. Dies bedeutet, dass Sie nicht direkt von der Renderer-Benutzeroberfläche auf Node.js-Module oder Systemressourcen (wie das Lesen von Dateien oder das Abfragen von Datenbanken) zugreifen können.

Um diese Lücke sicher zu schließen, verwendet Electron die Interprozess-Kommunikation (IPC).

In diesem Leitfaden erklären wir, wie Electron IPC funktioniert, und untersuchen die drei grundlegenden Kommunikationsmuster anhand von echten, produktionsreifen Codebeispielen.


1. Renderer zu Hauptprozess (Einweg / One-Way)

Dieses Muster wird verwendet, wenn der Renderer einen Befehl oder eine Aktion an den Hauptprozess senden möchte, ohne auf eine Antwort zu warten. Ein typisches Beispiel ist das Klicken auf eine Schaltfläche in der Benutzeroberfläche, um das Anwendungsfenster zu minimieren oder zu schließen.

Sehen wir uns an, wie dies in den drei Schlüsseldateien implementiert wird: main.js, preload.js und renderer.js.

Hauptprozess (main.js)

Wir verwenden ipcMain.on, um auf Ereignisse aus dem Renderer-Prozess zu hören.

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  });
  win.loadFile('index.html');
}

// Auf das 'close-app'-Ereignis des Renderers hören
ipcMain.on('close-app', () => {
  app.quit();
});

Preload-Skript (preload.js)

Wir verwenden contextBridge.exposeInMainWorld, um dem Renderer einen sicheren Wrapper bereitzustellen, ohne das gesamte ipcRenderer-Modul offenzulegen.

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  closeApp: () => ipcRenderer.send('close-app')
});

Renderer-Prozess (renderer.js)

Wir rufen die auf dem window-Objekt bereitgestellte Funktion auf.

const closeButton = document.getElementById('close-btn');

closeButton.addEventListener('click', () => {
  window.electronAPI.closeApp();
});

2. Renderer zu Hauptprozess (Zweiwege / Anforderung-Antwort)

Dieses Muster wird verwendet, wenn der Renderer Daten anfordern oder eine Systemoperation im Hauptprozess auslösen und auf das Ergebnis warten muss (z. B. das Lesen einer Datei oder eine sichere Datenbankabfrage).

Wir verwenden ipcMain.handle im Hauptprozess und ipcRenderer.invoke im Preload-Skript.

Hauptprozess (main.js)

Auf die Anforderung mit ipcMain.handle hören und die Daten asynchron zurückgeben.

const { ipcMain } = require('electron');
const fs = require('fs/promises');

// Die 'read-file'-Anforderung asynchron verarbeiten
ipcMain.handle('read-file', async (event, filePath) => {
  try {
    const data = await fs.readFile(filePath, 'utf-8');
    return { success: true, content: data };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

Preload-Skript (preload.js)

Einen asynchronen Wrapper bereitstellen, der ein Promise zurückgibt.

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  readFile: (filePath) => ipcRenderer.invoke('read-file', filePath)
});

Renderer-Prozess (renderer.js)

Verwenden Sie await, um auf die Auflösung des zurückgegebenen Promises zu warten.

const readBtn = document.getElementById('read-btn');

readBtn.addEventListener('click', async () => {
  const result = await window.electronAPI.readFile('/path/to/file.txt');
  if (result.success) {
    console.log('Dateiinhalt:', result.content);
  } else {
    console.error('Fehler beim Lesen der Datei:', result.error);
  }
});

3. Hauptprozess zu Renderer (Einweg-Benachrichtigung)

Dieses Muster wird verwendet, wenn der Hauptprozess Aktualisierungen oder Benachrichtigungen an den Renderer-Prozess senden muss (z. B. Aktualisierungen der Download-Fortschrittsleiste, Klicks auf Anwendungsmenüs oder Statusaktualisierungen von Hintergrunddiensten).

Wir verwenden webContents.send im Hauptprozess und ipcRenderer.on im Preload-Skript.

Hauptprozess (main.js)

Die webContents des aktiven Fensters abrufen und die Nachricht senden.

// Beispiel: Senden von Download-Fortschrittsaktualisierungen
function trackDownloadProgress(mainWindow) {
  let progress = 0;
  const interval = setInterval(() => {
    progress += 10;
    mainWindow.webContents.send('download-progress', progress);
    
    if (progress >= 100) {
      clearInterval(interval);
    }
  }, 1000);
}

Preload-Skript (preload.js)

Eine Abonnementmethode bereitstellen, die eine Callback-Funktion entgegennimmt. Es ist ratsam, eine Bereinigungsfunktion zurückzugeben, um den Listener zu entfernen.

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  onDownloadProgress: (callback) => {
    const subscription = (event, value) => callback(value);
    ipcRenderer.on('download-progress', subscription);
    
    // Bereinigungsfunktion zurückgeben, um Speicherlecks zu vermeiden
    return () => {
      ipcRenderer.removeListener('download-progress', subscription);
    };
  }
});

Renderer-Prozess (renderer.js)

Die Ereignisse abonnieren und die Benutzeroberfläche aktualisieren.

const progressBar = document.getElementById('progress-bar');

const unsubscribe = window.electronAPI.onDownloadProgress((progress) => {
  progressBar.style.width = `${progress}%`;
  progressBar.textContent = `${progress}%`;
  
  if (progress === 100) {
    console.log('Download abgeschlossen!');
    unsubscribe(); // Listener bereinigen, um Speicherlecks zu verhindern
  }
});

4. Sicherheits-Best-Practices

Bei der Arbeit mit Electron IPC sollte Sicherheit Ihre oberste Priorität sein. Schadcode, der in den Renderer-Prozess eingeschleust wird, kann das gesamte Betriebssystem gefährden, wenn IPC nicht gesichert ist.

Befolgen Sie diese wichtigen Regeln:

  1. Exponieren Sie ipcRenderer niemals direkt: Schreiben Sie nicht contextBridge.exposeInMainWorld('electron', ipcRenderer). Dies gibt dem Renderer uneingeschränkten Zugriff auf das Senden beliebiger IPC-Nachrichten und umgeht Sicherheitsbarrieren.
  2. Kontextisolierung aktiviert lassen: Stellen Sie in den webPreferences immer contextIsolation: true und nodeIntegration: false ein.
  3. Eingaben validieren: Validieren Sie Argumente, die im Hauptprozess empfangen werden (wie Dateipfade oder Datenbankabfragen), immer vor der Ausführung.
  4. Bevorzugen Sie ipcMain.handle gegenüber ipcMain.on + webContents.send: Für Anforderung-Antwort-Muster ist invoke/handle sauberer, löst Promises nativ auf und vermeidet das Vermischen mehrerer Listener-Callbacks.

Fazit

Das Verständnis der IPC-Kommunikation ist der Schlüssel zur Erstellung sicherer, performanter und robuster Electron-Anwendungen. Indem Sie das richtige Muster für die jeweilige Aufgabe verwenden und die Kontextisolierung erzwingen, können Sie die volle Leistung von Node.js auf dem Desktop nutzen und gleichzeitig Ihre Benutzer schützen.


Erkunden Sie weitere Entwickler-Einblicke im Ghaznix-Blog →