Skip to main content

tauri_mcp/tools/
debug.rs

1use crate::{Result, TauriMcpError};
2use reqwest::Client;
3use serde_json::Value;
4use std::collections::HashMap;
5use std::time::Duration;
6use tracing::{debug, error, info};
7
8pub struct DebugTools {
9    client: Client,
10    webdriver_sessions: HashMap<String, WebDriverSession>,
11}
12
13struct WebDriverSession {
14    session_id: String,
15    debug_port: u16,
16}
17
18impl DebugTools {
19    pub fn new() -> Self {
20        let client = Client::builder()
21            .timeout(Duration::from_secs(30))
22            .build()
23            .expect("Failed to create HTTP client");
24        
25        Self {
26            client,
27            webdriver_sessions: HashMap::new(),
28        }
29    }
30    
31    pub async fn execute_js(&self, process_id: &str, javascript_code: &str) -> Result<Value> {
32        info!("Executing JavaScript for process {}: {}", process_id, javascript_code);
33        
34        if let Some(session) = self.webdriver_sessions.get(process_id) {
35            self.execute_via_webdriver(session, javascript_code).await
36        } else {
37            self.execute_via_devtools(process_id, javascript_code).await
38        }
39    }
40    
41    pub async fn get_devtools_info(&self, process_id: &str) -> Result<Value> {
42        info!("Getting DevTools info for process: {}", process_id);
43        
44        let debug_port = self.find_debug_port(process_id).await?;
45        
46        let url = format!("http://localhost:{}/json/version", debug_port);
47        let response = self.client.get(&url)
48            .send()
49            .await
50            .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to get DevTools info: {}", e)))?;
51        
52        if !response.status().is_success() {
53            return Err(TauriMcpError::WebDriverError(format!("DevTools returned error: {}", response.status())));
54        }
55        
56        let info: Value = response.json().await
57            .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to parse DevTools response: {}", e)))?;
58        
59        Ok(serde_json::json!({
60            "debug_port": debug_port,
61            "devtools_url": format!("http://localhost:{}", debug_port),
62            "version_info": info,
63        }))
64    }
65    
66    pub async fn connect_webdriver(&mut self, process_id: &str, debug_port: u16) -> Result<()> {
67        info!("Connecting WebDriver for process {} on port {}", process_id, debug_port);
68        
69        let capabilities = serde_json::json!({
70            "capabilities": {
71                "alwaysMatch": {
72                    "browserName": "chrome",
73                    "goog:chromeOptions": {
74                        "debuggerAddress": format!("localhost:{}", debug_port)
75                    }
76                }
77            }
78        });
79        
80        let url = format!("http://localhost:9515/session");
81        let response = self.client.post(&url)
82            .json(&capabilities)
83            .send()
84            .await
85            .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to connect WebDriver: {}", e)))?;
86        
87        if !response.status().is_success() {
88            return Err(TauriMcpError::WebDriverError(format!("WebDriver connection failed: {}", response.status())));
89        }
90        
91        let session_data: Value = response.json().await
92            .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to parse WebDriver response: {}", e)))?;
93        
94        let session_id = session_data["value"]["sessionId"].as_str()
95            .ok_or_else(|| TauriMcpError::WebDriverError("No session ID in response".to_string()))?
96            .to_string();
97        
98        self.webdriver_sessions.insert(process_id.to_string(), WebDriverSession {
99            session_id,
100            debug_port,
101        });
102        
103        Ok(())
104    }
105    
106    pub async fn get_page_source(&self, process_id: &str) -> Result<String> {
107        info!("Getting page source for process: {}", process_id);
108        
109        if let Some(session) = self.webdriver_sessions.get(process_id) {
110            let url = format!("http://localhost:9515/session/{}/source", session.session_id);
111            let response = self.client.get(&url)
112                .send()
113                .await
114                .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to get page source: {}", e)))?;
115            
116            if !response.status().is_success() {
117                return Err(TauriMcpError::WebDriverError(format!("Failed to get page source: {}", response.status())));
118            }
119            
120            let data: Value = response.json().await
121                .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to parse response: {}", e)))?;
122            
123            Ok(data["value"].as_str().unwrap_or("").to_string())
124        } else {
125            Err(TauriMcpError::WebDriverError("No WebDriver session found".to_string()))
126        }
127    }
128    
129    pub async fn get_console_logs(&self, process_id: &str) -> Result<Vec<Value>> {
130        info!("Getting console logs for process: {}", process_id);
131        
132        if let Some(session) = self.webdriver_sessions.get(process_id) {
133            let url = format!("http://localhost:9515/session/{}/se/log", session.session_id);
134            let body = serde_json::json!({
135                "type": "browser"
136            });
137            
138            let response = self.client.post(&url)
139                .json(&body)
140                .send()
141                .await
142                .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to get console logs: {}", e)))?;
143            
144            if !response.status().is_success() {
145                return Err(TauriMcpError::WebDriverError(format!("Failed to get console logs: {}", response.status())));
146            }
147            
148            let data: Value = response.json().await
149                .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to parse response: {}", e)))?;
150            
151            Ok(data["value"].as_array().cloned().unwrap_or_default())
152        } else {
153            Err(TauriMcpError::WebDriverError("No WebDriver session found".to_string()))
154        }
155    }
156    
157    pub async fn take_element_screenshot(&self, process_id: &str, selector: &str) -> Result<String> {
158        info!("Taking element screenshot for process {}, selector: {}", process_id, selector);
159        
160        if let Some(session) = self.webdriver_sessions.get(process_id) {
161            let find_url = format!("http://localhost:9515/session/{}/element", session.session_id);
162            let find_body = serde_json::json!({
163                "using": "css selector",
164                "value": selector
165            });
166            
167            let find_response = self.client.post(&find_url)
168                .json(&find_body)
169                .send()
170                .await
171                .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to find element: {}", e)))?;
172            
173            if !find_response.status().is_success() {
174                return Err(TauriMcpError::WebDriverError(format!("Element not found: {}", selector)));
175            }
176            
177            let element_data: Value = find_response.json().await
178                .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to parse response: {}", e)))?;
179            
180            let element_id = element_data["value"]["element-6066-11e4-a52e-4f735466cecf"].as_str()
181                .or_else(|| element_data["value"]["ELEMENT"].as_str())
182                .ok_or_else(|| TauriMcpError::WebDriverError("No element ID in response".to_string()))?;
183            
184            let screenshot_url = format!("http://localhost:9515/session/{}/element/{}/screenshot", 
185                                       session.session_id, element_id);
186            
187            let screenshot_response = self.client.get(&screenshot_url)
188                .send()
189                .await
190                .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to take screenshot: {}", e)))?;
191            
192            if !screenshot_response.status().is_success() {
193                return Err(TauriMcpError::WebDriverError(format!("Failed to take screenshot: {}", screenshot_response.status())));
194            }
195            
196            let screenshot_data: Value = screenshot_response.json().await
197                .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to parse response: {}", e)))?;
198            
199            Ok(format!("data:image/png;base64,{}", screenshot_data["value"].as_str().unwrap_or("")))
200        } else {
201            Err(TauriMcpError::WebDriverError("No WebDriver session found".to_string()))
202        }
203    }
204    
205    async fn execute_via_webdriver(&self, session: &WebDriverSession, javascript_code: &str) -> Result<Value> {
206        let url = format!("http://localhost:9515/session/{}/execute/sync", session.session_id);
207        let body = serde_json::json!({
208            "script": javascript_code,
209            "args": []
210        });
211        
212        let response = self.client.post(&url)
213            .json(&body)
214            .send()
215            .await
216            .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to execute JavaScript: {}", e)))?;
217        
218        if !response.status().is_success() {
219            return Err(TauriMcpError::WebDriverError(format!("JavaScript execution failed: {}", response.status())));
220        }
221        
222        let data: Value = response.json().await
223            .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to parse response: {}", e)))?;
224        
225        Ok(data["value"].clone())
226    }
227    
228    async fn execute_via_devtools(&self, process_id: &str, javascript_code: &str) -> Result<Value> {
229        let debug_port = self.find_debug_port(process_id).await?;
230        
231        let list_url = format!("http://localhost:{}/json/list", debug_port);
232        let response = self.client.get(&list_url)
233            .send()
234            .await
235            .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to list pages: {}", e)))?;
236        
237        if !response.status().is_success() {
238            return Err(TauriMcpError::WebDriverError(format!("Failed to list pages: {}", response.status())));
239        }
240        
241        let pages: Vec<Value> = response.json().await
242            .map_err(|e| TauriMcpError::WebDriverError(format!("Failed to parse pages: {}", e)))?;
243        
244        if pages.is_empty() {
245            return Err(TauriMcpError::WebDriverError("No pages found".to_string()));
246        }
247        
248        let page_id = pages[0]["id"].as_str()
249            .ok_or_else(|| TauriMcpError::WebDriverError("No page ID found".to_string()))?;
250        
251        Ok(serde_json::json!({
252            "status": "Would execute JavaScript via DevTools",
253            "code": javascript_code,
254            "page_id": page_id,
255        }))
256    }
257    
258    async fn find_debug_port(&self, _process_id: &str) -> Result<u16> {
259        for port in 9222..9250 {
260            let url = format!("http://localhost:{}/json/version", port);
261            if let Ok(response) = self.client.get(&url).send().await {
262                if response.status().is_success() {
263                    return Ok(port);
264                }
265            }
266        }
267        
268        Err(TauriMcpError::WebDriverError("No debug port found".to_string()))
269    }
270}