1use serde_json::{json, Value};
20use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
21use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
22use tokio::net::TcpStream;
23use tokio::sync::mpsc;
24
25pub struct RemoteClient {
27 reader: Option<BufReader<OwnedReadHalf>>,
28 writer: OwnedWriteHalf,
29 next_id: u64,
30}
31
32impl RemoteClient {
33 pub async fn connect(addr: &str) -> Result<Self, String> {
35 let stream = TcpStream::connect(addr)
36 .await
37 .map_err(|e| format!("could not connect to {addr}: {e}"))?;
38 let (read_half, write_half) = stream.into_split();
39 Ok(Self {
40 reader: Some(BufReader::new(read_half)),
41 writer: write_half,
42 next_id: 1,
43 })
44 }
45
46 fn take_id(&mut self) -> u64 {
47 let id = self.next_id;
48 self.next_id += 1;
49 id
50 }
51
52 async fn write_value(&mut self, value: &Value) -> Result<(), String> {
53 let mut line = serde_json::to_string(value).map_err(|e| e.to_string())?;
54 line.push('\n');
55 self.writer
56 .write_all(line.as_bytes())
57 .await
58 .map_err(|e| format!("write failed: {e}"))?;
59 self.writer
60 .flush()
61 .await
62 .map_err(|e| format!("flush failed: {e}"))
63 }
64
65 pub async fn authenticate(&mut self, token: &str) -> Result<(), String> {
68 if token.is_empty() {
69 return Ok(());
70 }
71 self.call("authenticate", json!({"token": token}))
72 .await
73 .map(|_| ())
74 }
75
76 pub async fn call(&mut self, method: &str, params: Value) -> Result<Value, String> {
79 let id = self.take_id();
80 let request = json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params});
81 self.write_value(&request).await?;
82
83 let reader = self
84 .reader
85 .as_mut()
86 .ok_or("client already switched to streaming mode")?;
87 let mut line = String::new();
88 loop {
89 line.clear();
90 let n = reader
91 .read_line(&mut line)
92 .await
93 .map_err(|e| format!("read failed: {e}"))?;
94 if n == 0 {
95 return Err("server closed the connection".to_string());
96 }
97 let trimmed = line.trim();
98 if trimmed.is_empty() {
99 continue;
100 }
101 let value: Value = match serde_json::from_str(trimmed) {
102 Ok(v) => v,
103 Err(_) => continue,
104 };
105 if value.get("method").is_some() {
107 continue;
108 }
109 if value.get("id") == Some(&json!(id)) {
110 if let Some(error) = value.get("error") {
111 let message = error
112 .get("message")
113 .and_then(Value::as_str)
114 .map(str::to_owned)
115 .unwrap_or_else(|| error.to_string());
116 return Err(message);
117 }
118 return Ok(value.get("result").cloned().unwrap_or(Value::Null));
119 }
120 }
122 }
123
124 pub fn start_event_pump(&mut self, subscription_id: u64) -> mpsc::UnboundedReceiver<Value> {
128 let (tx, rx) = mpsc::unbounded_channel();
129 let Some(mut reader) = self.reader.take() else {
130 drop(tx);
131 return rx;
132 };
133 tokio::spawn(async move {
134 let mut line = String::new();
135 loop {
136 line.clear();
137 match reader.read_line(&mut line).await {
138 Ok(0) => break,
139 Ok(_) => {}
140 Err(_) => break,
141 }
142 let trimmed = line.trim();
143 if trimmed.is_empty() {
144 continue;
145 }
146 let Ok(value) = serde_json::from_str::<Value>(trimmed) else {
147 continue;
148 };
149 if value.get("method").and_then(Value::as_str) != Some("event") {
150 continue;
151 }
152 let params = value.get("params").cloned().unwrap_or(Value::Null);
153 if params.get("subscriptionId").and_then(Value::as_u64) != Some(subscription_id) {
154 continue;
155 }
156 let event = params.get("event").cloned().unwrap_or(Value::Null);
157 if tx.send(event).is_err() {
158 break;
159 }
160 if trimmed.contains("\"session_end\"") {
161 break;
162 }
163 }
164 });
165 rx
166 }
167
168 pub async fn send_command(&mut self, session_id: &str, command: Value) -> Result<(), String> {
170 let id = self.take_id();
171 let request = json!({
172 "jsonrpc": "2.0",
173 "id": id,
174 "method": "send",
175 "params": {"sessionId": session_id, "command": command},
176 });
177 self.write_value(&request).await
178 }
179
180 pub async fn stop_session(&mut self, session_id: &str) {
182 let id = self.take_id();
183 let request = json!({
184 "jsonrpc": "2.0",
185 "id": id,
186 "method": "stop_session",
187 "params": {"sessionId": session_id},
188 });
189 let _ = self.write_value(&request).await;
190 }
191}