//! df-relay 广播消息骨架 //! //! 设计依据:设计文档「Layer2」—— 云后端纯转发,无业务逻辑。 //! 中继消息骨架定义「谁发给谁」的路由语义: //! - `Event`:桌面端事件 → 广播给该 device 绑定的小程序 //! - `Command`:小程序指令 → 转发给该 device 绑定的桌面端 //! - `Control`:控制面消息(配对/心跳/在线状态) //! //! 与 df-tunnel::TunnelMessage 区分: //! - TunnelMessage 是隧道两端(桌面↔云)的传输单元 //! - BroadcastMessage 是云后端内部的路由单元(标识来源/去向/广播范围) //! //! AiChatEvent JSON 透传:relay 不解析 AiChatEvent(类型在 src-tauri/df-types, //! relay 不依赖避跨 crate 强耦合),payload 用 serde_json::Value 透传。 use serde::{Deserialize, Serialize}; use crate::conn::ConnId; /// 客户端类型(用于路由判断) #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum ClientKind { /// 桌面端(出站长连接,事件真相源) Device, /// 小程序(远程操作发起方) Miniapp, } /// 中继路由消息语义分类 /// /// - `Event`:桌面端 → 小程序(渲染事件,AiChatEvent 透传) /// - `Command`:小程序 → 桌面端(操作指令,触发 Tauri command) /// - `Control`:控制面(配对/心跳/Ping-Pong,不进业务流) #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum MessageKind { Event, Command, Control, } /// 中继路由消息骨架(云后端内部的路由单元) /// /// 每条消息携带 `device_id`(配对绑定键),云后端按 device_id /// 找到绑定的对端连接进行转发/广播。payload 为原始 JSON,转发时不解析业务字段, /// 保持轻量。 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BroadcastMessage { /// 配对绑定的设备 ID(桌面端首次配置时生成) pub device_id: String, /// 消息语义(Event / Command / Control) pub kind: MessageKind, /// 消息来源连接(用于鉴权校验:device 不能发 Command) pub source: ConnId, /// 来源客户端类型 pub from: ClientKind, /// 消息载荷(原始 JSON,透传不解析) pub payload: serde_json::Value, /// 时间戳(毫秒,用于冲突判断 —— 桌面端为真相源,但保留时间戳辅助) pub ts: i64, } impl BroadcastMessage { /// 便捷构造:device 发出的事件 pub fn from_device( device_id: impl Into, source: ConnId, payload: serde_json::Value, ts: i64, ) -> Self { Self { device_id: device_id.into(), kind: MessageKind::Event, source, from: ClientKind::Device, payload, ts, } } /// 便捷构造:小程序发出的指令 pub fn from_miniapp( device_id: impl Into, source: ConnId, payload: serde_json::Value, ts: i64, ) -> Self { Self { device_id: device_id.into(), kind: MessageKind::Command, source, from: ClientKind::Miniapp, payload, ts, } } /// 便捷构造:控制面消息 pub fn control( device_id: impl Into, source: ConnId, from: ClientKind, payload: serde_json::Value, ts: i64, ) -> Self { Self { device_id: device_id.into(), kind: MessageKind::Control, source, from, payload, ts, } } } /// 客户端在线状态(用于离线降级提示) #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] pub enum PresenceState { /// 在线 Online, /// 离线(对端可缓存指令待重连) Offline, } /// 控制面消息(配对绑定 / 心跳 / 在线状态广播) /// /// 仅作为 payload 的语义约定,relay 对 Control 消息同样透传;部分控制语义 /// (如 Ping/Pong 心跳)由 WS 连接层直接处理,不进 BroadcastMessage 流。 #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "control_kind", rename_all = "snake_case")] pub enum ControlMessage { /// 配对绑定请求(小程序持 token 申请绑定 device_id) Pair { device_id: String, token: String }, /// 心跳(保活 + 在线确认) Heartbeat { device_id: String }, /// 在线状态变更通知 Presence { device_id: String, state: PresenceState }, /// 心跳 Ping(WS 应用层心跳,与协议层 Ping 区分) Ping, /// 心跳 Pong Pong, }