Skip to content

LinPlayer 插件宿主清单(V1 详细草案)

0. 文档状态

本文面向 LinPlayer 宿主开发者,用于统一插件系统的产品边界、协议定义、跨端交付范围和实现顺序。

本文是 目标 V1 规范,不等于当前仓库已经全部实现。阅读本文时应始终区分两件事:

  • 当前代码基线已经支持了什么
  • 宿主为了正式开放多人协作开发插件,还必须补齐什么

当前 V1 目标平台:

  • PC:Windows、macOS
  • Mobile:iOS、Android

本文暂不覆盖:

  • Linux
  • TV

如果未来要扩展到 Linux / TV,应在 V1 完成后另写平台适配附录,而不是在当前版本里混写。

1. 文档目的

宿主文档需要解决四个问题:

  • 给宿主开发者一份明确的交付边界,知道插件系统的 V1 到底要做多大
  • 给协议维护者一份统一标准,避免代码、插件仓库、文档三套口径不一致
  • 给跨端开发者一份平台清单,避免桌面端先做一套、移动端再补一套且行为不一致
  • 给后续插件作者一份稳定预期,知道哪些能力是“可依赖的”,哪些还只是计划

2. 术语定义

为了避免讨论时混淆,先固定下面这些术语:

  • 宿主:LinPlayer 主应用
  • 插件:由独立 manifest.json + script + assets 组成的可安装扩展包
  • 页面:插件提供的独立页面,由宿主放入“插件中心”或推荐入口
  • 插槽:宿主预留给插件注入 UI 的位置
  • 运行时:宿主加载并执行插件脚本的环境
  • UI Schema:插件返回的 JSON UI 描述,由宿主渲染
  • Action:插件返回给宿主执行的动作,如 toastnavigate
  • 目标端:插件声明支持的平台端,V1 关注 pcmobile
  • 市场仓库:托管插件清单、版本、阻断列表和静态展示页面的仓库
  • Kill switch:通过 blocked.json 对某个插件或某个版本进行紧急禁用

3. 产品目标

V1 要满足的核心目标只有三类:

3.1 插件可以提供独立页面

用户可以在宿主里打开一个插件页面,像打开首页、工具页、专题页一样使用插件功能。

这种页面需要支持:

  • 标题
  • 页面入口名
  • 插件内导航
  • 列表、分组、卡片、双栏等信息展示
  • 加载、空态、错误态

3.2 插件可以注入卡片和小组件

插件可以在宿主预留位置里插入一块 UI,例如:

  • 首页顶部卡片
  • 首页底部补充信息
  • 详情页动作区按钮
  • 详情页底部扩展区
  • 播放器右上角工具按钮

3.3 插件可以有自定义数据来源

插件可以自己决定数据来自哪里,例如:

  • 某个公开网站
  • 某个 JSON API
  • 某个聚合服务

但是数据获取必须经过宿主提供的 受控网络能力,不能直接绕过宿主。

4. 明确不做什么

V1 明确不做以下能力:

  • 不允许插件直接注入任意 Flutter Widget
  • 不允许插件直接注入任意 iOS / Android / macOS / Windows 原生控件
  • 不允许插件常驻后台执行任务
  • 不允许插件做定时任务、计划任务、后台同步
  • 不允许插件直接修改宿主数据库或内部业务状态
  • 不允许插件直接接管播放器内核、媒体库、路由系统
  • 不允许插件以“完整网页嵌入模式”作为主要协议
  • 不允许插件自定义新的宿主 Action 类型并要求宿主立即支持

这意味着:

  • V1 的“自定义组件”不是“任意原生组件注入”,而是“用宿主白名单组件拼装出自定义卡片和页面”
  • V1 的“自定义数据来源”不是“宿主提供通用爬虫 DSL”,而是“插件通过 ctx.net.request() 自己抓取和整理数据”
  • V1 可以允许受控 webview 节点,但它只能作为辅助展示组件,不能替代宿主页面结构、路由和 Action 体系

5. V1 能力分层

为了避免插件系统一开始就失控,V1 能力按下面五层拆开:

5.1 安装与治理层

负责:

  • 安装
  • 校验
  • 启用 / 禁用
  • 卸载
  • 更新
  • block / kill switch

5.2 运行时层

负责:

  • 加载插件入口脚本
  • 向插件函数传入 ctx
  • 管理插件调用超时、异常和生命周期

5.3 渲染层

负责:

  • 把插件返回的 UI Schema 渲染为宿主 UI
  • 控制白名单组件
  • 处理插件触发的交互事件

5.4 页面与插槽层

负责:

  • 插件独立页面
  • 插件插槽注入
  • 跨插件页面导航

5.5 市场与运营层

负责:

  • registry.json
  • 市场展示页
  • 更新检查
  • blocked.json
  • 插件来源可信度

6. 当前代码基线

当前仓库已经有一部分插件基础设施:

  • [x] 支持通过 manifest.json URL 手动安装插件
  • [x] 支持按 files[].size + files[].sha256 下载和校验资源
  • [x] 安装时会读取仓库根 blocked.json
  • [x] 有 JS 运行时桥接
  • [x] 有 ctx.net.request / ctx.storage / ctx.log / ctx.settings
  • [x] 已支持 pages
  • [x] 已支持 slots
  • [x] 桌面端已实现 pages.entry=true 顶栏入口

当前仍然明显缺少:

  • [ ] 启动时复查已安装插件是否被 block
  • [ ] 定期复查已安装插件是否被 block
  • [ ] 读取 registry.json 作为市场入口
  • [ ] 插件更新检查与升级流程
  • [ ] 移动端与桌面端统一的 slot 挂点
  • [ ] section / grid / iconButton / chip / badge 这批 V1 目标组件
  • [ ] 正式的 webview 节点规范、导航拦截和跨端一致性
  • [ ] 统一且正式的“插件中心”跨端体验

7. 宿主总体架构

宿主插件系统推荐按下面的链路组织:

7.1 市场仓库

市场仓库负责托管:

  • plugins/<pluginId>/<version>/...
  • registry.json
  • blocked.json
  • schemas/
  • 说明文档和静态展示页

7.2 宿主安装器

宿主安装器负责:

  • 下载 manifest.json
  • 校验版本和协议兼容性
  • 下载 files[]
  • 校验文件大小和 sha256
  • 写入本地插件目录
  • 记录已安装插件列表

7.3 宿主运行时

宿主运行时负责:

  • 加载插件入口脚本
  • 构造 ctx
  • 调用 render()onEvent()
  • 转换 JS 返回值
  • 分发 Action
  • 回收运行时资源

7.4 宿主渲染器

宿主渲染器负责:

  • 渲染白名单 UI 节点
  • 处理节点事件
  • 处理加载 / 空态 / 错误态
  • 对未知节点优雅降级

7.5 宿主治理层

宿主治理层负责:

  • 启用 / 禁用插件
  • 启动时 block 检查
  • 定期 block 检查
  • 更新检查
  • 故障隔离和日志

8. 插件生命周期

V1 宿主必须明确实现完整生命周期,否则插件生态会失控。

8.1 发现

插件来源分两类:

  • 市场安装:从 registry.json 发现插件
  • 手动安装:用户粘贴某个版本的 manifest.json URL

8.2 安装

安装步骤:

  1. 下载 manifest.json
  2. 校验 schemaVersionapiVersionminHostVersion
  3. 校验目标端是否匹配当前平台
  4. 读取并校验 blocked.json
  5. 下载 files[]
  6. 校验 size
  7. 校验 sha256
  8. 写入本地目录
  9. 更新已安装插件索引

8.3 启用

启用插件不是“立刻常驻运行”,而是:

  • 把插件标记为可参与页面和插槽渲染
  • 在需要显示时才懒加载运行时

8.4 运行

运行条件:

  • 用户打开插件页面
  • 宿主页面渲染到某个 slot

宿主不应该在应用启动时一次性加载所有插件脚本。

8.5 交互

插件交互流程:

  1. 宿主调用 render(ctx, params, state)
  2. 插件返回 UI Schema
  3. 宿主渲染 UI
  4. 用户点击或触发事件
  5. 宿主调用 onEvent(ctx, event, state)
  6. 插件返回 stateactions
  7. 宿主更新状态并执行白名单动作

8.6 更新

V1 宿主必须支持:

  • 比较当前已安装版本和市场最新版本
  • 用户确认后升级插件
  • 升级后保留最新版本目录
  • 清理旧版本目录

8.7 禁用

禁用后的效果:

  • 插件不再出现在“插件中心”
  • 插件页面入口不再显示
  • 插槽不再参与渲染
  • 插件运行时实例被销毁

8.8 卸载

卸载后的效果:

  • 本地插件目录删除
  • 已安装索引删除
  • 页面和插槽入口全部消失
  • 插件私有存储建议一并清理

8.9 Block

当插件或某个版本被加入 blocked.json 时:

  • 新安装必须被拒绝
  • 已安装插件必须在启动时被重新检查
  • 已安装插件应自动禁用,并给用户明确提示

9. 协议治理清单

在正式开放给外部插件作者之前,宿主必须把协议治理做好。

9.1 固定版本

  • [ ] 固定 schemaVersion=1
  • [ ] 固定 apiVersion=1
  • [ ] 冻结 V1 字段集合

9.2 统一字段规则

  • [ ] id 使用统一命名规则,建议反向域名格式
  • [ ] version 必须是合法 SemVer
  • [ ] route 必须以 /plugin/ 开头
  • [ ] pages.onEvent 在 V1 中固定为必填
  • [ ] slots.onEvent 在 V1 中固定为必填
  • [ ] 对外 schema、仓库校验脚本、宿主解析器必须保持一致

9.3 兼容性策略

  • [ ] V1 期间新增能力只允许通过“向后兼容”的方式添加
  • [ ] 删除字段或改变字段语义必须升级 apiVersion
  • [ ] 宿主升级后不得悄悄破坏已有 V1 插件

10. 运行时契约

宿主运行时必须让插件作者清楚知道“宿主会怎么调我”。

10.1 入口脚本

插件入口脚本由宿主加载并执行。执行后,宿主需要能从全局作用域找到 manifest 声明的 handler。

统一调用方式:

js
globalThis[handlerName](ctx, ...args)

10.2 ctx 最小集合

V1 宿主至少要提供这些字段:

  • ctx.target
  • ctx.locale
  • ctx.timeZone
  • ctx.hostVersion
  • ctx.plugin
  • ctx.settings
  • ctx.net.request(req)
  • ctx.storage.get(key)
  • ctx.storage.set(key, value)
  • ctx.storage.remove(key)
  • ctx.log(level, message, extra?)

10.3 超时与异常

宿主必须:

  • [ ] 对插件函数调用设置超时
  • [ ] 捕获脚本异常
  • [ ] 将异常转化为宿主可展示的错误信息
  • [ ] 防止单个插件异常导致宿主页面整体崩溃

10.4 生命周期回收

宿主必须:

  • [ ] 页面销毁时回收运行时
  • [ ] 插槽组件销毁时回收运行时
  • [ ] 禁用 / 卸载时回收运行时

10.5 脚本运行时与 webview 隔离

宿主必须明确区分两类环境:

  • 插件入口脚本运行在宿主控制的脚本运行时里
  • webview 节点加载的是受控网页内容,不属于插件脚本运行时

宿主必须保证:

  • [ ] webview 页面不能直接访问 ctx
  • [ ] webview 页面不能直接访问宿主 JS bridge
  • [ ] 插件不能依赖网页内脚本直接调用宿主私有 API
  • [ ] 插件如果要改变 webview 内容,只能通过重新返回 schema 让宿主重渲染

11. 数据来源策略

这是 V1 最容易被做乱的一块,必须明确。

11.1 V1 不做声明式数据源 DSL

V1 不额外设计独立的 dataSources DSL 让宿主去执行。

原因:

  • 宿主实现复杂度高
  • 跨端一致性成本高
  • 插件作者真实需求主要是“拉数据并展示”,不是“把抓数逻辑移交给宿主”

11.2 V1 的数据来源实现方式

V1 统一采用:

  • 插件自己通过 ctx.net.request() 拉取数据
  • 插件自己完成解析、标准化和拼装 UI Schema
  • 宿主只负责网络权限、日志、存储、渲染和动作执行

11.3 宿主必须提供的支撑

  • [ ] 网络能力
  • [ ] 私有缓存存储
  • [ ] 超时和并发控制
  • [ ] 域名白名单
  • [ ] 错误日志

11.4 推荐的数据流

推荐插件作者按下面顺序组织代码:

  1. 获取远程数据
  2. 解析原始数据
  3. 标准化为页面用的数据模型
  4. 生成 UI Schema
  5. 在失败时返回 errorempty

宿主文档必须把这条推荐路径写清楚,避免插件作者一上来就把远程 HTML 原样塞给宿主。

12. UI Schema 宿主责任

宿主必须对外公布 V1 白名单节点,并且真正实现这些节点。

12.1 V1 白名单节点

  • [ ] page
  • [ ] section
  • [ ] row
  • [ ] column
  • [ ] list
  • [ ] grid
  • [ ] card
  • [ ] divider
  • [ ] spacer
  • [ ] text
  • [ ] markdown
  • [ ] image
  • [ ] webview
  • [ ] button
  • [ ] iconButton
  • [ ] chip
  • [ ] badge
  • [ ] loading
  • [ ] empty
  • [ ] error

12.2 宿主必须保证的行为

  • [ ] 所有节点都由宿主渲染
  • [ ] 所有可交互节点都只能发出宿主认可的事件
  • [ ] 未识别节点不会导致崩溃
  • [ ] 未识别节点会给出明确降级提示
  • [ ] 图标通过字符串白名单映射,不接受原生对象

12.3 宿主推荐的组件语义

为了让插件作者能做出“像首页一样”的页面,宿主需要给出语义而不是只给 row/column

建议:

  • page:页面根容器
  • section:带标题的区块
  • grid:卡片网格
  • card:一块信息或操作卡片
  • badge:信息标签
  • chip:轻量操作项
  • iconButton:图标按钮,适合工具栏和播放器区

12.4 webview 组件规范

webview 是 V1 允许的受控补充节点,用来承载少量必须保留网页原始交互的内容。 它不是插件主要协议,也不是“把整站塞进宿主”的兜底方案。

V1 只保证 webview 在插件 page 场景可用。 对于 slot 场景,宿主可以直接拒绝渲染,或只允许固定高度的小块内容。

最小 schema 建议:

json
{
  "type": "webview",
  "props": {
    "src": "https://example.com/embed/help",
    "height": 420,
    "title": "帮助页",
    "allowExternalNavigation": false,
    "showProgress": true
  }
}

字段规则:

  • src 必填,必须是绝对 http/https URL
  • height 必填,单位按宿主逻辑像素解释;宿主可以设置最小值和最大值
  • title 可选,用于标题补充、无障碍和错误提示
  • allowExternalNavigation 可选,默认 false
  • showProgress 可选,默认 true

宿主必须保证:

  • [ ] 顶层初始 URL 和后续顶层导航都受域名白名单控制;V1 统一复用 permissions.network.domains
  • [ ] 默认禁用弹窗、新窗口、文件下载和非 http/https scheme
  • [ ] 网页环境不得注入 ctx、宿主 JS bridge 或插件私有状态
  • [ ] webview 加载失败必须显示错误态,而不是白屏
  • [ ] 页面关闭、插件禁用、插件卸载时必须销毁对应 webview 实例
  • [ ] 插件不能通过 webview 劫持宿主返回键、返回手势或路由栈

宿主推荐策略:

  • 对超出白名单的跳转,降级为 openUrl 或给出明确提示
  • slot 中默认禁用 webview,确有需要时只允许固定高度且不可抢占主滚动
  • 如平台支持,按插件 ID 做 cookie / storage 隔离;做不到时至少不得复用宿主认证态

明确不允许:

  • 插件把首页或唯一页面做成整页远端 SPA
  • 插件依赖网页内脚本直接调用宿主私有 API
  • 插件把大面积可滚动 webview 注入核心 slot

13. Action 白名单

V1 Action 必须严格受控。

13.1 宿主必须支持

  • [ ] toast
  • [ ] navigate
  • [ ] openUrl

13.2 宿主必须保证

  • [ ] 未知 Action 直接忽略
  • [ ] Action 执行失败不导致插件崩溃
  • [ ] navigate 只允许应用内路由
  • [ ] openUrl 只允许系统允许的外链打开方式

14. 页面能力清单

插件页面是你这次需求里的核心,因此宿主必须把页面能力做完整。

14.1 插件中心

宿主必须提供统一“插件中心”,至少包含:

  • [ ] 页面列表
  • [ ] 名称
  • [ ] 描述
  • [ ] 图标
  • [ ] 当前版本
  • [ ] 启用状态
  • [ ] 打开入口

14.2 页面排序

排序规则建议固定:

  1. order 升序
  2. title 升序
  3. plugin.id + page.id 作为最终稳定排序键

14.3 推荐入口

对于 pages.entry=true

  • Windows / macOS:可以在首页顶栏放少量推荐入口
  • iOS / Android:可以在插件中心中优先显示,也可以额外放一个首页入口

14.4 页面形态

宿主必须支持这些页面形态:

  • 单栏工具页
  • 多 section 信息页
  • 双栏专题页
  • 卡片网格页
  • “网页内容结构化展示”页面

补充约束:

  • webview 只能作为上述页面中的辅助块,不能替代页面主结构

14.5 页面参数

宿主打开页面时,可以传:

  • page 来源标识
  • 当前上下文参数
  • 插件内跳转参数

宿主必须保证:

  • 参数为空时插件仍能正常打开
  • 参数格式不符合预期时插件能收到空对象,而不是崩溃

15. 插槽能力清单

V1 统一 slot 集合如下。

15.1 home.feed.beforeSections

定位:

  • 首页主内容区之前

用途:

  • 插件入口卡片
  • 活动卡片
  • 推荐卡片

15.2 home.feed.afterSections

定位:

  • 首页主内容区之后

用途:

  • 补充说明
  • 外部内容流
  • 底部统计或专题入口

15.3 detail.hero.actions

定位:

  • 详情页主操作区附近

用途:

  • 增加按钮
  • 增加快捷操作

15.4 detail.sections.bottom

定位:

  • 详情页下半部分扩展区

用途:

  • 附加信息卡片
  • 第三方数据补充
  • 外链导航

15.5 player.appbar.trailing

定位:

  • 播放器右侧工具区

用途:

  • 小型图标按钮
  • 状态提示
  • 快捷入口

15.6 插槽治理要求

  • [ ] 同一 slotId 多贡献按 priority 降序
  • [ ] 每个 slotId 限制最大渲染数,建议不超过 6
  • [ ] 插槽崩溃不影响宿主主页面
  • [ ] 插槽过高时允许宿主裁剪或限制
  • [ ] 插槽交互不得劫持系统返回键或宿主关键手势

16. 市场与版本治理

如果没有市场治理,插件生态很快会不可维护。

16.1 registry.json

V1 市场必须提供 registry.json,至少用于:

  • 展示插件列表
  • 展示最新版本
  • 展示支持平台
  • 给宿主做更新检查

16.2 blocked.json

blocked.json 必须承担:

  • 阻止新用户安装被禁用插件
  • 阻止用户继续使用已知危险版本

16.3 安装链接规范

建议强制要求:

  • 安装链接指向某个版本的 manifest.json
  • 推荐使用 tag 或 commit SHA
  • 避免使用会变化的 main 分支 URL 作为长期安装链接

16.4 手动安装保留

即使市场能力上线,也应该保留“手动粘贴 manifest URL 安装”,因为:

  • 便于开发调试
  • 便于灰度测试
  • 便于私有插件验证

17. 安全与审计

17.1 网络权限

宿主必须做到:

  • [ ] permissions.network.enabled 默认关闭
  • [ ] permissions.network.domains 默认空数组
  • [ ] 只允许 http/https
  • [ ] 控制超时
  • [ ] 控制并发
  • [ ] 控制最大响应体大小

17.2 资源隔离

宿主必须做到:

  • [ ] 单个插件失败不拖垮宿主页面
  • [ ] 单个插件超时不拖垮宿主渲染线程
  • [ ] 插件日志可以按插件 ID 定位

17.3 外链策略

宿主必须明确:

  • [ ] 哪些 URL 允许 openUrl
  • [ ] 是否提示用户离开应用
  • [ ] 是否屏蔽危险 scheme

17.4 webview 安全边界

宿主必须明确:

  • [ ] webview 顶层导航白名单与 permissions.network.domains 对齐
  • [ ] 禁止 window.open / popup / 自定义 scheme 自动拉起
  • [ ] 不向网页注入宿主 bridge、token 或内部认证态
  • [ ] 白名单拦截、加载失败和证书异常都有明确可见提示

18. 跨端交付清单

18.1 通用清单

  • [ ] 协议一致
  • [ ] 页面和插槽行为一致
  • [ ] Action 一致
  • [ ] 安装和更新流程一致
  • [ ] 错误处理一致
  • [ ] webview 的导航拦截、错误态和销毁时机一致

18.2 Windows / macOS

  • [ ] 首页顶栏 entry 入口
  • [ ] 双栏 / 网格布局
  • [ ] 鼠标点击与滚轮体验
  • [ ] 窗口 resize 稳定
  • [ ] iconButton 可在工具栏中正常显示

18.3 iOS / Android

  • [ ] 单栏优先布局
  • [ ] SafeArea 合规
  • [ ] 点击区域满足触屏尺寸要求
  • [ ] 系统返回与插件路由栈一致
  • [ ] 插槽不遮挡主操作区和底部手势区

19. 测试矩阵

V1 开放前,宿主侧至少要有以下测试:

19.1 安装测试

  • [ ] URL 非法
  • [ ] manifest 缺字段
  • [ ] 版本不兼容
  • [ ] sha256 不匹配
  • [ ] 文件缺失
  • [ ] 被 block

19.2 运行时测试

  • [ ] render() 正常返回
  • [ ] render() 抛异常
  • [ ] onEvent() 抛异常
  • [ ] 超时
  • [ ] 空 schema
  • [ ] 未知节点
  • [ ] webview 不能访问宿主脚本运行时

19.3 交互测试

  • [ ] toast
  • [ ] navigate
  • [ ] openUrl
  • [ ] 页面打开和关闭
  • [ ] 插槽交互
  • [ ] webview 白名单内导航
  • [ ] webview 外域跳转拦截
  • [ ] webview 销毁后不残留页面实例

19.4 数据测试

  • [ ] 网络超时
  • [ ] 白名单域名限制
  • [ ] JSON 解析错误
  • [ ] 空数据
  • [ ] 缓存命中与缓存失效

19.5 平台测试

  • [ ] Windows
  • [ ] macOS
  • [ ] iOS
  • [ ] Android

20. 推荐的宿主实施顺序

如果要务实推进,而不是把范围一次性摊大,建议按下面顺序做:

第一阶段:协议冻结

  • 固定 manifest 字段
  • 固定白名单节点
  • 固定 slot 集合
  • 固定 Action 集合

第二阶段:补 UI 能力

  • 实现 section
  • 实现 grid
  • 实现 iconButton
  • 实现 chip
  • 实现 badge
  • 实现受控 webview

第三阶段:补移动端挂点

  • 把 5 个核心 slot 在 iOS / Android 上挂齐
  • 把插件中心做成正式入口

第四阶段:补治理能力

  • registry.json
  • 更新检查
  • 启动时 block 检查
  • 定期 block 检查

第五阶段:样例验收

  • 至少 2 到 3 个插件跨端验证通过
  • 至少 1 个首页型页面插件
  • 至少 1 个插槽型工具插件

21. 当前代码入口

宿主侧主要实现入口如下:

  • lib/services/plugins/plugin_manager.dart
  • lib/services/plugins/plugin_runtime_v1.dart
  • lib/plugins/plugins_page.dart
  • lib/plugins/plugin_page_host_page.dart
  • lib/plugins/plugin_slot_area.dart
  • lib/plugins/plugin_schema_renderer.dart
  • lib/desktop_ui/pages/desktop_home_page.dart

22. V1 完成标准

只有当下面这些条件同时满足,才适合正式开放给多人共同创建插件:

  • [ ] 协议和 schema 已冻结
  • [ ] Windows、macOS、iOS、Android 都能稳定打开插件页面
  • [ ] 5 个核心 slot 已跨端挂齐
  • [ ] 白名单 UI 节点已全部实现
  • [ ] 市场安装、手动安装、更新、block 流程都跑通
  • [ ] 至少有一组跨端样例插件通过验收
  • [ ] 对外插件作者文档已经与宿主实现完全一致

Built with VitePress