8.3 KiB
H5 访问
H5 是桌面服务的可选浏览器入口。启用后,手机可以通过局域网或你自己的反向代理访问同一套会话、消息、附件和权限流程。
它不是公开 SaaS,也不是多租户账号系统。任何拿到有效 H5 Token 的设备都可以访问桌面服务暴露的核心能力,只应在可信网络、VPN 或自己维护的 HTTPS 代理中使用。
推荐结构:一个地址、一个端口
当前服务端可以同时提供:
- H5 静态页面
- REST API
- Provider 代理
- WebSocket / SDK 连接
因此推荐让浏览器只访问一个来源:
http://192.168.1.20:3456/
同源路径不需要再维护独立的前端地址,也能减少 Token 和 WebSocket 代理配置错误。源码运行时默认服务端口是 3456;发布版应以设置页显示的“当前端口”为准。
安全策略不会因为前后端同源就自动信任一个远程浏览器。该来源仍需出现在 H5 允许来源中;桌面端正常开启流程会沿用已保存的 H5 配置,源码或手工部署应按下文显式填写 allowedOrigins。
桌面端开启步骤
- 打开「设置 → H5 访问」。
- 打开「启用 H5 访问」,阅读风险提示后确认。
- 查看自动生成的 H5 链接和“当前端口”。
- 如果保存的局域网 IP 已失效,使用设置页建议的新 IP,或手动填写电脑当前的局域网 IP。
- 需要稳定书签或反向代理时,在“固定端口”填写
3456或其他浏览器允许的端口并保存。 - 修改固定端口后重启桌面端,确认“当前端口”已经变成新值。
- 用手机扫描二维码,或复制“扫码链接”到可信设备。
二维码链接会携带 Server URL 和 H5 Token。浏览器连接成功后会在本机保存连接信息,后续打开可继续使用。
访问主机 / IP
普通局域网访问只填写电脑真实的私有 IP,例如:
192.168.1.20
设置页会结合当前服务端口生成完整地址。如果电脑切换 Wi-Fi、网线或 VPN,原 IP 可能不再属于当前网卡;桌面端会提示切换到可用地址。
使用反向代理时可以直接填写完整 URL:
https://cc.example.com
桌面端无法替你验证外部域名、隧道或证书是否持续可用。
固定端口
不设置固定端口时,桌面端会使用当前运行端口,并尽量复用已有端口。下列场景建议固定:
- 手机书签需要长期不变。
- 防火墙只允许指定端口。
- Nginx、Caddy 或隧道固定转发一个上游端口。
端口必须是 1024–65535 之间且浏览器允许访问的整数。修改后要重启应用才会生效。
断连保活
手机锁屏、切后台或短暂换网时:
- 正在执行的任务不会因为 H5 断连被直接停止。
- 任务可以在后台完成,重连后查看结果。
- 只有任务已经空闲且无人连接时,才会在保活时间到期后停止对应 CLI。
默认空闲保活时间为 30 秒,可配置范围是 5 秒到 24 小时。调大数值会让无人观察的空闲进程保留更久,不应把它当作云端永久任务。
Token 生命周期
- Token 会保存在本机设置中,重启桌面端后仍可查看和生成二维码。
- 关闭 H5 会立即拒绝远程访问,但会保留 Token,方便以后重新启用已配对设备。
- 重新生成 Token 会轮换凭证,旧二维码和旧 Token 立即失效。
- 不要把带 Token 的扫码链接贴到群聊、公开 Issue、日志截图或公共网页。
怀疑链接泄露时,应重新生成 Token,而不只是关闭再打开 H5。
手机端可用范围
H5 优先保证对话主流程:
- 会话列表与项目切换。
- 消息发送、停止和流式回复。
- 图片和文件附件。
- 权限按钮与 AI 提问。
- 消息复制、分叉等操作。
@文件引用与适合触屏的输入布局。- 手机安全区、软键盘和横竖屏下的基础布局。
桌面工作区、底部终端、原生文件“打开方式”、Computer Use 系统授权和桌面宠物不属于 H5 的完整等价体验。
从源码运行同源 H5
先构建 Web UI,再让服务端在 3456 同时提供页面与 API:
# 项目根目录
bun install
cd desktop
bun install
bun run build
cd ..
SERVER_HOST=0.0.0.0 \
SERVER_PORT=3456 \
CLAUDE_H5_AUTO_PUBLIC_URL=1 \
bun run src/server/index.ts
如果不是从项目根目录启动,设置:
CLAUDE_H5_DIST_DIR=/absolute/path/to/claude-code-haha/desktop/dist
没有 desktop/dist 时,控制 API 可以启动,但浏览器根路径会返回 404。
在服务器本机启用
H5 控制接口只接受本机可信请求。另开一个服务器终端:
curl -sS -X POST http://127.0.0.1:3456/api/h5-access/enable
响应中的 token 是完整 Token。为局域网地址设置同源入口和固定端口:
curl -sS -X PUT http://127.0.0.1:3456/api/h5-access \
-H 'Content-Type: application/json' \
--data '{
"allowedOrigins": ["http://192.168.1.20:3456"],
"publicBaseUrl": "http://192.168.1.20:3456",
"fixedPort": 3456,
"disconnectGraceSeconds": 30
}'
把示例 IP 替换成服务器真实的局域网 IP。不要填写另一台机器或已经失效的网卡地址。
随后打开:
http://192.168.1.20:3456/
更安全的个人方案是保持 SERVER_HOST=127.0.0.1,通过 SSH、VPN 或受控隧道访问;单纯 SSH 转发到本机时不需要启用 H5。
反向代理
推荐用一个 HTTPS 域名把静态页面和所有后端路径转到同一个上游:
https://cc.example.com -> http://127.0.0.1:3456
至少确保下列路径进入同一个桌面服务:
//api/*/proxy/*/ws/*
WebSocket 路径必须支持协议升级。
Nginx 必要请求头
反向代理连接本机服务时,后端看到的来源地址通常是 127.0.0.1。必须保留公开 Host,或传递标准代理头,防止远程请求被误判为本地直连:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
Caddy 默认会保留原始 Host 并补充 X-Forwarded-*。如果自定义 header_up,不要同时把 Host 改成 127.0.0.1 并删除全部代理头。
反向代理域名也要加入 H5 允许来源。可从服务器本机调用控制接口:
curl -sS -X PUT http://127.0.0.1:3456/api/h5-access \
-H 'Content-Type: application/json' \
--data '{
"allowedOrigins": ["https://cc.example.com"],
"publicBaseUrl": "https://cc.example.com"
}'
如果必须把页面与 API 放在不同来源,需要把页面来源加入 allowedOrigins,并另外维护 Server URL。不要使用通配来源。除非现有基础设施强制要求,否则优先使用同源方案。
安全检查
- H5 默认关闭。
- 远程 REST、Provider 代理与 WebSocket 都需要有效 Token。
- 本机回环豁免不会扩展到真实局域网或带代理痕迹的请求。
- 远程预览与桌面可信 session 分离,不会自动获得摄像头、麦克风或通知权限。
- 不使用时关闭 H5。
- 泄露时重新生成 Token。
- 公网使用 HTTPS、VPN、访问控制与防火墙,不要只依赖一个长期 Token。
- 不要把桌面服务直接暴露给不受信任的团队或互联网扫描。
排查
| 现象 | 检查 |
|---|---|
| 二维码打不开 | 核对访问主机是否属于当前网卡、手机与电脑是否同网 |
| 地址端口不一致 | 以“当前端口”为准;固定端口修改后重启 |
| 页面 404 | 构建 desktop/dist,或检查 CLAUDE_H5_DIST_DIR |
| Token 无效 | 确认 H5 已启用;重新生成后更新旧书签 |
| 浏览器提示 CORS | 把浏览器地址栏中的来源精确加入 allowedOrigins,不要使用 * |
| REST 可用但消息不流式 | 检查 /ws/* 的 WebSocket upgrade |
| 反代后被当成本机或被拒绝 | 保留公开 Host 和标准代理头 |
| 切换网络后旧 IP 失效 | 在设置页应用建议 IP 或更新反向代理地址 |
| 手机切后台后连接消失 | 重新打开页面;运行中任务应继续,空闲任务受保活时间限制 |
仍无法定位时,到「设置 → 诊断」复制错误摘要与事件 ID。分享诊断材料前检查其中是否含私有域名、IP、用户名或路径。