1use ratatui::widgets::TableState;
2use std::collections::HashMap;
3use tokio::sync::{mpsc, oneshot};
4
5#[derive(Debug, Clone)]
6pub struct JsonRpcMessage {
7 pub id: Option<serde_json::Value>,
8 pub method: Option<String>,
9 pub params: Option<serde_json::Value>,
10 pub result: Option<serde_json::Value>,
11 pub error: Option<serde_json::Value>,
12 pub timestamp: std::time::SystemTime,
13 pub direction: MessageDirection,
14 pub transport: TransportType,
15 pub headers: Option<HashMap<String, String>>,
16}
17
18#[derive(Debug, Clone)]
19pub struct JsonRpcExchange {
20 pub id: Option<serde_json::Value>,
21 pub method: Option<String>,
22 pub request: Option<JsonRpcMessage>,
23 pub response: Option<JsonRpcMessage>,
24 #[allow(dead_code)] pub timestamp: std::time::SystemTime,
26 pub transport: TransportType,
27}
28
29#[derive(Debug, Clone)]
30pub enum MessageDirection {
31 Request,
32 Response,
33}
34
35#[derive(Debug, Clone)]
36pub enum TransportType {
37 Http,
38 #[allow(dead_code)] WebSocket,
40}
41
42#[derive(Debug, Clone, PartialEq)]
43pub enum InputMode {
44 Normal,
45 EditingTarget,
46}
47
48#[derive(Debug, Clone, PartialEq)]
49pub enum AppMode {
50 Normal, Paused, Intercepting, }
54
55#[derive(Debug)]
56pub enum ProxyDecision {
57 Allow(Option<serde_json::Value>, Option<HashMap<String, String>>), Block, Complete(serde_json::Value), }
61
62#[allow(dead_code)]
63pub struct PendingRequest {
64 pub id: String,
65 pub original_request: JsonRpcMessage,
66 pub modified_request: Option<String>, pub modified_headers: Option<HashMap<String, String>>, pub decision_sender: oneshot::Sender<ProxyDecision>,
69}
70
71#[allow(dead_code)]
72pub struct App {
73 pub exchanges: Vec<JsonRpcExchange>,
74 pub selected_exchange: usize,
75 pub table_state: TableState,
76 pub details_scroll: usize,
77 pub intercept_details_scroll: usize, pub proxy_config: ProxyConfig,
79 pub is_running: bool,
80 pub message_receiver: Option<mpsc::UnboundedReceiver<JsonRpcMessage>>,
81 pub input_mode: InputMode,
82 pub input_buffer: String,
83 pub app_mode: AppMode, pub pending_requests: Vec<PendingRequest>, pub selected_pending: usize, pub request_editor_buffer: String, }
88
89#[derive(Debug)]
90#[allow(dead_code)]
91pub struct ProxyConfig {
92 pub listen_port: u16,
93 pub target_url: String,
94 pub transport: TransportType,
95}
96
97impl Default for App {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103impl App {
104 pub fn new() -> Self {
105 let mut table_state = TableState::default();
106 table_state.select(Some(0));
107
108 Self {
109 exchanges: Vec::new(),
110 selected_exchange: 0,
111 table_state,
112 details_scroll: 0,
113 intercept_details_scroll: 0,
114 proxy_config: ProxyConfig {
115 listen_port: 8080,
116 target_url: "".to_string(),
117 transport: TransportType::Http,
118 },
119 is_running: true,
120 message_receiver: None,
121 input_mode: InputMode::Normal,
122 input_buffer: String::new(),
123 app_mode: AppMode::Normal,
124 pending_requests: Vec::new(),
125 selected_pending: 0,
126 request_editor_buffer: String::new(),
127 }
128 }
129
130 pub fn new_with_receiver(receiver: mpsc::UnboundedReceiver<JsonRpcMessage>) -> Self {
131 let mut table_state = TableState::default();
132 table_state.select(Some(0));
133
134 Self {
135 exchanges: Vec::new(),
136 selected_exchange: 0,
137 table_state,
138 details_scroll: 0,
139 intercept_details_scroll: 0,
140 proxy_config: ProxyConfig {
141 listen_port: 8080,
142 target_url: "".to_string(),
143 transport: TransportType::Http,
144 },
145 is_running: true,
146 message_receiver: Some(receiver),
147 input_mode: InputMode::Normal,
148 input_buffer: String::new(),
149 app_mode: AppMode::Normal,
150 pending_requests: Vec::new(),
151 selected_pending: 0,
152 request_editor_buffer: String::new(),
153 }
154 }
155
156 pub fn check_for_new_messages(&mut self) {
157 if let Some(receiver) = &mut self.message_receiver {
158 let mut new_messages = Vec::new();
159 while let Ok(message) = receiver.try_recv() {
160 new_messages.push(message);
161 }
162 for message in new_messages {
163 self.add_message(message);
164 }
165 }
166 }
167
168 pub fn add_message(&mut self, mut message: JsonRpcMessage) {
169 if let Some(ref mut error) = message.error {
171 if let Some(data) = error.get_mut("data") {
172 if let Some(data_str) = data.as_str() {
173 let sanitized = data_str
174 .chars()
175 .filter(|c| c.is_ascii() && (!c.is_control() || *c == '\n' || *c == '\t'))
176 .take(500)
177 .collect::<String>();
178 *data = serde_json::Value::String(sanitized);
179 }
180 }
181 }
182
183 match message.direction {
184 MessageDirection::Request => {
185 let exchange = JsonRpcExchange {
187 id: message.id.clone(),
188 method: message.method.clone(),
189 request: Some(message.clone()),
190 response: None,
191 timestamp: message.timestamp,
192 transport: message.transport.clone(),
193 };
194 self.exchanges.push(exchange);
195 }
196 MessageDirection::Response => {
197 if let Some(exchange) = self
199 .exchanges
200 .iter_mut()
201 .rev()
202 .find(|e| e.id == message.id && e.response.is_none())
203 {
204 exchange.response = Some(message);
205 } else {
206 let exchange = JsonRpcExchange {
208 id: message.id.clone(),
209 method: None,
210 request: None,
211 response: Some(message.clone()),
212 timestamp: message.timestamp,
213 transport: message.transport.clone(),
214 };
215 self.exchanges.push(exchange);
216 }
217 }
218 }
219 }
220
221 pub fn get_selected_exchange(&self) -> Option<&JsonRpcExchange> {
222 self.exchanges.get(self.selected_exchange)
223 }
224
225 pub fn select_next(&mut self) {
226 if !self.exchanges.is_empty() {
227 self.selected_exchange = (self.selected_exchange + 1) % self.exchanges.len();
228 self.table_state.select(Some(self.selected_exchange));
229 self.reset_details_scroll();
230 }
231 }
232
233 pub fn select_previous(&mut self) {
234 if !self.exchanges.is_empty() {
235 self.selected_exchange = if self.selected_exchange == 0 {
236 self.exchanges.len() - 1
237 } else {
238 self.selected_exchange - 1
239 };
240 self.table_state.select(Some(self.selected_exchange));
241 self.reset_details_scroll();
242 }
243 }
244
245 pub fn toggle_proxy(&mut self) {
246 self.is_running = !self.is_running;
247 }
248
249 pub fn scroll_details_up(&mut self) {
250 if self.details_scroll > 0 {
251 self.details_scroll -= 1;
252 }
253 }
254
255 pub fn scroll_details_down(&mut self, max_lines: usize, visible_lines: usize) {
256 if max_lines > visible_lines && self.details_scroll < max_lines - visible_lines {
257 self.details_scroll += 1;
258 }
259 }
260
261 pub fn reset_details_scroll(&mut self) {
262 self.details_scroll = 0;
263 }
264
265 pub fn scroll_intercept_details_up(&mut self) {
267 if self.intercept_details_scroll > 0 {
268 self.intercept_details_scroll -= 1;
269 }
270 }
271
272 pub fn scroll_intercept_details_down(&mut self, max_lines: usize, visible_lines: usize) {
273 if max_lines > visible_lines && self.intercept_details_scroll < max_lines - visible_lines {
274 self.intercept_details_scroll += 1;
275 }
276 }
277
278 pub fn reset_intercept_details_scroll(&mut self) {
279 self.intercept_details_scroll = 0;
280 }
281
282 pub fn page_down_intercept_details(&mut self, visible_lines: usize) {
283 let page_size = visible_lines / 2; self.intercept_details_scroll += page_size;
285 }
286
287 pub fn page_up_intercept_details(&mut self) {
288 let page_size = 10; self.intercept_details_scroll = self.intercept_details_scroll.saturating_sub(page_size);
290 }
291
292 pub fn goto_top_intercept_details(&mut self) {
293 self.intercept_details_scroll = 0;
294 }
295
296 pub fn goto_bottom_intercept_details(&mut self, max_lines: usize, visible_lines: usize) {
297 if max_lines > visible_lines {
298 self.intercept_details_scroll = max_lines - visible_lines;
299 }
300 }
301
302 pub fn page_down_details(&mut self, visible_lines: usize) {
304 let page_size = visible_lines / 2; self.details_scroll += page_size;
306 }
307
308 pub fn page_up_details(&mut self) {
309 let page_size = 10; self.details_scroll = self.details_scroll.saturating_sub(page_size);
311 }
312
313 pub fn goto_top_details(&mut self) {
314 self.details_scroll = 0;
315 }
316
317 pub fn goto_bottom_details(&mut self, max_lines: usize, visible_lines: usize) {
318 if max_lines > visible_lines {
319 self.details_scroll = max_lines - visible_lines;
320 }
321 }
322
323 pub fn start_editing_target(&mut self) {
326 self.input_mode = InputMode::EditingTarget;
327 self.input_buffer.clear();
328 }
329
330 pub fn cancel_editing(&mut self) {
331 self.input_mode = InputMode::Normal;
332 self.input_buffer.clear();
333 }
334
335 pub fn confirm_target_edit(&mut self) {
336 if !self.input_buffer.trim().is_empty() {
337 self.proxy_config.target_url = self.input_buffer.trim().to_string();
338 }
339 self.input_mode = InputMode::Normal;
340 self.input_buffer.clear();
341 }
342
343 pub fn handle_input_char(&mut self, c: char) {
344 if self.input_mode == InputMode::EditingTarget {
345 self.input_buffer.push(c);
346 }
347 }
348
349 pub fn handle_backspace(&mut self) {
350 if self.input_mode == InputMode::EditingTarget {
351 self.input_buffer.pop();
352 }
353 }
354
355 pub fn get_details_content_lines(&self) -> usize {
356 if let Some(exchange) = self.get_selected_exchange() {
357 let mut line_count = 0;
358
359 line_count += 3; if let Some(request) = &exchange.request {
364 line_count += 2; if let Some(headers) = &request.headers {
367 line_count += 2; line_count += headers.len();
369 }
370
371 line_count += 2; let mut request_json = serde_json::Map::new();
375 request_json.insert(
376 "jsonrpc".to_string(),
377 serde_json::Value::String("2.0".to_string()),
378 );
379 if let Some(id) = &request.id {
380 request_json.insert("id".to_string(), id.clone());
381 }
382 if let Some(method) = &request.method {
383 request_json.insert(
384 "method".to_string(),
385 serde_json::Value::String(method.clone()),
386 );
387 }
388 if let Some(params) = &request.params {
389 request_json.insert("params".to_string(), params.clone());
390 }
391
392 if let Ok(json_str) =
393 serde_json::to_string_pretty(&serde_json::Value::Object(request_json))
394 {
395 line_count += json_str.lines().count();
396 }
397 }
398
399 if let Some(response) = &exchange.response {
401 line_count += 2; if let Some(headers) = &response.headers {
404 line_count += 2; line_count += headers.len();
406 }
407
408 line_count += 2; let mut response_json = serde_json::Map::new();
412 response_json.insert(
413 "jsonrpc".to_string(),
414 serde_json::Value::String("2.0".to_string()),
415 );
416 if let Some(id) = &response.id {
417 response_json.insert("id".to_string(), id.clone());
418 }
419 if let Some(result) = &response.result {
420 response_json.insert("result".to_string(), result.clone());
421 }
422 if let Some(error) = &response.error {
423 response_json.insert("error".to_string(), error.clone());
424 }
425
426 if let Ok(json_str) =
427 serde_json::to_string_pretty(&serde_json::Value::Object(response_json))
428 {
429 line_count += json_str.lines().count();
430 }
431 } else {
432 line_count += 2; }
434
435 line_count
436 } else {
437 1 }
439 }
440
441 pub fn toggle_pause_mode(&mut self) {
443 self.app_mode = match self.app_mode {
444 AppMode::Normal => AppMode::Paused,
445 AppMode::Paused => AppMode::Normal,
446 AppMode::Intercepting => AppMode::Normal,
447 };
448 }
449
450 pub fn select_next_pending(&mut self) {
451 if !self.pending_requests.is_empty() {
452 self.selected_pending = (self.selected_pending + 1) % self.pending_requests.len();
453 self.reset_intercept_details_scroll();
454 }
455 }
456
457 pub fn select_previous_pending(&mut self) {
458 if !self.pending_requests.is_empty() {
459 self.selected_pending = if self.selected_pending == 0 {
460 self.pending_requests.len() - 1
461 } else {
462 self.selected_pending - 1
463 };
464 self.reset_intercept_details_scroll();
465 }
466 }
467
468 pub fn get_selected_pending(&self) -> Option<&PendingRequest> {
469 self.pending_requests.get(self.selected_pending)
470 }
471
472 pub fn allow_selected_request(&mut self) {
473 if self.selected_pending < self.pending_requests.len() {
474 let pending = self.pending_requests.remove(self.selected_pending);
475 if self.selected_pending > 0 && self.selected_pending >= self.pending_requests.len() {
476 self.selected_pending -= 1;
477 }
478
479 let decision = if let Some(ref modified_json) = pending.modified_request {
481 if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(modified_json) {
482 ProxyDecision::Allow(Some(parsed), pending.modified_headers.clone())
483 } else {
484 ProxyDecision::Allow(None, pending.modified_headers.clone())
485 }
487 } else {
488 ProxyDecision::Allow(None, pending.modified_headers.clone()) };
490
491 let _ = pending.decision_sender.send(decision);
492 }
493 }
494
495 pub fn block_selected_request(&mut self) {
496 if self.selected_pending < self.pending_requests.len() {
497 let pending = self.pending_requests.remove(self.selected_pending);
498 if self.selected_pending > 0 && self.selected_pending >= self.pending_requests.len() {
499 self.selected_pending -= 1;
500 }
501
502 let _ = pending.decision_sender.send(ProxyDecision::Block);
504 }
505 }
506
507 pub fn resume_all_requests(&mut self) {
508 for pending in self.pending_requests.drain(..) {
509 let _ = pending
510 .decision_sender
511 .send(ProxyDecision::Allow(None, None));
512 }
513 self.selected_pending = 0;
514 self.app_mode = AppMode::Normal;
515 }
516
517 pub fn get_pending_request_json(&self) -> Option<String> {
518 if let Some(pending) = self.get_selected_pending() {
519 let json_value = serde_json::json!({
521 "jsonrpc": "2.0",
522 "method": pending.original_request.method,
523 "params": pending.original_request.params,
524 "id": pending.original_request.id
525 });
526
527 serde_json::to_string_pretty(&json_value).ok()
529 } else {
530 None
531 }
532 }
533
534 pub fn apply_edited_json(&mut self, edited_json: String) -> Result<(), String> {
535 if self.selected_pending >= self.pending_requests.len() {
536 return Err("No pending request selected".to_string());
537 }
538
539 let parsed: serde_json::Value =
541 serde_json::from_str(&edited_json).map_err(|e| format!("Invalid JSON: {}", e))?;
542
543 if parsed.get("jsonrpc") != Some(&serde_json::Value::String("2.0".to_string())) {
545 return Err("Missing or invalid 'jsonrpc' field".to_string());
546 }
547
548 if parsed.get("method").is_none() {
549 return Err("Missing 'method' field".to_string());
550 }
551
552 self.pending_requests[self.selected_pending].modified_request = Some(edited_json);
554
555 Ok(())
556 }
557
558 pub fn get_pending_request_headers(&self) -> Option<String> {
559 if let Some(pending) = self.get_selected_pending() {
560 let headers = pending
562 .modified_headers
563 .as_ref()
564 .or(pending.original_request.headers.as_ref());
565
566 if let Some(headers) = headers {
567 let mut header_lines = Vec::new();
569 for (key, value) in headers {
570 header_lines.push(format!("{}: {}", key, value));
571 }
572 Some(header_lines.join("\n"))
573 } else {
574 Some(
575 "# No headers\n# Add headers in the format:\n# header-name: header-value"
576 .to_string(),
577 )
578 }
579 } else {
580 None
581 }
582 }
583
584 pub fn apply_edited_headers(&mut self, edited_headers: String) -> Result<(), String> {
585 if self.selected_pending >= self.pending_requests.len() {
586 return Err("No pending request selected".to_string());
587 }
588
589 let mut headers = HashMap::new();
590
591 for line in edited_headers.lines() {
592 let line = line.trim();
593
594 if line.is_empty() || line.starts_with('#') {
596 continue;
597 }
598
599 if let Some(colon_pos) = line.find(':') {
601 let key = line[..colon_pos].trim().to_string();
602 let value = line[colon_pos + 1..].trim().to_string();
603
604 if !key.is_empty() {
605 headers.insert(key, value);
606 }
607 } else {
608 return Err(format!(
609 "Invalid header format: '{}'. Use 'key: value' format.",
610 line
611 ));
612 }
613 }
614
615 self.pending_requests[self.selected_pending].modified_headers = Some(headers);
617
618 Ok(())
619 }
620
621 pub fn get_pending_response_template(&self) -> Option<String> {
622 if let Some(pending) = self.get_selected_pending() {
623 let response_template = serde_json::json!({
625 "jsonrpc": "2.0",
626 "id": pending.original_request.id,
627 "result": "custom response"
628 });
629
630 serde_json::to_string_pretty(&response_template).ok()
632 } else {
633 None
634 }
635 }
636
637 pub fn complete_selected_request(&mut self, response_json: String) -> Result<(), String> {
638 if self.selected_pending >= self.pending_requests.len() {
639 return Err("No pending request selected".to_string());
640 }
641
642 let parsed: serde_json::Value =
644 serde_json::from_str(&response_json).map_err(|e| format!("Invalid JSON: {}", e))?;
645
646 if parsed.get("jsonrpc") != Some(&serde_json::Value::String("2.0".to_string())) {
648 return Err("Missing or invalid 'jsonrpc' field".to_string());
649 }
650
651 if parsed.get("id").is_none() {
652 return Err("Missing 'id' field".to_string());
653 }
654
655 let has_result = parsed.get("result").is_some();
657 let has_error = parsed.get("error").is_some();
658
659 if !has_result && !has_error {
660 return Err("Response must have either 'result' or 'error' field".to_string());
661 }
662
663 if has_result && has_error {
664 return Err("Response cannot have both 'result' and 'error' fields".to_string());
665 }
666
667 let pending = self.pending_requests.remove(self.selected_pending);
669 if self.selected_pending > 0 && self.selected_pending >= self.pending_requests.len() {
670 self.selected_pending -= 1;
671 }
672
673 let _ = pending
674 .decision_sender
675 .send(ProxyDecision::Complete(parsed));
676
677 Ok(())
678 }
679
680 pub async fn send_new_request(&self, request_json: String) -> Result<(), String> {
681 let parsed: serde_json::Value =
683 serde_json::from_str(&request_json).map_err(|e| format!("Invalid JSON: {}", e))?;
684
685 if parsed.get("jsonrpc") != Some(&serde_json::Value::String("2.0".to_string())) {
687 return Err("Missing or invalid 'jsonrpc' field".to_string());
688 }
689
690 if parsed.get("method").is_none() {
691 return Err("Missing 'method' field".to_string());
692 }
693
694 if self.proxy_config.target_url.trim().is_empty() {
696 return Err("Target URL is not set. Press 't' to set a target URL first.".to_string());
697 }
698
699 let client = reqwest::Client::new();
700
701 let url = if matches!(self.app_mode, AppMode::Paused | AppMode::Intercepting) {
704 &self.proxy_config.target_url
705 } else {
706 &format!("http://localhost:{}", self.proxy_config.listen_port)
708 };
709
710 let response = client
711 .post(url)
712 .header("Content-Type", "application/json")
713 .body(request_json)
714 .send()
715 .await
716 .map_err(|e| format!("Failed to send request: {}", e))?;
717
718 if !response.status().is_success() {
719 return Err(format!("Request failed with status: {}", response.status()));
720 }
721
722 Ok(())
723 }
724}