Skip to main content

headless_engine/api/
rpc.rs

1use crate::browser::engine::BrowserEngine;
2use crate::network::fingerprint::DeviceProfile;
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Deserialize, Serialize, Clone)]
7pub struct RpcRequest {
8    pub jsonrpc: Option<String>,
9    pub id: Option<serde_json::Value>,
10    pub method: String,
11    #[serde(default)]
12    pub params: serde_json::Value,
13}
14
15#[derive(Debug, Serialize, Deserialize, Clone)]
16pub struct RpcError {
17    pub code: i32,
18    pub message: String,
19}
20
21#[derive(Debug, Serialize, Deserialize, Clone)]
22pub struct RpcResponse {
23    pub jsonrpc: String,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub id: Option<serde_json::Value>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub result: Option<serde_json::Value>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub error: Option<RpcError>,
30}
31
32impl RpcResponse {
33    pub fn success(id: Option<serde_json::Value>, result: serde_json::Value) -> Self {
34        Self {
35            jsonrpc: "2.0".to_string(),
36            id,
37            result: Some(result),
38            error: None,
39        }
40    }
41
42    pub fn error(id: Option<serde_json::Value>, code: i32, message: impl Into<String>) -> Self {
43        Self {
44            jsonrpc: "2.0".to_string(),
45            id,
46            result: None,
47            error: Some(RpcError {
48                code,
49                message: message.into(),
50            }),
51        }
52    }
53}
54
55pub struct JsonRpcHandler {
56    pub engine: BrowserEngine,
57}
58
59impl JsonRpcHandler {
60    pub fn new() -> Result<Self> {
61        Ok(Self {
62            engine: BrowserEngine::new()?,
63        })
64    }
65
66    pub async fn handle_line(&mut self, line: &str) -> RpcResponse {
67        let req: RpcRequest = match serde_json::from_str(line) {
68            Ok(r) => r,
69            Err(e) => return RpcResponse::error(None, -32700, format!("Parse error: {}", e)),
70        };
71
72        self.handle_request(req).await
73    }
74
75    pub async fn handle_request(&mut self, req: RpcRequest) -> RpcResponse {
76        let id = req.id.clone();
77        let method = req.method.as_str();
78        let params = &req.params;
79
80        match method {
81            // Engine Methods
82            "engine.createTab" | "createTab" => {
83                let profile: Option<DeviceProfile> = params
84                    .get("profile")
85                    .and_then(|p| serde_json::from_value(p.clone()).ok());
86                match self.engine.create_tab(profile) {
87                    Ok(tab_id) => RpcResponse::success(id, serde_json::json!({ "tab_id": tab_id })),
88                    Err(e) => RpcResponse::error(id, -32000, format!("CreateTab error: {}", e)),
89                }
90            }
91            "engine.closeTab" | "closeTab" => {
92                let tab_id = params.get("tab_id").and_then(|v| v.as_str()).unwrap_or("");
93                let closed = self.engine.close_tab(tab_id);
94                RpcResponse::success(id, serde_json::json!({ "closed": closed }))
95            }
96            "engine.listTabs" | "listTabs" => {
97                let tabs = self.engine.list_tabs();
98                RpcResponse::success(id, serde_json::json!({ "tabs": tabs, "count": tabs.len() }))
99            }
100
101            // Tab Methods (with optional tab_id)
102            "tab.navigate" | "navigate" | "Navigate" => {
103                let url = params.get("url").and_then(|v| v.as_str()).unwrap_or("");
104                if url.is_empty() {
105                    return RpcResponse::error(id, -32602, "Missing 'url' parameter");
106                }
107                let tab = match self.get_target_tab_mut(params) {
108                    Ok(t) => t,
109                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
110                };
111                match tab.navigate(url).await {
112                    Ok(report) => match serde_json::to_value(report) {
113                        Ok(v) => RpcResponse::success(id, v),
114                        Err(e) => {
115                            RpcResponse::error(id, -32000, format!("Serialization failed: {}", e))
116                        }
117                    },
118                    Err(e) => RpcResponse::error(id, -32000, format!("Navigation failed: {}", e)),
119                }
120            }
121            "tab.setContent" | "tab.setHtml" | "setContent" | "setHtml" => {
122                let html = params.get("html").and_then(|v| v.as_str()).unwrap_or("");
123                let url = params.get("url").and_then(|v| v.as_str());
124                let tab = match self.get_target_tab_mut(params) {
125                    Ok(t) => t,
126                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
127                };
128                match tab.set_content(html, url) {
129                    Ok(report) => match serde_json::to_value(report) {
130                        Ok(v) => RpcResponse::success(id, v),
131                        Err(e) => {
132                            RpcResponse::error(id, -32000, format!("Serialization failed: {}", e))
133                        }
134                    },
135                    Err(e) => RpcResponse::error(id, -32000, format!("SetContent failed: {}", e)),
136                }
137            }
138            "tab.observe" | "observe" | "Observe" => {
139                let tab = match self.get_target_tab_mut(params) {
140                    Ok(t) => t,
141                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
142                };
143                match tab.observe() {
144                    Some(obs) => match serde_json::to_value(obs) {
145                        Ok(v) => RpcResponse::success(id, v),
146                        Err(e) => {
147                            RpcResponse::error(id, -32000, format!("Serialization failed: {}", e))
148                        }
149                    },
150                    None => RpcResponse::error(id, -32000, "No page loaded. Call navigate first."),
151                }
152            }
153            "tab.extractInteractive" | "extractInteractive" => {
154                let tab = match self.get_target_tab_mut(params) {
155                    Ok(t) => t,
156                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
157                };
158                let elements = tab.extract_interactive_elements();
159                RpcResponse::success(
160                    id,
161                    serde_json::json!({ "elements": elements, "count": elements.len() }),
162                )
163            }
164            "tab.screenshot" | "screenshot" | "Screenshot" => {
165                let tab = match self.get_target_tab_mut(params) {
166                    Ok(t) => t,
167                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
168                };
169                match tab.screenshot_async().await {
170                    Some(shot) => match serde_json::to_value(shot) {
171                        Ok(v) => RpcResponse::success(id, v),
172                        Err(e) => {
173                            RpcResponse::error(id, -32000, format!("Serialization failed: {}", e))
174                        }
175                    },
176                    None => RpcResponse::error(id, -32000, "No page loaded. Call navigate first."),
177                }
178            }
179            "tab.screenshotLayout" | "screenshotLayout" => {
180                let tab = match self.get_target_tab_mut(params) {
181                    Ok(t) => t,
182                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
183                };
184                match tab.screenshot_layout() {
185                    Some(layout) => {
186                        RpcResponse::success(id, serde_json::json!({ "layout": layout }))
187                    }
188                    None => RpcResponse::error(id, -32000, "No page loaded. Call navigate first."),
189                }
190            }
191            "tab.screenshotSvg" | "screenshotSvg" => {
192                let tab = match self.get_target_tab_mut(params) {
193                    Ok(t) => t,
194                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
195                };
196                match tab.screenshot_svg() {
197                    Some(svg) => RpcResponse::success(id, serde_json::json!({ "svg": svg })),
198                    None => RpcResponse::error(id, -32000, "No page loaded. Call navigate first."),
199                }
200            }
201            "tab.extractMarkdown" | "extractMarkdown" | "ExtractMarkdown" => {
202                let selector = params.get("selector").and_then(|v| v.as_str());
203                let tab = match self.get_target_tab_mut(params) {
204                    Ok(t) => t,
205                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
206                };
207                match tab.extract_markdown(selector) {
208                    Some(markdown) => RpcResponse::success(
209                        id,
210                        serde_json::json!({ "markdown": markdown, "length": markdown.len() }),
211                    ),
212                    None => RpcResponse::error(id, -32000, "No DOM loaded. Call navigate first."),
213                }
214            }
215            "tab.extractResults" | "extractResults" | "ExtractResults" => {
216                let tab = match self.get_target_tab_mut(params) {
217                    Ok(t) => t,
218                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
219                };
220                match tab.extract_search_results() {
221                    Some(results) => match serde_json::to_value(results) {
222                        Ok(v) => RpcResponse::success(id, v),
223                        Err(e) => {
224                            RpcResponse::error(id, -32000, format!("Serialization failed: {}", e))
225                        }
226                    },
227                    None => RpcResponse::error(
228                        id,
229                        -32000,
230                        "No search results available. Call navigate first.",
231                    ),
232                }
233            }
234            "tab.extractLinks" | "extractLinks" | "ExtractLinks" => {
235                let tab = match self.get_target_tab_mut(params) {
236                    Ok(t) => t,
237                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
238                };
239                let links = tab.extract_links();
240                RpcResponse::success(
241                    id,
242                    serde_json::json!({ "links": links, "count": links.len() }),
243                )
244            }
245            "tab.extractForms" | "extractForms" | "ExtractForms" => {
246                let tab = match self.get_target_tab_mut(params) {
247                    Ok(t) => t,
248                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
249                };
250                let forms = tab.extract_forms();
251                RpcResponse::success(
252                    id,
253                    serde_json::json!({ "forms": forms, "count": forms.len() }),
254                )
255            }
256            "tab.extractDom" | "extractDom" | "ExtractDom" => {
257                let selector = params.get("selector").and_then(|v| v.as_str());
258                let tab = match self.get_target_tab_mut(params) {
259                    Ok(t) => t,
260                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
261                };
262                match tab.extract_dom(selector) {
263                    Some(html) => RpcResponse::success(
264                        id,
265                        serde_json::json!({ "html": html, "length": html.len() }),
266                    ),
267                    None => RpcResponse::error(id, -32000, "No DOM loaded. Call navigate first."),
268                }
269            }
270            "tab.click" | "click" | "Click" | "tab.actClick" => {
271                let target = params
272                    .get("target")
273                    .or_else(|| params.get("index"))
274                    .map(|v| {
275                        if v.is_number() {
276                            v.to_string()
277                        } else {
278                            v.as_str().unwrap_or("").to_string()
279                        }
280                    })
281                    .unwrap_or_default();
282                if target.is_empty() {
283                    return RpcResponse::error(id, -32602, "Missing 'target' or 'index' parameter");
284                }
285                let tab = match self.get_target_tab_mut(params) {
286                    Ok(t) => t,
287                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
288                };
289                match tab.act_click(&target).await {
290                    Ok(Some(report)) => RpcResponse::success(
291                        id,
292                        serde_json::json!({ "action": "navigated", "report": report }),
293                    ),
294                    Ok(None) => {
295                        RpcResponse::success(id, serde_json::json!({ "action": "clicked_in_page" }))
296                    }
297                    Err(e) => RpcResponse::error(id, -32000, format!("Click error: {}", e)),
298                }
299            }
300            "tab.type" | "type" | "Type" | "tab.actType" => {
301                let selector = params
302                    .get("selector")
303                    .or_else(|| params.get("target"))
304                    .or_else(|| params.get("index"))
305                    .map(|v| {
306                        if v.is_number() {
307                            v.to_string()
308                        } else {
309                            v.as_str().unwrap_or("").to_string()
310                        }
311                    })
312                    .unwrap_or_default();
313                let text = params.get("text").and_then(|v| v.as_str()).unwrap_or("");
314                let tab = match self.get_target_tab_mut(params) {
315                    Ok(t) => t,
316                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
317                };
318                match tab.act_type(&selector, text).await {
319                    Ok(res) => RpcResponse::success(id, serde_json::json!({ "status": res })),
320                    Err(e) => RpcResponse::error(id, -32000, format!("Type error: {}", e)),
321                }
322            }
323            "tab.evaluateJs" | "evaluateJs" | "EvaluateJs" => {
324                let code = params.get("code").and_then(|v| v.as_str()).unwrap_or("");
325                let tab = match self.get_target_tab_mut(params) {
326                    Ok(t) => t,
327                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
328                };
329                match tab.evaluate_js(code) {
330                    Ok(res) => RpcResponse::success(id, serde_json::json!({ "result": res })),
331                    Err(e) => RpcResponse::error(id, -32000, format!("JS Error: {}", e)),
332                }
333            }
334            "tab.setProfile" | "setProfile" | "SetProfile" => {
335                let profile: DeviceProfile = match params
336                    .get("profile")
337                    .and_then(|p| serde_json::from_value(p.clone()).ok())
338                {
339                    Some(p) => p,
340                    None => {
341                        return RpcResponse::error(
342                            id,
343                            -32602,
344                            "Invalid or missing 'profile' parameter",
345                        )
346                    }
347                };
348                let tab = match self.get_target_tab_mut(params) {
349                    Ok(t) => t,
350                    Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
351                };
352                match tab.set_profile(profile) {
353                    Ok(_) => RpcResponse::success(
354                        id,
355                        serde_json::json!({ "status": "profile_updated", "profile": profile }),
356                    ),
357                    Err(e) => RpcResponse::error(id, -32000, format!("SetProfile error: {}", e)),
358                }
359            }
360            "ping" => RpcResponse::success(id, serde_json::json!({ "pong": true })),
361            "shutdown" | "Shutdown" => {
362                RpcResponse::success(id, serde_json::json!({ "status": "shutting down" }))
363            }
364            _ => RpcResponse::error(id, -32601, format!("Method not found: {}", method)),
365        }
366    }
367
368    fn get_target_tab_mut(
369        &mut self,
370        params: &serde_json::Value,
371    ) -> Result<&mut crate::browser::tab::BrowserTab> {
372        if let Some(tab_id) = params.get("tab_id").and_then(|v| v.as_str()) {
373            self.engine
374                .get_tab_mut(tab_id)
375                .ok_or_else(|| anyhow::anyhow!("Tab '{}' not found", tab_id))
376        } else {
377            self.engine.default_tab_mut()
378        }
379    }
380}