توضیح ارتباطات IPC در الکترون به همراه مثال‌های واقعی

نمودار ارتباطات بین فرآیندی (IPC) در الکترون

الکترون (Electron) یکی از محبوب‌ترین فریم‌ورک‌ها برای توسعه برنامه‌های دسکتاپ چندسکویی با استفاده از فناوری‌های وب مانند HTML، CSS و جاوا اسکریپت است. در زیر پوسته، الکترون از یک معماری چند فرآیندی متشکل از یک فرآیند اصلی (Main Process) (اجراکننده Node.js) و یک یا چند فرآیند رندرکننده (Renderer Process) (اجراکننده Chromium برای نمایش رابط کاربری) استفاده می‌کند.

به دلیل ریسک‌های امنیتی، برنامه‌های مدرن الکترون فرآیند رندرکننده را از سیستم‌عامل ایزوله می‌کنند. این بدان معناست که شما نمی‌توانید مستقیماً از رابط کاربری رندرکننده به ماژول‌های Node.js یا منابع سیستم (مانند خواندن فایل‌ها یا پرس‌وجو از پایگاه داده) دسترسی داشته باشید.

برای پر کردن امن این شکاف، الکترون از ارتباطات بین فرآیندی (IPC - Inter-Process Communication) استفاده می‌کند.

در این راهنما، نحوه کارکرد IPC در الکترون را توضیح خواهیم داد و سه الگوی اصلی ارتباطی را به همراه مثال‌های کد واقعی و آماده برای استفاده در محیط عملیاتی بررسی می‌کنیم.


۱. از رندرکننده به فرآیند اصلی (یک‌طرفه / 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.js)

ما از contextBridge.exposeInMainWorld برای تعریف یک لفافه (wrapper) امن برای رندرکننده استفاده می‌کنیم بدون اینکه کل ماژول 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();
});

۲. از رندرکننده به فرآیند اصلی (دو‌طرفه / درخواست - پاسخ)

این الگو زمانی استفاده می‌شود که رندرکننده نیاز دارد تا داده‌هایی را درخواست کند یا یک عملیات سیستمی را در فرآیند اصلی آغاز کرده و منتظر نتیجه بماند (مثلاً خواندن یک فایل یا اجرای یک پرس‌وجوی امن از پایگاه داده).

ما از ipcMain.handle در فرآیند اصلی و ipcRenderer.invoke در اسکریپت پیش‌بارگذاری استفاده می‌کنیم.

فرآیند اصلی (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.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);
  }
});

۳. از فرآیند اصلی به رندرکننده (اعلان یک‌طرفه)

این الگو زمانی استفاده می‌شود که فرآیند اصلی نیاز دارد تا به‌روزرسانی‌ها یا اعلان‌هایی را به فرآیند رندرکننده ارسال کند (مانند به‌روزرسانی نوار پیشرفت دانلود، کلیک روی منوهای برنامه، یا به‌روزرسانی وضعیت سرویس پس‌زمینه).

ما از webContents.send در فرآیند اصلی و ipcRenderer.on در اسکریپت پیش‌بارگذاری استفاده می‌کنیم.

فرآیند اصلی (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.js)

تعریف یک متد اشتراک که یک تابع Callback دریافت می‌کند. بهتر است یک تابع پاک‌سازی (cleanup) جهت حذف شنونده (listener) بازگردانده شود.

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(); // پاک‌سازی شنونده برای جلوگیری از نشت حافظه
  }
});

۴. اقدامات امنیتی توصیه‌شده

هنگام کار با Electron IPC، امنیت باید اولویت اول شما باشد. اگر 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 تمیزتر هستند، Promiseها را به صورت نیتیو مدیریت می‌کنند و از تداخل توابع شنونده مختلف جلوگیری می‌کنند.

نتیجه‌گیری

درک ارتباطات IPC کلید توسعه برنامه‌های امن، کارآمد و پایدار در الکترون است. با استفاده از الگوی مناسب برای هر کار و اعمال ایزوله‌سازی محیط، می‌توانید از تمام قدرت Node.js روی دسکتاپ بهره‌مند شوید در حالی که امنیت کاربران خود را حفظ می‌کنید.


دیدگاه‌های بیشتری را در وبلاگ Ghaznix دنبال کنید →