1use std::collections::HashMap;
7use std::sync::Arc;
8use std::time::Duration;
9
10use futures_util::{SinkExt, StreamExt};
11use serde_json::Value;
12use tokio::net::TcpStream;
13use tokio::sync::{mpsc, oneshot, Mutex};
14use tokio_tungstenite::tungstenite::Message;
15use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
16
17pub mod discovery;
18
19const DEFAULT_TIMEOUT_MS: u64 = 35_000;
20
21type _WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
22
23struct PendingRequest {
24 tx: oneshot::Sender<Result<Value, String>>,
25}
26
27pub struct ConnectorClient {
29 write_tx: Option<mpsc::UnboundedSender<String>>,
30 pending: Arc<Mutex<HashMap<String, PendingRequest>>>,
31 _reader_handle: Option<tokio::task::JoinHandle<()>>,
32}
33
34impl ConnectorClient {
35 pub fn new() -> Self {
36 Self {
37 write_tx: None,
38 pending: Arc::new(Mutex::new(HashMap::new())),
39 _reader_handle: None,
40 }
41 }
42
43 pub async fn connect(&mut self, host: &str, port: u16) -> Result<(), String> {
45 self.disconnect().await;
46
47 let url = format!("ws://{host}:{port}");
48 let (ws, _) = tokio_tungstenite::connect_async(&url)
49 .await
50 .map_err(|e| format!("WebSocket connection failed: {e}"))?;
51
52 let (ws_write, ws_read) = ws.split();
53
54 let (write_tx, mut write_rx) = mpsc::unbounded_channel::<String>();
56 let writer_handle = tokio::spawn(async move {
57 let mut ws_write = ws_write;
58 while let Some(msg) = write_rx.recv().await {
59 if ws_write.send(Message::Text(msg.into())).await.is_err() {
60 break;
61 }
62 }
63 });
64
65 let pending = self.pending.clone();
67 let reader_handle = tokio::spawn(async move {
68 let mut ws_read = ws_read;
69 while let Some(Ok(msg)) = ws_read.next().await {
70 if let Message::Text(text) = msg {
71 let text: &str = text.as_ref();
72 if let Ok(response) = serde_json::from_str::<Value>(text) {
73 let id = response
74 .get("id")
75 .and_then(|v| v.as_str())
76 .unwrap_or("")
77 .to_string();
78
79 let mut pending = pending.lock().await;
80 if let Some(req) = pending.remove(&id) {
81 let result = if let Some(error) = response.get("error") {
82 Err(error.as_str().unwrap_or("Unknown error").to_string())
83 } else {
84 Ok(response.get("result").cloned().unwrap_or(Value::Null))
85 };
86 let _ = req.tx.send(result);
87 }
88 }
89 }
90 }
91 let mut pending = pending.lock().await;
93 for (_, req) in pending.drain() {
94 let _ = req.tx.send(Err("Connection closed".to_string()));
95 }
96 drop(writer_handle);
97 });
98
99 self.write_tx = Some(write_tx);
100 self._reader_handle = Some(reader_handle);
101
102 Ok(())
103 }
104
105 pub async fn disconnect(&mut self) {
107 self.write_tx = None;
108 if let Some(handle) = self._reader_handle.take() {
109 handle.abort();
110 }
111 let mut pending = self.pending.lock().await;
112 for (_, req) in pending.drain() {
113 let _ = req.tx.send(Err("Disconnected".to_string()));
114 }
115 }
116
117 pub fn is_connected(&self) -> bool {
119 self.write_tx.is_some()
120 }
121
122 pub async fn send(&self, command: Value) -> Result<Value, String> {
124 self.send_with_timeout(command, DEFAULT_TIMEOUT_MS).await
125 }
126
127 pub async fn send_with_timeout(
129 &self,
130 command: Value,
131 timeout_ms: u64,
132 ) -> Result<Value, String> {
133 let write_tx = self
134 .write_tx
135 .as_ref()
136 .ok_or_else(|| "Not connected".to_string())?;
137
138 let id = uuid::Uuid::new_v4().to_string();
139 let (tx, rx) = oneshot::channel();
140
141 {
142 let mut pending = self.pending.lock().await;
143 pending.insert(id.clone(), PendingRequest { tx });
144 }
145
146 let mut msg = match command {
148 Value::Object(map) => map,
149 _ => return Err("Command must be a JSON object".to_string()),
150 };
151 msg.insert("id".to_string(), Value::String(id.clone()));
152
153 let json = serde_json::to_string(&msg).map_err(|e| e.to_string())?;
154 write_tx
155 .send(json)
156 .map_err(|_| "Send failed: connection closed".to_string())?;
157
158 match tokio::time::timeout(Duration::from_millis(timeout_ms), rx).await {
160 Ok(Ok(result)) => result,
161 Ok(Err(_)) => {
162 self.pending.lock().await.remove(&id);
163 Err("Response channel closed".to_string())
164 }
165 Err(_) => {
166 self.pending.lock().await.remove(&id);
167 Err("Request timeout".to_string())
168 }
169 }
170 }
171}
172
173impl Default for ConnectorClient {
174 fn default() -> Self {
175 Self::new()
176 }
177}