Skip to main content

eoka_agent/spa/
mod.rs

1//! SPA router detection and manipulation.
2//!
3//! This module provides tools for detecting and navigating Single Page Applications
4//! without requiring page reloads. It supports:
5//!
6//! - React Router (v5 and v6)
7//! - Next.js (App Router and Pages Router)
8//! - Vue Router
9//! - Remix
10//! - History API fallback (works with any SPA)
11
12mod detect;
13mod navigate;
14
15pub use detect::detect_router;
16pub use navigate::{history_go, spa_navigate};
17
18use serde::{Deserialize, Serialize};
19use std::collections::HashMap;
20
21/// Detected SPA router type.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "kebab-case")]
24pub enum RouterType {
25    /// React Router v6 or Remix
26    ReactRouter,
27    /// Next.js (App or Pages router)
28    NextJs,
29    /// Vue Router
30    VueRouter,
31    /// Angular Router
32    AngularRouter,
33    /// History API (fallback, works with most SPAs)
34    HistoryApi,
35    /// Could not detect any SPA router
36    Unknown,
37}
38
39impl std::fmt::Display for RouterType {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            RouterType::ReactRouter => write!(f, "React Router"),
43            RouterType::NextJs => write!(f, "Next.js"),
44            RouterType::VueRouter => write!(f, "Vue Router"),
45            RouterType::AngularRouter => write!(f, "Angular Router"),
46            RouterType::HistoryApi => write!(f, "History API"),
47            RouterType::Unknown => write!(f, "Unknown"),
48        }
49    }
50}
51
52/// Information about the detected SPA router.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct SpaRouterInfo {
55    /// Detected router type.
56    pub router_type: RouterType,
57    /// Current path (from location.pathname).
58    pub current_path: String,
59    /// Query parameters as key-value pairs.
60    pub query_params: HashMap<String, String>,
61    /// Hash fragment (without #).
62    pub hash: String,
63    /// Whether programmatic navigation is available.
64    pub can_navigate: bool,
65    /// Additional router-specific details.
66    pub details: Option<String>,
67}
68
69impl std::fmt::Display for SpaRouterInfo {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        writeln!(f, "Router: {}", self.router_type)?;
72        writeln!(f, "Path: {}", self.current_path)?;
73        if !self.query_params.is_empty() {
74            writeln!(f, "Query: {:?}", self.query_params)?;
75        }
76        if !self.hash.is_empty() {
77            writeln!(f, "Hash: #{}", self.hash)?;
78        }
79        writeln!(
80            f,
81            "Can navigate: {}",
82            if self.can_navigate { "yes" } else { "no" }
83        )?;
84        if let Some(ref details) = self.details {
85            writeln!(f, "Details: {}", details)?;
86        }
87        Ok(())
88    }
89}