cdpkit/lib.rs
1mod error;
2mod inner;
3mod listeners;
4mod types;
5
6// Generated CDP protocol definitions
7#[allow(clippy::all)]
8#[allow(deprecated)]
9pub mod protocol;
10
11pub use error::CdpError;
12pub use types::Method;
13
14// Re-export all CDP domains
15pub use protocol::*;
16
17use inner::CDPInner;
18use std::sync::Arc;
19
20/// Chrome DevTools Protocol client
21pub struct CDP {
22 pub(crate) inner: Arc<CDPInner>,
23}
24
25impl CDP {
26 /// Connect to Chrome by host and port (most common usage)
27 ///
28 /// Automatically discovers the WebSocket URL from Chrome's debugging endpoint.
29 ///
30 /// # Arguments
31 /// * `host` - The host and port (e.g., "localhost:9222" or "http://localhost:9222")
32 ///
33 /// # Example
34 /// ```no_run
35 /// # use cdpkit::CDP;
36 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
37 /// // Connect to default Chrome debugging port
38 /// let cdp = CDP::connect("localhost:9222").await?;
39 /// # Ok(())
40 /// # }
41 /// ```
42 pub async fn connect(host: &str) -> Result<Self, CdpError> {
43 // If it's a WebSocket URL, connect directly
44 if host.starts_with("ws://") || host.starts_with("wss://") {
45 return Self::connect_ws(host).await;
46 }
47
48 // Otherwise, auto-discover the WebSocket URL
49 let ws_url = discover_ws_url(host).await?;
50 Self::connect_ws(&ws_url).await
51 }
52
53 /// Connect directly using a WebSocket URL (advanced usage)
54 ///
55 /// Use this when you already have the full WebSocket URL.
56 ///
57 /// # Example
58 /// ```no_run
59 /// # use cdpkit::CDP;
60 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
61 /// let cdp = CDP::connect_ws("ws://localhost:9222/devtools/browser/...").await?;
62 /// # Ok(())
63 /// # }
64 /// ```
65 pub async fn connect_ws(url: &str) -> Result<Self, CdpError> {
66 let inner = CDPInner::connect(url).await?;
67 Ok(Self { inner })
68 }
69
70 /// Send a method
71 pub async fn send<C: Method>(
72 &self,
73 cmd: C,
74 session_id: Option<&str>,
75 ) -> Result<C::Response, CdpError> {
76 self.inner.send_command(cmd, session_id).await
77 }
78
79 /// Send a raw CDP command with a dynamic method name and JSON params.
80 ///
81 /// This is useful for sending arbitrary CDP commands that don't have
82 /// a corresponding typed `Method` implementation.
83 ///
84 /// # Arguments
85 /// * `method` - The CDP method name (e.g., "Page.navigate")
86 /// * `params` - The method parameters as a JSON value
87 /// * `session_id` - Optional CDP session ID for targeting a specific session
88 ///
89 /// # Example
90 /// ```no_run
91 /// # use cdpkit::CDP;
92 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
93 /// let cdp = CDP::connect("localhost:9222").await?;
94 /// let result = cdp.send_raw("Page.navigate", serde_json::json!({"url": "https://example.com"}), None).await?;
95 /// # Ok(())
96 /// # }
97 /// ```
98 pub async fn send_raw(
99 &self,
100 method: &str,
101 params: serde_json::Value,
102 session_id: Option<&str>,
103 ) -> Result<serde_json::Value, CdpError> {
104 self.inner.send_raw(method, params, session_id).await
105 }
106
107 /// Subscribe to CDP events by event name.
108 ///
109 /// Returns a stream of deserialized event objects.
110 ///
111 /// # Arguments
112 /// * `event_name` - The CDP event name (e.g., "Network.requestWillBeSent")
113 pub fn event_stream<T>(
114 &self,
115 event_name: &str,
116 ) -> std::pin::Pin<Box<dyn futures::Stream<Item = T> + Send>>
117 where
118 T: serde::de::DeserializeOwned + Send + 'static,
119 {
120 self.inner.event_stream(event_name)
121 }
122
123 /// Get the CDP protocol version this library was built with
124 pub fn version() -> &'static str {
125 CDP_VERSION
126 }
127}
128
129/// Discover WebSocket URL from Chrome's remote debugging endpoint
130async fn discover_ws_url(host: &str) -> Result<String, CdpError> {
131 use serde_json::Value;
132
133 // Normalize host to include http:// if not present
134 let base_url = if host.starts_with("http://") || host.starts_with("https://") {
135 host.to_string()
136 } else {
137 format!("http://{}", host)
138 };
139
140 // Fetch the JSON endpoint
141 let url = format!("{}/json/version", base_url);
142 let response = reqwest::get(&url)
143 .await
144 .map_err(|e| CdpError::ConnectionFailed(e.to_string()))?;
145
146 let json: Value = response
147 .json()
148 .await
149 .map_err(|e| CdpError::ConnectionFailed(e.to_string()))?;
150
151 // Extract webSocketDebuggerUrl
152 json.get("webSocketDebuggerUrl")
153 .and_then(|v| v.as_str())
154 .map(|s| s.to_string())
155 .ok_or_else(|| CdpError::ConnectionFailed("No webSocketDebuggerUrl found".to_string()))
156}