Refactor Mihomo API integration and remove crate_mihomo_api

- Removed the `mihomo_api` crate and its dependencies from the project.
- Introduced `IpcManager` for handling IPC communication with Mihomo.
- Implemented IPC methods for managing proxies, connections, and configurations.
- Updated `MihomoManager` to utilize `IpcManager` instead of the removed crate.
- Added platform-specific IPC socket path handling for macOS, Linux, and Windows.
- Cleaned up related tests and configuration files.
This commit is contained in:
Tunglies
2025-06-25 01:32:26 +08:00
Unverified
parent 2c9aa4bca7
commit 945e1715b8
12 changed files with 453 additions and 400 deletions

496
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -70,15 +70,15 @@ getrandom = "0.3.3"
futures = "0.3.31"
sys-locale = "0.3.2"
async-trait = "0.1.88"
mihomo_api = { path = "src_crates/crate_mihomo_api" }
ab_glyph = "0.2.29"
tungstenite = "0.27.0"
libc = "0.2.174"
gethostname = "1.0.2"
hmac = "0.12.1"
sha2 = "0.10.9"
hex = "0.4.3"
scopeguard = "1.2.0"
tauri-plugin-notification = "2.3.0"
dashmap = "7.0.0-rc2"
kode-bridge = "0.1.0"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
@@ -142,9 +142,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
criterion = "0.6.0"
tempfile = "3.20.0"
[workspace]
members = ["src_crates/crate_mihomo_api"]
[[bench]]
name = "draft_benchmark"
harness = false
# [patch.crates-io]
# bitflags = { git = "https://github.com/bitflags/bitflags", rev = "2.9.0" }
# zerocopy = { git = "https://github.com/google/zerocopy", rev = "v0.8.24" }
# tungstenite = { git = "https://github.com/snapview/tungstenite-rs", rev = "v0.26.2" }

View File

@@ -0,0 +1,130 @@
use kode_bridge::{errors::AnyError, IpcHttpClient};
use serde_json::json;
pub struct IpcManager {
client: IpcHttpClient,
}
impl IpcManager {
pub async fn new(socket_path: &str) -> Self {
IpcManager {
client: IpcHttpClient::new(socket_path).await,
}
}
}
impl IpcManager {
pub async fn send_request(
&self,
method: &str,
path: &str,
body: Option<&serde_json::Value>,
) -> Result<serde_json::Value, AnyError> {
// Ok(self.client.request(method, path, body).await?.json()?)
let response = self.client.request(method, path, body).await?;
if method == "PATCH" {
if response.status == 204 {
Ok(serde_json::json!({"code": 204}))
} else {
Ok(response.json()?)
}
} else if method == "PUT" {
Ok(json!(response.body))
} else {
Ok(response.json()?)
}
}
pub async fn get_refresh_proxies(&self) -> Result<serde_json::Value, AnyError> {
let url = "/proxies";
self.send_request("GET", url, None).await
}
pub async fn get_providers_proxies(&self) -> Result<serde_json::Value, AnyError> {
let url = "/providers/proxies";
self.send_request("GET", url, None).await
}
pub async fn close_all_connections(&self) -> Result<(), AnyError> {
let url = "/connections";
let response = self.send_request("DELETE", url, None).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(AnyError::from(
response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string(),
))
}
}
}
impl IpcManager {
pub async fn is_mihomo_running(&self) -> Result<(), AnyError> {
let url = "/version";
let _response = self.send_request("GET", url, None).await?;
Ok(())
}
pub async fn put_configs_force(&self, clash_config_path: &str) -> Result<(), AnyError> {
let url = "/configs?force=true";
let payload = serde_json::json!({
"path": clash_config_path,
});
let _response = self.send_request("PUT", url, Some(&payload)).await?;
Ok(())
}
pub async fn patch_configs(&self, config: serde_json::Value) -> Result<(), AnyError> {
let url = "/configs";
let response = self.send_request("PATCH", url, Some(&config)).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(AnyError::from(
response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string(),
))
}
}
pub async fn test_proxy_delay(
&self,
name: &str,
test_url: Option<String>,
timeout: i32,
) -> Result<serde_json::Value, AnyError> {
let test_url =
test_url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string());
let url = format!(
"/proxies/{}/delay?url={}&timeout={}",
name, test_url, timeout
);
let response = self.send_request("GET", &url, None).await?;
Ok(response)
}
pub async fn get_connections(&self) -> Result<serde_json::Value, AnyError> {
let url = "/connections";
let response = self.send_request("GET", url, None).await?;
Ok(response)
}
pub async fn delete_connection(&self, id: &str) -> Result<(), AnyError> {
let url = format!("/connections/{}", id);
let response = self.send_request("DELETE", &url, None).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(AnyError::from(
response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string(),
))
}
}
}

3
src-tauri/src/ipc/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod general;
pub use general::IpcManager;

View File

@@ -3,6 +3,7 @@ pub mod config;
mod core;
mod enhance;
mod feat;
mod ipc;
mod module;
mod process;
mod state;

View File

@@ -1,5 +1,7 @@
use crate::config::Config;
use mihomo_api;
use crate::ipc::IpcManager;
#[cfg(unix)]
use crate::utils::dirs::{ipc_path, path_to_str};
use once_cell::sync::Lazy;
use parking_lot::{Mutex, RwLock};
use std::time::{Duration, Instant};
@@ -15,7 +17,7 @@ pub struct Rate {
}
// 缓存MihomoManager实例
struct MihomoCache {
manager: mihomo_api::MihomoManager,
manager: IpcManager,
created_at: Instant,
server: String,
}
@@ -34,7 +36,7 @@ impl MihomoManager {
&INSTANCE
}
pub fn global() -> mihomo_api::MihomoManager {
pub fn global() -> IpcManager {
let instance = MihomoManager::__global();
// 尝试从缓存读取(只需读锁)
@@ -66,7 +68,7 @@ impl MihomoManager {
if cache_entry.server == current_server
&& cache_entry.created_at.elapsed() < CACHE_TTL
{
return cache_entry.manager.clone();
return cache_entry.manager;
}
}
}
@@ -74,13 +76,16 @@ impl MihomoManager {
// 创建新实例
let (current_server, headers) = MihomoManager::get_clash_client_info()
.unwrap_or_else(|| (String::new(), HeaderMap::new()));
let manager = mihomo_api::MihomoManager::new(current_server.clone(), headers);
// ! unix
#[cfg(unix)]
let manager = IpcManager::new(path_to_str(ipc_path()?));
// ! windows
// 更新缓存
{
let mut cache = instance.mihomo_cache.write();
*cache = Some(MihomoCache {
manager: manager.clone(),
manager: manager,
created_at: Instant::now(),
server: current_server,
});

View File

@@ -242,3 +242,21 @@ pub fn get_encryption_key() -> Result<Vec<u8>> {
Ok(key)
}
}
#[cfg(target_os = "macos")]
pub fn ipc_path() -> Result<PathBuf> {
let res_dir = app_resources_dir()?;
Ok(res_dir.join("mihomo.sock"))
}
#[cfg(target_os = "linux")]
pub fn ipc_path() -> Result<PathBuf> {
let res_dir = app_resources_dir()?;
Ok(res_dir.join("mihomo.sock"))
}
#[cfg(target_os = "windows")]
pub fn ipc_path() -> Result<PathBuf> {
let res_dir = app_resources_dir()?;
Ok(res_dir.join(r"\\.\pipe\mihomo"))
}

View File

@@ -1,2 +0,0 @@
# LOCAL_SOCK="/Users/tunglies/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev.dev/mihomo.sock"
LOCAL_SOCK="/Users/tunglies/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/mihomo.sock"

View File

@@ -1,11 +0,0 @@
[package]
name = "mihomo_api"
edition = "2024"
[dependencies]
reqwest = { version = "0.12.22", features = ["json"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
tokio = { version = "1.46.1", features = ["rt", "macros", "time"] }
[dev-dependencies]

View File

@@ -1,147 +0,0 @@
use reqwest::{Method, header::HeaderMap};
use serde_json::{Value, json};
use std::time::Duration;
pub mod model;
pub use model::MihomoManager;
impl MihomoManager {
pub fn new(mihomo_server: String, headers: HeaderMap) -> Self {
let client = reqwest::ClientBuilder::new()
.default_headers(headers)
.no_proxy()
.timeout(Duration::from_secs(15))
.pool_max_idle_per_host(5)
.pool_idle_timeout(Duration::from_secs(15))
.build()
.expect("Failed to build reqwest client");
Self {
mihomo_server,
client,
}
}
async fn send_request(
&self,
method: Method,
url: String,
data: Option<serde_json::Value>,
) -> Result<serde_json::Value, String> {
let client_response = self
.client
.request(method.clone(), &url)
.json(&data.unwrap_or(json!({})))
.send()
.await
.map_err(|e| e.to_string())?;
let response = match method {
Method::PATCH => {
let status = client_response.status();
if status.as_u16() == 204 {
json!({"code": 204})
} else {
client_response
.json::<serde_json::Value>()
.await
.map_err(|e| e.to_string())?
}
}
Method::PUT => json!(client_response.text().await.map_err(|e| e.to_string())?),
_ => client_response
.json::<serde_json::Value>()
.await
.map_err(|e| e.to_string())?,
};
Ok(response)
}
pub async fn get_refresh_proxies(&self) -> Result<Value, String> {
let url = format!("{}/proxies", self.mihomo_server);
let proxies = self.send_request(Method::GET, url, None).await?;
Ok(proxies)
}
pub async fn get_providers_proxies(&self) -> Result<Value, String> {
let url = format!("{}/providers/proxies", self.mihomo_server);
let providers_proxies = self.send_request(Method::GET, url, None).await?;
Ok(providers_proxies)
}
pub async fn close_all_connections(&self) -> Result<(), String> {
let url = format!("{}/connections", self.mihomo_server);
let response = self.send_request(Method::DELETE, url, None).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string())
}
}
}
impl MihomoManager {
pub async fn is_mihomo_running(&self) -> Result<(), String> {
let url = format!("{}/version", self.mihomo_server);
let _response = self.send_request(Method::GET, url, None).await?;
Ok(())
}
pub async fn put_configs_force(&self, clash_config_path: &str) -> Result<(), String> {
let url = format!("{}/configs?force=true", self.mihomo_server);
let payload = serde_json::json!({
"path": clash_config_path,
});
let _response = self.send_request(Method::PUT, url, Some(payload)).await?;
Ok(())
}
pub async fn patch_configs(&self, config: serde_json::Value) -> Result<(), String> {
let url = format!("{}/configs", self.mihomo_server);
let response = self.send_request(Method::PATCH, url, Some(config)).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string())
}
}
pub async fn test_proxy_delay(
&self,
name: &str,
test_url: Option<String>,
timeout: i32,
) -> Result<serde_json::Value, String> {
let test_url = test_url.unwrap_or("https://cp.cloudflare.com/generate_204".to_string());
let url = format!(
"{}/proxies/{}/delay?url={}&timeout={}",
self.mihomo_server, name, test_url, timeout
);
let response = self.send_request(Method::GET, url, None).await?;
Ok(response)
}
pub async fn get_connections(&self) -> Result<serde_json::Value, String> {
let url = format!("{}/connections", self.mihomo_server);
let response = self.send_request(Method::GET, url, None).await?;
Ok(response)
}
pub async fn delete_connection(&self, id: &str) -> Result<(), String> {
let url = format!("{}/connections/{}", self.mihomo_server, id);
let response = self.send_request(Method::DELETE, url, None).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string())
}
}
}

View File

@@ -1,5 +0,0 @@
#[derive(Clone)]
pub struct MihomoManager {
pub(crate) mihomo_server: String,
pub(crate) client: reqwest::Client,
}

View File

@@ -1,7 +0,0 @@
use reqwest::header::HeaderMap;
#[test]
fn test_mihomo_manager_init() {
let _ = mihomo_api::MihomoManager::new("url".into(), HeaderMap::new());
assert_eq!(true, true);
}