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
Unverified
parent 02e19bb132
commit 4905b44c8a
7 changed files with 72 additions and 94 deletions

View File

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

75
src-tauri/Cargo.lock generated
View File

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

View File

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

View File

@@ -2,9 +2,18 @@ use kode_bridge::{
errors::{AnyError, AnyResult},
IpcHttpClient, LegacyResponse,
};
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
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::{
logging,
utils::{dirs::ipc_path, logging::Type},
@@ -108,7 +117,7 @@ impl IpcManager {
}
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 response = self.send_request("DELETE", &url, None).await?;
if response["code"] == 204 {
@@ -176,11 +185,13 @@ impl IpcManager {
) -> AnyResult<serde_json::Value> {
let test_url =
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 url = format!("/proxies/{encoded_name}/delay?url={encoded_test_url}&timeout={timeout}");
let response = self.send_request("GET", &url, None).await?;
Ok(response)
let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string();
// 测速URL不再编码直接传递
let url = format!("/proxies/{encoded_name}/delay?url={test_url}&timeout={timeout}");
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<()> {
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 response = self.send_request("PUT", &url, None).await?;
if response["code"] == 204 {
@@ -254,7 +265,7 @@ impl IpcManager {
// 代理相关
pub async fn update_proxy(&self, group: &str, proxy: &str) -> AnyResult<()> {
// 使用 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 payload = serde_json::json!({
"name": proxy
@@ -299,7 +310,7 @@ impl IpcManager {
}
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 response = self.send_request("GET", &url, None).await?;
if response["code"] == 204 {
@@ -315,7 +326,7 @@ impl IpcManager {
}
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 response = self.send_request("PUT", &url, None).await?;
if response["code"] == 204 {
@@ -338,11 +349,13 @@ impl IpcManager {
timeout: i32,
) -> AnyResult<serde_json::Value> {
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 url =
format!("/group/{encoded_group_name}/delay?url={encoded_test_url}&timeout={timeout}");
self.send_request("GET", &url, None).await
let encoded_group_name = utf8_percent_encode(group_name, URL_PATH_ENCODE_SET).to_string();
// 测速URL不再编码直接传递
let url = format!("/group/{encoded_group_name}/delay?url={test_url}&timeout={timeout}");
let response = self.send_request("GET", &url, None).await;
response
}
// 调试相关

View File

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

View File

@@ -181,7 +181,7 @@ interface ISystemMonitorOverview {
};
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 {
return (
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(
status: any,
): "active" | "inactive" | "error" | "unknown" {
): "active" | "inactive" | "error" | "unknown" | "healthy" {
if (
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";
}