Skip to content

插件通信

概述

本文档描述了插件系统中插件与网页系统之间的双向通信机制,以及如何在实际业务中使用。

设计思想

核心思路:插件作为"中间层"连接主进程和渲染进程

Preview

网页系统 API

插件管理 API

上传插件

javascript
// 选择插件文件
const filePath = await window.customPluginAPI.selectPluginFile();

// 上传插件
const result = await window.customPluginAPI.uploadPlugin(filePath);
// 返回: { success: true, pluginId: "plugin-1234567890" }

管理插件

javascript
// 获取插件列表
const plugins = await window.customPluginAPI.getPluginList();
// 返回: [{ id, name, version, status: "running" | "stopped", ... }]

// 启动插件
await window.customPluginAPI.startPlugin(pluginId);

// 停止插件
await window.customPluginAPI.stopPlugin(pluginId);

// 删除插件
await window.customPluginAPI.deletePlugin(pluginId);

// 更新插件
await window.customPluginAPI.updatePlugin(pluginId, newFilePath);

插件通信 API

监听插件消息

javascript
// 监听插件发来的消息
const unsubscribe = window.customPluginAPI.onPluginMessage((message) => {
  console.log('收到插件消息:', message);
  // message 结构: { pluginId, event, data }

  switch (message.event) {
    case 'login-success':
      handleLoginSuccess(message.data);
      break;
    case 'data-synced':
      refreshData(message.data);
      break;
  }
});

// 取消监听
unsubscribe();

向插件发送消息

javascript
// 向指定插件发送消息
window.customPluginAPI.sendToPlugin(
  'com.example.user-manager', // 插件ID
  'user-login', // 事件名
  { username, password }, // 数据
);

响应插件调用

javascript
// 监听插件的调用请求
const unsubscribe = window.customPluginAPI.onPluginInvoke((request, reply) => {
  console.log('插件调用方法:', request.method);
  // request: { type, requestId, method, args, pluginId }

  switch (request.method) {
    case 'getSystemStatus':
      reply({ online: true, timestamp: Date.now() });
      break;

    case 'getUserPreferences':
      reply({ theme: 'dark', language: 'zh-CN' });
      break;

    default:
      reply({ error: 'unknown_method' });
  }
});

// 取消监听
unsubscribe();

插件 API

Window API(操作网页)

插件中可以使用 window 对象来操作网页,需要在 manifest.json 中声明相应权限。

基础 API(无需权限)

javascript
// 获取页面信息
const url = await window.getURL(); // 当前页面URL
const title = await window.getTitle(); // 页面标题
const ua = await window.getUserAgent(); // UserAgent

// 重载页面
await window.reload();

DOM API(需要 dom 权限)

javascript
// 查询元素
const element = await window.querySelector('.user-info');
// 返回: { tagName, className, id, textContent, innerHTML, value }

const elements = await window.querySelectorAll('button');
// 返回: [{ tagName, className, id, textContent }, ...]

// 修改样式
await window.setStyles('.main-content', {
  backgroundColor: '#f5f5f5',
  padding: '20px',
});

// 添加/删除CSS类
await window.addClass('.header', 'active');
await window.removeClass('.header', 'hidden');

// 设置属性
await window.setAttribute('.input', 'disabled', 'true');

Storage API(需要 storage 权限)

javascript
// localStorage
const token = await window.getLocalStorage('auth-token');
await window.setLocalStorage('auth-token', 'xxx');
await window.removeLocalStorage('auth-token');

// sessionStorage
const temp = await window.getSessionStorage('temp-data');
await window.setSessionStorage('temp-data', 'xxx');

Notification API(需要 notification 权限)

javascript
// 发送通知
await window.notify('登录成功', '欢迎回来!', {
  icon: '/icon.png',
  tag: 'login',
});

Eval API(需要 eval 权限,高危)

javascript
// 执行任意JavaScript代码
const buttonCount = await window.eval(`
  document.querySelectorAll('button').length
`);

IPC API(与网页通信)

向网页发送消息

javascript
// 发送消息给网页
ipc.send('login-success', {
  user: { id: 1, name: '张三' },
});

ipc.send('data-synced', {
  timestamp: Date.now(),
  count: 100,
});

监听网页消息

javascript
// 监听网页发来的消息
ipc.on('user-login', (data) => {
  console.log('收到登录请求:', data);
  // data: { username, password }
  handleLogin(data);
});

ipc.on('user-logout', async () => {
  console.log('收到登出请求');
  await handleLogout();
});

// 移除监听
ipc.off('user-login');

调用网页方法

javascript
// 调用网页方法并等待返回
const status = await ipc.invoke('getSystemStatus');
// 返回: { online: true, timestamp: 1699999999 }

const preferences = await ipc.invoke('getUserPreferences');
// 返回: { theme: 'dark', language: 'zh-CN' }

// 超时或错误
const result = await ipc.invoke('unknownMethod');
// 返回: { error: 'timeout' } 或 { error: 'window_unavailable' }

其他 API

javascript
// 使用项目封装的logger
logger.info('插件启动成功');
logger.warn('警告信息');
logger.error('错误信息', error);

// 使用允许的模块(需在 manifest.json 的 permissions 中声明)
const axios = require('axios');
const crypto = require('crypto-js');

// 定时器
const timer = setInterval(() => {
  logger.info('定时任务执行');
}, 5000);

clearInterval(timer);

完整示例

插件示例:用户管理插件

manifest.json

json
{
  "id": "com.example.user-manager",
  "name": "用户管理插件",
  "version": "1.0.0",
  "author": "开发者",
  "description": "处理用户登录、权限验证等业务",
  "main": "index.js",
  "permissions": ["axios", "dom", "storage", "notification"]
}

index.js

javascript
module.exports = {
  /**
   * 插件启动
   */
  start: async function () {
    logger.info('=== 用户管理插件启动 ===');

    // 1. 检查是否已登录
    const token = await window.getLocalStorage('auth-token');
    if (token) {
      logger.info('用户已登录,验证 token');
      await this.validateToken(token);
    }

    // 2. 监听网页登录请求
    ipc.on('user-login', async (data) => {
      logger.info('收到登录请求:', data.username);
      await this.handleLogin(data);
    });

    // 3. 监听网页登出请求
    ipc.on('user-logout', async () => {
      logger.info('收到登出请求');
      await this.handleLogout();
    });

    // 4. 定时刷新 token
    this.refreshTimer = setInterval(
      async () => {
        await this.refreshToken();
      },
      15 * 60 * 1000,
    ); // 每15分钟

    logger.info('=== 用户管理插件启动完成 ===');
  },

  /**
   * 处理登录
   */
  handleLogin: async function (credentials) {
    try {
      // 1. 调用后端API
      const axios = require('axios');
      const response = await axios.post('https://api.example.com/auth/login', {
        username: credentials.username,
        password: credentials.password,
      });

      if (response.data.success) {
        const { token, user } = response.data;

        // 2. 保存token
        await window.setLocalStorage('auth-token', token);
        await window.setLocalStorage('user-info', JSON.stringify(user));

        // 3. 通知网页登录成功
        ipc.send('login-success', { user });

        // 4. 显示通知
        await window.notify('登录成功', `欢迎回来,${user.name}!`);

        logger.info('登录成功:', user.name);
      } else {
        // 登录失败
        ipc.send('login-failed', { message: response.data.message });
      }
    } catch (error) {
      logger.error('登录异常:', error);
      ipc.send('login-error', { message: '登录服务异常' });
    }
  },

  /**
   * 处理登出
   */
  handleLogout: async function () {
    try {
      // 1. 清除本地存储
      await window.removeLocalStorage('auth-token');
      await window.removeLocalStorage('user-info');

      // 2. 通知网页
      ipc.send('logout-success');

      logger.info('登出成功');
    } catch (error) {
      logger.error('登出异常:', error);
    }
  },

  /**
   * 验证token
   */
  validateToken: async function (token) {
    try {
      const axios = require('axios');
      const response = await axios.get('https://api.example.com/auth/validate', {
        headers: { Authorization: `Bearer ${token}` },
      });

      if (response.data.valid) {
        logger.info('Token 有效');
        return true;
      } else {
        logger.warn('Token 已失效');
        await this.handleLogout();
        return false;
      }
    } catch (error) {
      logger.error('Token 验证失败:', error);
      return false;
    }
  },

  /**
   * 刷新token
   */
  refreshToken: async function () {
    try {
      const token = await window.getLocalStorage('auth-token');
      if (!token) return;

      const axios = require('axios');
      const response = await axios.post('https://api.example.com/auth/refresh', {
        token,
      });

      if (response.data.token) {
        await window.setLocalStorage('auth-token', response.data.token);
        logger.info('Token 刷新成功');
      }
    } catch (error) {
      logger.error('Token 刷新失败:', error);
    }
  },

  /**
   * 插件停止
   */
  destroy: async function () {
    logger.info('=== 用户管理插件停止 ===');

    if (this.refreshTimer) {
      clearInterval(this.refreshTimer);
    }
  },
};

网页系统示例:登录页面(Vue 3)

vue
<template>
  <div class="login-container">
    <div v-if="!isLoggedIn" class="login-panel">
      <h2>用户登录</h2>
      <input v-model="username" placeholder="用户名" />
      <input v-model="password" type="password" placeholder="密码" />
      <button @click="handleLogin">登录</button>
      <p v-if="error" class="error">{{ error }}</p>
    </div>

    <div v-else class="user-panel">
      <h2>欢迎,{{ currentUser?.name }}</h2>
      <button @click="handleLogout">登出</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

const username = ref('');
const password = ref('');
const error = ref('');
const isLoggedIn = ref(false);
const currentUser = ref(null);

let unsubscribe = null;

onMounted(() => {
  // 监听插件消息
  unsubscribe = window.customPluginAPI.onPluginMessage((message) => {
    console.log('收到插件消息:', message);

    switch (message.event) {
      case 'login-success':
        isLoggedIn.value = true;
        currentUser.value = message.data.user;
        error.value = '';
        console.log('登录成功:', message.data.user);
        break;

      case 'login-failed':
        error.value = message.data.message;
        console.log('登录失败:', message.data.message);
        break;

      case 'login-error':
        error.value = message.data.message;
        console.log('登录错误:', message.data.message);
        break;

      case 'logout-success':
        isLoggedIn.value = false;
        currentUser.value = null;
        console.log('登出成功');
        break;
    }
  });
});

onUnmounted(() => {
  unsubscribe?.();
});

// 处理登录
function handleLogin() {
  error.value = '';

  // 发送登录请求给插件
  window.customPluginAPI.sendToPlugin('com.example.user-manager', 'user-login', {
    username: username.value,
    password: password.value,
  });
}

// 处理登出
function handleLogout() {
  window.customPluginAPI.sendToPlugin('com.example.user-manager', 'user-logout');
}
</script>

<style scoped>
.login-container {
  max-width: 400px;
  margin: 50px auto;
  padding: 20px;
}

.login-panel,
.user-panel {
  border: 1px solid #ddd;
  padding: 20px;
  border-radius: 8px;
}

input {
  display: block;
  width: 100%;
  margin: 10px 0;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

button {
  margin: 10px 5px 0 0;
  padding: 10px 20px;
  background: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background: #45a049;
}

.error {
  color: red;
  margin-top: 10px;
}
</style>

权限系统

权限配置

manifest.json 中声明插件需要的权限:

json
{
  "permissions": [
    "axios", // 使用 axios 模块
    "crypto-js", // 使用 crypto-js 模块
    "dom", // DOM 操作权限
    "storage", // 存储访问权限
    "notification", // 通知权限
    "eval" // 执行任意代码权限(高危)
  ]
}

权限说明

权限说明风险等级
axios允许使用 axios 进行网络请求
crypto-js允许使用加密库
dom允许操作网页DOM
storage允许访问 localStorage/sessionStorage
notification允许发送系统通知
eval允许执行任意 JavaScript 代码

通信流程图

网页 → 插件

Preview

插件 → 网页

Preview

插件调用网页方法

Preview

最佳实践

错误处理

javascript
// 插件中
try {
  await this.handleLogin(data);
} catch (error) {
  logger.error('登录失败:', error);
  ipc.send('login-error', {
    message: error.message || '未知错误',
  });
}

// 网页中
window.customPluginAPI.onPluginMessage((message) => {
  if (message.event === 'login-error') {
    ElMessage.error(message.data.message);
  }
});

超时处理

javascript
// 插件调用网页方法时有5秒超时
const result = await ipc.invoke('someMethod');
if (result.error === 'timeout') {
  logger.warn('网页响应超时');
  // 处理超时逻辑
}

清理资源

javascript
// 插件的 destroy 方法会自动清理 IPC 监听
destroy: async function() {
  // 清理定时器
  if (this.timer) clearInterval(this.timer);

  // 其他清理逻辑
  // IPC 监听器会自动清理,无需手动处理
}

调试技巧

查看日志

插件日志会自动写入到项目日志文件:

logs/2025-11/2025-11-11.log

控制台输出

javascript
// 插件中使用 logger
logger.info('调试信息');
logger.debug('详细调试信息');

// 也可以使用 console(仅主进程控制台可见)
console.log('主进程日志');

网页控制台

javascript
// 网页中监听所有插件消息
window.customPluginAPI.onPluginMessage((message) => {
  console.log('📨 插件消息:', message);
});

常见问题

Q1: 插件无法发送消息?

A: 检查主窗口是否存在,插件发送消息需要主窗口处于活动状态。

Q2: 网页收不到插件消息?

A: 确保在 onMounted 中注册了 onPluginMessage 监听器,且插件ID正确。

Q3: invoke 一直超时?

A: 确保网页端注册了 onPluginInvoke 监听器,并正确调用 reply 方法。

Q4: 权限不足?

A: 在 manifest.jsonpermissions 数组中添加相应权限。


附录:完整 API 速查表

网页端 API

javascript
// 插件管理
window.customPluginAPI.selectPluginFile();
window.customPluginAPI.uploadPlugin(filePath);
window.customPluginAPI.deletePlugin(pluginId);
window.customPluginAPI.updatePlugin(pluginId, filePath);
window.customPluginAPI.startPlugin(pluginId);
window.customPluginAPI.stopPlugin(pluginId);
window.customPluginAPI.getPluginList();

// 插件通信
window.customPluginAPI.onPluginMessage(callback);
window.customPluginAPI.sendToPlugin(pluginId, event, data);
window.customPluginAPI.onPluginInvoke((request, reply) => {});

插件端 API

javascript
// Window API
window.getURL();
window.getTitle();
window.getUserAgent();
window.reload();
window.querySelector(selector);
window.querySelectorAll(selector);
window.setStyles(selector, styles);
window.addClass(selector, className);
window.removeClass(selector, className);
window.setAttribute(selector, attr, value);
window.getLocalStorage(key);
window.setLocalStorage(key, value);
window.removeLocalStorage(key);
window.getSessionStorage(key);
window.setSessionStorage(key, value);
window.notify(title, body, options);
window.eval(code);

// IPC API
ipc.send(event, data);
ipc.on(event, handler);
ipc.off(event);
ipc.invoke(method, ...args);

// 其他
logger.info / warn / error / debug();
require('axios');
require('crypto-js');
setTimeout / setInterval / clearTimeout / clearInterval;

基于 MIT 许可发布