主题
Widget SDK API
Widget SDK 是一个无构建依赖的单文件脚本(zbj-widget-sdk.js),由主播酱内嵌服务托管。挂件页面引入后会在全局 window.ZBJ 上暴露 API,并自动完成初始化与平台事件订阅。作者无需处理底层协议。
1. 引入方式
1.1 已安装挂件(同源托管)
html
<script src="/widget-sdk/zbj-widget-sdk.js"></script>1.2 开发者模式(挂件运行在自有 dev server,跨源)
html
<script src="http://127.0.0.1:9855/widget-sdk/zbj-widget-sdk.js"></script>客户端内嵌服务端口默认为 9855。宿主在 iframe URL 的 ?zbjHost= 查询参数中始终透传当前 host,SDK 会自动读取以容纳未来端口变更。
2. 完整类型声明
挂件项目可从 /widget-sdk/zbj-widget-sdk.d.ts 拉取最新声明。下方接口为权威定义。
ts
/* 主播酱创意工坊 · Widget SDK v1 类型声明 */
export type ZBJMode = "edit" | "preview" | "virtualcam";
export type ZBJSurface = "canvas";
export type ZBJPlatform =
| "bilibili" | "douyin" | "kuaishou" | "shipinhao" | "xiaohongshu";
export interface ZBJSize { width: number; height: number; }
/* 事件载荷见 events.md §1,此处省略具体接口体 */
export interface ZBJBaseEvent { /* ... */ }
export interface ZBJGiftEvent extends ZBJBaseEvent { /* ... */ }
export interface ZBJLikeEvent extends ZBJBaseEvent { /* ... */ }
export interface ZBJChatEvent extends ZBJBaseEvent { /* ... */ }
export interface ZBJEnterEvent extends ZBJBaseEvent { /* ... */ }
export interface ZBJSocialEvent extends ZBJBaseEvent { /* ... */ }
export interface ZBJSubscribeEvent extends ZBJBaseEvent { /* ... */ }
export interface ZBJRoomEvent { /* ... */ }
export type ZBJAnyEvent =
| ZBJGiftEvent | ZBJLikeEvent | ZBJChatEvent | ZBJEnterEvent
| ZBJSocialEvent | ZBJSubscribeEvent | ZBJRoomEvent;
export interface ZBJEventMap {
gift: ZBJGiftEvent;
like: ZBJLikeEvent;
chat: ZBJChatEvent;
enter: ZBJEnterEvent;
social: ZBJSocialEvent;
subscribe: ZBJSubscribeEvent;
room: ZBJRoomEvent;
message: ZBJAnyEvent; // 全量原始流
}
export interface ZBJGift {
id: string | number;
name: string;
price: number;
icon: string;
}
export interface ZBJRoomInfo {
platform: ZBJPlatform;
roomId: string;
memberCount: number;
totalUserCount: number;
followCount: number;
}
export interface ZBJSdk {
/** SDK 主版本号,恒为 1 */
readonly sdkVersion: 1;
/* ── 生命周期与上下文 ──────────────────────────────────── */
/** init 完成后触发;SDK 启动时从 URL hash 同步读出 init,因此通常在同步代码末尾即 ready */
ready(callback: () => void): void;
readonly mode: ZBJMode;
readonly surface: ZBJSurface;
readonly platform: ZBJPlatform;
readonly widgetId: string;
readonly instanceId: string;
readonly size: Readonly<ZBJSize>;
onResize(callback: (size: ZBJSize) => void): void;
/** 请求宿主把元素尺寸改为指定值;宿主可拒绝(超出 min/maxSize) */
requestResize(width: number, height: number): void;
/* ── 配置与主题 ───────────────────────────────────────── */
/** manifest.config 当前值;同时已写入 CSS 变量 --zbj-<key> */
readonly config: Readonly<Record<string, unknown>>;
onConfigChange(
callback: (config: Readonly<Record<string, unknown>>) => void
): void;
/** manifest.theme 当前值;同时已写入 CSS 变量 --zbj-<key> */
readonly theme: Readonly<Record<string, unknown>>;
onThemeChange(
callback: (theme: Readonly<Record<string, unknown>>) => void
): void;
/* ── 实时事件 ─────────────────────────────────────────── */
on<K extends keyof ZBJEventMap>(
event: K,
cb: (e: ZBJEventMap[K]) => void
): void;
off<K extends keyof ZBJEventMap>(
event: K,
cb: (e: ZBJEventMap[K]) => void
): void;
/* ── 数据查询 RPC(经宿主中转)─────────────────────────── */
invoke<T = unknown>(method: string, ...args: unknown[]): Promise<T>;
/** 当前平台礼物列表;等价于 invoke("getGifts") */
getGifts(): Promise<ZBJGift[]>;
/** 当前房间信息快照;等价于 invoke("getRoomInfo") */
getRoomInfo(): Promise<ZBJRoomInfo>;
/* ── 实例级持久化(随场景一起保存)──────────────────────── */
readonly storage: {
get<T = unknown>(key: string): T | undefined;
set(key: string, value: unknown): void;
remove(key: string): void;
keys(): string[];
};
/* ── 工具 ─────────────────────────────────────────────── */
/** 日志转发到宿主开发者控制台,附带挂件标识前缀 */
log(...args: unknown[]): void;
}
declare global {
const ZBJ: ZBJSdk;
interface Window { ZBJ: ZBJSdk; }
}3. 行为契约
3.1 生命周期
| 阶段 | 触发条件 |
|---|---|
| 脚本加载 | iframe 文档解析到 <script src="…/zbj-widget-sdk.js"> |
| init | SDK 启动时同步从 location.hash 的 zbjInit 参数读取宿主预填的 mode/platform/config/theme/widgetState/size/host/wsUrl 等,立即写入 ctx 并应用 CSS 变量。没有握手消息:iframe 加载即 ready |
ready | init 写入完成后置 ready=true,microtask 触发所有 ZBJ.ready 注册的回调;之后注册的回调立即补触发一次 |
| WS 连接 | init 应用后异步打开 wsUrl,仅用于消费平台事件流(与宿主通信无关) |
| 配置变更 | 宿主每次下发 {source:"zbj-host", type:"config"/"theme"} postMessage,SDK 先更新 CSS 变量再触发 onConfigChange / onThemeChange |
| 尺寸变更 | 宿主每次下发 type: "resize" postMessage,SDK 更新内部 size 并触发 onResize |
SDK 内所有用户回调由 SDK try/catch 兜底;单个回调抛错经 console.error 上报,不会影响其它回调与后续事件分发。
3.2 模式 mode
| 取值 | 来源 | 行为 |
|---|---|---|
edit | 主窗口编辑区 | storage.set 经宿主持久化到 element.widgetState,随场景保存 |
preview | /preview 预览窗 | storage.set 仅在会话内有效,不落盘 |
virtualcam | 虚拟摄像头离屏窗 | 同 preview |
挂件应根据 mode 自行调整行为(如 virtualcam 模式下关闭交互态的视觉提示)。
3.3 配置与主题
ZBJ.config与ZBJ.theme均为只读快照;变更后会接收到回调,回调参数即为新对象。- 每次变更时,SDK 先调用
applyCssVars写入 CSS 变量(详见 开发约定 §3 CSS 变量桥),再触发回调。
3.4 实时事件 on / off
event必须是ZBJEventMap的键。未知键不会报错,永不触发。on("message", cb)收全量归一化事件流;其他键收对应类型的事件。- 平台事件由 SDK 在 iframe 内直连
/ws消费,详见 通信协议 §4 WebSocket 与 平台事件。
3.5 RPC invoke
ts
ZBJ.invoke<T>(method: string, ...args: unknown[]): Promise<T>| 条件 | 行为 |
|---|---|
method 在宿主白名单内 | 宿主代为请求并通过 rpc-result 回传结果 |
method 不在白名单 | Promise reject,错误信息 RPC method "<method>" not allowed |
| 10 秒内无响应 | Promise reject,错误信息 RPC timeout: <method> |
当前白名单(实现于 el-custom-widget.vue 的 handleRpc):
| method | 参数 | 返回 |
|---|---|---|
getGifts | — | ZBJGift[],当前平台礼物列表 |
getRoomInfo | — | ZBJRoomInfo,由 status store 与 platform 状态组装 |
getGifts() 与 getRoomInfo() 是 invoke 的语法糖,签名与类型更友好。
3.6 实例级持久化 storage
| 操作 | 行为 |
|---|---|
get(key) | 读本地缓存(init 下发的 widgetState + 后续 set 的乐观更新) |
set(key, value) | 本地缓存乐观更新;经 postMessage state 通知宿主;宿主在 edit 模式下写入 element.widgetState 并随场景持久化(preview / virtualcam 模式下仅作内存态) |
remove(key) | 同 set,但操作类型为 remove |
keys() | 返回当前本地缓存的所有键 |
值必须可 JSON.stringify 序列化。storage 是「实例级」的:同一挂件多次添加到画布产生的多个实例彼此隔离。
3.7 日志 log
ts
ZBJ.log("hello", { foo: 1 });会被宿主以 [widget] <widgetId> hello { foo: 1 } 的形式输出到主播酱主进程日志。生产环境下挂件页面的 console.log 可能被构建工具移除;通过 ZBJ.log 可在生产构建中保留日志能力。