Объяснение межпроцессного взаимодействия (IPC) в Electron на реальных примерах
Electron является одним из самых популярных фреймворков для создания кроссплатформенных настольных приложений с использованием веб-технологий, таких как HTML, CSS и JavaScript. Под капотом Electron работает мультипроцессная архитектура, состоящая из Основного процесса (запускающего Node.js) и одного или нескольких Процессов рендеринга (запускающих 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)
Предоставление метода подписки, который принимает функцию обратного вызова. Рекомендуется возвращать функцию очистки для удаления слушателя.
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. Рекомендации по безопасности
При работе с IPC в Electron безопасность должна быть вашим главным приоритетом. Вредоносный код, внедренный в процесс рендеринга, может скомпрометировать всю операционную систему, если IPC не защищен.
Соблюдайте эти важные правила:
- Никогда не раскрывайте
ipcRendererнапрямую: Не пишитеcontextBridge.exposeInMainWorld('electron', ipcRenderer). Это дает рендереру полный доступ к отправке любых IPC-сообщений в обход границ безопасности. - Держите изоляцию контекста включенной: Всегда устанавливайте
contextIsolation: trueиnodeIntegration: falseвwebPreferences. - Проверяйте входные данные: Всегда проверяйте аргументы, полученные в основном процессе (такие как пути к файлам или запросы к базе данных), перед их выполнением.
- Предпочитайте
ipcMain.handleвместоipcMain.on+webContents.send: Для шаблонов запрос-ответinvoke/handleявляется более чистым решением, изначально разрешает Promise и предотвращает путаницу между несколькими слушателями.
Заключение
Понимание межпроцессного взаимодействия является ключом к созданию безопасных, производительных и надежных приложений Electron. Используя правильный шаблон для задачи и обеспечивая изоляцию контекста, вы сможете использовать всю мощь Node.js на настольных компьютерах, обеспечивая при этом безопасность ваших пользователей.
Узнайте больше полезных советов для разработчиков в блоге Ghaznix →