Skip to content

架构设计

1. 设计目标

挂件作为一种画布元素存在于主播酱,与文本、图片、视频等内置元素并列。客户端提供:

  1. 运行容器:内嵌 HTTP 静态托管 + iframe 渲染;
  2. 实时数据:复用客户端已有的直播事件 WebSocket 广播,挂件在 iframe 内直连消费;
  3. 配置面板:依据 manifest.json 的字段 schema 自动生成。

挂件作者只需交付一个符合 manifest 规范 的页面包,不需要修改任何客户端代码。

2. 总体架构

┌─ 主播酱客户端(Electron)─────────────────────────────────────┐
│                                                                │
│  画布渲染层                                                     │
│   └─ 挂件容器组件                                               │
│        ├─ <iframe                                              │
│        │     src="http://127.0.0.1:9855/widgets/<id>/          │
│        │          index.html?zbjHost=…#zbjInit=<JSON>"         │
│        │     style="pointer-events:none">                      │
│        ├─ 出站 postMessage:config/theme/resize/test-event/      │
│        │             rpc-result                                │
│        └─ 入站 message:rpc/state/log/request-resize             │
│                                                                │
│  配置面板(按 manifest.config 自动生成 Naive UI 表单)           │
│  挂件库(已安装管理 / 开发者注册 / 本地导入)                    │
│                                                                │
└────────────────────────────────────────────────────────────────┘

                          ▼  iframe 内:作者静态页 + Widget SDK
┌─ Widget SDK (zbj-widget-sdk.js) ──────────────────────────────┐
│  init: 同步读 location.hash 的 zbjInit JSON,立即 ready         │
│  ZBJ.on('gift'|'chat'|'like'|…) ← 直连本机 /ws WebSocket        │
│  ZBJ.config / onConfigChange     ← parent.postMessage            │
│  ZBJ.invoke('getGifts')          ← postMessage RPC(宿主中转)   │
└────────────────────────────────────────────────────────────────┘

┌─ 主播酱内嵌服务 ──────────────────────────────────────────────┐
│  HTTP                                                          │
│   ├─ /widgets/<id>/*       → 已安装挂件目录                    │
│   ├─ /dev-widgets/<id>/*   → 开发者注册的本地 manifest 目录     │
│   └─ /widget-sdk/*         → Widget SDK 运行时                  │
│  WebSocket                                                     │
│   └─ /ws  → 直播平台事件流(gift/like/chat/...)                │
└────────────────────────────────────────────────────────────────┘

3. 渲染管线

挂件元素在以下三类场景下表现一致:

场景窗口模式说明
编辑主窗口编辑区edit元素实时运行,可拖拽 / 缩放
预览浏览器侧预览路由preview只读展示,storage.set 不落盘
推流虚拟摄像头离屏窗virtualcam同上;由 OSR 帧采集进入虚拟摄像头输出

三类场景共用同一份挂件代码,渲染同一个 iframe。iframe 子文档由 Chromium 正常合成与采集,因此挂件自动进入虚拟摄像头输出,无需做任何额外配置。

<iframe> 在所有场景下恒设置 pointer-events: none

  • 编辑模式下不阻碍画布编辑工具的选中 / 拖拽 / 缩放;
  • 预览与推流模式本无交互需求。

需要用户操作的玩法应通过直播事件驱动(如礼物触发动画);当前规范不支持挂件接收鼠标 / 键盘事件。

4. 数据通道分工

挂件运行涉及三条通道:

数据通道方向
初始 init(mode / platform / config / theme / widgetState / size / wsUrl 等)iframe URL #zbjInit=<JSON> hash,SDK 启动时同步读出宿主 → iframe
配置 / 主题 / 尺寸 / 测试事件 / RPC 结果window.postMessage宿主 → iframe
RPC / 状态持久化 / 日志 / 尺寸请求window.parent.postMessageiframe → 宿主
实时直播事件(礼物 / 弹幕 / 点赞 / 房间状态等)WebSocket,SDK 直连本机 /ws 订阅 event:"platform-message"服务端 → 挂件

完整协议见 通信协议

iframe 的对外网络行为只有两类:① 直连本机 /ws(WebSocket,不涉及 CORS);② 与父窗口 postMessage(同进程,不涉及 CORS)。开发者模式下挂件运行在自有 dev server 时,也无需调整服务端 CORS 配置。

5. 文件存储

用途位置
已安装挂件userData/widgets/<id>/(Windows: %APPDATA%\主播酱\widgets\<id>\
Widget SDK 运行时主播酱安装目录的 resources/widget-sdk/,由内嵌服务在 /widget-sdk/ 下提供
实例数据(ZBJ.storage随场景一起持久化到客户端配置文件,与挂件元素绑定

6. 安全模型

维度策略
信任模型第一阶段为审核制 / 团队自研;挂件来源经审核,不面向公开市场创作者
隔离挂件运行在独立 iframe 文档中,与主播酱本体内存隔离
网络范围iframe 默认只能访问本机 /ws 与父窗口 postMessage;外部网络访问由 CSP 控制
CSP挂件资源响应携带宽松 CSP:放行 'self'data:blob:https:connect-src 放行 ws://127.0.0.1:*http://127.0.0.1:*
消息隔离宿主用 ev.source === iframe.contentWindow 把 message 精确路由到对应实例;不同挂件之间的 postMessage 不会串数据
安装期校验manifest schema 校验、id 唯一性、sdkVersion 兼容、解压路径穿越防护、包体积上限(默认 50 MB)、入口存在性等
暴露面/widgets/*/dev-widgets/* 通过 127.0.0.1 暴露,仅本机可达