شرح اتصالات Electron IPC مع أمثلة حقيقية
يعد إطار عمل Electron أحد أشهر أطر العمل لبناء تطبيقات سطح المكتب متعددة الأنظمة باستخدام تقنيات الويب مثل HTML و CSS و JavaScript. تحت الغطاء، يعتمد إطار العمل على بنية متعددة العمليات تتكون من العملية الرئيسية (Main Process) (التي تشغل Node.js) وعملية واحدة أو أكثر من عمليات العرض (Renderer Processes) (التي تشغل 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.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 في نص التحميل المسبق.
العملية الرئيسية (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);
}
});
3. من العملية الرئيسية إلى عملية العرض (إشعار باتجاه واحد)
يُستخدم هذا النمط عندما تحتاج العملية الرئيسية إلى إرسال تحديثات أو إشعارات إلى عملية العرض (على سبيل المثال، تحديثات شريط تقدم التنزيل، أو نقرات قوائم التطبيق، أو تحديثات حالة خدمة الخلفية).
نستخدم 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). يُنصح بإرجاع دالة تنظيف لإزالة المستمع.
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، يجب أن يكون الأمان هو أولويتك القصوى. يمكن للكود الخبيث الذي يتم حقنه في عملية العرض تعريض نظام التشغيل بأكمله للخطر إذا لم يتم تأمين ميزة IPC بشكل صحيح.
اتبع القواعد الهامة التالية:
- لا تعرض
ipcRendererمباشرة أبدًا: لا تكتبcontextBridge.exposeInMainWorld('electron', ipcRenderer). يمنح القيام بذلك عملية العرض وصولاً كاملاً لإرسال أي رسالة IPC، متجاوزًا الحدود الأمنية. - حافظ على تمكين عزل السياق (Context Isolation): قم دائمًا بضبط
contextIsolation: trueوnodeIntegration: falseفي إعداداتwebPreferences. - التحقق من صحة المدخلات: تحقق دائمًا من صحة المعاملات المستلمة في العملية الرئيسية (مثل مسارات الملفات أو استعلامات قاعدة البيانات) قبل تنفيذها.
- تفضيل
ipcMain.handleعلىipcMain.on+webContents.send: بالنسبة لأنماط الطلب والاستجابة، فإنinvoke/handleأنظف، ويحل الـ Promises بشكل طبيعي، ويتجنب خلط مستمعي الاستدعاء المتعددين.
خاتمة
فهم اتصالات IPC هو المفتاح لبناء تطبيقات Electron آمنة وذات أداء عالٍ وقوية. باستخدام النمط المناسب لكل مهمة وفرض عزل السياق، يمكنك تسخير القوة الكاملة لـ Node.js على نظام التشغيل مع الحفاظ على أمان مستخدميك.