From 52732e12ec54da8c2e946a1784bf54c8675a528a Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Thu, 12 Feb 2026 14:38:53 -0800 Subject: [PATCH] Fix license activation and plugin requests ignoring proxy settings (#393) Co-authored-by: Claude Opus 4.6 ( app_handle: AppHandle, query: &str, ) -> Result { - let http_client = yaak_api_client(&app_handle)?; + let app_version = app_handle.package_info().version.to_string(); + let http_client = yaak_api_client(&app_version)?; Ok(search_plugins(&http_client, query).await?) } @@ -147,7 +149,8 @@ pub async fn cmd_plugins_install( version: Option, ) -> Result<()> { let plugin_manager = Arc::new((*window.state::()).clone()); - let http_client = yaak_api_client(window.app_handle())?; + let app_version = window.app_handle().package_info().version.to_string(); + let http_client = yaak_api_client(&app_version)?; let query_manager = window.state::(); let plugin_context = window.plugin_context(); download_and_install( @@ -177,7 +180,8 @@ pub async fn cmd_plugins_uninstall( pub async fn cmd_plugins_updates( app_handle: AppHandle, ) -> Result { - let http_client = yaak_api_client(&app_handle)?; + let app_version = app_handle.package_info().version.to_string(); + let http_client = yaak_api_client(&app_version)?; let plugins = app_handle.db().list_plugins()?; Ok(check_plugin_updates(&http_client, plugins).await?) } @@ -186,7 +190,8 @@ pub async fn cmd_plugins_updates( pub async fn cmd_plugins_update_all( window: WebviewWindow, ) -> Result> { - let http_client = yaak_api_client(window.app_handle())?; + let app_version = window.app_handle().package_info().version.to_string(); + let http_client = yaak_api_client(&app_version)?; let plugins = window.db().list_plugins()?; // Get list of available updates (already filtered to only registry plugins) diff --git a/crates-tauri/yaak-app/src/updates.rs b/crates-tauri/yaak-app/src/updates.rs index b8f64a5a..c068a813 100644 --- a/crates-tauri/yaak-app/src/updates.rs +++ b/crates-tauri/yaak-app/src/updates.rs @@ -15,6 +15,9 @@ use ts_rs::TS; use yaak_models::util::generate_id; use yaak_plugins::manager::PluginManager; +use url::Url; +use yaak_api::get_system_proxy_url; + use crate::error::Error::GenericError; use crate::is_dev; @@ -87,8 +90,13 @@ impl YaakUpdater { info!("Checking for updates mode={} autodl={}", mode, auto_download); let w = window.clone(); - let update_check_result = w - .updater_builder() + let mut updater_builder = w.updater_builder(); + if let Some(proxy_url) = get_system_proxy_url() { + if let Ok(url) = Url::parse(&proxy_url) { + updater_builder = updater_builder.proxy(url); + } + } + let update_check_result = updater_builder .on_before_exit(move || { // Kill plugin manager before exit or NSIS installer will fail to replace sidecar // while it's running. diff --git a/crates-tauri/yaak-app/src/uri_scheme.rs b/crates-tauri/yaak-app/src/uri_scheme.rs index 52119241..d186bbfc 100644 --- a/crates-tauri/yaak-app/src/uri_scheme.rs +++ b/crates-tauri/yaak-app/src/uri_scheme.rs @@ -12,7 +12,7 @@ use yaak_models::util::generate_id; use yaak_plugins::events::{Color, ShowToastRequest}; use yaak_plugins::install::download_and_install; use yaak_plugins::manager::PluginManager; -use yaak_tauri_utils::api_client::yaak_api_client; +use yaak_api::yaak_api_client; pub(crate) async fn handle_deep_link( app_handle: &AppHandle, @@ -46,7 +46,8 @@ pub(crate) async fn handle_deep_link( let plugin_manager = Arc::new((*window.state::()).clone()); let query_manager = app_handle.db_manager(); - let http_client = yaak_api_client(app_handle)?; + let app_version = app_handle.package_info().version.to_string(); + let http_client = yaak_api_client(&app_version)?; let plugin_context = window.plugin_context(); let pv = download_and_install( plugin_manager, @@ -86,7 +87,8 @@ pub(crate) async fn handle_deep_link( return Ok(()); } - let resp = yaak_api_client(app_handle)?.get(file_url).send().await?; + let app_version = app_handle.package_info().version.to_string(); + let resp = yaak_api_client(&app_version)?.get(file_url).send().await?; let json = resp.bytes().await?; let p = app_handle .path() diff --git a/crates-tauri/yaak-license/Cargo.toml b/crates-tauri/yaak-license/Cargo.toml index 3560040a..4eb04489 100644 --- a/crates-tauri/yaak-license/Cargo.toml +++ b/crates-tauri/yaak-license/Cargo.toml @@ -16,7 +16,7 @@ thiserror = { workspace = true } ts-rs = { workspace = true } yaak-common = { workspace = true } yaak-models = { workspace = true } -yaak-tauri-utils = { workspace = true } +yaak-api = { workspace = true } [build-dependencies] tauri-plugin = { workspace = true, features = ["build"] } diff --git a/crates-tauri/yaak-license/src/error.rs b/crates-tauri/yaak-license/src/error.rs index 823260fe..99e1292d 100644 --- a/crates-tauri/yaak-license/src/error.rs +++ b/crates-tauri/yaak-license/src/error.rs @@ -16,7 +16,7 @@ pub enum Error { ModelError(#[from] yaak_models::error::Error), #[error(transparent)] - TauriUtilsError(#[from] yaak_tauri_utils::error::Error), + ApiError(#[from] yaak_api::Error), #[error("Internal server error")] ServerError, diff --git a/crates-tauri/yaak-license/src/license.rs b/crates-tauri/yaak-license/src/license.rs index dc6c185e..3f2c4aa3 100644 --- a/crates-tauri/yaak-license/src/license.rs +++ b/crates-tauri/yaak-license/src/license.rs @@ -11,7 +11,7 @@ use yaak_common::platform::get_os_str; use yaak_models::db_context::DbContext; use yaak_models::query_manager::QueryManager; use yaak_models::util::UpdateSource; -use yaak_tauri_utils::api_client::yaak_api_client; +use yaak_api::yaak_api_client; /// Extension trait for accessing the QueryManager from Tauri Manager types. /// This is needed temporarily until all crates are refactored to not use Tauri. @@ -118,11 +118,12 @@ pub async fn activate_license( license_key: &str, ) -> Result<()> { info!("Activating license {}", license_key); - let client = reqwest::Client::new(); + let app_version = window.app_handle().package_info().version.to_string(); + let client = yaak_api_client(&app_version)?; let payload = ActivateLicenseRequestPayload { license_key: license_key.to_string(), app_platform: get_os_str().to_string(), - app_version: window.app_handle().package_info().version.to_string(), + app_version, }; let response = client.post(build_url("/licenses/activate")).json(&payload).send().await?; @@ -155,11 +156,12 @@ pub async fn deactivate_license(window: &WebviewWindow) -> Result let app_handle = window.app_handle(); let activation_id = get_activation_id(app_handle).await; - let client = reqwest::Client::new(); + let app_version = window.app_handle().package_info().version.to_string(); + let client = yaak_api_client(&app_version)?; let path = format!("/licenses/activations/{}/deactivate", activation_id); let payload = DeactivateLicenseRequestPayload { app_platform: get_os_str().to_string(), - app_version: window.app_handle().package_info().version.to_string(), + app_version, }; let response = client.post(build_url(&path)).json(&payload).send().await?; @@ -186,9 +188,10 @@ pub async fn deactivate_license(window: &WebviewWindow) -> Result } pub async fn check_license(window: &WebviewWindow) -> Result { + let app_version = window.app_handle().package_info().version.to_string(); let payload = CheckActivationRequestPayload { app_platform: get_os_str().to_string(), - app_version: window.package_info().version.to_string(), + app_version, }; let activation_id = get_activation_id(window.app_handle()).await; @@ -204,7 +207,7 @@ pub async fn check_license(window: &WebviewWindow) -> Result { info!("Checking license activation"); // A license has been activated, so let's check the license server - let client = yaak_api_client(window.app_handle())?; + let client = yaak_api_client(&payload.app_version)?; let path = format!("/licenses/activations/{activation_id}/check-v2"); let response = client.post(build_url(&path)).json(&payload).send().await?; diff --git a/crates-tauri/yaak-tauri-utils/Cargo.toml b/crates-tauri/yaak-tauri-utils/Cargo.toml index 11652f1a..74272621 100644 --- a/crates-tauri/yaak-tauri-utils/Cargo.toml +++ b/crates-tauri/yaak-tauri-utils/Cargo.toml @@ -6,8 +6,4 @@ publish = false [dependencies] tauri = { workspace = true } -reqwest = { workspace = true, features = ["gzip"] } -thiserror = { workspace = true } -serde = { workspace = true, features = ["derive"] } regex = "1.11.0" -yaak-common = { workspace = true } diff --git a/crates-tauri/yaak-tauri-utils/src/api_client.rs b/crates-tauri/yaak-tauri-utils/src/api_client.rs deleted file mode 100644 index cc308126..00000000 --- a/crates-tauri/yaak-tauri-utils/src/api_client.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::error::Result; -use reqwest::Client; -use std::time::Duration; -use tauri::http::{HeaderMap, HeaderValue}; -use tauri::{AppHandle, Runtime}; -use yaak_common::platform::{get_ua_arch, get_ua_platform}; - -pub fn yaak_api_client(app_handle: &AppHandle) -> Result { - let platform = get_ua_platform(); - let version = app_handle.package_info().version.clone(); - let arch = get_ua_arch(); - let ua = format!("Yaak/{version} ({platform}; {arch})"); - let mut default_headers = HeaderMap::new(); - default_headers.insert("Accept", HeaderValue::from_str("application/json").unwrap()); - - let client = reqwest::ClientBuilder::new() - .timeout(Duration::from_secs(20)) - .default_headers(default_headers) - .gzip(true) - .user_agent(ua) - .build()?; - - Ok(client) -} diff --git a/crates-tauri/yaak-tauri-utils/src/error.rs b/crates-tauri/yaak-tauri-utils/src/error.rs deleted file mode 100644 index 46a9c103..00000000 --- a/crates-tauri/yaak-tauri-utils/src/error.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::{Serialize, Serializer}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - #[error(transparent)] - ReqwestError(#[from] reqwest::Error), -} - -impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} - -pub type Result = std::result::Result; diff --git a/crates-tauri/yaak-tauri-utils/src/lib.rs b/crates-tauri/yaak-tauri-utils/src/lib.rs index 719ee7f0..61b63f17 100644 --- a/crates-tauri/yaak-tauri-utils/src/lib.rs +++ b/crates-tauri/yaak-tauri-utils/src/lib.rs @@ -1,3 +1 @@ -pub mod api_client; -pub mod error; pub mod window; diff --git a/crates/yaak-api/Cargo.toml b/crates/yaak-api/Cargo.toml new file mode 100644 index 00000000..024ae852 --- /dev/null +++ b/crates/yaak-api/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "yaak-api" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +log = { workspace = true } +reqwest = { workspace = true, features = ["gzip"] } +sysproxy = "0.3" +thiserror = { workspace = true } +yaak-common = { workspace = true } diff --git a/crates/yaak-api/src/error.rs b/crates/yaak-api/src/error.rs new file mode 100644 index 00000000..2cdc6a11 --- /dev/null +++ b/crates/yaak-api/src/error.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + ReqwestError(#[from] reqwest::Error), +} + +pub type Result = std::result::Result; diff --git a/crates/yaak-api/src/lib.rs b/crates/yaak-api/src/lib.rs new file mode 100644 index 00000000..6e18de19 --- /dev/null +++ b/crates/yaak-api/src/lib.rs @@ -0,0 +1,70 @@ +mod error; + +pub use error::{Error, Result}; + +use log::{debug, warn}; +use reqwest::Client; +use reqwest::header::{HeaderMap, HeaderValue}; +use std::time::Duration; +use yaak_common::platform::{get_ua_arch, get_ua_platform}; + +/// Build a reqwest Client configured for Yaak's own API calls. +/// +/// Includes a custom User-Agent, JSON accept header, 20s timeout, gzip, +/// and automatic OS-level proxy detection via sysproxy. +pub fn yaak_api_client(version: &str) -> Result { + let platform = get_ua_platform(); + let arch = get_ua_arch(); + let ua = format!("Yaak/{version} ({platform}; {arch})"); + + let mut default_headers = HeaderMap::new(); + default_headers.insert("Accept", HeaderValue::from_str("application/json").unwrap()); + + let mut builder = reqwest::ClientBuilder::new() + .timeout(Duration::from_secs(20)) + .default_headers(default_headers) + .gzip(true) + .user_agent(ua); + + if let Some(sys) = get_enabled_system_proxy() { + let proxy_url = format!("http://{}:{}", sys.host, sys.port); + match reqwest::Proxy::all(&proxy_url) { + Ok(p) => { + let p = if !sys.bypass.is_empty() { + p.no_proxy(reqwest::NoProxy::from_string(&sys.bypass)) + } else { + p + }; + builder = builder.proxy(p); + } + Err(e) => { + warn!("Failed to configure system proxy: {e}"); + } + } + } + + Ok(builder.build()?) +} + +/// Returns the system proxy URL if one is enabled, e.g. `http://host:port`. +pub fn get_system_proxy_url() -> Option { + let sys = get_enabled_system_proxy()?; + Some(format!("http://{}:{}", sys.host, sys.port)) +} + +fn get_enabled_system_proxy() -> Option { + match sysproxy::Sysproxy::get_system_proxy() { + Ok(sys) if sys.enable => { + debug!("Detected system proxy: http://{}:{}", sys.host, sys.port); + Some(sys) + } + Ok(_) => { + debug!("System proxy detected but not enabled"); + None + } + Err(e) => { + debug!("Could not detect system proxy: {e}"); + None + } + } +}