use serde_json::{Value, json};
use tungstenite::{Message, connect};
pub struct DaemonClient {
socket: tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>,
request_counter: u64,
}
impl DaemonClient {
pub fn connect(url: &str, token: &str) -> Result<Self, String> {
let (mut socket, _) = connect(url).map_err(|e| format!("failed to connect: {e}"))?;
let auth_msg = json!({"type": "auth", "token": token});
socket
.send(Message::Text(auth_msg.to_string().into()))
.map_err(|e| format!("auth send failed: {e}"))?;
let response = socket
.read()
.map_err(|e| format!("auth response failed: {e}"))?;
if let Message::Text(text) = response {
let val: Value =
serde_json::from_str(&text).map_err(|e| format!("invalid auth response: {e}"))?;
if val.get("status").and_then(|v| v.as_str()) != Some("ok") {
let msg = val
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("unknown error");
return Err(format!("auth failed: {msg}"));
}
} else {
return Err("unexpected auth response type".into());
}
Ok(Self {
socket,
request_counter: 0,
})
}
pub fn request(&mut self, method: &str, params: Value) -> Result<Value, String> {
self.request_counter += 1;
let id = format!("cli-{}", self.request_counter);
let msg = json!({
"type": "request",
"id": id,
"method": method,
"params": params,
});
self.socket
.send(Message::Text(msg.to_string().into()))
.map_err(|e| format!("send failed: {e}"))?;
if let tungstenite::stream::MaybeTlsStream::Plain(ref s) = *self.socket.get_ref() {
let _ = s.set_read_timeout(Some(std::time::Duration::from_secs(30)));
}
loop {
let response = self
.socket
.read()
.map_err(|e| format!("read failed: {e}"))?;
if let Message::Text(text) = response {
let val: Value =
serde_json::from_str(&text).map_err(|e| format!("invalid response: {e}"))?;
if val.get("id").and_then(|v| v.as_str()) == Some(&id) {
if let Some(error) = val.get("error").and_then(|v| v.as_str()) {
return Err(error.to_string());
}
return Ok(val.get("result").cloned().unwrap_or(Value::Null));
}
}
}
}
}
pub fn connect_to_hub_or_exit() -> DaemonClient {
let vex_home = match vex_app::VexHome::new(None) {
Ok(h) => h,
Err(e) => {
eprintln!("Error: {e}");
std::process::exit(1);
}
};
let hub_config = vex_home.load_hub_config().unwrap_or_default();
let port = hub_config.web_port;
let url = format!("ws://127.0.0.1:{port}/ws");
let token = match vex_home.load_or_create_token() {
Ok(t) => t,
Err(e) => {
eprintln!("Error reading token: {e}");
std::process::exit(1);
}
};
match DaemonClient::connect(&url, &token) {
Ok(c) => c,
Err(e) => {
eprintln!("Error: {e}");
eprintln!("Is the hub running? Start it with: vex hub start");
std::process::exit(1);
}
}
}