host-app-bridge-sdk
v1.0.0
Published
Host-App 桥接 SDK - 实现 Host 和 Iframe App 之间的双向通信
Maintainers
Readme
host-app-bridge-sdk
Host-App 桥接 SDK - 实现 Host(宿主对话界面)和 App(运行在 Iframe 中的插件)之间的双向通信。
特性
- 🚀 极简 API 设计
- 🔒 内置安全校验(Origin 验证、消息格式验证)
- 🤝 自动握手机制
- 📦 兼容 OpenAI Apps SDK 标准
- 💪 完整的 TypeScript 支持
- 🔧 MCP 工具调用结果通知
安装
pnpm add host-app-bridge-sdk快速开始
App 端使用
import { Bridge, MESSAGE_TYPES } from "host-app-bridge-sdk";
// 创建 Bridge 实例(自动发送 APP_READY)
const bridge = new Bridge({ debug: true });
// 监听来自 Host 的消息
bridge.on(MESSAGE_TYPES.CONTEXT_UPDATE, data => {
console.log("收到上下文更新:", data);
});
bridge.on(MESSAGE_TYPES.INIT_DATA, data => {
console.log("收到初始化数据:", data);
});
// 向 Host 发送 MCP 结果通知
bridge.notifyMCPSuccess("search", { items: [...] });Host 端使用
import { HostProvider, MESSAGE_TYPES } from "host-app-bridge-sdk";
// 创建 HostProvider 实例
const hostProvider = new HostProvider({ debug: true });
// 注册 Iframe App
const iframe = document.getElementById("app-iframe") as HTMLIFrameElement;
hostProvider.registerApp(iframe);
// 监听 App 就绪事件
hostProvider.onAppReady((iframe, data) => {
console.log("App 已就绪:", data);
// 推送初始化数据
hostProvider.pushInitData(iframe, { theme: "dark", token: "xxx" });
});
// 推送上下文数据
hostProvider.pushContext(iframe, { conversationId: "123" });架构概述
┌─────────────────────────────────────────────────────────────┐
│ Host 应用 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ HostProvider │ │
│ │ - 注册/注销 Iframe App │ │
│ │ - 监听 App 消息 │ │
│ │ - 推送上下文/指令/初始化数据 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ window.postMessage │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Iframe App │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Bridge │ │ │
│ │ │ - 监听 Host 消息 │ │ │
│ │ │ - 向 Host 发送通知 │ │ │
│ │ │ - MCP 结果通知 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘通信流程
App Host
│ │
│ ──────── APP_READY ────────────► │ 1. App 加载完成,发送就绪消息
│ │
│ ◄─────── INIT_DATA ──────────── │ 2. Host 推送初始化数据
│ │
│ ◄────── CONTEXT_UPDATE ──────── │ 3. Host 推送上下文更新
│ │
│ ◄─────── AI_COMMAND ─────────── │ 4. Host 推送 AI 指令
│ │
│ ──────── MCP_RESULT ───────────► │ 5. App 通知 MCP 调用结果
│ │API 参考
Bridge 类
App 端使用的桥接 SDK,在 Iframe 中运行。
构造函数
new Bridge(options?: BridgeOptions)BridgeOptions:
| 参数 | 类型 | 默认值 | 说明 |
| ---------------- | ---------- | ------- | ------------------ |
| allowedOrigins | string[] | ['*'] | 允许的 origin 列表 |
| debug | boolean | false | 是否开启调试模式 |
方法
on(type: string, callback: (payload: any) => void): void
监听来自 Host 的消息。
bridge.on(MESSAGE_TYPES.CONTEXT_UPDATE, data => {
console.log("收到上下文:", data.context);
});notifyHost(type: string, payload?: any): void
向 Host 发送通知消息。
bridge.notifyHost("custom_event", { action: "click", target: "button" });notifyMCPResult(toolName: string, success: boolean, result?: any, error?: string, callId?: string): void
通知 Host MCP 工具调用结果(标准化格式)。
// 成功调用
bridge.notifyMCPResult("search", true, { items: [...] });
// 失败调用
bridge.notifyMCPResult("search", false, undefined, "搜索服务不可用");
// 带调用 ID
bridge.notifyMCPResult("search", true, { items: [...] }, undefined, "call_123");notifyMCPSuccess(toolName: string, result: any, callId?: string): void
便捷方法,通知 Host MCP 工具调用成功。
const result = await mcpClient.call("search", { query: "test" });
bridge.notifyMCPSuccess("search", result);notifyMCPError(toolName: string, error: string, callId?: string): void
便捷方法,通知 Host MCP 工具调用失败。
try {
await mcpClient.call("search", { query: "test" });
} catch (err) {
bridge.notifyMCPError("search", err.message);
}off(type: string, callback?: Function): void
移除消息监听器。
// 移除特定回调
bridge.off(MESSAGE_TYPES.CONTEXT_UPDATE, myCallback);
// 移除该类型所有监听器
bridge.off(MESSAGE_TYPES.CONTEXT_UPDATE);getIsReady(): boolean
获取 Bridge 是否已就绪。
if (bridge.getIsReady()) {
// Bridge 已就绪,可以正常通信
}setAllowedOrigins(origins: string[]): void
更新允许的 origin 列表。
bridge.setAllowedOrigins(["https://trusted-host.com"]);addAllowedOrigin(origin: string): void
添加允许的 origin。
bridge.addAllowedOrigin("https://another-trusted-host.com");destroy(): void
销毁 Bridge 实例,清理资源。
// 在组件卸载时调用
bridge.destroy();HostProvider 类
Host 端使用的管理类,负责管理与 Iframe App 的通信。
构造函数
new HostProvider(options?: HostProviderOptions)HostProviderOptions:
| 参数 | 类型 | 默认值 | 说明 |
| ---------------- | ---------- | ------- | -------------------------- |
| allowedOrigins | string[] | ['*'] | 允许的 origin 列表 |
| debug | boolean | false | 是否开启调试模式 |
| initTimeout | number | 10000 | APP_READY 超时时间(毫秒) |
方法
registerApp(iframe: HTMLIFrameElement, expectedOrigin?: string): void
注册 Iframe App。
const iframe = document.getElementById("app-iframe") as HTMLIFrameElement;
hostProvider.registerApp(iframe, "https://app-domain.com");unregisterApp(iframe: HTMLIFrameElement): void
注销 Iframe App。
hostProvider.unregisterApp(iframe);sendToApp(iframe: HTMLIFrameElement, type: string, payload?: any): boolean
向指定 App 发送消息,返回是否发送成功。
const success = hostProvider.sendToApp(iframe, "custom_message", { data: "test" });pushContext(iframe: HTMLIFrameElement, context: any): void
推送上下文数据给 App。
hostProvider.pushContext(iframe, {
conversationId: "conv_123",
userId: "user_456",
messages: [...]
});pushCommand(iframe: HTMLIFrameElement, command: any): void
推送 AI 指令给 App。
hostProvider.pushCommand(iframe, {
action: "search",
params: { query: "test" }
});pushInitData(iframe: HTMLIFrameElement, data: any): void
推送初始化数据给 App。
hostProvider.pushInitData(iframe, {
theme: "dark",
token: "auth_token_xxx",
config: { apiBaseUrl: "/api" }
});getAppStatus(iframe: HTMLIFrameElement): AppStatus
获取 App 状态。
const status = hostProvider.getAppStatus(iframe);
// 返回: "ready" | "loading" | "error" | "unknown"onAppReady(callback: AppReadyCallback): void
监听 App 就绪事件。
hostProvider.onAppReady((iframe, data) => {
console.log("App 已就绪:", data.version, data.capabilities);
});onMessage(callback: MessageCallback): void
监听所有来自 App 的消息。
hostProvider.onMessage((iframe, type, payload) => {
console.log(`收到消息 [${type}]:`, payload);
});offAppReady(callback: AppReadyCallback): void
移除 App 就绪事件监听。
offMessage(callback: MessageCallback): void
移除消息事件监听。
getRegisteredApps(): HTMLIFrameElement[]
获取所有已注册的 App。
const apps = hostProvider.getRegisteredApps();
console.log(`已注册 ${apps.length} 个 App`);getReadyApps(): HTMLIFrameElement[]
获取所有已就绪的 App。
const readyApps = hostProvider.getReadyApps();
readyApps.forEach(iframe => {
hostProvider.pushContext(iframe, newContext);
});setAllowedOrigins(origins: string[]): void
更新允许的 origin 列表。
addAllowedOrigin(origin: string): void
添加允许的 origin。
removeAllowedOrigin(origin: string): void
移除允许的 origin。
destroy(): void
销毁 HostProvider 实例,清理资源。
消息类型常量
import { MESSAGE_TYPES } from "@zhixing/host-app-bridge-sdk";
// 握手相关
MESSAGE_TYPES.APP_READY; // "app_ready" - App 就绪消息
MESSAGE_TYPES.INIT; // "init" - 初始化消息
MESSAGE_TYPES.INIT_DATA; // "init_data" - 初始化数据消息
// 上下文同步
MESSAGE_TYPES.CONTEXT_UPDATE; // "context_update" - 上下文更新消息
// AI 指令
MESSAGE_TYPES.AI_COMMAND; // "ai_command" - AI 指令消息
// MCP 结果
MESSAGE_TYPES.MCP_RESULT; // "mcp_result" - MCP 调用结果消息类型定义
// 消息结构
interface BridgeMessage {
type: string;
payload?: any;
id?: string;
timestamp?: number;
}
// App 状态
type AppStatus = "ready" | "loading" | "error" | "unknown";
// App 就绪数据
interface AppReadyData {
version: string;
capabilities: string[];
}
// 初始化数据
interface InitData {
context?: any;
theme?: "light" | "dark";
token?: string;
config?: any;
timestamp?: number;
}
// MCP 结果数据
interface MCPResultData {
success: boolean;
result?: any;
error?: string;
toolName: string;
callId?: string;
}
// 回调类型
type AppReadyCallback = (iframe: HTMLIFrameElement, data: any) => void;
type MessageCallback = (iframe: HTMLIFrameElement, type: string, payload: any) => void;安全模块
import {
validateOrigin,
validateMessageFormat,
SecurityValidator
} from "@zhixing/host-app-bridge-sdk";
// 验证 origin
const originResult = validateOrigin("https://example.com", ["https://example.com"]);
if (originResult.valid) {
// origin 有效
}
// 验证消息格式
const messageResult = validateMessageFormat({ type: "test", payload: {} });
if (messageResult.valid) {
// 消息格式有效
}
// 使用 SecurityValidator 类
const validator = new SecurityValidator({
allowedOrigins: ["https://trusted.com"],
strictMode: true
});Vue 3 集成示例
App 端 (Iframe 内)
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import { Bridge, MESSAGE_TYPES } from "@zhixing/host-app-bridge-sdk";
const bridge = ref<Bridge>();
const context = ref<any>(null);
onMounted(() => {
bridge.value = new Bridge({
allowedOrigins: ["http://localhost:3000"],
debug: true
});
// 监听上下文更新
bridge.value.on(MESSAGE_TYPES.CONTEXT_UPDATE, data => {
context.value = data.context;
});
// 监听初始化数据
bridge.value.on(MESSAGE_TYPES.INIT_DATA, data => {
console.log("初始化数据:", data);
});
});
onUnmounted(() => {
bridge.value?.destroy();
});
// MCP 工具调用
async function callSearchTool(query: string) {
try {
const result = await fetch("/api/mcp/search", {
method: "POST",
body: JSON.stringify({ query })
}).then(r => r.json());
bridge.value?.notifyMCPSuccess("search", result);
return result;
} catch (error) {
bridge.value?.notifyMCPError("search", error.message);
throw error;
}
}
</script>Host 端
<template>
<div class="host-container">
<iframe ref="appIframe" :src="appUrl" @load="onIframeLoad" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { HostProvider, MESSAGE_TYPES } from "@zhixing/host-app-bridge-sdk";
const appIframe = ref<HTMLIFrameElement>();
const appUrl = "https://your-app-domain.com";
let hostProvider: HostProvider;
onMounted(() => {
hostProvider = new HostProvider({
allowedOrigins: ["https://your-app-domain.com"],
debug: true
});
// 监听 App 就绪
hostProvider.onAppReady((iframe, data) => {
console.log("App 已就绪:", data);
// 推送初始化数据
hostProvider.pushInitData(iframe, {
theme: "dark",
token: localStorage.getItem("token")
});
});
// 监听 MCP 结果
hostProvider.onMessage((iframe, type, payload) => {
if (type === MESSAGE_TYPES.MCP_RESULT) {
console.log("MCP 结果:", payload);
}
});
});
function onIframeLoad() {
if (appIframe.value) {
hostProvider.registerApp(appIframe.value);
}
}
// 推送上下文更新
function updateContext(newContext: any) {
if (appIframe.value) {
hostProvider.pushContext(appIframe.value, newContext);
}
}
onUnmounted(() => {
hostProvider?.destroy();
});
</script>安全最佳实践
- 配置具体的 origin 列表:生产环境不要使用
['*'],应该配置具体的域名。
// ❌ 不推荐
new Bridge({ allowedOrigins: ["*"] });
// ✅ 推荐
new Bridge({ allowedOrigins: ["https://trusted-host.com"] });验证消息来源:SDK 内置了 origin 验证,确保只接收来自可信来源的消息。
使用 HTTPS:生产环境应该使用 HTTPS 协议。
定期更新 origin 列表:根据业务需求及时更新允许的 origin 列表。
调试
开启调试模式可以在控制台查看详细的消息日志:
const bridge = new Bridge({ debug: true });
const hostProvider = new HostProvider({ debug: true });调试模式会输出:
- 接收到的消息内容
- 发送的消息内容
- Origin 验证结果
- 消息格式验证结果
- 错误信息
示例文件
完整的使用示例请参考:
许可证
MIT
