From b888c7729e47e03adae73b376acef6db4db7f8cd Mon Sep 17 00:00:00 2001 From: huzibaca Date: Fri, 13 Sep 2024 03:21:55 +0800 Subject: [PATCH] refactor: url scheme implementation --- src-tauri/Cargo.lock | 100 ++++++++++++++++++++++++++-- src-tauri/Cargo.toml | 5 +- src-tauri/Info.plist | 17 ----- src-tauri/capabilities/desktop.json | 6 +- src-tauri/src/cmds.rs | 8 ++- src-tauri/src/main.rs | 18 ++++- src-tauri/src/utils/resolve.rs | 98 +++++++++++++++++---------- src-tauri/tauri.conf.json | 5 ++ src/services/cmds.ts | 1 + 9 files changed, 194 insertions(+), 64 deletions(-) mode change 100644 => 100755 src-tauri/Cargo.toml delete mode 100644 src-tauri/Info.plist mode change 100644 => 100755 src-tauri/capabilities/desktop.json mode change 100644 => 100755 src-tauri/src/main.rs mode change 100644 => 100755 src-tauri/tauri.conf.json diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 33d3ecc..bfcc5e9 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -933,6 +933,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-clipboard-manager", + "tauri-plugin-deep-link", "tauri-plugin-devtools", "tauri-plugin-dialog", "tauri-plugin-fs", @@ -942,6 +943,7 @@ dependencies = [ "tauri-plugin-shell", "tauri-plugin-updater", "tokio", + "url", "users", "warp", "window-shadows", @@ -1062,6 +1064,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -1565,6 +1587,15 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "dpi" version = "0.1.1" @@ -2627,7 +2658,7 @@ dependencies = [ "httpdate", "itoa 1.0.11", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -4018,6 +4049,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -4944,6 +4985,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -5984,7 +6036,7 @@ dependencies = [ [[package]] name = "tauri-plugin-clipboard-manager" version = "2.0.0-rc.3" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#9291e4d2caa31c883c71e55f2193bd8754d72f03" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#984110a978774712bad4d746ed06134d54debcd0" dependencies = [ "arboard", "image 0.24.9", @@ -5996,6 +6048,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tauri-plugin-deep-link" +version = "2.0.0-rc.4" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#984110a978774712bad4d746ed06134d54debcd0" +dependencies = [ + "dunce", + "log", + "rust-ini", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror", + "url", + "windows-registry", + "windows-result 0.2.0", +] + [[package]] name = "tauri-plugin-devtools" version = "2.0.0-rc.1" @@ -6026,7 +6097,7 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" version = "2.0.0-rc.5" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#9291e4d2caa31c883c71e55f2193bd8754d72f03" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#984110a978774712bad4d746ed06134d54debcd0" dependencies = [ "log", "raw-window-handle 0.6.2", @@ -6043,7 +6114,7 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" version = "2.0.0-rc.3" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#9291e4d2caa31c883c71e55f2193bd8754d72f03" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#984110a978774712bad4d746ed06134d54debcd0" dependencies = [ "anyhow", "dunce", @@ -6078,7 +6149,7 @@ dependencies = [ [[package]] name = "tauri-plugin-notification" version = "2.0.0-rc.4" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#9291e4d2caa31c883c71e55f2193bd8754d72f03" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#984110a978774712bad4d746ed06134d54debcd0" dependencies = [ "log", "notify-rust", @@ -6096,7 +6167,7 @@ dependencies = [ [[package]] name = "tauri-plugin-process" version = "2.0.0-rc.1" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#9291e4d2caa31c883c71e55f2193bd8754d72f03" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#984110a978774712bad4d746ed06134d54debcd0" dependencies = [ "tauri", "tauri-plugin", @@ -6105,7 +6176,7 @@ dependencies = [ [[package]] name = "tauri-plugin-shell" version = "2.0.0-rc.3" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#9291e4d2caa31c883c71e55f2193bd8754d72f03" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#984110a978774712bad4d746ed06134d54debcd0" dependencies = [ "encoding_rs", "log", @@ -6426,6 +6497,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -6800,6 +6880,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "try-lock" version = "0.2.5" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml old mode 100644 new mode 100755 index 73193ba..02d544d --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -51,12 +51,15 @@ tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", bra tauri-plugin-notification = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +tauri-plugin-deep-link = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } tauri-plugin-devtools = "2.0.0-rc" - +url = "2.5.2" [target.'cfg(windows)'.dependencies] runas = "=1.2.0" deelevate = "0.2.0" winreg = "0.52.0" +url = "2.5.2" + [target.'cfg(target_os = "linux")'.dependencies] users = "0.11.0" diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist deleted file mode 100644 index 954d43a..0000000 --- a/src-tauri/Info.plist +++ /dev/null @@ -1,17 +0,0 @@ - - - - -CFBundleURLTypes - - - CFBundleURLName - Clash Verge - CFBundleURLSchemes - - clash - - - - - \ No newline at end of file diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json old mode 100644 new mode 100755 index c4936c0..fbb332b --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -3,5 +3,9 @@ "platforms": ["macOS", "windows", "linux"], "webviews": ["main"], "windows": ["main"], - "permissions": ["global-shortcut:default", "updater:default"] + "permissions": [ + "global-shortcut:default", + "updater:default", + "deep-link:default" + ] } diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index a560fd8..69711c9 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -32,8 +32,12 @@ pub async fn enhance_profiles() -> CmdResult { } #[tauri::command] -pub async fn import_profile(url: String, option: Option) -> CmdResult { - let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?; +pub async fn import_profile( + url: String, + name: Option, + option: Option, +) -> CmdResult { + let item = wrap_err!(PrfItem::from_url(&url, name, None, option).await)?; wrap_err!(Config::profiles().data().append_item(item)) } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs old mode 100644 new mode 100755 index fe0fb9c..ca5cb10 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,7 +10,8 @@ mod enhance; mod feat; mod utils; -use crate::utils::{resolve, server}; +use crate::utils::{resolve, resolve::resolve_scheme, server}; +use tauri::Listener; fn main() -> std::io::Result<()> { // 单例检测 @@ -42,10 +43,25 @@ fn main() -> std::io::Result<()> { .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_deep_link::init()) .setup(|app| { + #[cfg(target_os = "linux")] + { + use tauri_plugin_deep_link::DeepLinkExt; + app.deep_link().register_all()?; + } + + app.listen("deep-link://new-url", |event| { + tauri::async_runtime::spawn(async move { + let payload = event.payload(); + log_err!(resolve_scheme(payload.to_string()).await); + }); + }); + tauri::async_runtime::block_on(async move { resolve::resolve_setup(app).await; }); + Ok(()) }) .invoke_handler(tauri::generate_handler![ diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 5408144..a20379c 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -2,12 +2,15 @@ use crate::cmds::import_profile; use crate::config::IVerge; use crate::utils::error; use crate::{config::Config, core::*, utils::init, utils::server}; -use crate::{log_err, trace_err}; -use anyhow::Result; +use crate::{error as er, log_err, trace_err}; +use anyhow::{bail, Result}; use once_cell::sync::OnceCell; +use percent_encoding::percent_decode_str; use serde_yaml::Mapping; use std::net::TcpListener; use tauri::{App, AppHandle, Manager}; + +use url::Url; //#[cfg(not(target_os = "linux"))] // use window_shadows::set_shadow; use tauri_plugin_notification::NotificationExt; @@ -96,14 +99,6 @@ pub async fn resolve_setup(app: &mut App) { log_err!(handle::Handle::update_systray_part()); log_err!(hotkey::Hotkey::global().init(app.app_handle())); log_err!(timer::Timer::global().init()); - - let argvs: Vec = std::env::args().collect(); - if argvs.len() > 1 { - let param = argvs[1].as_str(); - if param.starts_with("clash:") { - log_err!(resolve_scheme(argvs[1].to_owned()).await); - } - } } /// reset system proxy @@ -240,34 +235,67 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) -> } pub async fn resolve_scheme(param: String) -> Result<()> { - let url = param - .trim_start_matches("clash://install-config/?url=") - .trim_start_matches("clash://install-config?url="); + log::info!("received deep link: {}", param); + let param_str = if param.starts_with("[") && param.len() > 4 { + param + .get(2..param.len() - 2) + .ok_or_else(|| anyhow::anyhow!("Invalid string slice boundaries"))? + } else { + bail!("invalid deep link param: {:?}", param) + }; - let handle = handle::Handle::global(); - let app_handle = handle.app_handle.lock().clone(); - if let Some(app_handle) = app_handle.as_ref() { - match import_profile(url.to_string(), None).await { - Ok(_) => { - app_handle - .notification() - .builder() - .title("Clash Verge") - .body("Import profile success") - .show() - .unwrap(); - } - Err(e) => { - app_handle - .notification() - .builder() - .title("Clash Verge") - .body(format!("Import profile failed: {e}")) - .show() - .unwrap(); - log::error!("Import profile failed: {e}"); + // 解析 URL + let link_parsed = match Url::parse(param_str) { + Ok(url) => url, + Err(e) => { + bail!("failed to parse deep link: {:?}, param: {:?}", e, param); + } + }; + + if link_parsed.scheme() == "clash" || link_parsed.scheme() == "clash-verge" { + let name = link_parsed + .query_pairs() + .find(|(key, _)| key == "name") + .map(|(_, value)| value.into_owned()); + + let encode_url = link_parsed + .query_pairs() + .find(|(key, _)| key == "url") + .map(|(_, value)| value.into_owned()); + + match encode_url { + Some(url) => { + let decoded_url = percent_decode_str(url.as_ref()).decode_utf8_lossy(); + let handle = handle::Handle::global(); + let app_handle = handle.app_handle.lock().clone(); + if let Some(app_handle) = app_handle.as_ref() { + er!(format!("decode_url: {}", decoded_url)); + match import_profile(decoded_url.to_string(), name.clone(), None).await { + Ok(_) => { + app_handle + .notification() + .builder() + .title("Clash Verge") + .body("Import profile success") + .show() + .unwrap(); + } + Err(e) => { + app_handle + .notification() + .builder() + .title("Clash Verge") + .body(format!("Import profile failed: {e}")) + .show() + .unwrap(); + bail!("Import profile failed: {e}"); + } + } + } } + None => bail!("failed to get profile url"), } } + Ok(()) } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json old mode 100644 new mode 100755 index 0876ef1..7d1cc0e --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -34,6 +34,11 @@ "https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json", "https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json" ] + }, + "deep-link": { + "desktop": { + "schemes": ["clash", "clash-verge"] + } } }, "app": { diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 552da39..d757d60 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -63,6 +63,7 @@ export async function saveProfileFile(index: string, fileData: string) { export async function importProfile(url: string) { return invoke("import_profile", { url, + name: null, option: { with_proxy: true }, }); }