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}