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