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