Files
TERES_web_frontend/packages/iframe-bridge/README.md

141 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# @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 或继续迭代。