# 原生与 H5 Bridge 协议草案
> 文档版本：v1.0
> 最后更新：2026-04-02 08:28:05


本文档定义当前项目中 **原生小程序** 与 **H5 定制内容页** 之间的基础通信协议。

目标：

- 让 H5 能获取当前游戏上下文
- 让 H5 能请求原生能力
- 让原生能接收 H5 的结果回传
- 保持协议简单、稳定、可版本化
- 为后续拍照、录音、小游戏、结果页等扩展留出空间

---

## 0. 当前适用前提

本规范当前属于：

- 协议与实现预留
- 容器与回退机制先行

最近排查已经确认，当前最初使用的是**个人主体**小程序。  
在这个前提下，`web-view` 能力本身可能受限。

因此：

- Bridge 规范仍然应该先定义
- 容器页与回退机制也应该先实现
- 但在企业主体审核通过前，不应把 H5 接入是否成功完全归因于 bridge 代码本身

详细说明见：

- [platform-capability-notes.md](D:/dev/cmr-mini/doc/debug/平台能力说明.md)

---

## 1. 协议原则

### 原则 1：Bridge 要版本化

建议先固定：

- `content-v1`
- `result-v1`

后续升级时：

- 新增 `content-v2`
- 新增 `result-v2`

不要直接改旧协议。

### 原则 2：请求能力最小化

先只开放真正需要的能力，不要一开始做成“大而全总线”。

### 原则 3：原生控制核心状态

Bridge 只能做：

- 展示
- 上报
- 请求能力

不能让 H5 直接改比赛核心状态。

### 原则 4：消息必须可回执

每个请求都应有明确成功/失败返回，不允许 H5 靠超时猜测。

---

## 2. 通道模型

建议统一按“请求 / 响应 / 事件”三类消息组织：

- `request`
  H5 请求原生能力
- `response`
  原生返回能力执行结果
- `event`
  原生主动推送状态变化

推荐消息外壳：

```json
{
  "id": "req-001",
  "channel": "request",
  "type": "getGameContext",
  "payload": {}
}
```

响应：

```json
{
  "id": "req-001",
  "channel": "response",
  "type": "getGameContext",
  "ok": true,
  "payload": {}
}
```

---

## 3. 原生注入给 H5 的基础上下文

建议至少包含：

```json
{
  "bridgeVersion": "content-v1",
  "eventId": "sample-score-o-001",
  "mode": "score-o",
  "sessionId": "session-001",
  "sessionStatus": "running",
  "controlId": "control-3",
  "controlKind": "control",
  "title": "湖边步道",
  "body": "这里适合短暂停留观察周边地形。",
  "theme": "default-race"
}
```

对于结果页，可扩展为：

```json
{
  "bridgeVersion": "result-v1",
  "eventId": "sample-score-o-001",
  "mode": "score-o",
  "sessionId": "session-001",
  "summary": {
    "title": "比赛结束",
    "heroValue": "120",
    "rows": []
  }
}
```

---

## 4. H5 -> 原生：第一阶段推荐动作

建议第一阶段只支持这几个：

### `close`

作用：

- 关闭当前 H5 页面

示例：

```json
{
  "id": "req-001",
  "channel": "request",
  "type": "close",
  "payload": {}
}
```

### `getGameContext`

作用：

- 让 H5 主动获取最新上下文

### `takePhoto`

作用：

- 请求原生拍照

### `recordAudio`

作用：

- 请求原生录音

### `submitResult`

作用：

- 把 H5 内的任务结果、表单或作品结果提交回原生

示例：

```json
{
  "id": "req-002",
  "channel": "request",
  "type": "submitResult",
  "payload": {
    "taskId": "photo-task-1",
    "status": "completed",
    "assetId": "img-001"
  }
}
```

---

## 5. 建议第二阶段可扩展动作

等第一阶段跑稳后，再逐步加入：

- `uploadImage`
- `uploadAudio`
- `getLocation`
- `openMiniGame`
- `submitForm`
- `share`
- `restartSession`

这些先不要第一阶段全开。

---

## 6. 原生 -> H5：统一返回结构

建议统一返回：

```json
{
  "id": "req-002",
  "channel": "response",
  "type": "takePhoto",
  "ok": true,
  "payload": {
    "assetId": "img-001",
    "url": "https://example.com/assets/img-001.jpg"
  }
}
```

失败时：

```json
{
  "id": "req-002",
  "channel": "response",
  "type": "takePhoto",
  "ok": false,
  "error": {
    "code": "USER_CANCELLED",
    "message": "用户取消拍照"
  }
}
```

---

## 7. 原生 -> H5：推荐事件

原生可按需主动推送轻量事件：

- `contextUpdated`
- `sessionFinished`
- `sessionExited`
- `networkChanged`

但第一阶段要克制，避免高频推送。

不建议第一阶段主动高频推：

- GPS 实时位置流
- 指北针实时角度
- HUD 高频数字

这些不适合让 H5 主导。

---

## 8. 错误码建议

建议第一阶段统一几类错误：

- `USER_CANCELLED`
- `PERMISSION_DENIED`
- `NETWORK_ERROR`
- `UNSUPPORTED_ACTION`
- `BRIDGE_NOT_READY`
- `INTERNAL_ERROR`

这样 H5 侧更容易统一处理。

---

## 9. 安全与边界

### 9.1 H5 不直接改核心比赛状态

H5 不能直接决定：

- 是否打点成功
- 是否跳点成功
- 是否比赛结束

### 9.2 H5 只能请求能力

原生决定是否执行：

- 拍照
- 录音
- 上传
- 页面关闭

### 9.3 Bridge 能力按页面类型开放

例如：

- 内容页开放 `takePhoto`
- 结果页不一定开放

后续可做按 `bridgeVersion` 或 `pageType` 的能力白名单。

---

## 10. 第一阶段推荐支持范围

建议第一阶段只正式支持：

- `close`
- `getGameContext`
- `takePhoto`
- `recordAudio`
- `submitResult`

这样足够承接：

- 文创详情
- 拍照任务
- 语音留言
- 结果页回传动作

---

## 11. 不建议第一阶段支持的内容

先不要一上来开放：

- 任意写比赛状态
- 任意切换玩法
- 任意修改地图行为
- 任意控制打点
- 高频实时 telemetry 推送

这些都属于核心状态，应该继续由原生掌控。

---

## 12. 当前建议实施顺序

1. 先实现一个通用 H5 容器页
2. 先跑通 `content-v1`
3. 先支持 5 个最小动作
4. 再跑通一个简单结果页
5. 最后再扩桥接能力

---

## 13. 当前建议结论

Bridge 的第一阶段目标，不是做成万能总线，而是：

**稳定承接定制内容页与结果页的最小需求。**

先把：

- 关闭
- 获取上下文
- 拍照
- 录音
- 结果提交

这 5 条做稳，就足够支撑第一波客户定制需求。



