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.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.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.observe" | "observe" | "Observe" => {
122 let tab = match self.get_target_tab_mut(params) {
123 Ok(t) => t,
124 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
125 };
126 match tab.observe() {
127 Some(obs) => match serde_json::to_value(obs) {
128 Ok(v) => RpcResponse::success(id, v),
129 Err(e) => {
130 RpcResponse::error(id, -32000, format!("Serialization failed: {}", e))
131 }
132 },
133 None => RpcResponse::error(id, -32000, "No page loaded. Call navigate first."),
134 }
135 }
136 "tab.extractInteractive" | "extractInteractive" => {
137 let tab = match self.get_target_tab_mut(params) {
138 Ok(t) => t,
139 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
140 };
141 let elements = tab.extract_interactive_elements();
142 RpcResponse::success(
143 id,
144 serde_json::json!({ "elements": elements, "count": elements.len() }),
145 )
146 }
147 "tab.screenshot" | "screenshot" | "Screenshot" => {
148 let tab = match self.get_target_tab_mut(params) {
149 Ok(t) => t,
150 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
151 };
152 match tab.screenshot_async().await {
153 Some(shot) => match serde_json::to_value(shot) {
154 Ok(v) => RpcResponse::success(id, v),
155 Err(e) => {
156 RpcResponse::error(id, -32000, format!("Serialization failed: {}", e))
157 }
158 },
159 None => RpcResponse::error(id, -32000, "No page loaded. Call navigate first."),
160 }
161 }
162 "tab.screenshotLayout" | "screenshotLayout" => {
163 let tab = match self.get_target_tab_mut(params) {
164 Ok(t) => t,
165 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
166 };
167 match tab.screenshot_layout() {
168 Some(layout) => {
169 RpcResponse::success(id, serde_json::json!({ "layout": layout }))
170 }
171 None => RpcResponse::error(id, -32000, "No page loaded. Call navigate first."),
172 }
173 }
174 "tab.screenshotSvg" | "screenshotSvg" => {
175 let tab = match self.get_target_tab_mut(params) {
176 Ok(t) => t,
177 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
178 };
179 match tab.screenshot_svg() {
180 Some(svg) => RpcResponse::success(id, serde_json::json!({ "svg": svg })),
181 None => RpcResponse::error(id, -32000, "No page loaded. Call navigate first."),
182 }
183 }
184 "tab.extractMarkdown" | "extractMarkdown" | "ExtractMarkdown" => {
185 let selector = params.get("selector").and_then(|v| v.as_str());
186 let tab = match self.get_target_tab_mut(params) {
187 Ok(t) => t,
188 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
189 };
190 match tab.extract_markdown(selector) {
191 Some(markdown) => RpcResponse::success(
192 id,
193 serde_json::json!({ "markdown": markdown, "length": markdown.len() }),
194 ),
195 None => RpcResponse::error(id, -32000, "No DOM loaded. Call navigate first."),
196 }
197 }
198 "tab.extractResults" | "extractResults" | "ExtractResults" => {
199 let tab = match self.get_target_tab_mut(params) {
200 Ok(t) => t,
201 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
202 };
203 match tab.extract_search_results() {
204 Some(results) => match serde_json::to_value(results) {
205 Ok(v) => RpcResponse::success(id, v),
206 Err(e) => {
207 RpcResponse::error(id, -32000, format!("Serialization failed: {}", e))
208 }
209 },
210 None => RpcResponse::error(
211 id,
212 -32000,
213 "No search results available. Call navigate first.",
214 ),
215 }
216 }
217 "tab.extractLinks" | "extractLinks" | "ExtractLinks" => {
218 let tab = match self.get_target_tab_mut(params) {
219 Ok(t) => t,
220 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
221 };
222 let links = tab.extract_links();
223 RpcResponse::success(
224 id,
225 serde_json::json!({ "links": links, "count": links.len() }),
226 )
227 }
228 "tab.extractForms" | "extractForms" | "ExtractForms" => {
229 let tab = match self.get_target_tab_mut(params) {
230 Ok(t) => t,
231 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
232 };
233 let forms = tab.extract_forms();
234 RpcResponse::success(
235 id,
236 serde_json::json!({ "forms": forms, "count": forms.len() }),
237 )
238 }
239 "tab.extractDom" | "extractDom" | "ExtractDom" => {
240 let selector = params.get("selector").and_then(|v| v.as_str());
241 let tab = match self.get_target_tab_mut(params) {
242 Ok(t) => t,
243 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
244 };
245 match tab.extract_dom(selector) {
246 Some(html) => RpcResponse::success(
247 id,
248 serde_json::json!({ "html": html, "length": html.len() }),
249 ),
250 None => RpcResponse::error(id, -32000, "No DOM loaded. Call navigate first."),
251 }
252 }
253 "tab.click" | "click" | "Click" | "tab.actClick" => {
254 let target = params
255 .get("target")
256 .or_else(|| params.get("index"))
257 .map(|v| {
258 if v.is_number() {
259 v.to_string()
260 } else {
261 v.as_str().unwrap_or("").to_string()
262 }
263 })
264 .unwrap_or_default();
265 if target.is_empty() {
266 return RpcResponse::error(id, -32602, "Missing 'target' or 'index' parameter");
267 }
268 let tab = match self.get_target_tab_mut(params) {
269 Ok(t) => t,
270 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
271 };
272 match tab.act_click(&target).await {
273 Ok(Some(report)) => RpcResponse::success(
274 id,
275 serde_json::json!({ "action": "navigated", "report": report }),
276 ),
277 Ok(None) => {
278 RpcResponse::success(id, serde_json::json!({ "action": "clicked_in_page" }))
279 }
280 Err(e) => RpcResponse::error(id, -32000, format!("Click error: {}", e)),
281 }
282 }
283 "tab.type" | "type" | "Type" | "tab.actType" => {
284 let selector = params
285 .get("selector")
286 .or_else(|| params.get("target"))
287 .or_else(|| params.get("index"))
288 .map(|v| {
289 if v.is_number() {
290 v.to_string()
291 } else {
292 v.as_str().unwrap_or("").to_string()
293 }
294 })
295 .unwrap_or_default();
296 let text = params.get("text").and_then(|v| v.as_str()).unwrap_or("");
297 let tab = match self.get_target_tab_mut(params) {
298 Ok(t) => t,
299 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
300 };
301 match tab.act_type(&selector, text).await {
302 Ok(res) => RpcResponse::success(id, serde_json::json!({ "status": res })),
303 Err(e) => RpcResponse::error(id, -32000, format!("Type error: {}", e)),
304 }
305 }
306 "tab.evaluateJs" | "evaluateJs" | "EvaluateJs" => {
307 let code = params.get("code").and_then(|v| v.as_str()).unwrap_or("");
308 let tab = match self.get_target_tab_mut(params) {
309 Ok(t) => t,
310 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
311 };
312 match tab.evaluate_js(code) {
313 Ok(res) => RpcResponse::success(id, serde_json::json!({ "result": res })),
314 Err(e) => RpcResponse::error(id, -32000, format!("JS Error: {}", e)),
315 }
316 }
317 "tab.setProfile" | "setProfile" | "SetProfile" => {
318 let profile: DeviceProfile = match params
319 .get("profile")
320 .and_then(|p| serde_json::from_value(p.clone()).ok())
321 {
322 Some(p) => p,
323 None => {
324 return RpcResponse::error(
325 id,
326 -32602,
327 "Invalid or missing 'profile' parameter",
328 )
329 }
330 };
331 let tab = match self.get_target_tab_mut(params) {
332 Ok(t) => t,
333 Err(e) => return RpcResponse::error(id, -32000, e.to_string()),
334 };
335 match tab.set_profile(profile) {
336 Ok(_) => RpcResponse::success(
337 id,
338 serde_json::json!({ "status": "profile_updated", "profile": profile }),
339 ),
340 Err(e) => RpcResponse::error(id, -32000, format!("SetProfile error: {}", e)),
341 }
342 }
343 "ping" => RpcResponse::success(id, serde_json::json!({ "pong": true })),
344 "shutdown" | "Shutdown" => {
345 RpcResponse::success(id, serde_json::json!({ "status": "shutting down" }))
346 }
347 _ => RpcResponse::error(id, -32601, format!("Method not found: {}", method)),
348 }
349 }
350
351 fn get_target_tab_mut(
352 &mut self,
353 params: &serde_json::Value,
354 ) -> Result<&mut crate::browser::tab::BrowserTab> {
355 if let Some(tab_id) = params.get("tab_id").and_then(|v| v.as_str()) {
356 self.engine
357 .get_tab_mut(tab_id)
358 .ok_or_else(|| anyhow::anyhow!("Tab '{}' not found", tab_id))
359 } else {
360 self.engine.default_tab_mut()
361 }
362 }
363}