1use std::{net::TcpStream, time::{Duration, Instant}};
6use serde::Deserialize;
7use serde_json::{json, Value};
8use tungstenite::{client, WebSocket, error::Error, handshake::HandshakeError};
9use url::Url;
10
11pub type NetworkError = Error;
13pub type MessageParameter = Value;
15
16#[derive(Debug)]
17pub enum ClientError {
18 CannotConnect,
21 InvalidTab
23}
24impl From<reqwest::Error> for ClientError {
25 fn from(_: reqwest::Error) -> Self {
26 ClientError::CannotConnect
27 }
28}
29
30#[derive(Debug)]
31pub enum MessageError {
32 NetworkError(NetworkError),
34 InvalidRequest(Value),
36 InvalidResponse,
38 NoMessage
40}
41impl From<Error> for MessageError {
42 fn from(error: Error) -> Self {
43 match error {
44 Error::Utf8 => MessageError::InvalidResponse,
45 _ => MessageError::NetworkError(error),
46 }
47 }
48}
49
50#[allow(non_snake_case, dead_code)]
52#[derive(Deserialize)]
53pub struct Tab {
54 description: String,
55 devtoolsFrontendUrl: String,
56 id: String,
57 title: String,
58 r#type: String,
59 url: String,
60 webSocketDebuggerUrl: String
61}
62
63pub struct CdpClient {
67 host: String,
68 port: u16,
69}
70impl CdpClient {
71 pub fn new() -> Self {
73 Self::custom("localhost", 9222)
74 }
75
76 pub fn custom(host: &str, port: u16) -> Self {
78 Self { host: host.to_string(), port }
79 }
80
81 pub fn get_tabs(&self) -> Result<Vec<Tab>, ClientError> {
83 let tabs = reqwest::blocking::get(format!("http://{}:{}/json", self.host, self.port))?
84 .json::<Vec<Tab>>()?;
85 Ok(tabs)
86 }
87
88 pub fn connect_to_target(&self, target_id: &str) -> Result<CdpConnection, ClientError> {
104 let ws_url = format!("ws://{}:{}/devtools/page/{}", self.host, self.port, target_id);
105 CdpClient::make_connection(&ws_url, self.port)
106 }
107
108 pub fn connect_to_tab(&self, tab_index: usize) -> Result<CdpConnection, ClientError> {
119 let tabs = self.get_tabs()?;
120 let ws_url = match tabs.get(tab_index) {
121 Some(tab) => tab.webSocketDebuggerUrl.clone(),
122 None => return Err(ClientError::InvalidTab),
123 };
124
125 CdpClient::make_connection(&ws_url, self.port)
126 }
127
128 fn make_connection(url: &str, port: u16) -> Result<CdpConnection, ClientError> {
129 let url = Url::parse(&url).unwrap();
130 let mut addrs = url.socket_addrs(|| Some(port)).unwrap();
131 addrs.sort();
133
134 for addr in addrs {
135 if let Ok(stream) = TcpStream::connect(addr) {
136 stream.set_nonblocking(true).unwrap();
137
138 let mut result = client(url.clone(), stream);
139 loop {
140 match result {
141 Ok((socket, _)) => return Ok(CdpConnection::new(socket)),
142 Err(HandshakeError::Failure(_)) => return Err(ClientError::CannotConnect),
143 Err(HandshakeError::Interrupted(mid)) => result = mid.handshake(),
144 }
145 }
146 }
147 }
148
149
150 Err(ClientError::CannotConnect)
151 }
152
153}
154impl Default for CdpClient {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160#[macro_export]
161macro_rules! parms {
162 ($($name:literal, $value:expr),*) => {{
163 vec![$(($name, cdp_rs::MessageParameter::from($value))),*]
164 }};
165}
166
167pub struct CdpConnection {
170 socket: WebSocket<TcpStream>,
171 message_id: i64,
172}
173impl CdpConnection {
174 fn new(socket: WebSocket<TcpStream>) -> Self {
175 Self { socket, message_id: 1 }
176 }
177
178 pub fn send(&mut self, method: &'static str, parms: Vec<(&'static str, MessageParameter)>) -> Result<Value, MessageError> {
189 let message_id = self.message_id;
190 let mut map = serde_json::Map::new();
191 for p in parms {
192 map.insert(p.0.to_string(), p.1);
193 }
194
195 let data = json!({
196 "id": self.message_id,
197 "method": method,
198 "params": map
199 });
200
201 self.message_id += 1;
202 self.socket.write_message(tungstenite::Message::Text(data.to_string()))?;
203 let result = self.wait_for(None, |m| {
204 (m.get("error").is_some() || m.get("result").is_some()) &&
205 m["id"].as_i64().unwrap() == message_id
206 });
207
208 if let Ok(r) = &result {
210 if r.get("error").is_some() { return Err(MessageError::InvalidRequest(r.clone())) }
211 }
212 result
213 }
214
215 pub fn wait_message(&mut self) -> Result<Value, MessageError> {
227 if let Ok(msg) = self.socket.read_message() {
228 let text = msg.into_text()?;
229
230 return match serde_json::from_str::<Value>(&text) {
231 Err(_) => Err(MessageError::InvalidResponse),
232 Ok(m) => Ok(m)
233 }
234 }
235 Err(MessageError::NoMessage)
236 }
237
238 pub fn wait_event(&mut self, event: &str, timeout: Option<Duration>) -> Result<Value, MessageError> {
250 self.wait_for(timeout, |m| {
251 if let Some(method) = m.get("method") {
252 if method == event {
253 return true
254 }
255 }
256 return false
257 })
258 }
259
260 pub fn wait_for<F>(&mut self, timeout: Option<Duration>, f: F) -> Result<Value, MessageError>
271 where F: Fn(&Value) -> bool {
272
273 let timeout = match timeout {
274 Some(t) => t,
275 None => Duration::from_secs(300),
276 };
277
278 let now = Instant::now();
279 while Instant::now() - now < timeout {
280 let m = self.wait_message();
281 match m {
282 Ok(m) => if f(&m) { return Ok(m) },
283 Err(MessageError::NoMessage) => {},
284 _ => { break; }
285 }
286 }
287 Err(MessageError::NoMessage)
288 }
289
290}
291impl Drop for CdpConnection {
292 fn drop(&mut self) {
293 if self.socket.close(None).is_ok() {
294 for _ in 0..100 {
296 if matches!(self.socket.read_message(), Err(Error::ConnectionClosed) | Err(Error::AlreadyClosed)) {
297 break;
298 }
299 }
300 }
301 }
302}