Files
Genarrative/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md
T
kdletters d71fde8b9a 统一三端宿主桥接层结构
拆分移动壳能力清单与分发模块

拆分桌面壳能力清单与分发模块

补充微信壳协议与分发薄索引

同步三端桥接层门禁和文档
2026-06-18 18:41:34 +08:00

20 KiB
Raw Blame History

宿主壳能力统一协议

更新时间:2026-06-18

背景

当前主站已经同时运行在普通浏览器、微信小程序 web-view 和后续可能出现的原生 App WebView 中。登录、支付、分享、订阅授权、运行态分享目标同步等能力散落在业务组件和服务文件里,后续如果新增原生 App 壳,容易出现同一业务按宿主重复分叉。

本方案先建立 HostBridge 宿主壳协议,把浏览器、微信小程序壳和未来原生 App 壳统一成能力 adapter。固定玩法运行态仍作为平台内置 runtime,AI 生成 H5 仍走独立 sandbox 和受限 GameBridge;二者都不能直接拿完整宿主壳能力。

目标

  1. H5 业务层只判断宿主能力,不直接散落判断 wx.miniProgramMicroMessengerclientRuntime
  2. 微信小程序壳先作为 wechat_mini_program adapter 接入,保留现有登录、支付、分享、订阅授权行为。
  3. 未来原生 App 壳只新增 native_app adapter,不重写 H5 业务。
  4. 固定玩法继续读取作品数据、素材、运行态 snapshot 和后端裁决结果,不走代码包下载流程。
  5. AI H5 sandbox 只能通过受限 GameBridge 请求资产、上报事件和提交候选结果,不暴露登录、支付、token、完整用户资料。

非目标

  • 不重写 React 主站和现有玩法 runtime。
  • 不把固定玩法迁成远程代码包。
  • 不在本阶段实现 React Native / Expo 壳。
  • 不改变支付到账、任务、排行榜、发布、统计等后端裁决口径。

分层

H5 业务层
  -> HostBridge 能力接口
    -> browserHostBridge
    -> wechatMiniProgramHostBridge
    -> nativeAppHostBridge

AI H5 sandbox
  -> GameBridge 受限协议
  -> parent HostBridge adapter

桥接层文件结构按宿主统一为“协议 / 能力清单 / 分发 / 宿主容器行为”四类职责。微信小程序不硬套 Expo / Tauri 的 request 总线:miniprogram/host-bridge/protocol.js 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,dispatch.js 只作为 protocolwebViewpaymentshareGridsubscribeMessage 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别放在 webView.jspayment.jsshareGrid.jssubscribeMessage.jsminiprogram/shell/webView.jspayment.jsshareGrid.jssubscribeMessage.js 承接 Page 生命周期、wx.* 容器调用、WebView 容器行为、支付页和订阅页装配,页面目录只保留 Page(createWechat...Page()) 装配。Expo 移动壳使用 apps/mobile-shell/src/host-bridge/protocol.ts 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,capabilities.ts 承接能力清单与 iOS 差异能力,dispatch.ts 承接 method 分发和宿主能力调用,files.ts / share.ts 分别承接文件和分享能力,bridge.ts 只作为 WebView message 入口、request id replay 编排和对外 facadeapps/mobile-shell/App.tsx 只装配 apps/mobile-shell/src/shell/ShellApp.tsx,由 apps/mobile-shell/src/shell/*.ts(x) 承接 WebView 容器、URL、导航、网络、生命周期、安全区和 WebView policy。Tauri 桌面壳使用 apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs 承接 envelope、method 白名单、request 校验和 replay 状态,capabilities.rs 承接能力清单,dispatch.rs 承接 method 分发和宿主能力调用,files.rs / share.rs 分别承接文件和分享能力,mod.rs 只保留模块声明、必要 re-export、host_bridge_request command facade 和 replay 编排;apps/desktop-shell/src-tauri/src/shell/runtime.rsurl.rsnavigation.rsnetwork.rslifecycle.rsfile_drop.rsevents.rsdeep_link.rstray.rswebview.rs 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘和 WebView 门面,main.rs 只保留 Tauri builder / plugin / window 装配。npm run check:native-shells 会检查这些目录清单。

首批能力

  • getHostRuntime():识别 browserwechat_mini_programnative_app,并解析 hostCapabilities 能力声明;进入 native_app 后会通过真实 host.getRuntime 回读宿主 runtime 并缓存能力清单,未知能力会被丢弃。H5 业务只根据已声明或已回读的能力展示入口、发起宿主请求或走 fallback。
  • getHostAppearanceColorScheme():原生 App 宿主的受控外观查询入口。H5 可通过 appearance.getColorScheme 读取宿主当前 light / dark / unknown 配色模式;Expo 移动壳通过 React Native Appearance.getColorScheme() 读取系统偏好,Tauri 桌面壳通过主窗口 theme() 读取窗口主题。该能力只读,不改变 H5 主题,也不覆盖用户或系统偏好。
  • subscribeHostAppLifecycle():原生 App 宿主的受控生命周期事件入口。Expo 移动壳和 Tauri 桌面壳都声明 host.events,表示宿主会通过 HostBridge message 派发事件;其中 Expo 移动壳通过 React Native AppState 派发 app.lifecycle,Tauri 桌面壳通过主窗口 focus / blur 事件派发同名事件。host.events 不作为 request method,也不开放 Tauri event 插件或 React Native 私有事件 API。H5 只依赖统一的 active / inactive / background 状态和 focused 布尔值,原生细分状态只放在 nativeState 用于排障,不作为业务分支依据。H5 统一通过 useHostLifecycleActive() 把宿主状态折算为运行态可播放状态;WebAudio 背景音乐和固定玩法 <audio> 背景音乐都必须按该状态暂停 / 恢复,宿主进入后台、inactive 或窗口失焦时暂停,回到 active 且 focused 后只在原运行态、音源和用户音量仍允许时恢复。
  • getHostNetworkStatus() / subscribeHostNetworkStatusChange():原生 App 宿主的受控网络状态入口。Expo 移动壳通过 expo-network 查询并订阅真实系统网络状态;Tauri 桌面壳通过短超时连接 app.genarrative.world:443 查询主站可达性,并在主 WebView 内监听 online / offline 注入变化事件。H5 只依赖统一的 isConnectedisInternetReachable 和连接类型,不直接读取平台私有网络 API。平台外部生成队列概览通过 useHostNetworkOnline() 消费该状态,宿主未声明网络能力时保持原轮询行为,宿主明确离线或不可达时暂停轮询,恢复在线后重新刷新;该状态不替代后端队列事实或生成结果回读。
  • requestHostLogin():微信小程序跳转原生登录页;浏览器返回 false,由 H5 登录弹窗承接。
  • requestHostPayment():微信小程序支付跳转原生支付页;其它渠道返回 false,继续走 H5 / Native 二维码。
  • setHostShareTarget():把当前公开作品分享目标同步给宿主。
  • openHostShare():原生 App 宿主的受控分享入口。发布分享弹窗只在 hostCapabilities 声明 share.open 时展示“系统分享”,通过 share.open 把当前作品标题、作品号和公开 URL 交给宿主;Expo 移动壳打开系统分享面板,Tauri 桌面壳把分享文本写入系统剪贴板,宿主不可用或返回 unsupported 时显示失败并保留复制链接路径。
  • openHostShareGrid():微信小程序九宫格切图页。
  • writeHostClipboardText():原生 App 宿主的受控剪贴板入口。H5 复制服务在 native_app 中优先通过 clipboard.writeText 写入 Expo / Tauri 系统剪贴板;宿主不可用、拒绝或返回 unsupported 时继续回退到浏览器 Clipboard API 和 legacy selection copy。
  • readHostClipboardText():原生 App 宿主的受控剪贴板读取入口。H5 只能读取纯文本结果,宿主返回内容会按 HostBridge 契约限制到 100000 字符;Expo 移动壳通过 expo-clipboard 读取系统剪贴板文本,Tauri 桌面壳通过 Rust 侧 clipboard-manager 读取系统剪贴板文本。该能力不读取图片、HTML、文件列表或剪贴板监听事件,不把 Tauri / Expo 剪贴板插件 API 直接暴露给 H5;宿主未声明或读取失败时由 H5 视作失败并保留原流程。个人中心的邀请码和兑换码弹窗只在宿主声明 clipboard.readText 时显示“粘贴”,读取到的纯文本只填入现有输入框,不自动提交、不代表兑换成功。
  • requestHostHapticsImpact():原生 App 宿主的受控触觉反馈入口。Expo 移动壳通过 haptics.impact 调用 Expo Haptics,只接受 lightmediumheavy 三档 impact style,缺省为 light,未知值返回 invalid_request 且不触发设备反馈;H5 运行时点击反馈在 native_app 中优先请求宿主触觉,宿主不可用、拒绝或返回 unsupported 时继续回退到浏览器 navigator.vibrate
  • showHostLocalNotification():原生 App 宿主的受控即时本地通知入口。H5 只能传必填 title 和可选 body,两者都会去除首尾空白、折叠普通空白、限制长度并拒绝控制字符;Expo 移动壳通过 expo-notifications 请求通知权限、创建 Android 本地通知 channel 并立刻调度本地通知,Tauri 桌面壳通过 Rust 侧 tauri-plugin-notification 发送系统通知。该能力不包含远程推送、token 注册、定时提醒、后台远程通知或任意通知插件透传,宿主未声明、权限拒绝或系统失败时由 H5 视作失败并继续主流程。当前 H5 只在现有草稿生成任务收口为完成或失败时请求即时本地通知;通知按草稿来源去重,同一草稿重新进入生成中后才允许再次通知,不改变队列状态、弹窗、作品架或后端裁决。
  • setHostAppTitle():原生 App 宿主的受控窗口标题入口。H5 主站会按当前平台阶段先同步 document.title,再通过 app.setTitle 请求宿主窗口标题同步;Tauri 桌面壳支持该能力,Expo 移动壳不声明时静默忽略。
  • setHostAppBadgeCount():原生 App 宿主的受控应用角标入口。H5 只传 0-99999 的整数,0 表示清除角标;Expo 移动壳只在 iOS 声明 app.setBadgeCount 并通过 React Native PushNotificationIOS 设置应用图标角标,Android 不声明该能力;Tauri 桌面壳通过主窗口 set_badge_count 设置任务栏角标,底层平台不支持时返回明确错误,由 H5 视作失败并继续主流程。当前 H5 只把“可见作品架里未读的草稿生成完成更新”同步为角标数,同一个草稿有多个恢复 ID 时只计 1,已读、失败、生成中和不可见草稿不计入;宿主不支持或设置失败不改变 H5 红点、作品架或后端状态。
  • reloadHostWebView():原生 App 宿主的受控 WebView 刷新入口。H5 只能请求刷新当前承载主站的宿主 WebView;Expo 移动壳调用当前 react-native-webviewreload()Tauri 桌面壳调用主 WebviewWindow.reload()。该能力不接受 payload,不开放任意 URL 导航、脚本执行、Tauri guest API 或 RN WebView ref;成功只表示宿主已发起刷新,刷新后当前 H5 上下文会卸载。AuthGate 在登录态从未登录变为已登录、或从已登录变为未登录时优先调用该能力刷新当前容器;宿主未声明、返回失败或不可用时再回退浏览器 window.location.reload()
  • openHostExternalUrl():原生 App 宿主的受控外链入口。H5 中需要离开主站的外链在 native_app 下先通过 app.openExternalUrl 请求宿主系统浏览器打开;只允许 http:https:mailto:tel:,相对路径会先归一化到当前站点绝对 URL。宿主不可用或拒绝时回退浏览器外链行为,普通浏览器和小程序保持原有 <a> 语义。
  • navigateHostNativePage():受控跳转宿主页,供订阅授权、支付、登录等 adapter 复用。Expo 移动壳首版只接受同源 H5 route 并切换 WebView URLTauri 桌面壳同样只接受 https://app.genarrative.world 同源 H5 route 并在主窗口内跳转。真正原生页面、登录和支付能力必须等对应 SDK / 页面接入后再声明支持。
  • exportHostTextFile():原生 App 宿主的受控文本导出入口。Expo 移动壳通过 file.exportText 写入缓存文本文件并交给系统分享 / 保存面板;Tauri 桌面壳通过 file.exportText 打开系统保存对话框并写入用户选择的文件。文件名必须清洗,单次文本不超过 5 MiB,成功只返回文件名和字节数,不把本机绝对路径暴露给 H5;系统分享不可用或用户取消时返回明确错误,由 H5 fallback 承接。
  • importHostTextFile():原生 App 宿主的受控文本导入入口。Expo 移动壳通过 Expo DocumentPicker 打开系统文档选择器,Tauri 桌面壳通过系统文件选择框读取用户选择的文本文件;两端都只接受 text/plaintext/markdowntext/csvapplication/json 或对应扩展名,单次不超过 5 MiB,成功只返回清洗后的文件名、MIME、UTF-8 文本内容和字节数,不暴露设备本地 URI 或本机绝对路径,也不开放通用文件系统能力;用户取消时由 H5 facade 归为 false。创作 Agent 工作台在 native_app 且声明该能力时优先调用宿主文本导入,并把结果转换成现有浏览器 File 后继续复用后端 /api/runtime/creation-agent/document-inputs/parse 解析链路;普通浏览器、小程序和未声明能力的裁剪壳继续使用原文件输入。
  • exportHostImageFile():原生 App 宿主的受控图片导出入口。H5 只传自己生成的图片 base64Data、清洗后的文件名和允许的 image/png / image/jpeg / image/webp MIME;Expo 移动壳写入缓存图片后交给系统分享 / 保存面板,Tauri 桌面壳打开系统保存对话框并写入图片字节。单次图片不超过 5 MiB,成功只返回文件名和字节数,不回传本机绝对路径。当前分享卡下载在 native app 中优先走 file.exportImage,宿主未声明时保留浏览器下载路径。
  • importHostImageFile() / captureHostImageFile() / subscribeHostImageDrop():原生 App 宿主的受控图片导入入口。Expo 移动壳通过 Expo ImagePicker 请求相册权限并打开系统相册选择器,也可在声明 file.captureImage 时请求相机权限并打开系统相机拍摄图片;Tauri 壳通过系统文件选择框或主窗口拖拽事件读取用户选择 / 拖入的图片,不声明拍摄能力。图片能力都只接受 image/pngimage/jpegimage/webp,单次不超过 10 MiB,成功只返回文件名、MIME、base64 内容、字节数和可选拖入坐标,不暴露设备本地 URI 或本机绝对路径,也不开放通用文件系统能力;移动拍摄不请求麦克风权限。H5 的通用图片输入面板 CreativeImageInputPanelnative_app 且声明 file.importImage / file.captureImage 时分别调用宿主导入 / 拍摄,并把结果转换成现有 File 回调;反馈页上传凭证和个人资料头像上传在 native_app 且声明 file.importImage 时同样优先调用宿主图片导入,其中反馈页继续复用原有数量、大小、data URL 和提交 payload 校验,头像继续复用 H5 侧图片类型、5 MiB 大小限制、方形裁剪与 updateAuthProfile 上传链路;在桌面壳同时声明 file.imageDropped 时,只有拖入坐标命中当前主图卡片且未被上层元素遮挡的面板会消费该事件。普通浏览器、小程序和未声明能力的裁剪壳继续使用浏览器文件输入。
  • importHostAudioFile():原生 App 宿主的受控音频导入入口。Expo 移动壳通过 Expo DocumentPicker 打开系统音频选择器,Tauri 壳通过系统文件选择框读取用户选择的音频;两端都只接受 audio/mpegaudio/mp4audio/wavaudio/oggaudio/webm 或对应扩展名,单次不超过 20 MiB,成功只返回清洗后的文件名、MIME、base64 内容和字节数,不暴露设备本地 URI 或本机绝对路径,也不开放通用文件系统能力。H5 的通用音频输入面板 CreativeAudioInputPanelnative_app 且声明 file.importAudio 时优先调用宿主导入,并把结果转换成现有 File 后继续复用 readFileAsAsset(file, 'uploaded') 音频处理链路;普通浏览器、小程序和未声明能力的裁剪壳继续使用浏览器文件输入。
  • exportHostAudioFile():原生 App 宿主的受控音频导出入口。H5 只传当前页面已持有的音频 base64Data、清洗后的文件名和允许的 audio/mpeg / audio/mp4 / audio/wav / audio/ogg / audio/webm MIME;Expo 移动壳写入缓存音频后交给系统分享 / 保存面板,Tauri 壳打开系统保存对话框并写入音频字节。单次音频不超过 20 MiB,成功只返回文件名和字节数,不回传本机绝对路径,也不让宿主代读任意本地文件。H5 的通用音频输入面板只在当前资产包含本地 BlobfileName 和允许 MIME 且宿主声明 file.exportAudio 时展示导出入口;远端已上传音频、浏览器、小程序和未声明能力的裁剪壳不展示该入口。

迁移顺序

  1. 新增 src/services/host-bridge/,沉淀宿主运行态识别和微信小程序 JS SDK 加载,并暴露通用 HostBridge 能力接口。
  2. authService 保留原导出,但内部委托 HostBridge,避免一次性改动 AuthGate。
  3. 分享弹窗、分享目标同步、九宫切图、微信小程序支付和订阅授权改用 HostBridge 通用接口;旧微信命名服务只作为兼容导出。
  4. 后续新增 native_app adapter 时只补桥接实现和测试,业务层不新增平台分叉;主 App 启动会触发一次 host.getRuntime 回读并订阅能力变化,避免裁剪壳或旧入口 URL 缺少 hostCapabilities 时长期隐藏真实可用能力。
  5. 每次新增或调整 native capability 后,必须运行 npm run check:native-shells,统一覆盖 H5 HostBridge 关键测试、三端桥接层文件结构门禁、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test 和桌面 release --no-bundle 构建烟测;排查单端问题时再单独运行 npm run mobile-shell:typechecknpm run mobile-shell:testnpm run mobile-shell:confignpm run mobile-shell:exportnpm run desktop-shell:typechecknpm run desktop-shell:testnpm run desktop-shell:build -- --no-bundle

验收

  • 微信小程序首点登录仍能打开原生登录页。
  • 小程序分享链接仍生成 /pages/web-view/index?targetPath=/works/detail&work=...
  • 小程序支付仍跳转 /pages/wechat-pay/index 并保留支付结果 hash 回灌确认。
  • 小程序订阅授权仍跳转 /pages/subscribe-message/index,且返回不阻断生成主链路。
  • 普通浏览器分享、H5 支付和 Native 二维码支付不受影响。
  • 原生壳统一验收入口 npm run check:native-shells 通过,能力白名单、壳 runtime 回包、URL hostCapabilities、H5 fallback、三端桥接层结构、两端壳实现、Expo managed config、移动端 production bundle、桌面 release 构建入口和三端生产壳临时替身词扫描没有漂移。

后续

  • 设计 native_apppostMessage 消息格式和回包超时策略。
  • 原生 App 壳的移动端采用 Expo + React Native,桌面端采用 Tauri;壳层只作为 HostBridge adapter,不重写现有 H5 主站和固定玩法 runtime。详细方案见 docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md
  • 为 AI H5 sandbox 单独定义 GameBridge,禁止直接依赖 HostBridge。
  • 将宿主能力、支付渠道和分享策略补充进移动端发布检查清单。