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.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}