文档中心 / 插件开发

插件开发

私有阁插件采用 Worker + 沙箱 UI 分离架构,四层安全约束模型(声明 → 授权 → 校验 → 隔离),支持 JavaScript / TypeScript / Vue 3 / React 多种技术栈模板,内置权限声明、文件读写、网络请求(域名白名单)、剪贴板、通知推送、多窗口管理等丰富 API。

私有阁插件由独立 Worker 和可选的沙箱 UI 组成,使用 manifest.json 声明身份、入口、窗口、命令、激活事件和最小权限。最终发布物是 .lyp 文件,可在客户端“应用中心 → 开发中”加载和测试。工具链、规范和示例随源码包 plugin-toolkit/ 目录一并交付。

环境与创建

需要 Node.js 20 或更高版本,以及 npm、pnpm、yarn 或 bun 中任意一种包管理器。最简单的创建方式:

bash
npm create siyouge-plugin@latest my-plugin
cd my-plugin
npm install
npm run doctor
npm run dev

可用模板:

模板场景
vanilla-js原生 JavaScript Worker + UI
vanilla-ts原生 TypeScript,适合入门
vue-tsVue 3 + TypeScript,推荐 UI 插件
react-tsReact + TypeScript
worker-only只提供后台命令,无 UI
custom自定义构建系统,输出标准产物

直接创建 Vue 插件:

bash
npm create siyouge-plugin@latest my-plugin -- --template vue-ts

项目结构

text
my-plugin/
├─ package.json
├─ manifest.json
├─ siyouge.plugin.config.mjs
├─ src/
│  ├─ worker.ts
│  └─ ui/
│     ├─ index.html
│     └─ main.ts
├─ dist/
│  ├─ worker.js
│  └─ ui/index.html
└─ artifacts/
  • Worker 是后台逻辑与命令入口。
  • UI 运行在隔离页面,通过 SDK Bridge 调用宿主能力。
  • dist 是开发模式加载的构建结果。
  • artifacts 存放最终 .lyp 包和发布元数据。

Manifest

最小示例:

json
{
  "manifestVersion": 1,
  "apiVersion": 1,
  "id": "com.example.my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "publisher": "Example",
  "description": "一个私有阁插件示例。",
  "engines": { "siyouge": ">=0.1.0" },
  "main": "dist/worker.js",
  "windows": [
    {
      "id": "main",
      "entry": "dist/ui/index.html",
      "primary": true,
      "title": "My Plugin",
      "decorations": { "mode": "siyouge", "tabs": false },
      "size": { "width": 900, "height": 650, "minWidth": 640, "minHeight": 480 },
      "behavior": { "resizable": true, "center": true, "singleton": true, "persistBounds": true }
    }
  ],
  "activationEvents": ["onCommand:com.example.my-plugin.run"],
  "contributes": {
    "commands": [{ "command": "com.example.my-plugin.run", "title": "运行插件" }]
  },
  "permissions": [{ "name": "storage" }]
}

新插件使用 windows,不要继续使用旧版 ui 字段。插件 ID 发布后必须保持不变;所有入口必须是插件根目录内的安全相对路径,不能包含 ..、绝对路径或 Windows 盘符。

权限模型

私有阁插件权限由四层共同约束:

1. Manifest 声明:插件先在 permissions 中声明所需能力。

2. 安装授权:安装 .lyp 时,客户端向用户展示权限并由用户确认。

3. 逐次校验:每次受保护调用都由 Rust 从插件注册表重新检查插件是否安装、启用并获得授权;插件不能自行传入授权列表绕过检查。

4. 运行隔离:Worker 和沙箱 UI 不能直接访问 Tauri IPC、Node.js、登录 Token、客户端 Store 或任意本地路径。

权限应按实际调用最小化声明。多声明权限不会自动获得能力,漏声明则会得到 PLUGIN_PERMISSION_DENIED 等错误。开发目录与正式安装包执行相同的权限检查。

一个同时读取用户资料、保存设置并请求远程 API 的插件可以这样声明:

json
"permissions": [
  { "name": "storage" },
  { "name": "user.profile.read" },
  {
    "name": "network.fetch",
    "scope": { "hosts": ["https://api.example.com"] }
  }
]

network.fetch 必须声明精确的 HTTPS origin。插件原生 fetch、XHR、WebSocket 和 EventSource 均被禁用,远程请求必须通过宿主 API;请求不能访问未列入 scope.hosts 的地址,也不能用宽泛通配符代替精确域名。

权限清单

Manifest v1 当前支持以下权限:

权限对应能力WorkerUI关键限制
storageapi.storage.*是是数据按插件和用户隔离;单值最大 256 KB
network.fetchapi.network.fetch是是必须配置精确 HTTPS scope.hosts;请求和响应有大小限制
user.profile.readapi.user.getCurrent()是是只返回用户资料副本,不返回 Token 或客户端 Store
notification.showapi.notifications.show()是是不应在通知中暴露敏感数据或频繁打扰用户
files.read文件选择、读取文件及插件数据目录插件数据目录是UI 选择器返回插件绑定的不透明句柄;不能传绝对路径
files.write保存文件及写入插件数据目录插件数据目录是安全相对路径;单文件当前最大 10 MB
clipboard.readapi.clipboard.readText()否是敏感能力,只应由明确的用户操作触发
clipboard.writeapi.clipboard.writeText()否是只能从插件 UI 调用
window.openapi.windows.open(windowId)否是只能打开本插件 Manifest 已声明的窗口
window.alwaysOnTopapi.window.setAlwaysOnTop()否是Manifest 窗口声明置顶时也必须申请
external.openapi.external.openUrl()否是只允许 https: 和 mailto:,不能启动本地程序或 Shell

当前插件窗口的关闭、最小化、最大化、聚焦、居中、调整尺寸和设置标题只影响调用者自己的窗口,因此不需要额外权限。跨窗口打开和置顶需要上表中的专门权限。

api.app.*、api.system.*、api.events.*、api.log.* 和 UI 调用本插件已声明命令等基础能力不需要 Manifest 权限;这不代表插件可以访问客户端内部状态,这些 API 只返回宿主明确暴露的数据。

文件权限不能让插件读取任意磁盘路径。UI 文件选择器返回与插件绑定的不透明句柄;Worker 只能读写自己的插件数据目录。..、绝对路径、跨插件句柄和���权窗口 ID 都会被拒绝。

Worker 与 UI

Worker 注册命令:

ts
siyouge.register({
  activate: (api) => api.log.info('plugin activated'),
  commands: {
    async 'com.example.my-plugin.run'(args, api) {
      await api.storage.set('lastArgs', args)
      return { ok: true }
    }
  },
  deactivate: () => undefined
})

UI 调用宿主 API:

ts
import { getSiyougeApi } from '@siyouge-im/plugin-sdk'

const api = getSiyougeApi()
const appearance = await api.system.getAppearance()
await api.storage.set('opened', true)
document.documentElement.dataset.theme = appearance.resolvedTheme

完整接口与限制以源码包 plugin-toolkit/docs/API.md、plugin-toolkit/docs/SPECIFICATION.md 和 SDK 类型定义为准。UI 不应依赖外链 CDN、外链字体、服务端直连或必须同源的路由模式;构建器会把标准 UI 打成包含脚本和样式的单 HTML。

客户端调试

保持 npm run dev 监听构建,然后:

1. 打开私有阁“应用中心 → 开发中”。

2. 开启开发者模式。

3. 点击“加载本地目录”。

4. 选择包含 manifest.json 的项目根目录。

5. 修改代码并等待构建完成。

6. 在开发插件卡片点击“重新加载”。

客户端只记录开发目录,不会复制或删除你的源码。调试主题、语言和事件订阅时,记得在 Vue onUnmounted 或 React effect cleanup 中取消订阅。

校验与打包

bash
npm run build
npm run validate
npm run pack

默认包名:

text
artifacts/<plugin-id>-<version>.lyp

检查安装包:

bash
npx siyouge-plugin inspect ./artifacts/com.example.my-plugin-1.0.0.lyp

pack 会生成同名 .release.json,包含包的 SHA-256、可选 Ed25519 签名和权限数组。签名需要通过 SIYOUGE_PLUGIN_PRIVATE_KEY 提供私钥;不要将私钥泄露给他人。

安装与发布

在“应用中心 → 开发中”选择“安装本地插件包”,安装刚生成的 .lyp,逐项验证:

1. 安装时展示的权限与 Manifest 一致。

2. 页面、命令、Worker 激活与持久化正常。

3. 深浅主题和中英文切换正常。

4. 停用、启用、升级、卸载以及保留/删除数据行为正确。

5. .lyp 的下载地址使用 HTTPS,市场记录中的 SHA-256 与打包输出一致。

远程发布前提升 SemVer,但保持插件 id 不变。开发目录测试不能替代真实 .lyp 安装测试。

工具链与 SDK

需要扩展官方工具链或开发官方级插件时,直接使用源码包内的 plugin-toolkit/ 工程:

bash
使用官方脚手架创建插件项目
cd siyouge-plug-in
pnpm install
pnpm check
pnpm contract:client
pnpm packages:pack
pnpm plugins:validate
pnpm plugins:pack
  • npm 工具包输出到 artifacts/npm。
  • 官方 .lyp 输出到 artifacts/official。
  • 官方插件源码位于 plugins/official,实验插件位于 plugins/labs。
  • 最完整、更新最及时的维护说明见 plugin-toolkit/docs/DEVELOPMENT.md。