Реализация межпроцессного взаимодействия (IPC) в Electron-приложении

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация межпроцессного взаимодействия (IPC) в Electron-приложении
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1240
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1167
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    867
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1084
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    847

Реализация межпроцессного взаимодействия (IPC) в Electron-приложении

IPC (Inter-Process Communication) в Electron — это единственный безопасный способ дать renderer-процессу доступ к системным ресурсам. Renderer работает в изолированном Chromium-контексте; всё, что связано с файловой системой, нативными API и Node.js — только через IPC и preload script.

Архитектура безопасного IPC

Renderer (Chromium)
    ↓ window.electronAPI.someMethod()
Preload Script (contextBridge)
    ↓ ipcRenderer.invoke('channel', payload)
Main Process (Node.js)
    ↓ ipcMain.handle('channel', handler)

Preload script — граница безопасности. Он экспонирует в renderer ровно то API, которое нужно, и ничего лишнего.

Preload: типизированный мост

// main/preload.ts
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';

// Типы для статического анализа
type FileInfo = { path: string; content: string; size: number };
type UpdateInfo = { version: string; releaseNotes?: string };

const api = {
  // invoke — запрос с ответом (аналог HTTP request/response)
  fs: {
    readFile: (path: string): Promise<FileInfo> =>
      ipcRenderer.invoke('fs:readFile', path),

    writeFile: (path: string, content: string): Promise<void> =>
      ipcRenderer.invoke('fs:writeFile', path, content),

    showOpenDialog: (options: Electron.OpenDialogOptions): Promise<string[] | null> =>
      ipcRenderer.invoke('dialog:showOpen', options),

    showSaveDialog: (options: Electron.SaveDialogOptions): Promise<string | null> =>
      ipcRenderer.invoke('dialog:showSave', options),
  },

  app: {
    getVersion: (): Promise<string> =>
      ipcRenderer.invoke('app:getVersion'),

    getPath: (name: string): Promise<string> =>
      ipcRenderer.invoke('app:getPath', name),
  },

  window: {
    // send — одностороннее сообщение без ответа
    minimize: () => ipcRenderer.send('window:minimize'),
    maximize: () => ipcRenderer.send('window:maximize'),
    close: () => ipcRenderer.send('window:close'),

    setTitle: (title: string) => ipcRenderer.send('window:setTitle', title),
  },

  // Подписка на события из main процесса
  on: {
    updateAvailable: (callback: (info: UpdateInfo) => void) => {
      const listener = (_: IpcRendererEvent, info: UpdateInfo) => callback(info);
      ipcRenderer.on('update:available', listener);
      // Возвращаем функцию для отписки
      return () => ipcRenderer.removeListener('update:available', listener);
    },

    networkChange: (callback: (isOnline: boolean) => void) => {
      const listener = (_: IpcRendererEvent, isOnline: boolean) => callback(isOnline);
      ipcRenderer.on('network:change', listener);
      return () => ipcRenderer.removeListener('network:change', listener);
    },
  }
};

contextBridge.exposeInMainWorld('electronAPI', api);

// Тип для использования в renderer
export type ElectronAPI = typeof api;

Типизация в renderer

// renderer/types/electron.d.ts
import type { ElectronAPI } from '../../main/preload';

declare global {
  interface Window {
    electronAPI: ElectronAPI;
  }
}

Main: обработчики IPC

// main/ipc-handlers.js
const { ipcMain, app, dialog, BrowserWindow } = require('electron');
const fs = require('fs/promises');
const path = require('path');

function registerHandlers() {
  // handle — для invoke (с ответом)
  ipcMain.handle('fs:readFile', async (event, filePath) => {
    // Валидация — renderer не должен читать произвольные файлы
    const resolvedPath = path.resolve(filePath);
    const allowedDirs = [
      app.getPath('documents'),
      app.getPath('downloads'),
      app.getPath('userData')
    ];

    const isAllowed = allowedDirs.some(dir => resolvedPath.startsWith(dir));
    if (!isAllowed) {
      throw new Error(`Access denied: ${resolvedPath}`);
    }

    const content = await fs.readFile(resolvedPath, 'utf-8');
    const stat = await fs.stat(resolvedPath);

    return {
      path: resolvedPath,
      content,
      size: stat.size
    };
  });

  ipcMain.handle('fs:writeFile', async (event, filePath, content) => {
    await fs.writeFile(filePath, content, 'utf-8');
  });

  ipcMain.handle('dialog:showOpen', async (event, options) => {
    const win = BrowserWindow.fromWebContents(event.sender);
    const result = await dialog.showOpenDialog(win, options);
    return result.canceled ? null : result.filePaths;
  });

  ipcMain.handle('dialog:showSave', async (event, options) => {
    const win = BrowserWindow.fromWebContents(event.sender);
    const result = await dialog.showSaveDialog(win, options);
    return result.canceled ? null : result.filePath;
  });

  ipcMain.handle('app:getVersion', () => app.getVersion());

  ipcMain.handle('app:getPath', (event, name) => {
    const allowed = ['home', 'appData', 'userData', 'documents', 'downloads', 'temp'];
    if (!allowed.includes(name)) throw new Error(`Path not allowed: ${name}`);
    return app.getPath(name);
  });

  // on — для send (без ответа)
  ipcMain.on('window:minimize', (event) => {
    BrowserWindow.fromWebContents(event.sender)?.minimize();
  });

  ipcMain.on('window:maximize', (event) => {
    const win = BrowserWindow.fromWebContents(event.sender);
    win?.isMaximized() ? win.unmaximize() : win?.maximize();
  });

  ipcMain.on('window:close', (event) => {
    BrowserWindow.fromWebContents(event.sender)?.close();
  });

  ipcMain.on('window:setTitle', (event, title) => {
    BrowserWindow.fromWebContents(event.sender)?.setTitle(title);
  });
}

module.exports = { registerHandlers };

Стриминг данных через IPC

Для передачи больших объёмов данных или прогресса используйте port или серию событий:

// main/ipc-handlers.js — стриминг через messageChannel
ipcMain.handle('fs:readLargeFile', async (event, filePath) => {
  const { port1, port2 } = new MessageChannelMain();

  // Отправляем port renderer-у
  event.sender.postMessage('port', null, [port1]);

  // Стримим файл через port
  const stream = require('fs').createReadStream(filePath, { encoding: 'utf8' });

  stream.on('data', (chunk) => {
    port2.postMessage({ type: 'chunk', data: chunk });
  });

  stream.on('end', () => {
    port2.postMessage({ type: 'end' });
    port2.close();
  });

  stream.on('error', (err) => {
    port2.postMessage({ type: 'error', message: err.message });
    port2.close();
  });
});
// renderer — получение порта и чтение стрима
ipcRenderer.on('port', (event) => {
  const [port] = event.ports;

  let fullContent = '';

  port.onmessage = (event) => {
    if (event.data.type === 'chunk') {
      fullContent += event.data.data;
      onProgress?.(fullContent.length);
    } else if (event.data.type === 'end') {
      onComplete(fullContent);
    } else if (event.data.type === 'error') {
      onError(event.data.message);
    }
  };

  port.start();
});

await ipcRenderer.invoke('fs:readLargeFile', path);

Broadcast из main во все окна

Когда нужно уведомить все открытые окна:

// main/broadcast.js
const { BrowserWindow } = require('electron');

function broadcast(channel, data) {
  BrowserWindow.getAllWindows().forEach(win => {
    if (!win.isDestroyed()) {
      win.webContents.send(channel, data);
    }
  });
}

module.exports = { broadcast };

// Использование в любом месте main process
const { broadcast } = require('./broadcast');
broadcast('network:change', { isOnline: true });
broadcast('settings:updated', newSettings);

Отладка IPC

В renderer DevTools (консоль):

// Временное логирование всех IPC событий
const origInvoke = ipcRenderer.invoke.bind(ipcRenderer);
ipcRenderer.invoke = (channel, ...args) => {
  console.log('[IPC→]', channel, args);
  return origInvoke(channel, ...args).then(result => {
    console.log('[IPC←]', channel, result);
    return result;
  });
};

Типичные ошибки: забыть return true в ipcMain.on при асинхронном ответе через sendResponse; передавать не сериализуемые объекты через IPC (Error объекты, функции, DOM nodes); не обрабатывать ошибки в ipcMain.handle — необработанный Promise rejection крашит обработчик и renderer получит IpcError без деталей.