Skip to content

IPC 通信

通信架构

Preview

IPC Manager

管理器架构

IPC Manager 负责统一管理所有 IPC 处理器的注册、生命周期和错误处理:

javascript
class IpcManager {
  // Handler 配置表
  static HANDLERS = [
    {
      name: 'NativeFocus',
      handler: NativeFocusIpcHandler,
      priority: 1, // 最高优先级
      timeout: 2000, // 超时时间(毫秒)
      required: true, // 是否必需
    },
    {
      name: 'FloatingBall',
      handler: FloatingBallIpcHandler,
      priority: 1,
      timeout: 2000,
      required: false,
    },
    {
      name: 'Print',
      handler: PrintIpcHandler,
      priority: 2,
      timeout: 3000,
      required: false,
    },
    {
      name: 'Plugin',
      handler: PluginIpcHandler,
      priority: 2,
      timeout: 5000,
      required: false,
    },
    {
      name: 'Machine',
      handler: MachineIpcHandler,
      priority: 2,
      timeout: 15000,
      required: false,
    },
  ];
}

配置项说明

配置项说明示例
nameHandler 名称'Machine'
handlerHandler 类MachineIpcHandler
priority优先级(1最高)1, 2
timeout注册超时时间(ms)2000
required是否必需(失败时终止启动)true, false

注册流程

javascript
/**
 * 注册所有 IPC 处理器(按优先级分组并行)
 */
static async registerHandlers() {
  const startTime = Date.now();

  try {
    // 按优先级分组
    const priorityGroups = this.HANDLERS.reduce((groups, handler) => {
      const priority = handler.priority;
      if (!groups[priority]) groups[priority] = [];
      groups[priority].push(handler);
      return groups;
    }, {});

    // 按优先级顺序执行,同一优先级并行注册
    const allResults = [];
    for (const priority of Object.keys(priorityGroups).sort()) {
      const handlers = priorityGroups[priority];

      logger.info(`注册优先级 ${priority} 的 ${handlers.length} 个 Handler (并行)...`);

      // 同一优先级的 Handler 并行注册
      const results = await Promise.allSettled(
        handlers.map((config) => this.registerHandler(config))
      );

      // 提取结果
      const groupResults = results.map((result, index) => {
        if (result.status === 'fulfilled') {
          return result.value;
        } else {
          const config = handlers[index];
          return {
            success: false,
            name: config.name,
            error: result.reason.message,
            required: config.required,
          };
        }
      });

      allResults.push(...groupResults);

      // 检查是否有必需的 Handler 失败
      const criticalFailure = groupResults.find((r) => !r.success && r.required);
      if (criticalFailure) {
        logger.error(`关键 Handler [${criticalFailure.name}] 注册失败,终止启动`);
        await this.removeHandlers();
        throw new Error(`关键 IPC Handler 注册失败: ${criticalFailure.error}`);
      }
    }

    // 汇总统计
    const successCount = allResults.filter((r) => r.success).length;
    const failedCount = allResults.filter((r) => !r.success).length;
    const elapsed = Date.now() - startTime;

    logger.info(
      `IPC Handler 注册完成: 成功 ${successCount}/${this.HANDLERS.length}, ` +
      `失败 ${failedCount}, 耗时 ${elapsed}ms`
    );

    // 记录失败的非必需 Handler
    allResults
      .filter((r) => !r.success)
      .forEach((r) => logger.warn(`非关键 Handler [${r.name}] 已降级运行: ${r.error}`));
  } catch (error) {
    logger.error('IPC 处理器注册失败:', error);
    await this.removeHandlers();
    throw error;
  }
}

超时保护

javascript
/**
 * 超时包装器
 */
static withTimeout(promise, ms, name) {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error(`${name} 注册超时 (${ms}ms)`)), ms)
    )
  ]);
}

错误隔离

javascript
/**
 * 注册单个 Handler(带错误隔离)
 */
static async registerHandler(config) {
  const { name, handler, timeout, required } = config;
  const startTime = Date.now();

  try {
    logger.info(`开始注册 ${name} Handler...`);

    // 带超时保护的注册
    await this.withTimeout(handler.register(), timeout, name);

    // 记录成功状态
    this.registeredHandlers.add(name);
    const elapsed = Date.now() - startTime;
    logger.info(`${name} Handler 注册成功 (${elapsed}ms)`);

    return { success: true, name };
  } catch (error) {
    const elapsed = Date.now() - startTime;
    logger.error(`${name} Handler 注册失败 (${elapsed}ms):`, error);

    // 必需的 Handler 失败,向上抛出错误
    if (required) {
      throw new Error(`关键 Handler [${name}] 注册失败: ${error.message}`);
    }

    // 非必需的失败,记录但不中断流程
    return { success: false, name, error: error.message };
  }
}

Handler 实现规范

基本结构

每个 IPC Handler 应该实现以下方法:

javascript
class ExampleIpcHandler {
  /**
   * 注册 IPC 处理器
   */
  static async register() {
    // 注册 IPC 监听器
    ipcMain.handle('example-channel', this.handleExample);
    ipcMain.on('example-event', this.handleEvent);
  }

  /**
   * 移除 IPC 处理器
   */
  static removeHandlers() {
    // 移除所有监听器
    ipcMain.removeHandler('example-channel');
    ipcMain.removeListener('example-event', this.handleEvent);
  }

  /**
   * 处理 IPC 请求
   */
  static async handleExample(event, data) {
    try {
      // 处理逻辑
      return { success: true, data: result };
    } catch (error) {
      logger.error('处理失败:', error);
      return { success: false, error: error.message };
    }
  }

  /**
   * 处理 IPC 事件
   */
  static handleEvent(event, data) {
    // 事件处理逻辑
  }
}

IPC 通信模式

1. 单向通信(Renderer → Main)

javascript
// 主进程
ipcMain.on('channel-name', (event, data) => {
  console.log('收到消息:', data);
});

// 渲染进程(通过 preload)
ipcRenderer.send('channel-name', { message: 'Hello' });

2. 请求-响应(Renderer → Main → Renderer)

javascript
// 主进程
ipcMain.handle('channel-name', async (event, data) => {
  const result = await processData(data);
  return result;
});

// 渲染进程(通过 preload)
const result = await ipcRenderer.invoke('channel-name', { query: 'data' });

3. 主进程推送(Main → Renderer)

javascript
// 主进程
win.webContents.send('channel-name', { message: 'Update' });

// 渲染进程(通过 preload)
ipcRenderer.on('channel-name', (event, data) => {
  console.log('收到推送:', data);
});

4. 双向通信

javascript
// 主进程发送并等待响应
win.webContents.send('request-channel', { data });
ipcMain.once('response-channel', (event, response) => {
  console.log('收到响应:', response);
});

// 渲染进程
ipcRenderer.on('request-channel', (event, data) => {
  // 处理并响应
  ipcRenderer.send('response-channel', { result });
});

Preload 脚本

安全的 API 暴露

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

// 暴露安全的 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
  // 发送消息到主进程
  send: (channel, data) => {
    const validChannels = ['channel-1', 'channel-2'];
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, data);
    }
  },

  // 调用主进程方法并获取返回值
  invoke: async (channel, data) => {
    const validChannels = ['get-data', 'process-data'];
    if (validChannels.includes(channel)) {
      return await ipcRenderer.invoke(channel, data);
    }
  },

  // 监听主进程消息
  on: (channel, callback) => {
    const validChannels = ['update-status', 'notification'];
    if (validChannels.includes(channel)) {
      const subscription = (event, ...args) => callback(...args);
      ipcRenderer.on(channel, subscription);

      // 返回取消订阅函数
      return () => {
        ipcRenderer.removeListener(channel, subscription);
      };
    }
  },

  // 监听一次
  once: (channel, callback) => {
    const validChannels = ['init-complete'];
    if (validChannels.includes(channel)) {
      ipcRenderer.once(channel, (event, ...args) => callback(...args));
    }
  },
});

渲染进程使用

javascript
// 在网页中使用
// 发送消息
window.electronAPI.send('channel-1', { data: 'value' });

// 调用方法
const result = await window.electronAPI.invoke('get-data', { id: 123 });

// 监听消息
const unsubscribe = window.electronAPI.on('update-status', (status) => {
  console.log('状态更新:', status);
});

// 取消监听
unsubscribe();

内置 IPC Handlers

1. MachineIpcHandler - 机器信息

javascript
// 主进程
ipcMain.handle('get-machine-info', async () => {
  return MachineService.getMachineInfoData();
});

// 渲染进程
const machineInfo = await window.electronAPI.invoke('get-machine-info');
console.log('机器信息:', machineInfo);

2. NativeFocusIpcHandler - 焦点处理

javascript
// 主进程
ipcMain.on('set-native-focus', (event, options) => {
  NativeFocusHandler.setFocus(options);
});

// 渲染进程
window.electronAPI.send('set-native-focus', { target: 'input' });

3. PrintIpcHandler - 打印功能

javascript
// 主进程
ipcMain.handle('print-page', async (event, options) => {
  return await PrintService.print(options);
});

// 渲染进程
const result = await window.electronAPI.invoke('print-page', {
  silent: false,
  printBackground: true,
});

4. PluginIpcHandler - 插件管理

javascript
// 主进程
ipcMain.handle('plugin-list', async () => {
  return await PluginLoader.getPluginList();
});

ipcMain.handle('plugin-start', async (event, pluginId) => {
  return await PluginLoader.startPlugin(pluginId);
});

// 渲染进程
const plugins = await window.electronAPI.invoke('plugin-list');
await window.electronAPI.invoke('plugin-start', 'plugin-id');

5. FloatingBallIpcHandler - 悬浮球

javascript
// 主进程
ipcMain.handle('show-floating-ball', async () => {
  FloatingBallService.showFloatingBall();
});

ipcMain.handle('hide-floating-ball', async () => {
  FloatingBallService.hideFloatingBall();
});

// 渲染进程
await window.electronAPI.invoke('show-floating-ball');
await window.electronAPI.invoke('hide-floating-ball');

错误处理

统一错误响应格式

javascript
// 成功响应
{
  success: true,
  data: { ... }
}

// 错误响应
{
  success: false,
  error: 'Error message',
  code: 'ERROR_CODE'
}

Handler 中的错误处理

javascript
static async handleRequest(event, data) {
  try {
    const result = await processData(data);
    return { success: true, data: result };
  } catch (error) {
    logger.error('处理请求失败:', error);
    return {
      success: false,
      error: error.message,
      code: error.code || 'UNKNOWN_ERROR'
    };
  }
}

渲染进程中的错误处理

javascript
try {
  const result = await window.electronAPI.invoke('channel-name', data);

  if (!result.success) {
    throw new Error(result.error);
  }

  // 处理成功结果
  console.log(result.data);
} catch (error) {
  console.error('调用失败:', error);
  // 显示错误提示
}

性能优化

1. 避免频繁 IPC 调用

javascript
// ❌ 不推荐:频繁调用
setInterval(() => {
  window.electronAPI.invoke('get-status');
}, 100);

// ✅ 推荐:使用事件推送
window.electronAPI.on('status-update', (status) => {
  console.log('状态:', status);
});

// 主进程定时推送
setInterval(() => {
  win.webContents.send('status-update', getStatus());
}, 1000);

2. 批量处理

javascript
// ❌ 不推荐:逐个处理
for (const item of items) {
  await window.electronAPI.invoke('process-item', item);
}

// ✅ 推荐:批量处理
await window.electronAPI.invoke('process-items', items);

3. 数据缓存

javascript
// 缓存机器信息,避免重复获取
let machineInfoCache = null;

async function getMachineInfo() {
  if (!machineInfoCache) {
    machineInfoCache = await window.electronAPI.invoke('get-machine-info');
  }
  return machineInfoCache;
}

4. 使用 MessagePort(大数据传输)

javascript
// 主进程
ipcMain.on('create-port', (event) => {
  const { port1, port2 } = new MessageChannelMain();
  event.sender.postMessage('port', null, [port2]);

  port1.on('message', (event) => {
    // 处理大数据
  });
});

// 渲染进程
ipcRenderer.once('port', (event) => {
  const port = event.ports[0];
  port.postMessage(largeData);
});

安全最佳实践

1. 白名单验证

javascript
// Preload 中验证频道白名单
contextBridge.exposeInMainWorld('electronAPI', {
  invoke: async (channel, data) => {
    const validChannels = ['get-data', 'save-data'];
    if (!validChannels.includes(channel)) {
      throw new Error(`Invalid channel: ${channel}`);
    }
    return await ipcRenderer.invoke(channel, data);
  },
});

2. 参数验证

javascript
// 主进程中验证参数
ipcMain.handle('save-data', async (event, data) => {
  // 验证数据结构
  if (!data || typeof data.id !== 'number') {
    throw new Error('Invalid data format');
  }

  // 清理危险字符
  const sanitized = sanitizeInput(data);

  return await saveData(sanitized);
});

3. 权限检查

javascript
// 检查来源窗口权限
ipcMain.handle('admin-action', async (event, data) => {
  const sender = BrowserWindow.fromWebContents(event.sender);

  if (!isAuthorizedWindow(sender)) {
    throw new Error('Unauthorized');
  }

  return await performAdminAction(data);
});

4. 限流

javascript
// 防止频繁调用
const rateLimiter = new Map();

ipcMain.handle('expensive-operation', async (event, data) => {
  const senderId = event.sender.id;
  const lastCall = rateLimiter.get(senderId) || 0;
  const now = Date.now();

  if (now - lastCall < 1000) {
    throw new Error('Rate limit exceeded');
  }

  rateLimiter.set(senderId, now);
  return await expensiveOperation(data);
});

健康检查

javascript
/**
 * 获取 IPC 系统健康状态
 */
static getHealthStatus() {
  const total = this.HANDLERS.length;
  const registered = this.registeredHandlers.size;
  const missing = this.HANDLERS
    .filter((h) => !this.registeredHandlers.has(h.name))
    .map((h) => h.name);

  return {
    healthy: registered === total,
    registered,
    total,
    missingHandlers: missing,
  };
}

// 使用示例
const health = IpcManager.getHealthStatus();
console.log('IPC 系统健康:', health);
// { healthy: true, registered: 5, total: 5, missingHandlers: [] }

相关文件

  • packages/main/src/apps/app-ipc-handler.js - IPC 管理器
  • packages/main/src/ipc-handlers/ - 各个 IPC 处理器
  • packages/main/src/preload/preload.js - 预加载脚本

相关文档

基于 MIT 许可发布