Initial implementation: relay server + common protocol + client stub
This commit is contained in:
commit
7285a33cff
17 changed files with 926 additions and 0 deletions
35
crates/common/src/error.rs
Normal file
35
crates/common/src/error.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HeliosError {
|
||||
/// WebSocket protocol error
|
||||
Protocol(String),
|
||||
/// JSON serialization/deserialization error
|
||||
Serialization(String),
|
||||
/// Session not found
|
||||
SessionNotFound(String),
|
||||
/// Request timed out waiting for client response
|
||||
Timeout(String),
|
||||
/// Generic internal error
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for HeliosError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
HeliosError::Protocol(msg) => write!(f, "Protocol error: {msg}"),
|
||||
HeliosError::Serialization(msg) => write!(f, "Serialization error: {msg}"),
|
||||
HeliosError::SessionNotFound(id) => write!(f, "Session not found: {id}"),
|
||||
HeliosError::Timeout(msg) => write!(f, "Request timed out: {msg}"),
|
||||
HeliosError::Internal(msg) => write!(f, "Internal error: {msg}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for HeliosError {}
|
||||
|
||||
impl From<serde_json::Error> for HeliosError {
|
||||
fn from(e: serde_json::Error) -> Self {
|
||||
HeliosError::Serialization(e.to_string())
|
||||
}
|
||||
}
|
||||
5
crates/common/src/lib.rs
Normal file
5
crates/common/src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod protocol;
|
||||
pub mod error;
|
||||
|
||||
pub use protocol::*;
|
||||
pub use error::*;
|
||||
118
crates/common/src/protocol.rs
Normal file
118
crates/common/src/protocol.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Messages sent from the relay server to a connected client
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ServerMessage {
|
||||
/// Request a screenshot from the client
|
||||
ScreenshotRequest { request_id: Uuid },
|
||||
/// Execute a shell command on the client
|
||||
ExecRequest {
|
||||
request_id: Uuid,
|
||||
command: String,
|
||||
},
|
||||
/// Simulate a mouse click
|
||||
ClickRequest {
|
||||
request_id: Uuid,
|
||||
x: i32,
|
||||
y: i32,
|
||||
button: MouseButton,
|
||||
},
|
||||
/// Type text on the client
|
||||
TypeRequest {
|
||||
request_id: Uuid,
|
||||
text: String,
|
||||
},
|
||||
/// Acknowledge a client message
|
||||
Ack { request_id: Uuid },
|
||||
/// Server-side error response
|
||||
Error {
|
||||
request_id: Option<Uuid>,
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Messages sent from the client to the relay server
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ClientMessage {
|
||||
/// Client registers itself with optional display name
|
||||
Hello { label: Option<String> },
|
||||
/// Response to a screenshot request — base64-encoded PNG
|
||||
ScreenshotResponse {
|
||||
request_id: Uuid,
|
||||
image_base64: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
/// Response to an exec request
|
||||
ExecResponse {
|
||||
request_id: Uuid,
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
exit_code: i32,
|
||||
},
|
||||
/// Generic acknowledgement for click/type
|
||||
Ack { request_id: Uuid },
|
||||
/// Client error response
|
||||
Error {
|
||||
request_id: Uuid,
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Mouse button variants
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
}
|
||||
|
||||
impl Default for MouseButton {
|
||||
fn default() -> Self {
|
||||
MouseButton::Left
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_server_message_serialization() {
|
||||
let msg = ServerMessage::ExecRequest {
|
||||
request_id: Uuid::nil(),
|
||||
command: "echo hello".into(),
|
||||
};
|
||||
let json = serde_json::to_string(&msg).unwrap();
|
||||
assert!(json.contains("exec_request"));
|
||||
assert!(json.contains("echo hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_message_serialization() {
|
||||
let msg = ClientMessage::Hello { label: Some("test-pc".into()) };
|
||||
let json = serde_json::to_string(&msg).unwrap();
|
||||
assert!(json.contains("hello"));
|
||||
assert!(json.contains("test-pc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip() {
|
||||
let msg = ClientMessage::ExecResponse {
|
||||
request_id: Uuid::nil(),
|
||||
stdout: "hello\n".into(),
|
||||
stderr: String::new(),
|
||||
exit_code: 0,
|
||||
};
|
||||
let json = serde_json::to_string(&msg).unwrap();
|
||||
let decoded: ClientMessage = serde_json::from_str(&json).unwrap();
|
||||
match decoded {
|
||||
ClientMessage::ExecResponse { exit_code, .. } => assert_eq!(exit_code, 0),
|
||||
_ => panic!("wrong variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue