141 lines
5.5 KiB
Markdown
141 lines
5.5 KiB
Markdown
|
|
# @teres/iframe-bridge
|
|||
|
|
|
|||
|
|
一个基于 [Penpal](https://github.com/Aaronius/penpal) 的轻量 iframe 通信桥,帮助宿主页面与子应用(iframe)安全、稳定地进行双向方法调用与状态同步。
|
|||
|
|
|
|||
|
|
## 为什么需要它
|
|||
|
|
- 将跨项目的交互(如语言切换、导航、关闭、就绪通知)抽象为清晰的 API。
|
|||
|
|
- 在 Monorepo 或多项目场景下,提供“宿主 ↔ 子应用”的统一通信约定。
|
|||
|
|
- 屏蔽 Penpal 细节,专注业务方法与类型定义。
|
|||
|
|
|
|||
|
|
## 安装与版本
|
|||
|
|
- 已在工作空间内作为包使用(`packages/iframe-bridge`)。
|
|||
|
|
- 依赖:`penpal@^6.2.1`。
|
|||
|
|
|
|||
|
|
## 目录结构
|
|||
|
|
```
|
|||
|
|
packages/iframe-bridge/
|
|||
|
|
├── package.json
|
|||
|
|
├── README.md
|
|||
|
|
└── src/
|
|||
|
|
├── index.ts # 入口聚合与导出(不含业务逻辑)
|
|||
|
|
├── types.ts # 对外类型:HostApi、ChildApi
|
|||
|
|
├── path.ts # 路径转换与是否嵌入判断
|
|||
|
|
├── client.ts # 子端到父端的连接与客户端方法
|
|||
|
|
├── host.ts # 宿主侧创建与管理与子端的连接
|
|||
|
|
└── logger.ts # 统一日志工具
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 核心概念
|
|||
|
|
- Host(宿主):包含 `iframe` 的父页面。
|
|||
|
|
- Child(子端):被嵌入在 `iframe` 中的子应用。子端暴露方法给宿主调用。
|
|||
|
|
- Penpal:在宿主与子端之间建立安全的双向 RPC(方法调用)连接。
|
|||
|
|
|
|||
|
|
## 快速上手
|
|||
|
|
|
|||
|
|
### 宿主侧(Host)示例:创建连接并缓存子端引用
|
|||
|
|
```ts
|
|||
|
|
import { createPenpalHostBridge } from '@teres/iframe-bridge';
|
|||
|
|
|
|||
|
|
// iframeRef 指向你页面中的 <iframe>
|
|||
|
|
const { child, destroy } = await createPenpalHostBridge({
|
|||
|
|
iframe: iframeRef.current!,
|
|||
|
|
methods: {
|
|||
|
|
// 暴露给“子端”可调用的宿主方法
|
|||
|
|
navigate: (path: string) => {
|
|||
|
|
// 宿主导航逻辑
|
|||
|
|
},
|
|||
|
|
close: () => {
|
|||
|
|
// 关闭或返回逻辑
|
|||
|
|
},
|
|||
|
|
agentReady: (agentId?: string) => {
|
|||
|
|
// 子端就绪通知处理
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// child 是一个 Promise,解析后可拿到子端暴露的方法(例如 changeLanguage)
|
|||
|
|
await child; // 也可以缓存后续使用
|
|||
|
|
|
|||
|
|
// 组件卸载或需要重建连接时
|
|||
|
|
await destroy();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
在你的项目中(如 `src/pages/ragflow/iframe.tsx`)通常会把 `child` 引用存到一个管理器,供语言切换等场景调用。
|
|||
|
|
|
|||
|
|
### 子端侧(Child)示例:暴露方法并与宿主交互
|
|||
|
|
```ts
|
|||
|
|
import Bridge from '@teres/iframe-bridge';
|
|||
|
|
|
|||
|
|
// 在子应用初始化时调用(例如根组件 useEffect 中)
|
|||
|
|
Bridge.initChildBridge({
|
|||
|
|
// 暴露给“宿主”可调用的子端方法
|
|||
|
|
methods: {
|
|||
|
|
changeLanguage: async (lang: string) => {
|
|||
|
|
// 你的 i18n 切换逻辑,例如:
|
|||
|
|
// await i18n.changeLanguage(lang);
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 主动与宿主交互(可选)
|
|||
|
|
Bridge.clientReady();
|
|||
|
|
Bridge.clientNavigate('/some/path');
|
|||
|
|
Bridge.clientClose();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## API 参考
|
|||
|
|
|
|||
|
|
### 类型(types.ts)
|
|||
|
|
- `HostApi`
|
|||
|
|
- `navigate(path: string): void`
|
|||
|
|
- `close(): void`
|
|||
|
|
- `agentReady(agentId?: string): void`
|
|||
|
|
- `ChildApi`
|
|||
|
|
- `changeLanguage(lang: string): Promise<void>`
|
|||
|
|
|
|||
|
|
### 路径与嵌入(path.ts)
|
|||
|
|
- `isEmbedded(): boolean` 判断当前窗口是否运行在 iframe 中(且需要通信)。
|
|||
|
|
- `toHostPath(childPath: string): string` 将子端路径转换为宿主侧路由前缀路径。
|
|||
|
|
- `toChildPath(hostPath: string): string` 将宿主路径映射回子端路径。
|
|||
|
|
|
|||
|
|
### 子端客户端方法(client.ts)
|
|||
|
|
- `getClientHostApi(): Promise<HostApi>` 建立并缓存与宿主的连接。
|
|||
|
|
- `clientNavigate(path: string): Promise<void>` 请求宿主导航。
|
|||
|
|
- `clientClose(): Promise<void>` 请求宿主关闭或返回。
|
|||
|
|
- `clientReady(agentId?: string): Promise<void>` 通知宿主子端就绪。
|
|||
|
|
- `initChildBridge(options: { methods: Partial<ChildApi> }): Promise<void>` 在子端暴露方法供宿主调用(如 `changeLanguage`)。
|
|||
|
|
|
|||
|
|
### 宿主桥接(host.ts)
|
|||
|
|
- `createPenpalHostBridge({ iframe, methods })`
|
|||
|
|
- `iframe: HTMLIFrameElement`
|
|||
|
|
- `methods: Partial<HostApi>` 暴露给子端可调用的宿主方法。
|
|||
|
|
- 返回:`{ child: Promise<ChildExposed>, destroy: () => Promise<void> }`
|
|||
|
|
- `child`:解析后可获得子端暴露的方法(如 `changeLanguage`)。
|
|||
|
|
- `destroy`:销毁连接与事件监听。
|
|||
|
|
|
|||
|
|
## 语言联动最佳实践
|
|||
|
|
1. 子端在初始化中通过 `initChildBridge` 暴露 `changeLanguage`。
|
|||
|
|
2. 宿主在创建连接后缓存 `child` 引用。
|
|||
|
|
3. 宿主语言切换组件在变更时调用:
|
|||
|
|
```ts
|
|||
|
|
const child = await childPromise; // 取到缓存的子端引用
|
|||
|
|
await child.changeLanguage(nextLang);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 调试与排错
|
|||
|
|
- 确保 iframe 与宿主同源或允许跨源通信(Penpal 支持跨源,但需正确 URL)。
|
|||
|
|
- 连接建立需要子端加载完成;在子端 `initChildBridge` 之前调用子端方法会报错或超时。
|
|||
|
|
- 若需要观察连接过程,可在 `logger.ts` 中启用调试日志或在业务代码中打点。
|
|||
|
|
- 组件卸载时务必调用 `destroy()`,避免内存泄漏或事件残留。
|
|||
|
|
|
|||
|
|
## 设计说明
|
|||
|
|
- 入口 `index.ts` 仅做导出聚合,具体逻辑按职责拆分到 `types/logger/path/client/host`。
|
|||
|
|
- 子端侧会缓存一次宿主连接,避免重复握手,提高性能与稳定性。
|
|||
|
|
|
|||
|
|
## 常见问题
|
|||
|
|
- 调用时机过早:请在子端完成初始化(`initChildBridge`)后再由宿主调用子端方法。
|
|||
|
|
- 路径映射:`toHostPath`/`toChildPath` 默认适配当前项目前缀,如需自定义可扩展配置模块。
|
|||
|
|
- 异步方法:所有跨端调用均为异步,注意 `await` 与错误处理。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
若你需要把路径前缀改为可配置项或扩展更多方法(如主题切换、会话同步),欢迎提 issue 或继续迭代。
|