本文档定义 zcode-remote(ZCode 自建中继与远控网关)的整体架构、拓扑模型、状态机生命周期以及安全边界。
- 协议完全兼容:对齐 ZCode 官方协议契约(
packages/shared/src/web-remote-control.ts),保证未经任何魔改的 ZCode 桌面端及手机 Web 均可直接接入。 - 零业务状态(Stateless Relay):Relay 仅负责连接调度、HMAC 挑战应答鉴权、配对状态维护和双向帧透传,不运行任何 Agent 任务,不持久化会话记录与快照。
- 数据隐私红线(Zero Logging of Payloads):由于数据帧(
data)在传输层 TLS 解密后为明文 RPC / JSON,Relay 严禁记录任何payload内容或用户提示词。 - 轻量与多宿主(Multi-Target Deployment):核心逻辑为纯 TypeScript,无 C++ 原生绑定依赖,可运行于轻量 Node.js、Docker 容器以及 Cloudflare Workers / Serverless 运行时。
flowchart LR
subgraph Desktop["桌面端 (Device)"]
D_Agent["Agent / Session 运行时"]
D_Transport["WebRemoteControlTransport (Device)"]
D_Agent <--> D_Transport
end
subgraph Server["zcode-remote 服务端"]
direction TB
Static["Web 静态文件服务 (/remote/v4)"]
Relay["WebSocket Relay (/ws)"]
SessionMap["内存配对表 (deviceSid 索引)"]
Relay <--> SessionMap
end
subgraph Mobile["手机端 (Terminal)"]
M_Browser["手机移动浏览器 (PWA / Web)"]
M_Transport["WebRemoteControlTransport (Terminal)"]
M_Browser <--> M_Transport
end
%% 交互流
D_Transport -- "1. WSS 连接与注册/鉴权" --> Relay
Mobile -- "2. 扫码打开网页" --> Static
M_Transport -- "3. WSS 连接与挑战配对" --> Relay
D_Transport <-- "4. 双向透传 data 帧" --> Relay
Relay <-- "4. 双向透传 data 帧" --> M_Transport
| 概念 | 角色/标识 | 说明 |
|---|---|---|
| Device | 桌面端客户端 (role: "device") |
运行在用户 PC 上,拥有真正的文件系统和 Agent 执行能力。 |
| Terminal | 网页远控端 (role: "terminal") |
运行在手机浏览器中,仅作为远程 UI 投影和输入控制器。 |
| deviceSid | 设备会话 ID(如 d_Xx8a...) |
唯一标识一次远控设备配对生命周期,由 Relay 分配或由 Desktop 持久化提供。 |
| deviceMid | 设备机器唯一标识 | 桌面客户端硬件/系统派生的唯一指纹,用于多实例分流。 |
| passHash | 配对密钥哈希 | passHash = sha256(password).base64,二维码中携带,用于双方 HMAC 证明。 |
| nonce | 随机挑战盐值 | Relay 发给客户端的一次性随机串,防止重放攻击。 |
stateDiagram-v2
[*] --> Disconnected
state Relay_Session {
Disconnected --> Waiting_Device: 等待桌面注册或重连
Waiting_Device --> Device_Registered: device_register_init / auth_init
Device_Registered --> Waiting_Terminal: device 鉴权通过 (waiting)
Waiting_Terminal --> Both_Authenticating: terminal 发起 auth_init
Both_Authenticating --> Matched: terminal 鉴权通过 (matched)
Matched --> Matched: 双向转发 data 帧 / 心跳
Matched --> Waiting_Terminal: terminal 断开 (降级为 waiting)
Matched --> Waiting_Device: device 断开 (保留 terminal 等待重连)
}
Relay_Session --> Disconnected: 双方超时断开 / 主动关闭
-
设备注册与初始连接 (Registration / Auth)
- 首次启动:Desktop 发送
device_register_init,Relay 生成新device_sid,保存该 sid 的pass_hash。 - 二次启动/持久化:Desktop 直接发送
auth_init(携带本地已有的device_sid)。 - 挑战应答:Relay 生成随机
nonce并返回auth_challenge,Desktop 计算 HMAC 证明回传auth_response。Relay 验证通过后进入waiting(等待手机接入)。
- 首次启动:Desktop 发送
-
手机接入与配对 (Terminal Pairing)
- 手机扫码打开网页,从 URL 参数中解析出
device_sid与pass_hash。 - 手机连入 Relay,发送
auth_init (role: "terminal", device_sid)。 - Relay 同样下发
auth_challenge (nonce),手机用本地 URL 中的pass_hash计算 Terminal 角色的 HMAC 证明并回传。 - Relay 验证通过后,将配对状态标记为
matched,向双端发送pair_status_ack { pair_status: "matched" }。
- 手机扫码打开网页,从 URL 参数中解析出
-
双向透传 (Data Forwarding)
- 一旦状态为
matched,任一端发送{ type: "data", payload: ... },Relay 立即原样转发给对端。 - 若对端离线,根据协议返回
INTERNAL或DEVICE_OFFLINE。
- 一旦状态为
-
单终端排他冲突处理 (Conflict & KICK)
- 同一个
device_sid在同一时间只允许连接一个手机 Terminal。 - 若已有活动 Terminal 时,新的 Terminal 尝试配对成功,旧 Terminal 将收到
{ type: "error", code: "KICKED" }并被主动关闭。
- 同一个
-
传输层加密:公网环境下必须强制开启 TLS(
wss://和https://)。 -
零数据落盘(Zero Data At Rest):Relay 进程仅在内存中维护
Map<deviceSid, RelaySession>,进程重启即销毁。日志中只允许记录连接建立、心跳和错误码,严禁将data帧写入持久化日志。 -
入口防护:分两层,职责不同。
基础层 —— 端口只绑本机(必做):
ports: ["127.0.0.1:8765:8765"]让中继无法被公网直连, 只接受同机反向代理转发。这是唯一两端零改动的防护方式,也是推荐做法。可选层 —— 入口凭据:中继自带
RELAY_AUTH_TOKEN,也可用 nginxauth_basic。 两者都需要把凭据注入到连接地址里,而 ZCode 两端默认都不携带:端 注入点 桌面端 ZCODE_WEB_REMOTE_CONTROL_RELAY_WS_URL里带?token=或user:pass@手机端 ZCODE_WEB_REMOTE_CONTROL_URL里带?relay=或user:pass@(会进入二维码)设置面板的「自定义服务器地址」不是有效注入点:
resolveWebRemoteControlCustomEndpointUrls会重建relayWsUrl,输入的 query 与 userinfo 都会被丢掉。注意凭据会明文出现在二维码、浏览器地址栏与桌面端启动配置里。只在你信任 「能看到二维码的人」时才用。详见 DEPLOYMENT.md 的专节说明。
-
辅助端点最小暴露:
/health只返回{"status":"ok"},不提供会话数、设备 mid、 机器名、进程 uptime 等任何内部状态。该端点随反向代理暴露到公网,存活探测只需要 HTTP 状态码,其余信息没有对外提供的正当理由。