主进程与渲染进程通信

深入理解 Electron 中的 IPC 通信机制、contextBridge 的使用、以及如何设计安全的进程间通信 API。


IPC 的必要性

在 Electron 中,渲染进程默认不能直接访问 Node.js 和系统原生能力。如果渲染进程需要读写文件、访问数据库、调用系统 API,必须通过 IPC 让主进程代为执行。

这种设计出于安全考虑——让渲染进程成为一个"沙箱",只运行纯粹的 Web 代码。


IPC 的两种模式

1. 双向请求-响应模式(invoke/handle)

// 主进程注册 handler
const { ipcMain } = require('electron');

ipcMain.handle('db:query', async (event, sql) => {
  const db = require('better-sqlite3')('myapp.db');
  return db.prepare(sql).all();
});
// 渲染进程发起调用
const results = await window.electronAPI.query('SELECT * FROM users');

2. 单向消息模式(send/on)

// 渲染进程发送消息
ipcRenderer.send('log:info', { message: 'User clicked button' });

// 主进程监听
ipcMain.on('log:info', (event, data) => {
  console.log('Log:', data.message);
  // 不回复
});

contextBridge:安全 API 暴露

contextBridge 允许你在渲染进程的全局对象上安全地暴露 API:

// preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('api', {
  // 文件操作
  readFile: (path) => ipcRenderer.invoke('file:read', path),
  writeFile: (path, content) =>
    ipcRenderer.invoke('file:write', path, content),

  // 数据库操作
  query: (sql) => ipcRenderer.invoke('db:query', sql),

  // 系统信息
  getPlatform: () => process.platform,

  // 事件订阅
  onNotification: (callback) => {
    const handler = (event, data) => callback(data);
    ipcRenderer.on('notification', handler);
    return () => ipcRenderer.removeListener('notification', handler);
  }
});

设计良好的 IPC API

原则一:暴露最小权限

只暴露应用真正需要的 API,不要暴露所有 Node.js 能力:

// 错误:暴露了整个 child_process
contextBridge.exposeInMainWorld('api', {
  exec: (cmd) => require('child_process').exec(cmd) // 危险!
});

// 正确:只暴露特定功能
contextBridge.exposeInMainWorld('api', {
  openExternal: (url) => require('electron').shell.openExternal(url)
});

原则二:验证输入

主进程的 handler 必须验证所有来自渲染进程的输入:

// 错误:直接使用用户输入的路径
ipcMain.handle('file:read', async (event, filePath) => {
  return require('fs').readFileSync(filePath); // 危险!
});

// 正确:验证并限制路径
ipcMain.handle('file:read', async (event, filePath) => {
  const allowedDir = app.getPath('userData');
  const resolved = require('path').resolve(filePath);
  if (!resolved.startsWith(allowedDir)) {
    throw new Error('Access denied');
  }
  return require('fs').readFileSync(resolved);
});

IPC 与 Vue/React 集成

Vue 插件方式

// electronApi.js
import { ipcRenderer } from 'electron';

export default {
  install(Vue) {
    Vue.prototype.$electron = {
      readFile: (path) => ipcRenderer.invoke('file:read', path),
      writeFile: (path, content) =>
        ipcRenderer.invoke('file:write', path, content)
    };
  }
};

// main.js
Vue.use(electronApi);

// 组件中使用
this.$electron.readFile('/some/path');

React Hook 方式

// useElectron.js
import { useState, useEffect } from 'react';
import { ipcRenderer } from 'electron';

export function useElectron() {
  return {
    readFile: (path) => ipcRenderer.invoke('file:read', path),
    writeFile: (path, content) =>
      ipcRenderer.invoke('file:write', path, content)
  };
}

// 组件中使用
const { readFile } = useElectron();

这一章想说的

IPC 是 Electron 的核心通信机制:

  1. invoke/handle:请求-响应模式,适合需要返回值的操作
  2. send/on:单向消息模式,适合通知类操作
  3. contextBridge:安全暴露 API 的方式

安全设计原则:最小权限 + 输入验证


延展阅读