diff --git a/Cargo.lock b/Cargo.lock index 88aa831..962f2dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,7 +295,6 @@ dependencies = [ "serde_json", "tempfile", "thiserror 2.0.18", - "ureq", ] [[package]] @@ -309,6 +308,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-single-instance", + "verhub-sdk", ] [[package]] @@ -491,6 +491,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chrono" version = "0.4.45" @@ -528,29 +534,10 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "percent-encoding", "time", "version_check", ] -[[package]] -name = "cookie_store" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" -dependencies = [ - "cookie", - "document-features", - "idna", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -576,7 +563,7 @@ dependencies = [ "bitflags 2.13.0", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -825,15 +812,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - [[package]] name = "dom_query" version = "0.27.0" @@ -1048,6 +1026,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1055,7 +1042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1069,6 +1056,12 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1564,6 +1557,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -1955,12 +1964,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - [[package]] name = "lock_api" version = "0.4.14" @@ -2050,6 +2053,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2080,6 +2100,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -2111,7 +2143,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", "syn 2.0.118", @@ -2240,6 +2272,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.0", "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -2318,6 +2351,49 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2334,6 +2410,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_info" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "windows-sys 0.61.2", +] + [[package]] name = "pango" version = "0.18.3" @@ -2713,50 +2804,72 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.13.4" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", "futures-core", - "futures-util", "http", "http-body", "http-body-util", "hyper", + "hyper-tls", "hyper-util", "js-sys", "log", + "native-tls", "percent-encoding", "pin-project-lite", + "rustls-pki-types", "serde", "serde_json", + "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-util", + "tokio-native-tls", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", ] [[package]] -name = "ring" -version = "0.17.14" +name = "reqwest" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", ] [[package]] @@ -2787,21 +2900,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.23.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - [[package]] name = "rustls-pki-types" version = "1.15.0" @@ -2812,21 +2910,16 @@ dependencies = [ ] [[package]] -name = "rustls-webpki" -version = "0.103.13" +name = "rustversion" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] -name = "rustversion" +name = "ryu" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -2837,6 +2930,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -2894,6 +2996,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.36.1" @@ -3018,6 +3143,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.21.0" @@ -3226,12 +3363,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "swift-rs" version = "1.0.7" @@ -3384,7 +3515,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.13.4", "serde", "serde_json", "serde_repr", @@ -3713,6 +3844,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -3941,7 +4082,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4026,44 +4167,6 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "ureq" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" -dependencies = [ - "base64 0.22.1", - "cookie_store", - "flate2", - "log", - "percent-encoding", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "ureq-proto", - "utf8-zero", - "webpki-roots", -] - -[[package]] -name = "ureq-proto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" -dependencies = [ - "base64 0.22.1", - "http", - "httparse", - "log", -] - [[package]] name = "url" version = "2.5.8" @@ -4089,12 +4192,6 @@ dependencies = [ "url", ] -[[package]] -name = "utf8-zero" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -4113,6 +4210,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "verhub-sdk" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61704ff45c9dd4e311e21b254be2405e8e3c9a81a5c1ad0ef8e8bb123fe65115" +dependencies = [ + "log", + "os_info", + "percent-encoding", + "reqwest 0.12.28", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", +] + [[package]] name = "version-compare" version = "0.2.1" @@ -4313,15 +4432,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "webpki-roots" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webview2-com" version = "0.38.2" @@ -4594,15 +4704,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 437eb12..cdfa8d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ regex = "1.11.1" tokio = { version = "1.52.3", features = ["rt-multi-thread", "macros", "net", "io-util", "sync", "time"] } windows = "0.62.2" tempfile = "3.27.0" -ureq = { version = "3.1.2", default-features = false, features = ["json", "rustls", "gzip"] } +verhub-sdk = { version = "0.2.5", default-features = false, features = ["native-tls"] } [profile.release] opt-level = "z" diff --git a/README.en.md b/README.en.md index a7007db..04f54e9 100644 --- a/README.en.md +++ b/README.en.md @@ -10,7 +10,7 @@ Github Release Version Github Repo License -GitHub Actions Workflow Status +GitHub Actions Workflow Status Supported Platform

diff --git a/README.md b/README.md index 8f65847..303d479 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Github Release Version Github Repo License -GitHub Actions Workflow Status +GitHub Actions Workflow Status Supported Platform

diff --git a/README.zh-TW.md b/README.zh-TW.md index 5bb00a4..448793e 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -10,7 +10,7 @@ Github Release Version Github Repo License -GitHub Actions Workflow Status +GitHub Actions Workflow Status Supported Platform

diff --git a/apps/config/src-tauri/Cargo.toml b/apps/config/src-tauri/Cargo.toml index 7b5bd84..6069384 100644 --- a/apps/config/src-tauri/Cargo.toml +++ b/apps/config/src-tauri/Cargo.toml @@ -20,3 +20,4 @@ serde_json = { workspace = true } bosskey-common = { path = "../../../crates/common" } bosskey-core = { path = "../../../crates/core" } tauri-plugin-single-instance = "2.4.2" +verhub-sdk = { workspace = true } diff --git a/apps/config/src-tauri/src/lib.rs b/apps/config/src-tauri/src/lib.rs index 3f40c32..f8116fc 100644 --- a/apps/config/src-tauri/src/lib.rs +++ b/apps/config/src-tauri/src/lib.rs @@ -4,11 +4,12 @@ use std::time::Duration; use bosskey_common::Config; use bosskey_common::ipc::{Command, PipeClient, Response}; use bosskey_common::model::WindowInfo; -use bosskey_common::verhub; use bosskey_core::i18n::{self, Msg}; use serde::Serialize; use tauri::{Emitter, Manager}; +mod verhub; + const CORE_EXE: &str = "Boss Key.exe"; fn exe_dir() -> PathBuf { @@ -387,10 +388,10 @@ fn app_info() -> AppInfo { } } -/// 用系统默认浏览器打开外部链接。仅放行 http/https。 +/// 用系统默认浏览器打开外部链接。仅放行 http/https/mailto(与前端 markdown 白名单一致)。 #[tauri::command] async fn open_external(url: String) -> Result<(), String> { - if !url.starts_with("https://") && !url.starts_with("http://") { + if !url.starts_with("https://") && !url.starts_with("http://") && !url.starts_with("mailto:") { return Err(i18n::t(Msg::ErrUrlSchemeNotAllowed).to_string()); } blocking(move || bosskey_core::shell::open(&url)).await @@ -399,16 +400,17 @@ async fn open_external(url: String) -> Result<(), String> { /// 检查更新。`required=true` 即强制更新,界面须阻断使用。 #[tauri::command] async fn verhub_check_update(include_preview: bool) -> Result { - blocking(move || { - verhub::check_update(env!("CARGO_PKG_VERSION"), include_preview).map_err(|e| e.to_string()) - }) - .await + verhub::check_update(env!("CARGO_PKG_VERSION"), include_preview) + .await + .map_err(|e| e.to_string()) } /// 公告列表(本平台可见的,从新到旧)。 #[tauri::command] async fn verhub_announcements(limit: u32) -> Result, String> { - blocking(move || verhub::announcements(limit.clamp(1, 50)).map_err(|e| e.to_string())).await + verhub::announcements(limit.clamp(1, 50)) + .await + .map_err(|e| e.to_string()) } /// `contact` 可空——留了才好回复用户。 @@ -421,38 +423,26 @@ async fn verhub_submit_feedback( if content.trim().is_empty() { return Err(i18n::t(Msg::ErrFeedbackEmpty).to_string()); } - blocking(move || { - let feedback = verhub::Feedback { - rating: rating.map(|r| r.clamp(1, 5)), - content, - platform: verhub::PLATFORM, - custom_data: serde_json::json!({ - "app_version": env!("CARGO_PKG_VERSION"), - "os": os_description(), - "contact": contact.trim(), - }), - }; - verhub::submit_feedback(&feedback).map_err(|e| e.to_string()) - }) - .await + let custom_data = serde_json::json!({ + "app_version": env!("CARGO_PKG_VERSION"), + "os": os_description(), + "contact": contact.trim(), + }); + verhub::submit_feedback(content, rating.map(|r| r.clamp(1, 5)), custom_data) + .await + .map_err(|e| e.to_string()) } /// 上报一段日志 #[tauri::command] async fn verhub_upload_log(content: String) -> Result<(), String> { - blocking(move || { - verhub::upload_log( - verhub::LogLevel::Error, - &content, - serde_json::json!({ - "app_version": env!("CARGO_PKG_VERSION"), - "os": os_description(), - - }), - ) + let device_info = serde_json::json!({ + "app_version": env!("CARGO_PKG_VERSION"), + "os": os_description(), + }); + verhub::upload_log(&content, device_info) + .await .map_err(|e| e.to_string()) - }) - .await } /// 最新日志文件的末尾若干行。 diff --git a/apps/config/src-tauri/src/verhub.rs b/apps/config/src-tauri/src/verhub.rs new file mode 100644 index 0000000..5f8e9b2 --- /dev/null +++ b/apps/config/src-tauri/src/verhub.rs @@ -0,0 +1,235 @@ +//! Verhub 客户端:版本 / 公告 / 反馈 / 日志,基于官方 verhub-sdk。 +//! +//! 只用公开端点(无需凭据)。HTTP 由 SDK 完成;本模块把 SDK 的响应类型映射成 +//! 前端 IPC 契约所需的可序列化 DTO,字段名保持不变。 + +use std::time::Duration; + +use serde::Serialize; +use verhub_sdk::VerhubClient; +use verhub_sdk::models::{ + AnnouncementItem, CheckUpdateInput, CreateFeedbackInput, JsonObject, ListAnnouncementsOptions, + LogLevel, Platform, UploadLogInput, VersionDownloadLink, VersionItem, +}; + +/// Verhub 基础路径。 +pub const BASE_URL: &str = "https://verhub.hanloth.cn/api/v1"; +pub const PROJECT_KEY: &str = "ivanhanloth-boss-key"; +/// 客户端平台(本程序只发行 Windows 版)。 +pub const PLATFORM: Platform = Platform::Windows; + +const TIMEOUT: Duration = Duration::from_secs(10); +const LOG_CONTENT_MAX: usize = 4096; + +type Result = verhub_sdk::Result; + +/// 构造公开接口客户端;User-Agent 追加 `BossKey/{版本}` 以便服务端识别。 +fn client() -> Result { + VerhubClient::builder(BASE_URL) + .project_key(PROJECT_KEY) + .platform(PLATFORM) + .timeout(TIMEOUT) + .app_identifier(concat!("BossKey/", env!("CARGO_PKG_VERSION"))) + .build() +} + +/// 把 `serde_json::Value` 收敛为 JSON 对象;非对象一律丢弃。 +fn json_object(value: serde_json::Value) -> Option { + match value { + serde_json::Value::Object(map) => Some(map), + _ => None, + } +} + +#[derive(Debug, Clone, Default, Serialize)] +pub struct DownloadLink { + pub url: String, + pub name: Option, + pub platform: Option, +} + +#[derive(Debug, Clone, Default, Serialize)] +pub struct Version { + pub id: String, + pub version: String, + pub comparable_version: String, + pub title: Option, + /// 更新说明(Markdown)。 + pub content: Option, + pub download_url: Option, + pub download_links: Vec, + pub forced: bool, + pub is_latest: bool, + pub is_preview: bool, + pub is_milestone: bool, + pub is_deprecated: bool, + pub published_at: i64, +} + +#[derive(Debug, Clone, Default, Serialize)] +pub struct CheckUpdate { + pub should_update: bool, + /// 强制更新。 + pub required: bool, + pub reason_codes: Vec, + pub current_version: Option, + pub latest_version: Option, + /// 该升到哪个版本(可能是里程碑版本,而非最新版)。 + pub target_version: Option, +} + +#[derive(Debug, Clone, Default, Serialize)] +pub struct Announcement { + pub id: String, + pub title: String, + pub content: String, + pub is_pinned: bool, + pub is_hidden: bool, + pub author: Option, + pub published_at: i64, +} + +fn map_link(link: VersionDownloadLink) -> DownloadLink { + DownloadLink { + url: link.url, + name: link.name, + platform: link.platform, + } +} + +fn map_version(version: VersionItem) -> Version { + Version { + id: version.id, + version: version.version, + comparable_version: version.comparable_version, + title: version.title, + content: version.content, + download_url: version.download_url, + download_links: version.download_links.into_iter().map(map_link).collect(), + forced: version.forced, + is_latest: version.is_latest, + is_preview: version.is_preview, + is_milestone: version.is_milestone, + is_deprecated: version.is_deprecated, + published_at: version.published_at, + } +} + +fn map_announcement(item: AnnouncementItem) -> Announcement { + Announcement { + id: item.id, + title: item.title, + content: item.content, + is_pinned: item.is_pinned, + is_hidden: item.is_hidden, + author: item.author, + published_at: item.published_at, + } +} + +/// 检查更新:把当前版本发给 Verhub,由服务端判断是否需要更新、是否强制。 +pub async fn check_update(current_version: &str, include_preview: bool) -> Result { + let input = CheckUpdateInput { + current_version: Some(current_version.to_string()), + current_comparable_version: Some(current_version.to_string()), + include_preview: Some(include_preview), + }; + let resp = client()?.public().check_update(&input).await?; + Ok(CheckUpdate { + should_update: resp.should_update, + required: resp.required, + reason_codes: resp.reason_codes, + current_version: resp.current_version, + latest_version: Some(map_version(resp.latest_version)), + target_version: resp.target_version.map(map_version), + }) +} + +/// 公告列表(只要本平台 / 全平台的),从新到旧,并滤掉隐藏公告。 +pub async fn announcements(limit: u32) -> Result> { + let options = ListAnnouncementsOptions { + limit: Some(limit), + platform: Some(PLATFORM), + ..Default::default() + }; + let resp = client()?.public().list_announcements(&options).await?; + Ok(resp + .data + .into_iter() + .filter(|a| !a.is_hidden) + .map(map_announcement) + .collect()) +} + +/// 提交客户端反馈。`rating` 为 1..=5;`custom_data` 携带附加信息。 +pub async fn submit_feedback( + content: String, + rating: Option, + custom_data: serde_json::Value, +) -> Result<()> { + let input = CreateFeedbackInput { + content, + rating, + platform: Some(PLATFORM), + custom_data: json_object(custom_data), + ..Default::default() + }; + client()?.public().create_feedback(&input).await?; + Ok(()) +} + +/// 上报一条错误日志;内容超长会被截断到 Verhub 的上限内。 +pub async fn upload_log(content: &str, device_info: serde_json::Value) -> Result<()> { + let input = UploadLogInput { + level: LogLevel::Error.into(), + content: truncate_log(content), + device_info: json_object(device_info), + custom_data: None, + }; + client()?.public().upload_log(&input).await?; + Ok(()) +} + +/// 截到上限以内,按字符边界切以避免切碎多字节字符。 +fn truncate_log(content: &str) -> String { + if content.len() <= LOG_CONTENT_MAX { + return content.to_string(); + } + const MARK: &str = "…(日志过长,已截断前半部分)\n"; + let budget = LOG_CONTENT_MAX - MARK.len(); + // 保留末尾(出错现场)。 + let start = content.len() - budget; + let start = (start..content.len()) + .find(|i| content.is_char_boundary(*i)) + .unwrap_or(content.len()); + format!("{MARK}{}", &content[start..]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn truncate_keeps_tail_within_limit() { + let long = "错误".repeat(4000); // 远超 4096 字节 + let out = truncate_log(&long); + assert!( + out.len() <= LOG_CONTENT_MAX, + "截断后仍超上限: {}", + out.len() + ); + assert!(out.contains("已截断")); + assert!(out.ends_with('误')); // 保的是末尾(出错现场) + } + + #[test] + fn truncate_leaves_short_content_alone() { + assert_eq!(truncate_log("崩了"), "崩了"); + } + + #[test] + fn log_level_maps_to_verhub_numbers() { + assert_eq!(u8::from(LogLevel::Debug), 0); + assert_eq!(u8::from(LogLevel::Error), 3); + } +} diff --git a/apps/config/ui/src/components/AboutPanel.svelte b/apps/config/ui/src/components/AboutPanel.svelte index 1e54f07..cf12f5c 100644 --- a/apps/config/ui/src/components/AboutPanel.svelte +++ b/apps/config/ui/src/components/AboutPanel.svelte @@ -7,6 +7,7 @@ import IconRefreshCw from "~icons/lucide/refresh-cw"; import IconStar from "~icons/lucide/star"; import Card from "./Card.svelte"; + import Markdown from "./Markdown.svelte"; import SettingRow from "./SettingRow.svelte"; import Toggle from "./Toggle.svelte"; import { @@ -129,7 +130,7 @@ {#if a.is_pinned}{t("about.pinned")}{/if} {formatTime(a.published_at)} -
{a.content}
+
{/each} @@ -164,7 +165,7 @@