fix: resolve speed test functionality issue after IPC migration #4221, #4218 (#4228)

* chore(deps): update cargo dependencies

* fix: sysinfo crate use limit features

* fix: update headers-core dependency and kode-bridge version; enhance system monitor status validation

* fix: extend overall_status type in ISystemMonitorOverview to include 'healthy'

* refactor: update URL encoding strategy in IpcManager and cmdGetProxyDelay function

* fix: resolve speed test functionality issue after IPC migration

* fix: resolve speed test functionality issue after IPC migration #4221, #4218

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
Tunglies
2025-07-27 02:07:00 +08:00
committed by GitHub
parent 02e19bb132
commit 4905b44c8a
7 changed files with 72 additions and 94 deletions

View File

@@ -41,6 +41,7 @@
- 改进核心启动/停止/重启后的状态刷新机制 - 改进核心启动/停止/重启后的状态刷新机制
- 修复 `Windows` 安装器删除用户自启问题 - 修复 `Windows` 安装器删除用户自启问题
- 修复 `Windows` 安装器参数使用错误问题 - 修复 `Windows` 安装器参数使用错误问题
- 修复 `IPC` 迁移后测速功能异常
### 🔧 技术改进 ### 🔧 技术改进

75
src-tauri/Cargo.lock generated
View File

@@ -888,21 +888,11 @@ dependencies = [
[[package]] [[package]]
name = "bzip2" name = "bzip2"
version = "0.5.2" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff"
dependencies = [ dependencies = [
"bzip2-sys", "libbz2-rs-sys",
]
[[package]]
name = "bzip2-sys"
version = "0.1.13+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
dependencies = [
"cc",
"pkg-config",
] ]
[[package]] [[package]]
@@ -2833,28 +2823,13 @@ checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bytes", "bytes",
"headers-core 0.2.0", "headers-core",
"http 0.2.12", "http 0.2.12",
"httpdate", "httpdate",
"mime", "mime",
"sha1", "sha1",
] ]
[[package]]
name = "headers"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
dependencies = [
"base64 0.22.1",
"bytes",
"headers-core 0.3.0",
"http 1.3.1",
"httpdate",
"mime",
"sha1",
]
[[package]] [[package]]
name = "headers-core" name = "headers-core"
version = "0.2.0" version = "0.2.0"
@@ -2864,15 +2839,6 @@ dependencies = [
"http 0.2.12", "http 0.2.12",
] ]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http 1.3.1",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@@ -3745,22 +3711,18 @@ dependencies = [
[[package]] [[package]]
name = "kode-bridge" name = "kode-bridge"
version = "0.1.5" version = "0.1.6-rc"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "971cfb2bdf5db3721fc822240b4e6e05b5d3aa8c85eb5f7ad4dc25ed0a3ad7e0" checksum = "11bf66a2690fdac4a30e3e60c5bb7e4479db318f2cd6eb95a353acf79d35855a"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures", "futures",
"headers 0.4.1",
"http 1.3.1", "http 1.3.1",
"http-body 1.0.1",
"http-body-util",
"httparse", "httparse",
"hyper 1.6.0",
"interprocess", "interprocess",
"once_cell",
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.12", "thiserror 2.0.12",
@@ -3819,6 +3781,12 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "libbz2-rs-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775bf80d5878ab7c2b1080b5351a48b2f737d9f6f8b383574eebcc22be0dfccb"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.174" version = "0.2.174"
@@ -5381,6 +5349,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppmd-rust"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.21" version = "0.2.21"
@@ -6940,9 +6914,9 @@ dependencies = [
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.35.2" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
dependencies = [ dependencies = [
"libc", "libc",
"memchr", "memchr",
@@ -8549,7 +8523,7 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"headers 0.3.9", "headers",
"http 0.2.12", "http 0.2.12",
"hyper 0.14.32", "hyper 0.14.32",
"log", "log",
@@ -9839,9 +9813,9 @@ dependencies = [
[[package]] [[package]]
name = "zip" name = "zip"
version = "4.2.0" version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ab361742de920c5535880f89bbd611ee62002bf11341d16a5f057bb8ba6899" checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b"
dependencies = [ dependencies = [
"aes", "aes",
"arbitrary", "arbitrary",
@@ -9856,6 +9830,7 @@ dependencies = [
"liblzma", "liblzma",
"memchr", "memchr",
"pbkdf2", "pbkdf2",
"ppmd-rust",
"sha1", "sha1",
"time", "time",
"zeroize", "zeroize",

View File

@@ -13,7 +13,7 @@ build = "build.rs"
identifier = "io.github.clash-verge-rev.clash-verge-rev" identifier = "io.github.clash-verge-rev.clash-verge-rev"
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.3.0", features = [] } tauri-build = { version = "2.3.1", features = [] }
[dependencies] [dependencies]
warp = "0.3.7" warp = "0.3.7"
@@ -25,9 +25,9 @@ dunce = "1.0.5"
log4rs = "1.3.0" log4rs = "1.3.0"
nanoid = "0.4" nanoid = "0.4"
chrono = "0.4.41" chrono = "0.4.41"
sysinfo = "=0.35.2" sysinfo = { version = "0.36.1", features = ["network", "system"] }
boa_engine = "0.20.0" boa_engine = "0.20.0"
serde_json = "1.0.140" serde_json = "1.0.141"
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
once_cell = "1.21.3" once_cell = "1.21.3"
lazy_static = "1.5.0" lazy_static = "1.5.0"
@@ -47,23 +47,23 @@ reqwest = { version = "0.12.22", features = ["json", "rustls-tls", "cookies"] }
regex = "1.11.1" regex = "1.11.1"
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" } sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
image = "0.25.6" image = "0.25.6"
tauri = { version = "2.6.2", features = [ tauri = { version = "2.7.0", features = [
"protocol-asset", "protocol-asset",
"devtools", "devtools",
"tray-icon", "tray-icon",
"image-ico", "image-ico",
"image-png", "image-png",
] } ] }
network-interface = { version = "2.0.1", features = ["serde"] } network-interface = { version = "2.0.2", features = ["serde"] }
tauri-plugin-shell = "2.3.0" tauri-plugin-shell = "2.3.0"
tauri-plugin-dialog = "2.3.0" tauri-plugin-dialog = "2.3.1"
tauri-plugin-fs = "2.4.0" tauri-plugin-fs = "2.4.1"
tauri-plugin-process = "2.3.0" tauri-plugin-process = "2.3.0"
tauri-plugin-clipboard-manager = "2.3.0" tauri-plugin-clipboard-manager = "2.3.0"
tauri-plugin-deep-link = "2.4.0" tauri-plugin-deep-link = "2.4.1"
tauri-plugin-devtools = "2.0.0" tauri-plugin-devtools = "2.0.0"
tauri-plugin-window-state = "2.3.0" tauri-plugin-window-state = "2.4.0"
zip = "=4.2.0" zip = "4.3.0"
reqwest_dav = "0.2.1" reqwest_dav = "0.2.1"
aes-gcm = { version = "0.10.3", features = ["std"] } aes-gcm = { version = "0.10.3", features = ["std"] }
base64 = "0.22.1" base64 = "0.22.1"
@@ -77,7 +77,7 @@ hmac = "0.12.1"
sha2 = "0.10.9" sha2 = "0.10.9"
hex = "0.4.3" hex = "0.4.3"
scopeguard = "1.2.0" scopeguard = "1.2.0"
kode-bridge = "0.1.5" kode-bridge = "0.1.6-rc"
dashmap = "6.1.0" dashmap = "6.1.0"
tauri-plugin-notification = "2.3.0" tauri-plugin-notification = "2.3.0"

View File

@@ -2,9 +2,18 @@ use kode_bridge::{
errors::{AnyError, AnyResult}, errors::{AnyError, AnyResult},
IpcHttpClient, LegacyResponse, IpcHttpClient, LegacyResponse,
}; };
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
use std::sync::OnceLock; use std::sync::OnceLock;
// 定义用于URL路径的编码集合只编码真正必要的字符
const URL_PATH_ENCODE_SET: &AsciiSet = &CONTROLS
.add(b' ') // 空格
.add(b'/') // 斜杠
.add(b'?') // 问号
.add(b'#') // 井号
.add(b'&') // 和号
.add(b'%'); // 百分号
use crate::{ use crate::{
logging, logging,
utils::{dirs::ipc_path, logging::Type}, utils::{dirs::ipc_path, logging::Type},
@@ -108,7 +117,7 @@ impl IpcManager {
} }
pub async fn delete_connection(&self, id: &str) -> AnyResult<()> { pub async fn delete_connection(&self, id: &str) -> AnyResult<()> {
let encoded_id = utf8_percent_encode(id, NON_ALPHANUMERIC).to_string(); let encoded_id = utf8_percent_encode(id, URL_PATH_ENCODE_SET).to_string();
let url = format!("/connections/{encoded_id}"); let url = format!("/connections/{encoded_id}");
let response = self.send_request("DELETE", &url, None).await?; let response = self.send_request("DELETE", &url, None).await?;
if response["code"] == 204 { if response["code"] == 204 {
@@ -176,11 +185,13 @@ impl IpcManager {
) -> AnyResult<serde_json::Value> { ) -> AnyResult<serde_json::Value> {
let test_url = let test_url =
test_url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string()); test_url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string());
let encoded_name = utf8_percent_encode(name, NON_ALPHANUMERIC).to_string();
let encoded_test_url = utf8_percent_encode(&test_url, NON_ALPHANUMERIC).to_string(); let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string();
let url = format!("/proxies/{encoded_name}/delay?url={encoded_test_url}&timeout={timeout}"); // 测速URL不再编码直接传递
let response = self.send_request("GET", &url, None).await?; let url = format!("/proxies/{encoded_name}/delay?url={test_url}&timeout={timeout}");
Ok(response)
let response = self.send_request("GET", &url, None).await;
response
} }
// 版本和配置相关 // 版本和配置相关
@@ -236,7 +247,7 @@ impl IpcManager {
} }
pub async fn update_rule_provider(&self, name: &str) -> AnyResult<()> { pub async fn update_rule_provider(&self, name: &str) -> AnyResult<()> {
let encoded_name = utf8_percent_encode(name, NON_ALPHANUMERIC).to_string(); let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string();
let url = format!("/providers/rules/{encoded_name}"); let url = format!("/providers/rules/{encoded_name}");
let response = self.send_request("PUT", &url, None).await?; let response = self.send_request("PUT", &url, None).await?;
if response["code"] == 204 { if response["code"] == 204 {
@@ -254,7 +265,7 @@ impl IpcManager {
// 代理相关 // 代理相关
pub async fn update_proxy(&self, group: &str, proxy: &str) -> AnyResult<()> { pub async fn update_proxy(&self, group: &str, proxy: &str) -> AnyResult<()> {
// 使用 percent-encoding 进行正确的 URL 编码 // 使用 percent-encoding 进行正确的 URL 编码
let encoded_group = utf8_percent_encode(group, NON_ALPHANUMERIC).to_string(); let encoded_group = utf8_percent_encode(group, URL_PATH_ENCODE_SET).to_string();
let url = format!("/proxies/{encoded_group}"); let url = format!("/proxies/{encoded_group}");
let payload = serde_json::json!({ let payload = serde_json::json!({
"name": proxy "name": proxy
@@ -299,7 +310,7 @@ impl IpcManager {
} }
pub async fn proxy_provider_health_check(&self, name: &str) -> AnyResult<()> { pub async fn proxy_provider_health_check(&self, name: &str) -> AnyResult<()> {
let encoded_name = utf8_percent_encode(name, NON_ALPHANUMERIC).to_string(); let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string();
let url = format!("/providers/proxies/{encoded_name}/healthcheck"); let url = format!("/providers/proxies/{encoded_name}/healthcheck");
let response = self.send_request("GET", &url, None).await?; let response = self.send_request("GET", &url, None).await?;
if response["code"] == 204 { if response["code"] == 204 {
@@ -315,7 +326,7 @@ impl IpcManager {
} }
pub async fn update_proxy_provider(&self, name: &str) -> AnyResult<()> { pub async fn update_proxy_provider(&self, name: &str) -> AnyResult<()> {
let encoded_name = utf8_percent_encode(name, NON_ALPHANUMERIC).to_string(); let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string();
let url = format!("/providers/proxies/{encoded_name}"); let url = format!("/providers/proxies/{encoded_name}");
let response = self.send_request("PUT", &url, None).await?; let response = self.send_request("PUT", &url, None).await?;
if response["code"] == 204 { if response["code"] == 204 {
@@ -338,11 +349,13 @@ impl IpcManager {
timeout: i32, timeout: i32,
) -> AnyResult<serde_json::Value> { ) -> AnyResult<serde_json::Value> {
let test_url = url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string()); let test_url = url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string());
let encoded_group_name = utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string();
let encoded_test_url = utf8_percent_encode(&test_url, NON_ALPHANUMERIC).to_string(); let encoded_group_name = utf8_percent_encode(group_name, URL_PATH_ENCODE_SET).to_string();
let url = // 测速URL不再编码直接传递
format!("/group/{encoded_group_name}/delay?url={encoded_test_url}&timeout={timeout}"); let url = format!("/group/{encoded_group_name}/delay?url={test_url}&timeout={timeout}");
self.send_request("GET", &url, None).await
let response = self.send_request("GET", &url, None).await;
response
} }
// 调试相关 // 调试相关

View File

@@ -512,12 +512,9 @@ export async function cmdGetProxyDelay(
) { ) {
// 确保URL不为空 // 确保URL不为空
const testUrl = url || "https://cp.cloudflare.com/generate_204"; const testUrl = url || "https://cp.cloudflare.com/generate_204";
console.log(
`[API] 调用延迟测试API代理: ${name}, 超时: ${timeout}ms, URL: ${testUrl}`,
);
try { try {
name = encodeURIComponent(name); // 不再在前端编码代理名称,由后端统一处理编码
const result = await invoke<{ delay: number }>( const result = await invoke<{ delay: number }>(
"clash_api_get_proxy_delay", "clash_api_get_proxy_delay",
{ {
@@ -529,20 +526,12 @@ export async function cmdGetProxyDelay(
// 验证返回结果中是否有delay字段并且值是一个有效的数字 // 验证返回结果中是否有delay字段并且值是一个有效的数字
if (result && typeof result.delay === "number") { if (result && typeof result.delay === "number") {
console.log(
`[API] 延迟测试API调用成功代理: ${name}, 延迟: ${result.delay}ms`,
);
return result; return result;
} else { } else {
console.error(
`[API] 延迟测试API返回无效结果代理: ${name}, 结果:`,
result,
);
// 返回一个有效的结果对象,但标记为超时 // 返回一个有效的结果对象,但标记为超时
return { delay: 1e6 }; return { delay: 1e6 };
} }
} catch (error) { } catch (error) {
console.error(`[API] 延迟测试API调用失败代理: ${name}`, error);
// 返回一个有效的结果对象,但标记为错误 // 返回一个有效的结果对象,但标记为错误
return { delay: 1e6 }; return { delay: 1e6 };
} }

View File

@@ -181,7 +181,7 @@ interface ISystemMonitorOverview {
}; };
is_fresh: boolean; is_fresh: boolean;
}; };
overall_status: "active" | "inactive" | "error" | "unknown"; overall_status: "active" | "inactive" | "error" | "unknown" | "healthy";
} }
// 类型安全的数据验证器 // 类型安全的数据验证器

View File

@@ -138,7 +138,7 @@ export class SystemMonitorValidator implements ISystemMonitorOverviewValidator {
private validateOverallStatus(status: any): boolean { private validateOverallStatus(status: any): boolean {
return ( return (
typeof status === "string" && typeof status === "string" &&
["active", "inactive", "error", "unknown"].includes(status) ["active", "inactive", "error", "unknown", "healthy"].includes(status)
); );
} }
@@ -190,12 +190,12 @@ export class SystemMonitorValidator implements ISystemMonitorOverviewValidator {
private sanitizeOverallStatus( private sanitizeOverallStatus(
status: any, status: any,
): "active" | "inactive" | "error" | "unknown" { ): "active" | "inactive" | "error" | "unknown" | "healthy" {
if ( if (
typeof status === "string" && typeof status === "string" &&
["active", "inactive", "error", "unknown"].includes(status) ["active", "inactive", "error", "unknown", "healthy"].includes(status)
) { ) {
return status as "active" | "inactive" | "error" | "unknown"; return status as "active" | "inactive" | "error" | "unknown" | "healthy";
} }
return "unknown"; return "unknown";
} }