תקשורת IPC ב-Electron מוסברת עם דוגמאות אמיתיות

דיאגרמת תקשורת בין תהליכים ב-Electron

Electron הוא אחד מפריימוורקי הפיתוח הפופולריים ביותר לבניית אפליקציות שולחן עבודה חוצות-פלטפורמות באמצעות טכנולוגיות אינטרנט כמו HTML, CSS ו-JavaScript. מתחת למכסה המנוע, Electron מריץ ארכיטקטורה מרובת-תהליכים המורכבת מתהליך ראשי (Main Process) (המריץ את Node.js) ותהליך אחד או יותר של תהליך רנדור (Renderer Process) (המריץ את Chromium כדי לרנדר את ממשק המשתמש).

בשל סיכוני אבטחה, אפליקציות Electron מודרניות מבודדות את תהליך הרנדור ממערכת ההפעלה. המשמעות היא שלא ניתן לגשת למודולים של Node.js או למשאבי מערכת (כמו קריאת קבצים או שאילתות למאגר ננתונים) ישירות מממשק המשתמש של תהליך הרנדור.

כדי לגשר על פער זה בבטחה, Electron משתמש בתקשורת בין-תהליכית (IPC).

במדריך זה, נסביר כיצד עובד מנגנון ה-IPC ב-Electron ונבחן את שלושת דפוסי התקשורת הבסיסיים ביותר עם דוגמאות קוד אמיתיות ומוכנות לייצור.


1. מתהליך רנדור לתהליך הראשי (חד-כיווני / One-Way)

דפוס זה משמש כאשר תהליך הרנדור רוצה לשלוח פקודה או פעולה לתהליך הראשי מבלי להמתין לתגובה כלשהי. דוגמה נפוצה היא לחיצה על כפתור בממשק המשתמש כדי למזער או לסגור את חלון האפליקציה.

בואו נראה כיצד זה מיושם בשלושת הקבצים המרכזיים: main.js, preload.js, ו-renderer.js.

התהליך הראשי (main.js)

אנו משתמשים ב-ipcMain.on כדי להאזין לאירועים מתהליך הרנדור.

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');
}

// האזנה לאירוע 'close-app' מתהליך הרנדור
ipcMain.on('close-app', () => {
  app.quit();
});

סקריפט preload (preload.js)

אנו משתמשים ב-contextBridge.exposeInMainWorld כדי לחשוף מעטפת בטוחה לתהליך הרנדור מבלי לחשוף את כל מודول ipcRenderer.

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

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

תהליך הרנדור (renderer.js)

אנו קוראים לפונקציה שנחשפה על אובייקט ה-window.

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

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

2. מתהליך רנדור לתהליך הראשי (דו-כיווני / בקשה-תגובה)

דפוס זה משמש כאשר תהליך הרנדור צריך לבקש נתונים או להפעיל פעולת מערכת מהתהליך הראשי ולהמתין לתוצאה (למשל, קריאת קובץ או ביצוע שאילתת מסד נתונים מאובטחת).

אנו משתמשים ב-ipcMain.handle בתהליך הראשי וב-ipcRenderer.invoke בסקריפט ה-Preload.

התהליך הראשי (main.js)

האזנה לבקשה באמצעות ipcMain.handle והחזרת הנתונים באופן אסינכרוני.

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

// טיפול בקריאת 'read-file' באופן אסינכרוני
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 (preload.js)

חשיפת מעטפת אסינכרונית שמחزירה Promise.

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

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

תהליך הרנדור (renderer.js)

שימוש ב-await כדי להמתין לפענוח ה-Promise שמוחזר.

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('תוכן הקובץ:', result.content);
  } else {
    console.error('קריאת הקובץ נכשלה:', result.error);
  }
});

3. מהתהליך הראשי לתהליך הרנדור (עדכון חד-כיווני)

דפוס זה משמש כאשר התהליך הראשי צריך לשלוح עדכונים או התראות לתהליך הרנדור (למשל, עדכוני סרגל התקדמות הורדה, לחיצות על תפריטי אפליקציה, או עדכוני סטטוס של שירות רקע).

אנו משתמשים ב-webContents.send בתהליך הראשי וב-ipcRenderer.on בסקריפט ה-Preload.

התהליך הראשי (main.js)

קבלת ה-webContents של החלון הפעיל ושליחת ההודעה.

// דוגמה: שליחת עדכוני התקדמות הורדה
function trackDownloadProgress(mainWindow) {
  let progress = 0;
  const interval = setInterval(() => {
    progress += 10;
    mainWindow.webContents.send('download-progress', progress);
    
    if (progress >= 100) {
      clearInterval(interval);
    }
  }, 1000);
}

סקריפט preload (preload.js)

חשיפת מתודת הרשמה שמקבלת פונקציית callback. מומלץ להחזיר פונקציית ניקוי כדי להסיר את המאזין.

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

contextBridge.exposeInMainWorld('electronAPI', {
  onDownloadProgress: (callback) => {
    const subscription = (event, value) => callback(value);
    ipcRenderer.on('download-progress', subscription);
    
    // החזרת פונקציית ניקוי למניעת דליפות זיכרון
    return () => {
      ipcRenderer.removeListener('download-progress', subscription);
    };
  }
});

תהליך הרנדור (renderer.js)

הרשמה לאירועים ועדכון ממשק המשתמש בהתאם.

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

const unsubscribe = window.electronAPI.onDownloadProgress((progress) => {
  progressBar.style.width = `${progress}%`;
  progressBar.textContent = `${progress}%`;
  
  if (progress === 100) {
    console.log('ההורדה הושלמה!');
    unsubscribe(); // ניקוי המאזין למניעת דליפות זיכרון
  }
});

4. שיטות עבודה מומלצות לאבטחה

בעבודה עם Electron IPC, האבטחה צריכה להיות בעדיפות עliונה. קוד זדוני שמוזרק לתהליך הרנדור עלול לסכן את מערכת ההפעלה כולה אם ה-IPC אינו מאובטח כראوي.

הקפידו על הכללים החשובים הבאים:

  1. לעולם אל תחשפו את ipcRenderer ישירות: אל תכתבו contextBridge.exposeInMainWorld('electron', ipcRenderer). פעולה זו מעניקה לתהליך הרנדור גישה מלאה לשלוח כל הודעת IPC שהיא, ובכך לעקוף את גבולות האבטחה.
  2. השאירו את בידוד ההקשר (Context Isolation) פעיל: הגדירו תמיד contextIsolation: true ו-nodeIntegration: false בתוך ה-webPreferences.
  3. בצעו וולידציה לקלטים: בצעו תמיד וולידציה לארגומנטים המתקבלים בתהליך הראشی (כמו נתיבי קבצים או שאילתות למסדי נתונים) לפני ביצועם.
  4. העדיפו את ipcMain.handle על פני ipcMain.on + webContents.send: עבור דפוסי בקשה-תגובה, השימוש ב-invoke/handle נקי יותר, פותר Promises באופן טבעי ומונע ערבוב של פונקציות האזנה רבות.

סיכום

הבנת תקשורת ה-IPC היא המפתח לבניית אפליקציות Electron מאובטחות, מהירות ויציבות. באמצעות שימוש בדפוס הנכון לכל משימה ואכיפת בידود הקשר, תוכלו לרתום את מלוא העוצמה של Node.js בסביבת שולחن העבודה תוך שמירה על ביטחון המשתמשים שלכם.


גלה תובנות נוספות למפתחים בבלוג של Ghaznix →