Files
TERES_web_frontend/packages/iframe-bridge

@teres/iframe-bridge

一个基于 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示例创建连接并缓存子端引用

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示例暴露方法并与宿主交互

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. 宿主语言切换组件在变更时调用:
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 或继续迭代。