From 6e191235a24d90b66d161e24cf3fb1ffc45fb364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Wed, 5 Nov 2025 11:39:57 +0100 Subject: [PATCH 1/8] Last handshake in stats --- src-tauri/Cargo.lock | 2 -- src-tauri/Cargo.toml | 3 ++- src-tauri/src/database/models/tunnel.rs | 3 +-- src-tauri/src/export.rs | 1 + src-tauri/src/utils.rs | 2 +- swift/build.sh | 10 +++++----- swift/extension/VPNExtension/Adapter.swift | 2 +- swift/plugin/Sources/Defguard/Stats.swift | 4 +++- 8 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a09c9e7b..04cf4d6e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -669,7 +669,6 @@ dependencies = [ [[package]] name = "boringtun" version = "0.6.0" -source = "git+https://github.com/DefGuard/wireguard-rs?rev=886186c1e088e4805ab8049436c28cf3ea26d727#886186c1e088e4805ab8049436c28cf3ea26d727" dependencies = [ "aead", "base64 0.22.1", @@ -1477,7 +1476,6 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" version = "0.9.0" -source = "git+https://github.com/DefGuard/wireguard-rs?rev=886186c1e088e4805ab8049436c28cf3ea26d727#886186c1e088e4805ab8049436c28cf3ea26d727" dependencies = [ "base64 0.22.1", "boringtun", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 44335cb0..5709c0f7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -4,7 +4,8 @@ default-members = [".", "cli"] [workspace.dependencies] clap = { version = "4.5", features = ["cargo", "derive", "env"] } -defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs", rev = "886186c1e088e4805ab8049436c28cf3ea26d727" } +# defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs", rev = "886186c1e088e4805ab8049436c28cf3ea26d727" } +defguard_wireguard_rs = { path = "../../wireguard-rs" } dirs-next = "2.0" prost = "0.14" reqwest = { version = "0.12", features = ["cookies", "json"] } diff --git a/src-tauri/src/database/models/tunnel.rs b/src-tauri/src/database/models/tunnel.rs index 7510ea0e..24aa7abf 100644 --- a/src-tauri/src/database/models/tunnel.rs +++ b/src-tauri/src/database/models/tunnel.rs @@ -1,5 +1,4 @@ -use core::fmt; -use std::time::SystemTime; +use std::{fmt, time::SystemTime}; use chrono::{NaiveDateTime, Utc}; use defguard_wireguard_rs::host::Peer; diff --git a/src-tauri/src/export.rs b/src-tauri/src/export.rs index ec89fd35..59c48b80 100644 --- a/src-tauri/src/export.rs +++ b/src-tauri/src/export.rs @@ -12,6 +12,7 @@ use swift_rs::{swift, SRObject, SRString}; pub(crate) struct Stats { pub(crate) tx_bytes: u64, pub(crate) rx_bytes: u64, + pub(crate) last_handshake: u64, } swift!(pub(crate) fn start_tunnel(json: &SRString) -> bool); diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 9132437c..f5d6a159 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -147,7 +147,7 @@ pub(crate) async fn stats_handler(_pool: DbPool, name: String, _connection_type: interval.tick().await; // TODO: check all known localtions/tunnels, not just `name`. if let Some(stats) = unsafe { tunnel_stats(&name.as_str().into()) } { - info!("Tunnel stats: {} {}", stats.tx_bytes, stats.rx_bytes); + info!("Tunnel stats: {} {} {}", stats.last_handshake, stats.tx_bytes, stats.rx_bytes); } } } diff --git a/swift/build.sh b/swift/build.sh index 04112714..d8475699 100755 --- a/swift/build.sh +++ b/swift/build.sh @@ -9,7 +9,7 @@ export MACOSX_DEPLOYMENT_TARGET=13.5 # Build BoringTun. -pushd boringtun +pushd ../../boringtun for TARGET in aarch64-apple-darwin x86_64-apple-darwin do @@ -45,9 +45,9 @@ popd # Build VPNExtension. -# if [ "${TAURI_ENV_DEBUG}" = 'false' ]; then +if [ "${TAURI_ENV_DEBUG}" = 'false' ]; then CONFIG=Release -# else -# CONFIG=Debug -# fi +else + CONFIG=Debug +fi xcodebuild -project extension/VPNExtension.xcodeproj -target VPNExtension -configuration ${CONFIG} build diff --git a/swift/extension/VPNExtension/Adapter.swift b/swift/extension/VPNExtension/Adapter.swift index f60864c7..65d289ac 100644 --- a/swift/extension/VPNExtension/Adapter.swift +++ b/swift/extension/VPNExtension/Adapter.swift @@ -112,7 +112,7 @@ enum State { // Obtain tunnel statistics. func stats() -> Stats? { if let stats = tunnel?.stats() { - return Stats(txBytes: stats.txBytes, rxBytes: stats.rxBytes) + return Stats(txBytes: stats.txBytes, rxBytes: stats.rxBytes, lastHandshake: stats.lastHandshake) } return nil } diff --git a/swift/plugin/Sources/Defguard/Stats.swift b/swift/plugin/Sources/Defguard/Stats.swift index b7f97569..e77437e6 100644 --- a/swift/plugin/Sources/Defguard/Stats.swift +++ b/swift/plugin/Sources/Defguard/Stats.swift @@ -3,9 +3,11 @@ import ObjectiveC public class Stats: NSObject, Codable { var txBytes: UInt64 var rxBytes: UInt64 + var lastHandshake: UInt64 - init(txBytes: UInt64, rxBytes: UInt64) { + init(txBytes: UInt64, rxBytes: UInt64, lastHandshake: UInt64) { self.txBytes = txBytes self.rxBytes = rxBytes + self.lastHandshake = lastHandshake } } From 260f9d998f5eb9423c9f6edbf892074b6b00dc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 7 Nov 2025 10:29:56 +0100 Subject: [PATCH 2/8] On macoS omit DAEMON_CLIENT --- src-tauri/Cargo.lock | 12 +- src-tauri/src/commands.rs | 161 +++++++++++++++------- src-tauri/src/database/models/location.rs | 35 +---- src-tauri/src/service/mod.rs | 4 +- src-tauri/src/utils.rs | 84 ++++++----- 5 files changed, 173 insertions(+), 123 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b40995f1..8e6c4751 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -869,9 +869,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.44" +version = "1.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" dependencies = [ "find-msvc-tools", "jobserver", @@ -1825,9 +1825,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" dependencies = [ "serde", "serde_core", @@ -4856,9 +4856,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 533c64fc..60142f8e 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -9,10 +9,14 @@ use chrono::{DateTime, Duration, NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::{Sqlite, Transaction}; use struct_patch::Patch; +#[cfg(target_os = "macos")] +use swift_rs::SRString; use tauri::{AppHandle, Emitter, Manager, State}; const UPDATE_URL: &str = "https://pkgs.defguard.net/api/update/check"; +#[cfg(target_os = "macos")] +use crate::export::stop_tunnel; #[cfg(not(target_os = "macos"))] use crate::service::{ proto::{ @@ -292,7 +296,7 @@ pub async fn save_device_config( info!("New instance {instance} created."); trace!("Created following instance: {instance:#?}"); - push_service_locations(&instance).await; + let locations = push_service_locations(&instance).await?; handle.emit(EventKey::InstanceUpdate.into(), ())?; let res: SaveDeviceConfigResponse = SaveDeviceConfigResponse { @@ -305,12 +309,14 @@ pub async fn save_device_config( } #[cfg(target_os = "macos")] -async fn push_service_locations(instance: &Instance) { +async fn push_service_locations(instance: &Instance) -> Result>, Error> { // Nothing here... yet + + Ok(Vec::new()) } #[cfg(not(target_os = "macos"))] -async fn push_service_locations(instance: &Instance) { +async fn push_service_locations(instance: &Instance) -> Result>, Error> { let locations = Location::find_by_instance_id(&*DB_POOL, instance.id, true).await?; trace!("Created following locations: {locations:#?}"); @@ -354,6 +360,8 @@ async fn push_service_locations(instance: &Instance) { instance.name, instance.id, ); } + + Ok(locations) } #[tauri::command(async)] @@ -670,55 +678,58 @@ pub(crate) async fn do_update_instance( .ok_or(Error::NotFound)? .prvkey; - if service_locations.is_empty() { - debug!( - "No service locations to process for instance {}({})", - instance.name, instance.id - ); - } else { - debug!( - "Processing {} service location(s) for instance {}({})", - service_locations.len(), - instance.name, - instance.id - ); + #[cfg(windows)] + { + if service_locations.is_empty() { + debug!( + "No service locations to process for instance {}({})", + instance.name, instance.id + ); + } else { + debug!( + "Processing {} service location(s) for instance {}({})", + service_locations.len(), + instance.name, + instance.id + ); - let save_request = SaveServiceLocationsRequest { - service_locations: service_locations.clone(), - instance_id: instance.uuid.clone(), - private_key: private_key.clone(), - }; + let save_request = SaveServiceLocationsRequest { + service_locations: service_locations.clone(), + instance_id: instance.uuid.clone(), + private_key: private_key.clone(), + }; - debug!( - "Sending request to daemon to save {} service location(s) for instance {}({})", - save_request.service_locations.len(), - instance.name, - instance.id - ); + debug!( + "Sending request to daemon to save {} service location(s) for instance {}({})", + save_request.service_locations.len(), + instance.name, + instance.id + ); - DAEMON_CLIENT - .clone() - .save_service_locations(save_request) - .await - .map_err(|err| { - error!( + DAEMON_CLIENT + .clone() + .save_service_locations(save_request) + .await + .map_err(|err| { + error!( "Error while saving service locations to the daemon for instance {}({}): {err}", instance.name, instance.id, ); - Error::InternalError(err.to_string()) - })?; + Error::InternalError(err.to_string()) + })?; - info!( - "Successfully saved {} service location(s) to daemon for instance {}({})", - service_locations.len(), - instance.name, - instance.id - ); + info!( + "Successfully saved {} service location(s) to daemon for instance {}({})", + service_locations.len(), + instance.name, + instance.id + ); - debug!( - "Completed processing all service locations for instance {}({})", - instance.name, instance.id - ); + debug!( + "Completed processing all service locations for instance {}({})", + instance.name, instance.id + ); + } } Ok(()) @@ -931,6 +942,56 @@ pub async fn update_location_routing( } } +#[cfg(target_os = "macos")] +#[tauri::command(async)] +pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), Error> { + let app_state = handle.state::(); + let mut transaction = DB_POOL.begin().await?; + + let Some(instance) = Instance::find_by_id(&mut *transaction, instance_id).await? else { + error!("Couldn't delete instance: instance with ID {instance_id} could not be found."); + return Err(Error::NotFound); + }; + debug!("The instance that is being deleted has been identified as {instance}"); + + let instance_locations = + Location::find_by_instance_id(&mut *transaction, instance_id, false).await?; + if !instance_locations.is_empty() { + debug!( + "Found locations associated with the instance {instance}, closing their connections." + ); + } + for location in instance_locations { + if let Some(connection) = app_state + .remove_connection(location.id, ConnectionType::Location) + .await + { + let result = unsafe { + let name: SRString = location.name.as_str().into(); + stop_tunnel(&name) + }; + error!("stop_tunnel() for location returned {result:?}"); + if !result { + return Err(Error::InternalError("Error from Swift".into())); + } + } + } + + instance.delete(&mut *transaction).await?; + + transaction.commit().await?; + + reload_tray_menu(&handle).await; + + let theme = { app_state.app_config.lock().unwrap().tray_theme }; + configure_tray_icon(&handle, theme).await?; + + handle.emit(EventKey::InstanceUpdate.into(), ())?; + info!("Successfully deleted instance {instance}."); + Ok(()) +} + +#[cfg(not(target_os = "macos"))] #[tauri::command(async)] pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), Error> { debug!("Deleting instance with ID {instance_id}"); @@ -983,8 +1044,7 @@ pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), E transaction.commit().await?; - DAEMON_CLIENT - .clone() + client .delete_service_locations(DeleteServiceLocationsRequest { instance_id: instance.uuid.clone(), }) @@ -999,7 +1059,6 @@ pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), E reload_tray_menu(&handle).await; - let app_state: State = handle.state(); let theme = { app_state.app_config.lock().unwrap().tray_theme }; configure_tray_icon(&handle, theme).await?; @@ -1089,6 +1148,14 @@ pub async fn tunnel_details(tunnel_id: Id) -> Result, Error> { } } +#[cfg(target_os = "macos")] +#[tauri::command(async)] +pub async fn delete_tunnel(tunnel_id: Id, handle: AppHandle) -> Result<(), Error> { + // TODO: implementation + Ok(()) +} + +#[cfg(not(target_os = "macos"))] #[tauri::command(async)] pub async fn delete_tunnel(tunnel_id: Id, handle: AppHandle) -> Result<(), Error> { debug!("Deleting tunnel with ID {tunnel_id}"); diff --git a/src-tauri/src/database/models/location.rs b/src-tauri/src/database/models/location.rs index 6226f5b6..3b092b50 100644 --- a/src-tauri/src/database/models/location.rs +++ b/src-tauri/src/database/models/location.rs @@ -58,7 +58,7 @@ impl From for ServiceLocationMode { } } -#[derive(Debug, Serialize, Deserialize, Eq, Hash)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Location { pub id: I, pub instance_id: Id, @@ -76,39 +76,6 @@ pub struct Location { pub service_location_mode: ServiceLocationMode, } -impl PartialEq for Location { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && - self.instance_id == other.instance_id && - self.name == other.name && - self.address == other.address && - self.pubkey == other.pubkey && - self.endpoint == other.endpoint && - self.allowed_ips == other.allowed_ips && - self.dns == other.dns && - // Ignore `route_all_traffic` flag as Defguard Core does not have it. - self.keepalive_interval == other.keepalive_interval && - self.location_mfa_mode == other.location_mfa_mode && - self.service_location_mode == other.service_location_mode - } -} - -impl PartialEq> for Location { - fn eq(&self, other: &Self) -> bool { - self.instance_id == other.instance_id && - self.name == other.name && - self.address == other.address && - self.pubkey == other.pubkey && - self.endpoint == other.endpoint && - self.allowed_ips == other.allowed_ips && - self.dns == other.dns && - // Ignore `route_all_traffic` flag as Defguard Core does not have it. - self.keepalive_interval == other.keepalive_interval && - self.location_mfa_mode == other.location_mfa_mode && - self.service_location_mode == other.service_location_mode - } -} - impl fmt::Display for Location { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}(ID: {})", self.name, self.id) diff --git a/src-tauri/src/service/mod.rs b/src-tauri/src/service/mod.rs index 3a1d8c05..3aa6fb41 100644 --- a/src-tauri/src/service/mod.rs +++ b/src-tauri/src/service/mod.rs @@ -137,7 +137,7 @@ impl DesktopDaemonService for DaemonService { &self, _request: tonic::Request, ) -> Result, Status> { - debug!("Saved service location request received, this is currently not supported on Unix systems"); + debug!("Save service location request received, this is currently not supported on Unix systems"); Ok(Response::new(())) } @@ -146,7 +146,7 @@ impl DesktopDaemonService for DaemonService { &self, _request: tonic::Request, ) -> Result, Status> { - debug!("Saved service location request received, this is currently not supported on Unix systems"); + debug!("Delete service location request received, this is currently not supported on Unix systems"); Ok(Response::new(())) } diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 3b120c0e..b76c48b3 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -295,7 +295,7 @@ pub fn load_log_targets() -> Vec { Vec::new() } -// helper function to get log file directory for the defguard-service daemon +/// Helper function to get log file directory for `defguard-service` daemon. #[must_use] pub fn get_service_log_dir() -> &'static Path { #[cfg(windows)] @@ -400,27 +400,27 @@ pub async fn setup_interface_tunnel(tunnel: &Tunnel, name: &str) -> Result Date: Mon, 10 Nov 2025 12:32:13 +0100 Subject: [PATCH 3/8] Collect stats --- package.json | 6 +- pnpm-lock.yaml | 466 +++++++++++++-------------- src-tauri/Cargo.lock | 156 ++++----- src-tauri/src/export.rs | 3 +- src-tauri/src/utils.rs | 11 +- swift/plugin/Sources/Wireguard.swift | 69 +++- 6 files changed, 390 insertions(+), 321 deletions(-) diff --git a/package.json b/package.json index cf107dc6..19c97ffc 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "react-router-dom": "^6.30.1", "react-use-websocket": "^4.13.0", "react-virtualized-auto-sizer": "^1.0.26", - "recharts": "^3.3.0", + "recharts": "^3.4.1", "rehype-sanitize": "^6.0.0", "rxjs": "^7.8.2", "use-breakpoint": "^4.0.10", @@ -113,7 +113,7 @@ "@svgr/cli": "^8.1.0", "@tanstack/react-query": "^5.90.7", "@tanstack/react-query-devtools": "^5.90.2", - "@tauri-apps/cli": "^2.9.3", + "@tauri-apps/cli": "^2.9.4", "@types/file-saver": "^2.0.7", "@types/lodash-es": "^4.17.12", "@types/node": "^24.10.0", @@ -129,7 +129,7 @@ "typedoc": "^0.28.14", "typesafe-i18n": "^5.26.2", "typescript": "^5.9.3", - "vite": "^7.2.1" + "vite": "^7.2.2" }, "volta": { "node": "20.5.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6df74075..4da77748 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,8 +168,8 @@ importers: specifier: ^1.0.26 version: 1.0.26(react-dom@19.2.0(react@19.2.0))(react@19.2.0) recharts: - specifier: ^3.3.0 - version: 3.3.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1) + specifier: ^3.4.1 + version: 3.4.1(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1) rehype-sanitize: specifier: ^6.0.0 version: 6.0.0 @@ -202,8 +202,8 @@ importers: specifier: ^5.90.2 version: 5.90.2(@tanstack/react-query@5.90.7(react@19.2.0))(react@19.2.0) '@tauri-apps/cli': - specifier: ^2.9.3 - version: 2.9.3 + specifier: ^2.9.4 + version: 2.9.4 '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -221,10 +221,10 @@ importers: version: 19.2.2(@types/react@19.2.2) '@vitejs/plugin-react': specifier: ^5.1.0 - version: 5.1.0(vite@7.2.1(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) + version: 5.1.0(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) '@vitejs/plugin-react-swc': specifier: ^4.2.1 - version: 4.2.1(vite@7.2.1(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) + version: 4.2.1(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -250,8 +250,8 @@ importers: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.2.1 - version: 7.2.1(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + specifier: ^7.2.2 + version: 7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) packages: @@ -626,8 +626,8 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@gerrit0/mini-shiki@3.14.0': - resolution: {integrity: sha512-c5X8fwPLOtUS8TVdqhynz9iV0GlOtFUT1ppXYzUUlEXe4kbZ/mvMT8wXoT8kCwUka+zsiloq7sD3pZ3+QVTuNQ==} + '@gerrit0/mini-shiki@3.15.0': + resolution: {integrity: sha512-L5IHdZIDa4bG4yJaOzfasOH/o22MCesY0mx+n6VATbaiCtMeR59pdRqYk4bEiQkIHfxsHPNgdi7VJlZb2FhdMQ==} '@hookform/devtools@4.4.0': resolution: {integrity: sha512-Mtlic+uigoYBPXlfvPBfiYYUZuyMrD3pTjDpVIhL6eCZTvQkHsKBSKeZCvXWUZr8fqrkzDg27N+ZuazLKq6Vmg==} @@ -774,127 +774,127 @@ packages: '@rolldown/pluginutils@1.0.0-beta.46': resolution: {integrity: sha512-xMNwJo/pHkEP/mhNVnW+zUiJDle6/hxrwO0mfSJuEVRbBfgrJFuUSRoZx/nYUw5pCjrysl9OkNXCkAdih8GCnA==} - '@rollup/rollup-android-arm-eabi@4.52.5': - resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + '@rollup/rollup-android-arm-eabi@4.53.2': + resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.5': - resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + '@rollup/rollup-android-arm64@4.53.2': + resolution: {integrity: sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.5': - resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + '@rollup/rollup-darwin-arm64@4.53.2': + resolution: {integrity: sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.5': - resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + '@rollup/rollup-darwin-x64@4.53.2': + resolution: {integrity: sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.5': - resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + '@rollup/rollup-freebsd-arm64@4.53.2': + resolution: {integrity: sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.5': - resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + '@rollup/rollup-freebsd-x64@4.53.2': + resolution: {integrity: sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': - resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.52.5': - resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.52.5': - resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + '@rollup/rollup-linux-arm64-gnu@4.53.2': + resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.52.5': - resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + '@rollup/rollup-linux-arm64-musl@4.53.2': + resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.52.5': - resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + '@rollup/rollup-linux-loong64-gnu@4.53.2': + resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.52.5': - resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.52.5': - resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.52.5': - resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + '@rollup/rollup-linux-riscv64-musl@4.53.2': + resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.52.5': - resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + '@rollup/rollup-linux-s390x-gnu@4.53.2': + resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.52.5': - resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + '@rollup/rollup-linux-x64-gnu@4.53.2': + resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.52.5': - resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + '@rollup/rollup-linux-x64-musl@4.53.2': + resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.52.5': - resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + '@rollup/rollup-openharmony-arm64@4.53.2': + resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.5': - resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + '@rollup/rollup-win32-arm64-msvc@4.53.2': + resolution: {integrity: sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.5': - resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + '@rollup/rollup-win32-ia32-msvc@4.53.2': + resolution: {integrity: sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.5': - resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + '@rollup/rollup-win32-x64-gnu@4.53.2': + resolution: {integrity: sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.5': - resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + '@rollup/rollup-win32-x64-msvc@4.53.2': + resolution: {integrity: sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==} cpu: [x64] os: [win32] - '@shikijs/engine-oniguruma@3.14.0': - resolution: {integrity: sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug==} + '@shikijs/engine-oniguruma@3.15.0': + resolution: {integrity: sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==} - '@shikijs/langs@3.14.0': - resolution: {integrity: sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg==} + '@shikijs/langs@3.15.0': + resolution: {integrity: sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==} - '@shikijs/themes@3.14.0': - resolution: {integrity: sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA==} + '@shikijs/themes@3.15.0': + resolution: {integrity: sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==} - '@shikijs/types@3.14.0': - resolution: {integrity: sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ==} + '@shikijs/types@3.15.0': + resolution: {integrity: sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -1014,68 +1014,68 @@ packages: peerDependencies: '@svgr/core': '*' - '@swc/core-darwin-arm64@1.15.0': - resolution: {integrity: sha512-TBKWkbnShnEjlIbO4/gfsrIgAqHBVqgPWLbWmPdZ80bF393yJcLgkrb7bZEnJs6FCbSSuGwZv2rx1jDR2zo6YA==} + '@swc/core-darwin-arm64@1.15.1': + resolution: {integrity: sha512-vEPrVxegWIjKEz+1VCVuKRY89jhokhSmQ/YXBWLnmLj9cI08G61RTZJvdsIcjYUjjTu7NgZlYVK+b2y0fbh11g==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.15.0': - resolution: {integrity: sha512-f5JKL1v1H56CIZc1pVn4RGPOfnWqPwmuHdpf4wesvXunF1Bx85YgcspW5YxwqG5J9g3nPU610UFuExJXVUzOiQ==} + '@swc/core-darwin-x64@1.15.1': + resolution: {integrity: sha512-z9QguKxE3aldvwKHHDg5OlKehasbJBF1lacn5CnN6SlrHbdwokXHFA3nIoO3Bh1Tw7bCgFtdIR4jKlTTn3kBZA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.15.0': - resolution: {integrity: sha512-duK6nG+WyuunnfsfiTUQdzC9Fk8cyDLqT9zyXvY2i2YgDu5+BH5W6wM5O4mDNCU5MocyB/SuF5YDF7XySnowiQ==} + '@swc/core-linux-arm-gnueabihf@1.15.1': + resolution: {integrity: sha512-yS2FHA8E4YeiPG9YeYk/6mKiCWuXR5RdYlCmtlGzKcjWbI4GXUVe7+p9C0M6myRt3zdj3M1knmJxk52MQA9EZQ==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.15.0': - resolution: {integrity: sha512-ITe9iDtTRXM98B91rvyPP6qDVbhUBnmA/j4UxrHlMQ0RlwpqTjfZYZkD0uclOxSZ6qIrOj/X5CaoJlDUuQ0+Cw==} + '@swc/core-linux-arm64-gnu@1.15.1': + resolution: {integrity: sha512-IFrjDu7+5Y61jLsUqBVXlXutDoPBX10eEeNTjW6C1yzm+cSTE7ayiKXMIFri4gEZ4VpXS6MUgkwjxtDpIXTh+w==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.15.0': - resolution: {integrity: sha512-Q5ldc2bzriuzYEoAuqJ9Vr3FyZhakk5hiwDbniZ8tlEXpbjBhbOleGf9/gkhLaouDnkNUEazFW9mtqwUTRdh7Q==} + '@swc/core-linux-arm64-musl@1.15.1': + resolution: {integrity: sha512-fKzP9mRQGbhc5QhJPIsqKNNX/jyWrZgBxmo3Nz1SPaepfCUc7RFmtcJQI5q8xAun3XabXjh90wqcY/OVyg2+Kg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.15.0': - resolution: {integrity: sha512-pY4is+jEpOxlYCSnI+7N8Oxbap9TmTz5YT84tUvRTlOlTBwFAUlWFCX0FRwWJlsfP0TxbqhIe8dNNzlsEmJbXQ==} + '@swc/core-linux-x64-gnu@1.15.1': + resolution: {integrity: sha512-ZLjMi138uTJxb+1wzo4cB8mIbJbAsSLWRNeHc1g1pMvkERPWOGlem+LEYkkzaFzCNv1J8aKcL653Vtw8INHQeg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.15.0': - resolution: {integrity: sha512-zYEt5eT8y8RUpoe7t5pjpoOdGu+/gSTExj8PV86efhj6ugB3bPlj3Y85ogdW3WMVXr4NvwqvzdaYGCZfXzSyVg==} + '@swc/core-linux-x64-musl@1.15.1': + resolution: {integrity: sha512-jvSI1IdsIYey5kOITzyajjofXOOySVitmLxb45OPUjoNojql4sDojvlW5zoHXXFePdA6qAX4Y6KbzAOV3T3ctA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.15.0': - resolution: {integrity: sha512-zC1rmOgFH5v2BCbByOazEqs0aRNpTdLRchDExfcCfgKgeaD+IdpUOqp7i3VG1YzkcnbuZjMlXfM0ugpt+CddoA==} + '@swc/core-win32-arm64-msvc@1.15.1': + resolution: {integrity: sha512-X/FcDtNrDdY9r4FcXHt9QxUqC/2FbQdvZobCKHlHe8vTSKhUHOilWl5EBtkFVfsEs4D5/yAri9e3bJbwyBhhBw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.15.0': - resolution: {integrity: sha512-7t9U9KwMwQblkdJIH+zX1V4q1o3o41i0HNO+VlnAHT5o+5qHJ963PHKJ/pX3P2UlZnBCY465orJuflAN4rAP9A==} + '@swc/core-win32-ia32-msvc@1.15.1': + resolution: {integrity: sha512-vfheiWBux8PpC87oy1cshcqzgH7alWYpnVq5jWe7xuVkjqjGGDbBUKuS84eJCdsWcVaB5EXIWLKt+11W3/BOwA==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.15.0': - resolution: {integrity: sha512-VE0Zod5vcs8iMLT64m5QS1DlTMXJFI/qSgtMDRx8rtZrnjt6/9NW8XUaiPJuRu8GluEO1hmHoyf1qlbY19gGSQ==} + '@swc/core-win32-x64-msvc@1.15.1': + resolution: {integrity: sha512-n3Ppn0LSov/IdlANq+8kxHqENuJRX5XtwQqPgQsgwKIcFq22u17NKfDs9vL5PwRsEHY6Xd67pnOqQX0h4AvbuQ==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.15.0': - resolution: {integrity: sha512-8SnJV+JV0rYbfSiEiUvYOmf62E7QwsEG+aZueqSlKoxFt0pw333+bgZSQXGUV6etXU88nxur0afVMaINujBMSw==} + '@swc/core@1.15.1': + resolution: {integrity: sha512-s9GN3M2jA32k+StvuS9uGe4ztf5KVGBdlJMMC6LR6Ah23Lq/CWKVcC3WeQi8qaAcLd+DiddoNCNMUWymLv+wWQ==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -1118,74 +1118,74 @@ packages: '@tauri-apps/api@2.9.0': resolution: {integrity: sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw==} - '@tauri-apps/cli-darwin-arm64@2.9.3': - resolution: {integrity: sha512-W8FQXZXQmQ0Fmj9UJXNrm2mLdIaLLriKVY7o/FzmizyIKTPIvHjfZALTNybbpTQRbJvKoGHLrW1DNzAWVDWJYg==} + '@tauri-apps/cli-darwin-arm64@2.9.4': + resolution: {integrity: sha512-9rHkMVtbMhe0AliVbrGpzMahOBg3rwV46JYRELxR9SN6iu1dvPOaMaiC4cP6M/aD1424ziXnnMdYU06RAH8oIw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tauri-apps/cli-darwin-x64@2.9.3': - resolution: {integrity: sha512-zDwu40rlshijt3TU6aRvzPUyVpapsx1sNfOlreDMTaMelQLHl6YoQzSRpLHYwrHrhimxyX2uDqnKIiuGel0Lhg==} + '@tauri-apps/cli-darwin-x64@2.9.4': + resolution: {integrity: sha512-VT9ymNuT06f5TLjCZW2hfSxbVtZDhORk7CDUDYiq5TiSYQdxkl8MVBy0CCFFcOk4QAkUmqmVUA9r3YZ/N/vPRQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tauri-apps/cli-linux-arm-gnueabihf@2.9.3': - resolution: {integrity: sha512-+Oc2OfcTRwYtW93VJqd/HOk77buORwC9IToj/qsEvM7bTMq6Kda4alpZprzwrCHYANSw+zD8PgjJdljTpe4p+g==} + '@tauri-apps/cli-linux-arm-gnueabihf@2.9.4': + resolution: {integrity: sha512-tTWkEPig+2z3Rk0zqZYfjUYcgD+aSm72wdrIhdYobxbQZOBw0zfn50YtWv+av7bm0SHvv75f0l7JuwgZM1HFow==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tauri-apps/cli-linux-arm64-gnu@2.9.3': - resolution: {integrity: sha512-59GqU/J1n9wFyAtleoQOaU0oVIo+kwQynEw4meFDoKRXszKGor6lTsbsS3r0QKLSPbc0o/yYGJhqqCtkYjb/eg==} + '@tauri-apps/cli-linux-arm64-gnu@2.9.4': + resolution: {integrity: sha512-ql6vJ611qoqRYHxkKPnb2vHa27U+YRKRmIpLMMBeZnfFtZ938eao7402AQCH1mO2+/8ioUhbpy9R/ZcLTXVmkg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tauri-apps/cli-linux-arm64-musl@2.9.3': - resolution: {integrity: sha512-fzvG+jEn5/iYGNH6Z2IRMheYFC4pJdXa19BR9fFm6Bdn2cuajRLDKdUcEME/DCtwqclphXtFZTrT4oezY5vI/A==} + '@tauri-apps/cli-linux-arm64-musl@2.9.4': + resolution: {integrity: sha512-vg7yNn7ICTi6hRrcA/6ff2UpZQP7un3xe3SEld5QM0prgridbKAiXGaCKr3BnUBx/rGXegQlD/wiLcWdiiraSw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tauri-apps/cli-linux-riscv64-gnu@2.9.3': - resolution: {integrity: sha512-qV8DZXI/fZwawk6T3Th1g6smiNC2KeQTk7XFgKvqZ6btC01z3UTsQmNGvI602zwm3Ld1TBZb4+rEWu2QmQimmw==} + '@tauri-apps/cli-linux-riscv64-gnu@2.9.4': + resolution: {integrity: sha512-l8L+3VxNk6yv5T/Z/gv5ysngmIpsai40B9p6NQQyqYqxImqYX37pqREoEBl1YwG7szGnDibpWhidPrWKR59OJA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - '@tauri-apps/cli-linux-x64-gnu@2.9.3': - resolution: {integrity: sha512-tquyEONCNRfqEBWEe4eAHnxFN5yY5lFkCuD4w79XLIovUxVftQ684+xLp7zkhntkt4y20SMj2AgJa/+MOlx4Kg==} + '@tauri-apps/cli-linux-x64-gnu@2.9.4': + resolution: {integrity: sha512-PepPhCXc/xVvE3foykNho46OmCyx47E/aG676vKTVp+mqin5d+IBqDL6wDKiGNT5OTTxKEyNlCQ81Xs2BQhhqA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tauri-apps/cli-linux-x64-musl@2.9.3': - resolution: {integrity: sha512-v2cBIB/6ji8DL+aiL5QUykU3ZO8OoJGyx50/qv2HQVzkf85KdaYSis3D/oVRemN/pcDz+vyCnnL3XnzFnDl4JQ==} + '@tauri-apps/cli-linux-x64-musl@2.9.4': + resolution: {integrity: sha512-zcd1QVffh5tZs1u1SCKUV/V7RRynebgYUNWHuV0FsIF1MjnULUChEXhAhug7usCDq4GZReMJOoXa6rukEozWIw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tauri-apps/cli-win32-arm64-msvc@2.9.3': - resolution: {integrity: sha512-ZGvBy7nvrHPbE0HeKp/ioaiw8bNgAHxWnb7JRZ4/G0A+oFj0SeSFxl9k5uU6FKnM7bHM23Gd1oeaDex9g5Fceg==} + '@tauri-apps/cli-win32-arm64-msvc@2.9.4': + resolution: {integrity: sha512-/7ZhnP6PY04bEob23q8MH/EoDISdmR1wuNm0k9d5HV7TDMd2GGCDa8dPXA4vJuglJKXIfXqxFmZ4L+J+MO42+w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tauri-apps/cli-win32-ia32-msvc@2.9.3': - resolution: {integrity: sha512-UsgIwOnpCoY9NK9/65QiwgmWVIE80LE7SwRYVblGtmlY9RYfsYvpbItwsovA/AcHMTiO+OCvS/q9yLeqS3m6Sg==} + '@tauri-apps/cli-win32-ia32-msvc@2.9.4': + resolution: {integrity: sha512-1LmAfaC4Cq+3O1Ir1ksdhczhdtFSTIV51tbAGtbV/mr348O+M52A/xwCCXQank0OcdBxy5BctqkMtuZnQvA8uQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@tauri-apps/cli-win32-x64-msvc@2.9.3': - resolution: {integrity: sha512-fmw7NrrHE5m49idCvJAx9T9bsupjdJ0a3p3DPCNCZRGANU6R1tA1L+KTlVuUtdAldX2NqU/9UPo2SCslYKgJHQ==} + '@tauri-apps/cli-win32-x64-msvc@2.9.4': + resolution: {integrity: sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tauri-apps/cli@2.9.3': - resolution: {integrity: sha512-BQ7iLUXTQcyG1PpzLWeVSmBCedYDpnA/6Cm/kRFGtqjTf/eVUlyYO5S2ee07tLum3nWwDBWTGFZeruO8yEukfA==} + '@tauri-apps/cli@2.9.4': + resolution: {integrity: sha512-pvylWC9QckrOS9ATWXIXcgu7g2hKK5xTL5ZQyZU/U0n9l88SEFGcWgLQNa8WZmd+wWIOWhkxOFcOl3i6ubDNNw==} engines: {node: '>= 10'} hasBin: true @@ -1440,8 +1440,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001753: - resolution: {integrity: sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==} + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1685,8 +1685,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.245: - resolution: {integrity: sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==} + electron-to-chromium@1.5.249: + resolution: {integrity: sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2560,8 +2560,8 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - recharts@3.3.0: - resolution: {integrity: sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==} + recharts@3.4.1: + resolution: {integrity: sha512-35kYg6JoOgwq8sE4rhYkVWwa6aAIgOtT+Ob0gitnShjwUwZmhrmy7Jco/5kJNF4PnLXgt9Hwq+geEMS+WrjU1g==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2609,8 +2609,8 @@ packages: engines: {node: '>= 0.4'} hasBin: true - rollup@4.52.5: - resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + rollup@4.53.2: + resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2899,8 +2899,8 @@ packages: victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - vite@7.2.1: - resolution: {integrity: sha512-qTl3VF7BvOupTR85Zc561sPEgxyUSNSvTQ9fit7DEMP7yPgvvIGm5Zfa1dOM+kOwWGNviK9uFM9ra77+OjK7lQ==} + vite@7.2.2: + resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3351,12 +3351,12 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@gerrit0/mini-shiki@3.14.0': + '@gerrit0/mini-shiki@3.15.0': dependencies: - '@shikijs/engine-oniguruma': 3.14.0 - '@shikijs/langs': 3.14.0 - '@shikijs/themes': 3.14.0 - '@shikijs/types': 3.14.0 + '@shikijs/engine-oniguruma': 3.15.0 + '@shikijs/langs': 3.15.0 + '@shikijs/themes': 3.15.0 + '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 '@hookform/devtools@4.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': @@ -3491,86 +3491,86 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.46': {} - '@rollup/rollup-android-arm-eabi@4.52.5': + '@rollup/rollup-android-arm-eabi@4.53.2': optional: true - '@rollup/rollup-android-arm64@4.52.5': + '@rollup/rollup-android-arm64@4.53.2': optional: true - '@rollup/rollup-darwin-arm64@4.52.5': + '@rollup/rollup-darwin-arm64@4.53.2': optional: true - '@rollup/rollup-darwin-x64@4.52.5': + '@rollup/rollup-darwin-x64@4.53.2': optional: true - '@rollup/rollup-freebsd-arm64@4.52.5': + '@rollup/rollup-freebsd-arm64@4.53.2': optional: true - '@rollup/rollup-freebsd-x64@4.52.5': + '@rollup/rollup-freebsd-x64@4.53.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.5': + '@rollup/rollup-linux-arm-musleabihf@4.53.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.5': + '@rollup/rollup-linux-arm64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.5': + '@rollup/rollup-linux-arm64-musl@4.53.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.5': + '@rollup/rollup-linux-loong64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.5': + '@rollup/rollup-linux-ppc64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.5': + '@rollup/rollup-linux-riscv64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.5': + '@rollup/rollup-linux-riscv64-musl@4.53.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.5': + '@rollup/rollup-linux-s390x-gnu@4.53.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.5': + '@rollup/rollup-linux-x64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-x64-musl@4.52.5': + '@rollup/rollup-linux-x64-musl@4.53.2': optional: true - '@rollup/rollup-openharmony-arm64@4.52.5': + '@rollup/rollup-openharmony-arm64@4.53.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.5': + '@rollup/rollup-win32-arm64-msvc@4.53.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.5': + '@rollup/rollup-win32-ia32-msvc@4.53.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.5': + '@rollup/rollup-win32-x64-gnu@4.53.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.5': + '@rollup/rollup-win32-x64-msvc@4.53.2': optional: true - '@shikijs/engine-oniguruma@3.14.0': + '@shikijs/engine-oniguruma@3.15.0': dependencies: - '@shikijs/types': 3.14.0 + '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.14.0': + '@shikijs/langs@3.15.0': dependencies: - '@shikijs/types': 3.14.0 + '@shikijs/types': 3.15.0 - '@shikijs/themes@3.14.0': + '@shikijs/themes@3.15.0': dependencies: - '@shikijs/types': 3.14.0 + '@shikijs/types': 3.15.0 - '@shikijs/types@3.14.0': + '@shikijs/types@3.15.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -3709,51 +3709,51 @@ snapshots: transitivePeerDependencies: - typescript - '@swc/core-darwin-arm64@1.15.0': + '@swc/core-darwin-arm64@1.15.1': optional: true - '@swc/core-darwin-x64@1.15.0': + '@swc/core-darwin-x64@1.15.1': optional: true - '@swc/core-linux-arm-gnueabihf@1.15.0': + '@swc/core-linux-arm-gnueabihf@1.15.1': optional: true - '@swc/core-linux-arm64-gnu@1.15.0': + '@swc/core-linux-arm64-gnu@1.15.1': optional: true - '@swc/core-linux-arm64-musl@1.15.0': + '@swc/core-linux-arm64-musl@1.15.1': optional: true - '@swc/core-linux-x64-gnu@1.15.0': + '@swc/core-linux-x64-gnu@1.15.1': optional: true - '@swc/core-linux-x64-musl@1.15.0': + '@swc/core-linux-x64-musl@1.15.1': optional: true - '@swc/core-win32-arm64-msvc@1.15.0': + '@swc/core-win32-arm64-msvc@1.15.1': optional: true - '@swc/core-win32-ia32-msvc@1.15.0': + '@swc/core-win32-ia32-msvc@1.15.1': optional: true - '@swc/core-win32-x64-msvc@1.15.0': + '@swc/core-win32-x64-msvc@1.15.1': optional: true - '@swc/core@1.15.0': + '@swc/core@1.15.1': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.25 optionalDependencies: - '@swc/core-darwin-arm64': 1.15.0 - '@swc/core-darwin-x64': 1.15.0 - '@swc/core-linux-arm-gnueabihf': 1.15.0 - '@swc/core-linux-arm64-gnu': 1.15.0 - '@swc/core-linux-arm64-musl': 1.15.0 - '@swc/core-linux-x64-gnu': 1.15.0 - '@swc/core-linux-x64-musl': 1.15.0 - '@swc/core-win32-arm64-msvc': 1.15.0 - '@swc/core-win32-ia32-msvc': 1.15.0 - '@swc/core-win32-x64-msvc': 1.15.0 + '@swc/core-darwin-arm64': 1.15.1 + '@swc/core-darwin-x64': 1.15.1 + '@swc/core-linux-arm-gnueabihf': 1.15.1 + '@swc/core-linux-arm64-gnu': 1.15.1 + '@swc/core-linux-arm64-musl': 1.15.1 + '@swc/core-linux-x64-gnu': 1.15.1 + '@swc/core-linux-x64-musl': 1.15.1 + '@swc/core-win32-arm64-msvc': 1.15.1 + '@swc/core-win32-ia32-msvc': 1.15.1 + '@swc/core-win32-x64-msvc': 1.15.1 '@swc/counter@0.1.3': {} @@ -3786,52 +3786,52 @@ snapshots: '@tauri-apps/api@2.9.0': {} - '@tauri-apps/cli-darwin-arm64@2.9.3': + '@tauri-apps/cli-darwin-arm64@2.9.4': optional: true - '@tauri-apps/cli-darwin-x64@2.9.3': + '@tauri-apps/cli-darwin-x64@2.9.4': optional: true - '@tauri-apps/cli-linux-arm-gnueabihf@2.9.3': + '@tauri-apps/cli-linux-arm-gnueabihf@2.9.4': optional: true - '@tauri-apps/cli-linux-arm64-gnu@2.9.3': + '@tauri-apps/cli-linux-arm64-gnu@2.9.4': optional: true - '@tauri-apps/cli-linux-arm64-musl@2.9.3': + '@tauri-apps/cli-linux-arm64-musl@2.9.4': optional: true - '@tauri-apps/cli-linux-riscv64-gnu@2.9.3': + '@tauri-apps/cli-linux-riscv64-gnu@2.9.4': optional: true - '@tauri-apps/cli-linux-x64-gnu@2.9.3': + '@tauri-apps/cli-linux-x64-gnu@2.9.4': optional: true - '@tauri-apps/cli-linux-x64-musl@2.9.3': + '@tauri-apps/cli-linux-x64-musl@2.9.4': optional: true - '@tauri-apps/cli-win32-arm64-msvc@2.9.3': + '@tauri-apps/cli-win32-arm64-msvc@2.9.4': optional: true - '@tauri-apps/cli-win32-ia32-msvc@2.9.3': + '@tauri-apps/cli-win32-ia32-msvc@2.9.4': optional: true - '@tauri-apps/cli-win32-x64-msvc@2.9.3': + '@tauri-apps/cli-win32-x64-msvc@2.9.4': optional: true - '@tauri-apps/cli@2.9.3': + '@tauri-apps/cli@2.9.4': optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.9.3 - '@tauri-apps/cli-darwin-x64': 2.9.3 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.9.3 - '@tauri-apps/cli-linux-arm64-gnu': 2.9.3 - '@tauri-apps/cli-linux-arm64-musl': 2.9.3 - '@tauri-apps/cli-linux-riscv64-gnu': 2.9.3 - '@tauri-apps/cli-linux-x64-gnu': 2.9.3 - '@tauri-apps/cli-linux-x64-musl': 2.9.3 - '@tauri-apps/cli-win32-arm64-msvc': 2.9.3 - '@tauri-apps/cli-win32-ia32-msvc': 2.9.3 - '@tauri-apps/cli-win32-x64-msvc': 2.9.3 + '@tauri-apps/cli-darwin-arm64': 2.9.4 + '@tauri-apps/cli-darwin-x64': 2.9.4 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.9.4 + '@tauri-apps/cli-linux-arm64-gnu': 2.9.4 + '@tauri-apps/cli-linux-arm64-musl': 2.9.4 + '@tauri-apps/cli-linux-riscv64-gnu': 2.9.4 + '@tauri-apps/cli-linux-x64-gnu': 2.9.4 + '@tauri-apps/cli-linux-x64-musl': 2.9.4 + '@tauri-apps/cli-win32-arm64-msvc': 2.9.4 + '@tauri-apps/cli-win32-ia32-msvc': 2.9.4 + '@tauri-apps/cli-win32-x64-msvc': 2.9.4 '@tauri-apps/plugin-clipboard-manager@2.3.2': dependencies: @@ -3983,15 +3983,15 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.0 - '@vitejs/plugin-react-swc@4.2.1(vite@7.2.1(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1))': + '@vitejs/plugin-react-swc@4.2.1(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.46 - '@swc/core': 1.15.0 - vite: 7.2.1(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + '@swc/core': 1.15.1 + vite: 7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@5.1.0(vite@7.2.1(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1))': + '@vitejs/plugin-react@5.1.0(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -3999,7 +3999,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.43 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.2.1(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + vite: 7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -4035,7 +4035,7 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.27.0 - caniuse-lite: 1.0.30001753 + caniuse-lite: 1.0.30001754 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -4077,8 +4077,8 @@ snapshots: browserslist@4.27.0: dependencies: baseline-browser-mapping: 2.8.25 - caniuse-lite: 1.0.30001753 - electron-to-chromium: 1.5.245 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.249 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.27.0) @@ -4105,7 +4105,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001753: {} + caniuse-lite@1.0.30001754: {} ccount@2.0.1: {} @@ -4345,7 +4345,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.245: {} + electron-to-chromium@1.5.249: {} emoji-regex@8.0.0: {} @@ -5399,7 +5399,7 @@ snapshots: readdirp@4.1.2: {} - recharts@3.3.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1): + recharts@3.4.1(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0) clsx: 2.1.1 @@ -5479,32 +5479,32 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - rollup@4.52.5: + rollup@4.53.2: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.5 - '@rollup/rollup-android-arm64': 4.52.5 - '@rollup/rollup-darwin-arm64': 4.52.5 - '@rollup/rollup-darwin-x64': 4.52.5 - '@rollup/rollup-freebsd-arm64': 4.52.5 - '@rollup/rollup-freebsd-x64': 4.52.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 - '@rollup/rollup-linux-arm-musleabihf': 4.52.5 - '@rollup/rollup-linux-arm64-gnu': 4.52.5 - '@rollup/rollup-linux-arm64-musl': 4.52.5 - '@rollup/rollup-linux-loong64-gnu': 4.52.5 - '@rollup/rollup-linux-ppc64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-musl': 4.52.5 - '@rollup/rollup-linux-s390x-gnu': 4.52.5 - '@rollup/rollup-linux-x64-gnu': 4.52.5 - '@rollup/rollup-linux-x64-musl': 4.52.5 - '@rollup/rollup-openharmony-arm64': 4.52.5 - '@rollup/rollup-win32-arm64-msvc': 4.52.5 - '@rollup/rollup-win32-ia32-msvc': 4.52.5 - '@rollup/rollup-win32-x64-gnu': 4.52.5 - '@rollup/rollup-win32-x64-msvc': 4.52.5 + '@rollup/rollup-android-arm-eabi': 4.53.2 + '@rollup/rollup-android-arm64': 4.53.2 + '@rollup/rollup-darwin-arm64': 4.53.2 + '@rollup/rollup-darwin-x64': 4.53.2 + '@rollup/rollup-freebsd-arm64': 4.53.2 + '@rollup/rollup-freebsd-x64': 4.53.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.2 + '@rollup/rollup-linux-arm-musleabihf': 4.53.2 + '@rollup/rollup-linux-arm64-gnu': 4.53.2 + '@rollup/rollup-linux-arm64-musl': 4.53.2 + '@rollup/rollup-linux-loong64-gnu': 4.53.2 + '@rollup/rollup-linux-ppc64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-musl': 4.53.2 + '@rollup/rollup-linux-s390x-gnu': 4.53.2 + '@rollup/rollup-linux-x64-gnu': 4.53.2 + '@rollup/rollup-linux-x64-musl': 4.53.2 + '@rollup/rollup-openharmony-arm64': 4.53.2 + '@rollup/rollup-win32-arm64-msvc': 4.53.2 + '@rollup/rollup-win32-ia32-msvc': 4.53.2 + '@rollup/rollup-win32-x64-gnu': 4.53.2 + '@rollup/rollup-win32-x64-msvc': 4.53.2 fsevents: 2.3.3 rxjs@7.8.2: @@ -5766,7 +5766,7 @@ snapshots: typedoc@0.28.14(typescript@5.9.3): dependencies: - '@gerrit0/mini-shiki': 3.14.0 + '@gerrit0/mini-shiki': 3.15.0 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 @@ -5878,13 +5878,13 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@7.2.1(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1): + vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.5 + rollup: 4.53.2 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.0 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 8e6c4751..ea524f61 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -234,7 +234,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -390,7 +390,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -456,7 +456,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -473,7 +473,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -710,7 +710,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -998,7 +998,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1257,7 +1257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1267,7 +1267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1294,7 +1294,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1342,7 +1342,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1356,7 +1356,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1367,7 +1367,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1378,7 +1378,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1535,7 +1535,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1545,7 +1545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1558,7 +1558,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1641,7 +1641,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1673,7 +1673,7 @@ checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1804,7 +1804,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -1911,7 +1911,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2026,7 +2026,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2140,7 +2140,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2417,7 +2417,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -2519,7 +2519,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -3400,7 +3400,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -3759,7 +3759,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4067,9 +4067,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags 2.10.0", "cfg-if", @@ -4088,7 +4088,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4108,9 +4108,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -4378,7 +4378,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4425,7 +4425,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4585,7 +4585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4684,7 +4684,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.109", + "syn 2.0.110", "tempfile", ] @@ -4698,7 +4698,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -4759,9 +4759,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "21.0.0" +version = "21.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5b6a0769a491a08b31ea5c62494a8f144ee0987d86d670a8af4df1e1b7cde75" +checksum = "8246feae3db61428fd0bb94285c690b460e4517d83152377543ca802357785f1" dependencies = [ "pulldown-cmark", ] @@ -5039,7 +5039,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5411,7 +5411,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5443,7 +5443,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5555,7 +5555,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5566,7 +5566,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5590,7 +5590,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5651,7 +5651,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5673,7 +5673,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5923,7 +5923,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -5946,7 +5946,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.109", + "syn 2.0.110", "tokio", "url", ] @@ -6131,7 +6131,7 @@ checksum = "68c6387c1c7b53060605101b63d93edca618c6cf7ce61839f2ec2a527419fdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6152,7 +6152,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6185,9 +6185,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.109" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -6211,7 +6211,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6305,7 +6305,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6412,7 +6412,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.109", + "syn 2.0.110", "tauri-utils", "thiserror 2.0.17", "time", @@ -6430,7 +6430,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "tauri-codegen", "tauri-utils", ] @@ -6845,7 +6845,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6856,7 +6856,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -6975,7 +6975,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -7159,7 +7159,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -7184,7 +7184,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.109", + "syn 2.0.110", "tempfile", "tonic-build", ] @@ -7270,7 +7270,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -7550,7 +7550,7 @@ dependencies = [ "indexmap 2.12.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -7565,7 +7565,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.109", + "syn 2.0.110", "toml 0.9.8", "uniffi_meta", ] @@ -7863,7 +7863,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -8073,7 +8073,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -8098,9 +8098,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +checksum = "009936b22a61d342859b5f0ea64681cbb35a358ab548e2a44a8cf0dac2d980b8" [[package]] name = "whoami" @@ -8281,7 +8281,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -8292,7 +8292,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -8917,7 +8917,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "synstructure", ] @@ -8965,7 +8965,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "zbus_names", "zvariant", "zvariant_utils", @@ -9000,7 +9000,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -9020,7 +9020,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "synstructure", ] @@ -9041,7 +9041,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -9074,7 +9074,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", ] [[package]] @@ -9116,7 +9116,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.110", "zvariant_utils", ] @@ -9129,6 +9129,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.109", + "syn 2.0.110", "winnow 0.7.13", ] diff --git a/src-tauri/src/export.rs b/src-tauri/src/export.rs index 59c48b80..e3d9ae48 100644 --- a/src-tauri/src/export.rs +++ b/src-tauri/src/export.rs @@ -5,7 +5,7 @@ use std::{net::IpAddr, str::FromStr}; use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; use serde::Serialize; use sqlx::SqliteExecutor; -use swift_rs::{swift, SRObject, SRString}; +use swift_rs::{swift, SRObject, SRObjectArray, SRString}; #[repr(C)] // Should match the declaration in Swift. @@ -18,6 +18,7 @@ pub(crate) struct Stats { swift!(pub(crate) fn start_tunnel(json: &SRString) -> bool); swift!(pub(crate) fn stop_tunnel(name: &SRString) -> bool); swift!(pub(crate) fn tunnel_stats(name: &SRString) -> Option>); +swift!(pub(crate) fn all_tunnel_stats() -> SRObjectArray); use crate::{ database::models::{location::Location, wireguard_keys::WireguardKeys, Id}, diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index b76c48b3..f1d4d996 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -22,7 +22,7 @@ use windows_sys::Win32::Foundation::ERROR_SERVICE_DOES_NOT_EXIST; #[cfg(windows)] use crate::active_connections::find_connection; #[cfg(target_os = "macos")] -use crate::export::{start_tunnel, stop_tunnel, tunnel_stats}; +use crate::export::{start_tunnel, stop_tunnel, all_tunnel_stats}; #[cfg(not(target_os = "macos"))] use crate::service::{ proto::{CreateInterfaceRequest, ReadInterfaceDataRequest, RemoveInterfaceRequest}, @@ -140,16 +140,17 @@ pub(crate) async fn setup_interface( } #[cfg(target_os = "macos")] -pub(crate) async fn stats_handler(_pool: DbPool, name: String, _connection_type: ConnectionType) { +pub(crate) async fn stats_handler(_pool: DbPool, _interface_name: String, _connection_type: ConnectionType) { const CHECK_INTERVAL: Duration = Duration::from_secs(5); let mut interval = tokio::time::interval(CHECK_INTERVAL); loop { + info!("Stats loop"); interval.tick().await; - // TODO: check all known locations/tunnels, not just `name`. - if let Some(stats) = unsafe { tunnel_stats(&name.as_str().into()) } { + let all_stats = unsafe { all_tunnel_stats() }; + for stats in all_stats.as_slice() { info!( - "Tunnel stats: {} {} {}", + "==> Stats: {} {} {}", stats.last_handshake, stats.tx_bytes, stats.rx_bytes ); } diff --git a/swift/plugin/Sources/Wireguard.swift b/swift/plugin/Sources/Wireguard.swift index 5788821a..42c57890 100644 --- a/swift/plugin/Sources/Wireguard.swift +++ b/swift/plugin/Sources/Wireguard.swift @@ -135,6 +135,74 @@ public func tunnelStats(name: SRString) -> Stats? { return result } +@_cdecl("all_tunnel_stats") +public func allTunnelStats() -> SRObjectArray { + // Blocking + let semaphore = DispatchSemaphore(value: 0) + var stats: [Stats] = [] + + // Get all tunnel provider managers. + NETunnelProviderManager.loadAllFromPreferences { managers, error in + guard let managers = managers else { + logger.info("No tunnel managers in user's settings") + return + } + guard error == nil else { + logger.warning( + "Error loading tunnel managers: \(error, privacy: .public)") + semaphore.signal() + return + } + logger.info("Loaded \(managers.count, privacy: .public) tunnel managers.") + + // `NETunnelProviderSession.sendProviderMessage()` is asynchronous, so use `DispatchGroup`. + let dispatchGroup = DispatchGroup() + + for manager in managers { + guard let tunnelProtocol = manager.protocolConfiguration as? NETunnelProviderProtocol + else { + continue + } + // Sometimes all managers from all apps come through, so filter by bundle ID. + if tunnelProtocol.providerBundleIdentifier != pluginAppId { + continue + } + if let providerManager = manager as NETunnelProviderManager? { + let session = providerManager.connection as! NETunnelProviderSession + do { + // TODO: data should contain a valid message. + let data = Data() + dispatchGroup.enter() + logger.info("Pre send provider message") + try session.sendProviderMessage(data) { response in + logger.info("Post send provider message") + if let data = response { + let decoder = JSONDecoder() + if let result = try? decoder.decode(Stats.self, from: data) { + stats.append(result) + } + } + dispatchGroup.leave() + } + } catch { + logger.error("Failed to send message to tunnel extension \(error)") + dispatchGroup.leave() + } + } + } + + // NOTE: `dispatchGroup.wait()` will cause a dead-lock, because it uses the same thread as + // `NETunnelProviderSession.sendProviderMessage()`. Use this pattern instead: + dispatchGroup.notify(queue: DispatchQueue.global()) { + semaphore.signal() + } + } + + semaphore.wait() + logger.info("Collected \(stats.count) stats") + return SRObjectArray(stats) +} + /// Save `TunnelConfiguration` to preferences. func saveConfig(_ config: TunnelConfiguration) { // Blocking @@ -181,7 +249,6 @@ func saveConfig(_ config: TunnelConfiguration) { semaphore.wait() } - /// Start VPN tunnel for a given `name`. func startVPN(name: String) { managerForName(name) { manager in From ed85054b562b32a3f3d450e0cfd6c02bc990e9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Wed, 12 Nov 2025 11:10:15 +0100 Subject: [PATCH 4/8] Switch to objc2 --- package.json | 2 +- pnpm-lock.yaml | 42 +- src-tauri/Cargo.lock | 53 +- src-tauri/Cargo.toml | 5 +- src-tauri/build.rs | 8 - src-tauri/src/apple.rs | 516 ++++++++++++++++++ src-tauri/src/commands.rs | 11 +- src-tauri/src/database/models/instance.rs | 2 +- src-tauri/src/database/models/tunnel.rs | 2 +- src-tauri/src/export.rs | 137 ----- src-tauri/src/lib.rs | 4 +- src-tauri/src/utils.rs | 96 +++- swift/extension/VPNExtension/Adapter.swift | 22 +- .../VPNExtension/PacketTunnelProvider.swift | 1 + swift/plugin/Sources/Defguard/Stats.swift | 7 +- .../Defguard/TunnelConfiguration.swift | 19 +- swift/plugin/Sources/Shared.swift | 70 --- swift/plugin/Sources/VPNError.swift | 39 -- swift/plugin/Sources/VPNManager.swift | 173 ------ swift/plugin/Sources/Wireguard.swift | 4 - swift/plugin/Sources/WireguardPlugin.swift | 485 ---------------- 21 files changed, 689 insertions(+), 1009 deletions(-) create mode 100644 src-tauri/src/apple.rs delete mode 100644 src-tauri/src/export.rs delete mode 100644 swift/plugin/Sources/Shared.swift delete mode 100644 swift/plugin/Sources/VPNError.swift delete mode 100644 swift/plugin/Sources/VPNManager.swift delete mode 100644 swift/plugin/Sources/WireguardPlugin.swift diff --git a/package.json b/package.json index 19c97ffc..86b27c72 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "@types/react-dom": "^19.2.2", "@vitejs/plugin-react": "^5.1.0", "@vitejs/plugin-react-swc": "^4.2.1", - "autoprefixer": "^10.4.21", + "autoprefixer": "^10.4.22", "npm-run-all": "^4.1.5", "postcss": "^8.5.6", "prettier": "^3.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4da77748..9246f2a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,8 +226,8 @@ importers: specifier: ^4.2.1 version: 4.2.1(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) autoprefixer: - specifier: ^10.4.21 - version: 10.4.21(postcss@8.5.6) + specifier: ^10.4.22 + version: 10.4.22(postcss@8.5.6) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -1368,8 +1368,8 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + autoprefixer@10.4.22: + resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -1406,8 +1406,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.27.0: - resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1685,8 +1685,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.249: - resolution: {integrity: sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==} + electron-to-chromium@1.5.250: + resolution: {integrity: sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1781,8 +1781,8 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} framer-motion@12.23.24: resolution: {integrity: sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==} @@ -3056,7 +3056,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.27.0 + browserslist: 4.28.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -4032,11 +4032,11 @@ snapshots: async-function@1.0.0: {} - autoprefixer@10.4.21(postcss@8.5.6): + autoprefixer@10.4.22(postcss@8.5.6): dependencies: - browserslist: 4.27.0 + browserslist: 4.28.0 caniuse-lite: 1.0.30001754 - fraction.js: 4.3.7 + fraction.js: 5.3.4 normalize-range: 0.1.2 picocolors: 1.1.1 postcss: 8.5.6 @@ -4074,13 +4074,13 @@ snapshots: fill-range: 7.1.1 optional: true - browserslist@4.27.0: + browserslist@4.28.0: dependencies: baseline-browser-mapping: 2.8.25 caniuse-lite: 1.0.30001754 - electron-to-chromium: 1.5.249 + electron-to-chromium: 1.5.250 node-releases: 2.0.27 - update-browserslist-db: 1.1.4(browserslist@4.27.0) + update-browserslist-db: 1.1.4(browserslist@4.28.0) byte-size@9.0.1: {} @@ -4345,7 +4345,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.249: {} + electron-to-chromium@1.5.250: {} emoji-regex@8.0.0: {} @@ -4497,7 +4497,7 @@ snapshots: dependencies: is-callable: 1.2.7 - fraction.js@4.3.7: {} + fraction.js@5.3.4: {} framer-motion@12.23.24(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: @@ -5823,9 +5823,9 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - update-browserslist-db@1.1.4(browserslist@4.27.0): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.27.0 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ea524f61..637f0f99 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1224,9 +1224,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1394,6 +1394,7 @@ dependencies = [ "anyhow", "async-stream", "base64 0.22.1", + "block2 0.6.2", "chrono", "clap", "common", @@ -1405,6 +1406,9 @@ dependencies = [ "known-folders", "log", "nix", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "objc2-network-extension", "prost", "regex", "reqwest", @@ -2282,9 +2286,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -2704,9 +2708,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" dependencies = [ "atomic-waker", "bytes", @@ -3301,9 +3305,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" dependencies = [ "cc", "libc", @@ -3690,9 +3694,9 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ "lazy_static", "libm", @@ -3972,6 +3976,20 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-network-extension" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9126ecdaea052ce2a6b7a894303d0806368b92c77165052da1c3eb52d9e5f9b1" +dependencies = [ + "block2 0.6.2", + "dispatch2", + "libc", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "objc2-security", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -4492,7 +4510,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", "indexmap 2.12.0", - "quick-xml 0.38.3", + "quick-xml 0.38.4", "serde", "time", ] @@ -4792,9 +4810,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.3" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] @@ -6766,10 +6784,11 @@ dependencies = [ [[package]] name = "tauri-winres" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" dependencies = [ + "dunce", "embed-resource", "toml 0.9.8", ] @@ -8098,9 +8117,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009936b22a61d342859b5f0ea64681cbb35a358ab548e2a44a8cf0dac2d980b8" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "whoami" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5709c0f7..a630a866 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -124,7 +124,10 @@ x25519-dalek = { version = "2", features = [ ] } [target.'cfg(target_os = "macos")'.dependencies] -swift-rs = "1.0" +block2 = "0.6" +objc2 = "0.6" +objc2-foundation = "0.3" +objc2-network-extension = "0.3" [target.'cfg(target_os = "macos")'.build-dependencies] swift-rs = { version = "1.0", features = ["build"] } diff --git a/src-tauri/build.rs b/src-tauri/build.rs index dd438547..91455f0e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -23,14 +23,6 @@ fn main() -> Result<(), Box> { tauri_build::build(); - #[cfg(target_os = "macos")] - swift_rs::SwiftLinker::new("13") - .with_ios("15") - .with_package("defguard-vpn-plugin", "../swift/plugin") - .link(); - println!("cargo:rerun-if-changed=proto"); - #[cfg(target_os = "macos")] - println!("cargo:rerun-if-changed=../swift"); Ok(()) } diff --git a/src-tauri/src/apple.rs b/src-tauri/src/apple.rs new file mode 100644 index 00000000..6ebaeaa9 --- /dev/null +++ b/src-tauri/src/apple.rs @@ -0,0 +1,516 @@ +//! Structures used for interchangeability with the Swift code. + +use std::{ + hint::spin_loop, + net::IpAddr, + str::FromStr, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::channel, + Arc, + }, +}; + +use block2::RcBlock; +use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; +use objc2::{rc::Retained, runtime::AnyObject}; +use objc2_foundation::{ + ns_string, NSArray, NSDictionary, NSError, NSMutableArray, NSMutableDictionary, NSNull, + NSNumber, NSString, +}; +use objc2_network_extension::{NETunnelProviderManager, NETunnelProviderProtocol}; +use serde::Serialize; +use sqlx::SqliteExecutor; + +use crate::{ + database::models::{location::Location, tunnel::Tunnel, wireguard_keys::WireguardKeys, Id}, + error::Error, + utils::{DEFAULT_ROUTE_IPV4, DEFAULT_ROUTE_IPV6}, +}; + +// const BUNDLE_ID: &str = "net.defguard"; +const PLUGIN_BUNDLE_ID: &str = "net.defguard.VPNExtension"; + +// Should match the declaration in Swift. +#[repr(C)] +pub(crate) struct Stats { + pub(crate) location_id: Option, + pub(crate) tunnel_id: Option, + pub(crate) tx_bytes: u64, + pub(crate) rx_bytes: u64, + pub(crate) last_handshake: u64, +} + +/// Find `NETunnelProviderManager` in system preferences. +fn manager_for_name(name: &str) -> Option> { + let name_string = NSString::from_str(&name); + let plugin_bundle_id = NSString::from_str(PLUGIN_BUNDLE_ID); + let (tx, rx) = channel(); + + let handler = RcBlock::new( + move |managers_ptr: *mut NSArray, error_ptr: *mut NSError| { + error!("ADAM is here"); + if !error_ptr.is_null() { + error!("Failed to load tunnel provider managers."); + return; + } + + let Some(managers) = (unsafe { managers_ptr.as_ref() }) else { + error!("No managers"); + return; + }; + + for manager in managers { + let Some(vpn_protocol) = (unsafe { manager.protocolConfiguration() }) else { + continue; + }; + let Ok(tunnel_protocol) = vpn_protocol.downcast::() + else { + error!("Failed to downcast to NETunnelProviderProtocol"); + continue; + }; + // Sometimes all managers from all apps come through, so filter by bundle ID. + if let Some(bundle_id) = unsafe { tunnel_protocol.providerBundleIdentifier() } { + if bundle_id != plugin_bundle_id { + continue; + } + } + if let Some(descr) = unsafe { manager.localizedDescription() } { + error!("Descripion {descr}"); + if descr == name_string { + tx.send(Some(manager)).unwrap(); + return; + } + } + } + + tx.send(None).unwrap(); + }, + ); + unsafe { + NETunnelProviderManager::loadAllFromPreferencesWithCompletionHandler(&*handler); + } + + rx.recv().unwrap() +} + +#[derive(Serialize)] +pub(crate) struct TunnelConfiguration { + #[serde(rename = "locationId")] + location_id: Option, + #[serde(rename = "tunnelId")] + tunnel_id: Option, + name: String, + #[serde(rename = "privateKey")] + private_key: String, + addresses: Vec, + #[serde(rename = "listenPort")] + listen_port: Option, + peers: Vec, + mtu: Option, + dns: Vec, + #[serde(rename = "dnsSearch")] + dns_search: Vec, +} + +impl TunnelConfiguration { + /// Convert to `NSDictionary`. + fn as_nsdict(&self) -> Retained> { + let dict = NSMutableDictionary::new(); + + if let Some(location_id) = self.location_id { + dict.insert( + ns_string!("locationId"), + NSNumber::new_i64(location_id).as_ref(), + ); + } + + if let Some(tunnel_id) = self.tunnel_id { + dict.insert( + ns_string!("tunnelId"), + NSNumber::new_i64(tunnel_id).as_ref(), + ); + } + + dict.insert(ns_string!("name"), NSString::from_str(&self.name).as_ref()); + + dict.insert( + ns_string!("privateKey"), + NSString::from_str(&self.private_key).as_ref(), + ); + + // IpAddrMask + let addresses = NSMutableArray::>::new(); + for addr in &self.addresses { + let addr_dict = NSMutableDictionary::::new(); + addr_dict.insert( + ns_string!("address"), + NSString::from_str(&addr.address.to_string()).as_ref(), + ); + addr_dict.insert(ns_string!("cidr"), NSNumber::new_u8(addr.cidr).as_ref()); + addresses.addObject(addr_dict.into_super().as_ref()); + } + dict.insert(ns_string!("addresses"), addresses.as_ref()); + + if let Some(listen_port) = self.listen_port { + dict.insert( + ns_string!("listenPort"), + NSNumber::new_u16(listen_port).as_ref(), + ); + } + + // Peer + let peers = NSMutableArray::>::new(); + for peer in &self.peers { + let peer_dict = NSMutableDictionary::::new(); + peer_dict.insert( + ns_string!("public_key"), + NSString::from_str(&peer.public_key.to_string()).as_ref(), + ); + + if let Some(preshared_key) = &peer.preshared_key { + peer_dict.insert( + ns_string!("preshared_key"), + NSString::from_str(&preshared_key.to_string()).as_ref(), + ); + } + + if let Some(endpoint) = &peer.endpoint { + peer_dict.insert( + ns_string!("endpoint"), + NSString::from_str(&endpoint.to_string()).as_ref(), + ); + } + + // Skipping: last_handshake + + peer_dict.insert( + ns_string!("tx_bytes"), + NSNumber::new_u64(peer.tx_bytes).as_ref(), + ); + peer_dict.insert( + ns_string!("rx_bytes"), + NSNumber::new_u64(peer.rx_bytes).as_ref(), + ); + + if let Some(persistent_keep_alive) = peer.persistent_keepalive_interval { + peer_dict.insert( + ns_string!("persistent_keepalive_interval"), + NSNumber::new_u16(persistent_keep_alive).as_ref(), + ); + } + + // IpAddrMask + let allowed_ips = NSMutableArray::>::new(); + for addr in &peer.allowed_ips { + let addr_dict = NSMutableDictionary::::new(); + addr_dict.insert( + ns_string!("address"), + NSString::from_str(&addr.address.to_string()).as_ref(), + ); + addr_dict.insert(ns_string!("cidr"), NSNumber::new_u8(addr.cidr).as_ref()); + allowed_ips.addObject(addr_dict.into_super().as_ref()); + } + peer_dict.insert(ns_string!("allowed_ips"), allowed_ips.as_ref()); + + peers.addObject(peer_dict.into_super().as_ref()); + } + dict.insert(ns_string!("peers"), peers.into_super().as_ref()); + + if let Some(mtu) = self.mtu { + dict.insert(ns_string!("mtu"), NSNumber::new_u32(mtu).as_ref()); + } + + let dns = NSMutableArray::::new(); + for entry in &self.dns { + dns.addObject(NSString::from_str(&entry.to_string()).as_ref()); + } + dict.insert(ns_string!("dns"), dns.as_ref()); + + let dns_search = NSMutableArray::::new(); + for entry in &self.dns_search { + dns_search.addObject(NSString::from_str(&entry).as_ref()); + } + dict.insert(ns_string!("dnsSearch"), dns_search.as_ref()); + + dict.into_super() + } + + /// Create or update system VPN settings with this configuration. + pub(crate) fn save(&self) { + unsafe { + let provider_manager = + manager_for_name(&self.name).unwrap_or_else(|| NETunnelProviderManager::new()); + + if let Some(vpn_protocol) = provider_manager.protocolConfiguration() { + if let Ok(tunnel_protocol) = vpn_protocol.downcast::() { + info!("CURRENT CONFIG {tunnel_protocol:?}"); + // if let Some(config) = tunnel_protocol.providerConfiguration() { + // info!("CURRENT CONFIG {config:?}"); + // } + } + } + + let tunnel_protocol = NETunnelProviderProtocol::new(); + let plugin_bundle_id = NSString::from_str(PLUGIN_BUNDLE_ID); + tunnel_protocol.setProviderBundleIdentifier(Some(&plugin_bundle_id)); + let server_address = self.peers.get(0).map_or(String::new(), |peer| { + peer.endpoint.map_or(String::new(), |sa| sa.to_string()) + }); + let server_address = NSString::from_str(&server_address); + // `serverAddress` must have a non-nil string value for the protocol configuration to be + // valid. + tunnel_protocol.setServerAddress(Some(&server_address)); + + let provider_config = self.as_nsdict(); + tunnel_protocol.setProviderConfiguration(Some(&*provider_config)); + + provider_manager.setProtocolConfiguration(Some(&tunnel_protocol)); + let name = NSString::from_str(&self.name); + provider_manager.setLocalizedDescription(Some(&name)); + provider_manager.setEnabled(true); + + // Save to preferences. + let spinlock = Arc::new(AtomicBool::new(false)); + let spinlock_clone = Arc::clone(&spinlock); + let name = self.name.clone(); + let handler = RcBlock::new(move |error_ptr: *mut NSError| { + if error_ptr.is_null() { + info!("Saved tunnel configuration for {name}"); + } else { + error!("Failed to save tunnel configuration for: {name}"); + } + spinlock_clone.store(true, Ordering::Release); + }); + provider_manager.saveToPreferencesWithCompletionHandler(Some(&*handler)); + while !spinlock.load(Ordering::Acquire) { + spin_loop(); + } + + // Sanity check + let descr = provider_manager.localizedDescription(); + info!("SANITY DESCR {descr:?}"); + if let Some(vpn_protocol) = provider_manager.protocolConfiguration() { + if let Ok(tunnel_protocol) = vpn_protocol.downcast::() { + info!("SAVED CONFIG {tunnel_protocol:?}"); + } + } + } + } +} + +/// IMPORTANT: This is currently for testing. Assume the config has been saved. +pub(crate) fn start_tunnel(name: &str) { + if let Some(provider_manager) = manager_for_name(name) { + if let Err(err) = unsafe { provider_manager.connection().startVPNTunnelAndReturnError() } { + error!("Failed to start VPN: {err}"); + } else { + info!("VPN started"); + } + } else { + error!("Couldn't find configuration from preferences for {name}"); + } +} + +/// IMPORTANT: This is currently for testing. Assume the config has been saved. +pub(crate) fn stop_tunnel(name: &str) -> bool { + if let Some(provider_manager) = manager_for_name(name) { + unsafe { + provider_manager.connection().stopVPNTunnel(); + } + info!("VPN stopped"); + true + } else { + error!("Couldn't find configuration from preferences for {name}"); + false + } +} + +/// IMPORTANT: This is currently for testing. Assume the config has been saved. +pub(crate) fn all_tunnel_stats() -> Vec { + Vec::::new() +} + +impl Location { + pub(crate) async fn tunnel_configurarion<'e, E>( + &self, + executor: E, + preshared_key: Option, + dns: Vec, + dns_search: Vec, + ) -> Result + where + E: SqliteExecutor<'e>, + { + debug!("Looking for WireGuard keys for location {self} instance"); + let Some(keys) = WireguardKeys::find_by_instance_id(executor, self.instance_id).await? + else { + error!("No keys found for instance: {}", self.instance_id); + return Err(Error::InternalError( + "No keys found for instance".to_string(), + )); + }; + debug!("WireGuard keys found for location {self} instance"); + + // prepare peer config + debug!("Decoding location {self} public key: {}.", self.pubkey); + let peer_key = Key::from_str(&self.pubkey)?; + debug!("Location {self} public key decoded: {peer_key}"); + let mut peer = Peer::new(peer_key); + + debug!("Parsing location {self} endpoint: {}", self.endpoint); + peer.set_endpoint(&self.endpoint)?; + peer.persistent_keepalive_interval = Some(25); + debug!("Parsed location {self} endpoint: {}", self.endpoint); + + if let Some(psk) = preshared_key { + debug!("Decoding location {self} preshared key."); + let peer_psk = Key::from_str(&psk)?; + info!("Location {self} preshared key decoded."); + peer.preshared_key = Some(peer_psk); + } + + debug!("Parsing location {self} allowed IPs: {}", self.allowed_ips); + let allowed_ips = if self.route_all_traffic { + debug!("Using all traffic routing for location {self}"); + vec![DEFAULT_ROUTE_IPV4.into(), DEFAULT_ROUTE_IPV6.into()] + } else { + debug!( + "Using predefined location {self} traffic: {}", + self.allowed_ips + ); + self.allowed_ips.split(',').map(str::to_string).collect() + }; + for allowed_ip in &allowed_ips { + match IpAddrMask::from_str(allowed_ip) { + Ok(addr) => { + peer.allowed_ips.push(addr); + } + Err(err) => { + // Handle the error from IpAddrMask::from_str, if needed + error!( + "Error parsing IP address {allowed_ip} while setting up interface for \ + location {self}, error details: {err}" + ); + } + } + } + debug!( + "Parsed allowed IPs for location {self}: {:?}", + peer.allowed_ips + ); + + let addresses = self + .address + .split(',') + .map(str::trim) + .map(IpAddrMask::from_str) + .collect::>() + .map_err(|err| { + let msg = format!("Failed to parse IP addresses '{}': {err}", self.address); + error!("{msg}"); + Error::InternalError(msg) + })?; + Ok(TunnelConfiguration { + location_id: Some(self.id), + tunnel_id: None, + name: self.name.clone(), + private_key: keys.prvkey, + addresses, + listen_port: Some(0), + peers: vec![peer], + mtu: None, + dns, + dns_search, + }) + } +} + +impl Tunnel { + pub(crate) async fn tunnel_configurarion<'e, E>( + &self, + executor: E, + dns: Vec, + dns_search: Vec, + ) -> Result + where + E: SqliteExecutor<'e>, + { + // prepare peer config + debug!("Decoding tunnel {self} public key: {}.", self.server_pubkey); + let peer_key = Key::from_str(&self.server_pubkey)?; + debug!("Tunnel {self} public key decoded."); + let mut peer = Peer::new(peer_key); + + debug!("Parsing tunnel {self} endpoint: {}", self.endpoint); + peer.set_endpoint(&self.endpoint)?; + peer.persistent_keepalive_interval = Some( + self.persistent_keep_alive + .try_into() + .expect("Failed to parse persistent keep alive"), + ); + debug!("Parsed tunnel {self} endpoint: {}", self.endpoint); + + if let Some(psk) = &self.preshared_key { + debug!("Decoding tunnel {self} preshared key."); + let peer_psk = Key::from_str(psk)?; + debug!("Preshared key for tunnel {self} decoded."); + peer.preshared_key = Some(peer_psk); + } + + debug!("Parsing tunnel {self} allowed ips: {:?}", self.allowed_ips); + let allowed_ips = if self.route_all_traffic { + debug!("Using all traffic routing for tunnel {self}"); + vec![DEFAULT_ROUTE_IPV4.into(), DEFAULT_ROUTE_IPV6.into()] + } else { + let msg = match &self.allowed_ips { + Some(ips) => { + format!("Using predefined location traffic for tunnel {self}: {ips}") + } + None => "No allowed IP addresses found in tunnel {self} configuration".to_string(), + }; + debug!("{msg}"); + self.allowed_ips + .as_ref() + .map(|ips| ips.split(',').map(str::to_string).collect()) + .unwrap_or_default() + }; + for allowed_ip in &allowed_ips { + match IpAddrMask::from_str(allowed_ip.trim()) { + Ok(addr) => { + peer.allowed_ips.push(addr); + } + Err(err) => { + // Handle the error from IpAddrMask::from_str, if needed + error!("Error parsing IP address {allowed_ip}: {err}"); + // Continue to the next iteration of the loop + } + } + } + debug!("Parsed tunnel {self} allowed IPs: {:?}", peer.allowed_ips); + + let addresses = self + .address + .split(',') + .map(str::trim) + .map(IpAddrMask::from_str) + .collect::>() + .map_err(|err| { + let msg = format!("Failed to parse IP addresses '{}': {err}", self.address); + error!("{msg}"); + Error::InternalError(msg) + })?; + Ok(TunnelConfiguration { + location_id: None, + tunnel_id: Some(self.id), + name: self.name.clone(), + private_key: self.prvkey.clone(), + addresses, + listen_port: Some(0), + peers: vec![peer], + mtu: None, + dns, + dns_search, + }) + } +} diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 60142f8e..5d682ee9 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -9,14 +9,12 @@ use chrono::{DateTime, Duration, NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::{Sqlite, Transaction}; use struct_patch::Patch; -#[cfg(target_os = "macos")] -use swift_rs::SRString; use tauri::{AppHandle, Emitter, Manager, State}; const UPDATE_URL: &str = "https://pkgs.defguard.net/api/update/check"; #[cfg(target_os = "macos")] -use crate::export::stop_tunnel; +use crate::apple::stop_tunnel; #[cfg(not(target_os = "macos"))] use crate::service::{ proto::{ @@ -966,13 +964,10 @@ pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), E .remove_connection(location.id, ConnectionType::Location) .await { - let result = unsafe { - let name: SRString = location.name.as_str().into(); - stop_tunnel(&name) - }; + let result = stop_tunnel(&location.name); error!("stop_tunnel() for location returned {result:?}"); if !result { - return Err(Error::InternalError("Error from Swift".into())); + return Err(Error::InternalError("Error from tunnel".into())); } } } diff --git a/src-tauri/src/database/models/instance.rs b/src-tauri/src/database/models/instance.rs index bcd74499..1a8339ba 100644 --- a/src-tauri/src/database/models/instance.rs +++ b/src-tauri/src/database/models/instance.rs @@ -1,4 +1,4 @@ -use core::fmt; +use std::fmt; use serde::{Deserialize, Serialize}; use sqlx::{query, query_as, SqliteExecutor}; diff --git a/src-tauri/src/database/models/tunnel.rs b/src-tauri/src/database/models/tunnel.rs index 24aa7abf..b53570d5 100644 --- a/src-tauri/src/database/models/tunnel.rs +++ b/src-tauri/src/database/models/tunnel.rs @@ -255,7 +255,7 @@ impl Tunnel { #[derive(Debug, Serialize, Deserialize)] pub struct TunnelStats { id: I, - pub tunnel_id: Id, + pub(crate) tunnel_id: Id, upload: i64, download: i64, pub(crate) last_handshake: i64, diff --git a/src-tauri/src/export.rs b/src-tauri/src/export.rs deleted file mode 100644 index e3d9ae48..00000000 --- a/src-tauri/src/export.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! Structures used for interchangeability with the Swift code. - -use std::{net::IpAddr, str::FromStr}; - -use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; -use serde::Serialize; -use sqlx::SqliteExecutor; -use swift_rs::{swift, SRObject, SRObjectArray, SRString}; - -#[repr(C)] -// Should match the declaration in Swift. -pub(crate) struct Stats { - pub(crate) tx_bytes: u64, - pub(crate) rx_bytes: u64, - pub(crate) last_handshake: u64, -} - -swift!(pub(crate) fn start_tunnel(json: &SRString) -> bool); -swift!(pub(crate) fn stop_tunnel(name: &SRString) -> bool); -swift!(pub(crate) fn tunnel_stats(name: &SRString) -> Option>); -swift!(pub(crate) fn all_tunnel_stats() -> SRObjectArray); - -use crate::{ - database::models::{location::Location, wireguard_keys::WireguardKeys, Id}, - error::Error, - utils::{DEFAULT_ROUTE_IPV4, DEFAULT_ROUTE_IPV6}, -}; - -#[derive(Serialize)] -pub(crate) struct TunnelConfiguration { - name: String, - #[serde(rename = "privateKey")] - private_key: String, - addresses: Vec, - #[serde(rename = "listenPort")] - listen_port: Option, - peers: Vec, - mtu: Option, - dns: Vec, - #[serde(rename = "dnsSearch")] - dns_search: Vec, -} - -impl Location { - pub(crate) async fn tunnel_configurarion<'e, E>( - &self, - executor: E, - preshared_key: Option, - dns: Vec, - dns_search: Vec, - ) -> Result - where - E: SqliteExecutor<'e>, - { - debug!("Looking for WireGuard keys for location {self} instance"); - let Some(keys) = WireguardKeys::find_by_instance_id(executor, self.instance_id).await? - else { - error!("No keys found for instance: {}", self.instance_id); - return Err(Error::InternalError( - "No keys found for instance".to_string(), - )); - }; - debug!("WireGuard keys found for location {self} instance"); - - // prepare peer config - debug!("Decoding location {self} public key: {}.", self.pubkey); - let peer_key = Key::from_str(&self.pubkey)?; - debug!("Location {self} public key decoded: {peer_key}"); - let mut peer = Peer::new(peer_key); - - debug!("Parsing location {self} endpoint: {}", self.endpoint); - peer.set_endpoint(&self.endpoint)?; - peer.persistent_keepalive_interval = Some(25); - debug!("Parsed location {self} endpoint: {}", self.endpoint); - - if let Some(psk) = preshared_key { - debug!("Decoding location {self} preshared key."); - let peer_psk = Key::from_str(&psk)?; - info!("Location {self} preshared key decoded."); - peer.preshared_key = Some(peer_psk); - } - - debug!("Parsing location {self} allowed IPs: {}", self.allowed_ips); - let allowed_ips = if self.route_all_traffic { - debug!("Using all traffic routing for location {self}"); - vec![DEFAULT_ROUTE_IPV4.into(), DEFAULT_ROUTE_IPV6.into()] - } else { - debug!( - "Using predefined location {self} traffic: {}", - self.allowed_ips - ); - self.allowed_ips.split(',').map(str::to_string).collect() - }; - for allowed_ip in &allowed_ips { - match IpAddrMask::from_str(allowed_ip) { - Ok(addr) => { - peer.allowed_ips.push(addr); - } - Err(err) => { - // Handle the error from IpAddrMask::from_str, if needed - error!( - "Error parsing IP address {allowed_ip} while setting up interface for \ - location {self}, error details: {err}" - ); - } - } - } - debug!( - "Parsed allowed IPs for location {self}: {:?}", - peer.allowed_ips - ); - - let addresses = self - .address - .split(',') - .map(str::trim) - .map(IpAddrMask::from_str) - .collect::>() - .map_err(|err| { - let msg = format!("Failed to parse IP addresses '{}': {err}", self.address); - error!("{msg}"); - Error::InternalError(msg) - })?; - let interface_config = TunnelConfiguration { - name: self.name.clone(), - private_key: keys.prvkey, - addresses, - listen_port: Some(0), - peers: vec![peer], - mtu: None, - dns, - dns_search, - }; - - Ok(interface_config) - } -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 16692304..432d71e2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,14 +17,14 @@ use self::database::models::{Id, NoId}; pub mod active_connections; pub mod app_config; +#[cfg(target_os = "macos")] +mod apple; pub mod appstate; pub mod commands; pub mod database; pub mod enterprise; pub mod error; pub mod events; -#[cfg(target_os = "macos")] -mod export; pub mod log_watcher; pub mod periodic; pub mod proto; diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index f1d4d996..e0a188c4 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -5,8 +5,6 @@ use std::{env, path::Path, process::Command, str::FromStr}; use common::{find_free_tcp_port, get_interface_name}; use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration}; use sqlx::query; -#[cfg(target_os = "macos")] -use swift_rs::SRString; use tauri::{AppHandle, Emitter, Manager}; #[cfg(not(target_os = "macos"))] use tonic::Code; @@ -22,7 +20,7 @@ use windows_sys::Win32::Foundation::ERROR_SERVICE_DOES_NOT_EXIST; #[cfg(windows)] use crate::active_connections::find_connection; #[cfg(target_os = "macos")] -use crate::export::{start_tunnel, stop_tunnel, all_tunnel_stats}; +use crate::apple::{all_tunnel_stats, start_tunnel, stop_tunnel}; #[cfg(not(target_os = "macos"))] use crate::service::{ proto::{CreateInterfaceRequest, ReadInterfaceDataRequest, RemoveInterfaceRequest}, @@ -50,6 +48,8 @@ use crate::{ pub(crate) static DEFAULT_ROUTE_IPV4: &str = "0.0.0.0/0"; pub(crate) static DEFAULT_ROUTE_IPV6: &str = "::/0"; +// Work-around MFA propagation delay. FIXME: remove once Core API is corrected. +static TUNNEL_START_DELAY: Duration = Duration::from_secs(1); /// Setup client interface for `Instance`. #[cfg(not(target_os = "macos"))] @@ -128,32 +128,84 @@ pub(crate) async fn setup_interface( .tunnel_configurarion(pool, preshared_key, dns, dns_search) .await?; - unsafe { - let json: SRString = serde_json::to_string(&tunnel_config) - .unwrap() - .as_str() - .into(); - let result = start_tunnel(&json); - error!("start_tunnel() returned {result:?}"); - } + tunnel_config.save(); + tokio::time::sleep(TUNNEL_START_DELAY).await; + start_tunnel(&location.name); + Ok(interface_name) } #[cfg(target_os = "macos")] -pub(crate) async fn stats_handler(_pool: DbPool, _interface_name: String, _connection_type: ConnectionType) { +pub(crate) async fn stats_handler( + pool: DbPool, + _interface_name: String, + _connection_type: ConnectionType, +) { const CHECK_INTERVAL: Duration = Duration::from_secs(5); let mut interval = tokio::time::interval(CHECK_INTERVAL); loop { info!("Stats loop"); interval.tick().await; - let all_stats = unsafe { all_tunnel_stats() }; - for stats in all_stats.as_slice() { + + let all_stats = all_tunnel_stats(); + if all_stats.len() == 0 { + continue; + } + // Let `all_stats` be `Send`. + let all_stats = all_stats.as_slice().to_owned(); + + // let mut transaction = match pool.begin().await { + // Ok(transactions) => transactions, + // Err(err) => { + // error!( + // "Failed to begin database transaction for saving location/tunnel stats: {err}", + // ); + // continue; + // } + // }; + + for stats in all_stats { info!( "==> Stats: {} {} {}", stats.last_handshake, stats.tx_bytes, stats.rx_bytes ); + + if let Some(location_id) = stats.location_id { + //let location_stats = LocationStats { + //}; + /*match location_stats.save(&mut *transaction).await { + Ok(_) => { + debug!("Saved network usage stats for location ID {location_id}"); + } + Err(err) => { + error!( + "Failed to save network usage stats for location ID {location_id}: \ + {err}" + ); + } + }*/ + } + if let Some(tunnel_id) = stats.tunnel_id { + //let tunnel_stats = TunnelStats { + //}; + /*match tunnel_stats.save(&mut *transaction).await { + Ok(_) => { + debug!("Saved network usage stats for tunnel ID {tunnel_id}"); + } + Err(err) => { + error!( + "Failed to save network usage stats for tunnel ID {tunnel_id}: \ + {err}" + ); + } + }*/ + } } + + // if let Err(err) = transaction.commit().await { + // error!("Failed to commit database transaction for saving location/tunnel stats: {err}"); + // } } } @@ -685,13 +737,10 @@ pub(crate) async fn disconnect_interface( #[cfg(target_os = "macos")] { - let result = unsafe { - let name: SRString = location.name.as_str().into(); - stop_tunnel(&name) - }; + let result = stop_tunnel(&location.name); error!("stop_tunnel() for location returned {result:?}"); if !result { - return Err(Error::InternalError("Error from Swift".into())); + return Err(Error::InternalError("Error from tunnel".into())); } } @@ -762,13 +811,10 @@ pub(crate) async fn disconnect_interface( #[cfg(target_os = "macos")] { - let result = unsafe { - let name: SRString = tunnel.name.as_str().into(); - stop_tunnel(&name) - }; - error!("stop_tunnel() for tunnel returned {result:?}"); + let result = stop_tunnel(&tunnel.name); + error!("stop_tunnel() for location returned {result:?}"); if !result { - return Err(Error::InternalError("Error from Swift".into())); + return Err(Error::InternalError("Error from tunnel".into())); } } diff --git a/swift/extension/VPNExtension/Adapter.swift b/swift/extension/VPNExtension/Adapter.swift index 65d289ac..0899bed9 100644 --- a/swift/extension/VPNExtension/Adapter.swift +++ b/swift/extension/VPNExtension/Adapter.swift @@ -31,6 +31,10 @@ enum State { /// Adapter state. private var state: State = .stopped + /// For statistics returned to Rust code. + var locationId: UInt64? + var tunnelId: UInt64? + private let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() /// Designated initializer. @@ -71,6 +75,8 @@ enum State { keepAlive: tunnelConfiguration.peers[0].persistentKeepAlive, index: 0 ) + locationId = tunnelConfiguration.locationId + tunnelId = tunnelConfiguration.tunnelId logger.info("Connecting to endpoint") guard let endpoint = tunnelConfiguration.peers[0].endpoint else { @@ -84,10 +90,6 @@ enum State { readPackets() state = .running - - // Test notifications - // let notificationName = CFNotificationName("net.defguard.client.start" as CFString) - // CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false) } func stop() { @@ -103,16 +105,18 @@ enum State { state = .stopped logger.info("Tunnel stopped") - - // Test notifications - // let notificationName = CFNotificationName("net.defguard.client.stop" as CFString) - // CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false) } // Obtain tunnel statistics. func stats() -> Stats? { if let stats = tunnel?.stats() { - return Stats(txBytes: stats.txBytes, rxBytes: stats.rxBytes, lastHandshake: stats.lastHandshake) + return Stats( + txBytes: stats.txBytes, + rxBytes: stats.rxBytes, + lastHandshake: stats.lastHandshake, + locationId: locationId, + tunnelId: tunnelId + ) } return nil } diff --git a/swift/extension/VPNExtension/PacketTunnelProvider.swift b/swift/extension/VPNExtension/PacketTunnelProvider.swift index addccb60..55c1d18f 100644 --- a/swift/extension/VPNExtension/PacketTunnelProvider.swift +++ b/swift/extension/VPNExtension/PacketTunnelProvider.swift @@ -22,6 +22,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { guard let protocolConfig = self.protocolConfiguration as? NETunnelProviderProtocol, let providerConfig = protocolConfig.providerConfiguration, let tunnelConfig = try? TunnelConfiguration.from(dictionary: providerConfig) else { + self.logger.error("Failed to parse tunnel configuration") completionHandler(WireGuardTunnelError.invalidTunnelConfiguration) return } diff --git a/swift/plugin/Sources/Defguard/Stats.swift b/swift/plugin/Sources/Defguard/Stats.swift index e77437e6..2c833446 100644 --- a/swift/plugin/Sources/Defguard/Stats.swift +++ b/swift/plugin/Sources/Defguard/Stats.swift @@ -4,10 +4,15 @@ public class Stats: NSObject, Codable { var txBytes: UInt64 var rxBytes: UInt64 var lastHandshake: UInt64 + // One or the other. + var locationId: UInt64? + var tunnelId: UInt64? - init(txBytes: UInt64, rxBytes: UInt64, lastHandshake: UInt64) { + init(txBytes: UInt64, rxBytes: UInt64, lastHandshake: UInt64, locationId: UInt64?, tunnelId: UInt64?) { self.txBytes = txBytes self.rxBytes = rxBytes self.lastHandshake = lastHandshake + self.locationId = locationId + self.tunnelId = tunnelId } } diff --git a/swift/plugin/Sources/Defguard/TunnelConfiguration.swift b/swift/plugin/Sources/Defguard/TunnelConfiguration.swift index a19c1e65..ded41f36 100644 --- a/swift/plugin/Sources/Defguard/TunnelConfiguration.swift +++ b/swift/plugin/Sources/Defguard/TunnelConfiguration.swift @@ -2,6 +2,10 @@ import Foundation import NetworkExtension final class TunnelConfiguration: Codable { + // One or the other. + var locationId: UInt64? + var tunnelId: UInt64? + var name: String var privateKey: String var addresses: [IpAddrMask] = [] @@ -78,8 +82,9 @@ final class TunnelConfiguration: Codable { // Routes to interface addresses. for addr_mask in addresses { if addr_mask.address is IPv4Address { - let route = NEIPv4Route(destinationAddress: "\(addr_mask.address)", - subnetMask: "\(addr_mask.mask())") + let route = NEIPv4Route( + destinationAddress: "\(addr_mask.address)", + subnetMask: "\(addr_mask.mask())") route.gatewayAddress = "\(addr_mask.address)" ipv4IncludedRoutes.append(route) } else if addr_mask.address is IPv6Address { @@ -97,12 +102,14 @@ final class TunnelConfiguration: Codable { for addr_mask in peer.allowedIPs { if addr_mask.address is IPv4Address { ipv4IncludedRoutes.append( - NEIPv4Route(destinationAddress: "\(addr_mask.address)", - subnetMask: "\(addr_mask.mask())")) + NEIPv4Route( + destinationAddress: "\(addr_mask.address)", + subnetMask: "\(addr_mask.mask())")) } else if addr_mask.address is IPv6Address { ipv6IncludedRoutes.append( - NEIPv6Route(destinationAddress: "\(addr_mask.address)", - networkPrefixLength: NSNumber(value: addr_mask.cidr))) + NEIPv6Route( + destinationAddress: "\(addr_mask.address)", + networkPrefixLength: NSNumber(value: addr_mask.cidr))) } } } diff --git a/swift/plugin/Sources/Shared.swift b/swift/plugin/Sources/Shared.swift deleted file mode 100644 index 35eaaacc..00000000 --- a/swift/plugin/Sources/Shared.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// THIS IS A SIMPLE TEMPORARY SOLUTION TO SHARE SOME TYPES BETWEEN THE POD AND VPNEXTENSION -// WE SHOULD PROBABLY COME UP WITH A BETTER SOLUTION IN THE FUTURE -// - -import Foundation - -let suiteName = "group.net.defguard.mobile" - -public enum TunnelTraffic: String, Codable { - case All = "all" - case Predefined = "predefined" -} - -public struct TunnelStartData: Codable { - public var publicKey: String - public var privateKey: String - public var address: String - public var dns: String? - public var endpoint: String - public var allowedIps: String - public var keepalive: Int - public var presharedKey: String? - public var traffic: TunnelTraffic - public var locationName: String - public var locationId: Int - public var instanceId: Int - - public init( - publicKey: String, privateKey: String, address: String, dns: String? = nil, - endpoint: String, allowedIps: String, keepalive: Int, presharedKey: String? = nil, - traffic: TunnelTraffic, locationName: String, locationId: Int, instanceId: Int - ) { - self.publicKey = publicKey - self.privateKey = privateKey - self.address = address - self.dns = dns - self.endpoint = endpoint - self.allowedIps = allowedIps - self.keepalive = keepalive - self.presharedKey = presharedKey - self.traffic = traffic - self.locationName = locationName - self.locationId = locationId - self.instanceId = instanceId - } -} - -public struct ActiveTunnelData: Codable { - var locationId: Int - var instanceId: Int - var traffic: TunnelTraffic - - init(fromConfig: TunnelStartData) { - self.locationId = fromConfig.locationId - self.instanceId = fromConfig.instanceId - self.traffic = fromConfig.traffic - } -} - -public enum WireguardEvent: String { - case tunnelUp = "tunnel_up" - case tunnelDown = "tunnel_down" - case tunnelWaiting = "tunnel_waiting" - case MFASessionExpired = "mfa_session_expired" -} - -public enum TunnelStopError: String { - case mfaSessionExpired = "mfa_session_expired" -} diff --git a/swift/plugin/Sources/VPNError.swift b/swift/plugin/Sources/VPNError.swift deleted file mode 100644 index 213ed1af..00000000 --- a/swift/plugin/Sources/VPNError.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation - -enum VPNError: Error, LocalizedError { - case invalidArguments(String) - case noManager(String = "No VPN manager available") - case configurationError(Error) - case timeoutError(String) - case saveError(Error) - case startError(Error) - case stopError(Error) - case invalidConfig - - var errorDescription: String? { - switch self { - case .invalidArguments(let msg): return "Invalid arguments: \(msg)" - case .noManager(let msg): return "\(msg)" - case .configurationError(let error): - return "Configuration parsing error: \(error.localizedDescription)" - case .timeoutError(let msg): return "Timeout: \(msg)" - case .saveError(let error): return "Save error: \(error.localizedDescription)" - case .startError(let error): return "Start error: \(error.localizedDescription)" - case .stopError(let error): return "Stop error: \(error.localizedDescription)" - case .invalidConfig: return "Invalid configuration for client connection" - } - } - -// private var code: String { -// switch self { -// case .invalidArguments: return "INVALID_ARGUMENTS" -// case .noManager: return "NO_MANAGER" -// case .configurationError: return "CONFIG_ERROR" -// case .timeoutError: return "TIMEOUT_ERROR" -// case .saveError: return "SAVE_ERROR" -// case .startError: return "START_ERROR" -// case .stopError: return "STOP_ERROR" -// case .invalidConfig: return "INVALID_CONFIG" -// } -// } -} diff --git a/swift/plugin/Sources/VPNManager.swift b/swift/plugin/Sources/VPNManager.swift deleted file mode 100644 index e6627337..00000000 --- a/swift/plugin/Sources/VPNManager.swift +++ /dev/null @@ -1,173 +0,0 @@ -import NetworkExtension -import os - -public enum VPNManagerError: Error { - case providerManagerNotSet -} - -/// Define protocol so `VPNManager` can be mocked for testing in `MockVPNManager`. -public protocol VPNManagement { - var providerManager: NETunnelProviderManager? { get } - var connectionStatus: NEVPNStatus? { get } - - func loadProviderManager( - name: String, - completion: @escaping (NETunnelProviderManager?) -> Void - ) - func saveProviderManager( - _ manager: NETunnelProviderManager, - completion: @escaping (Error?) -> Void - ) - func startTunnel() throws - func stopTunnel() throws - func handleVPNConfigurationChange() -} - -public class VPNManager: VPNManagement { - static let shared = VPNManager() - private var logger = Logger(subsystem: appId, category: "WireguardPlugin.VPNManager") - - public private(set) var providerManager: NETunnelProviderManager? - - public var connectionStatus: NEVPNStatus? { - providerManager?.connection.status - } - - func managerForConfig( - _ config: TunnelConfiguration, - completion: @escaping (NETunnelProviderManager?) -> Void - ) { - NETunnelProviderManager.loadAllFromPreferences { managers, error in - guard let managers = managers else { - self.logger.info("No tunnel managers in user's settings") - return - } - guard error == nil else { - self.logger.warning( - "Error loading tunnel managers: \(error, privacy: .public)") - self.providerManager = nil - completion(nil) - return - } - self.logger.info("Loaded \(managers.count, privacy: .public) tunnel managers.") - - // Find the right protocol manager. - self.providerManager = nil - for manager in managers { - if manager.localizedDescription != config.name { - continue - } - guard - let tunnelProtocol = manager.protocolConfiguration as? NETunnelProviderProtocol - else { - continue - } - // Sometimes all managers from all apps come through, so filter by bundle ID. - if tunnelProtocol.providerBundleIdentifier == pluginAppId { - self.providerManager = manager - break - } - } - if self.providerManager == nil { - self.logger.log("No VPN manager found") - } else { - self.logger.log( - "Loaded provider manager: \(String(describing: self.providerManager!.localizedDescription), privacy: .public)" - ) - } - completion(self.providerManager) - } - } - - /// Loads named provider manager from the system preferences. - public func loadProviderManager( - name: String, - completion: @escaping (NETunnelProviderManager?) -> Void - ) { - NETunnelProviderManager.loadAllFromPreferences { managers, error in - guard let managers = managers else { - self.logger.info("No tunnel managers in user's settings") - return - } - guard error == nil else { - self.logger.warning( - "Error loading tunnel managers: \(error, privacy: .public)") - self.providerManager = nil - completion(nil) - return - } - self.logger.info("Loaded \(managers.count, privacy: .public) tunnel managers.") - - // Find the right protocol manager. - self.providerManager = nil - for manager in managers { - if manager.localizedDescription != name { - continue - } - guard - let tunnelProtocol = manager.protocolConfiguration as? NETunnelProviderProtocol - else { - continue - } - // Sometimes all managers from all apps come through, so filter by bundle ID. - if tunnelProtocol.providerBundleIdentifier == pluginAppId { - self.providerManager = manager - break - } - } - if self.providerManager == nil { - self.logger.log("No VPN manager found") - } else { - self.logger.log( - "Loaded provider manager: \(String(describing: self.providerManager!.localizedDescription), privacy: .public)" - ) - } - completion(self.providerManager) - } - } - - /// Save the provider manager to system preferences. - public func saveProviderManager( - _ manager: NETunnelProviderManager, - completion: @escaping (Error?) -> Void - ) { - manager.saveToPreferences { error in - if let error = error { - self.logger.log("Failed to save provider manager: \(error, privacy: .public)") - completion(error) - } else { - self.providerManager = manager - completion(nil) - } - } - } - - public func handleVPNConfigurationChange() { - logger.log("VPN configuration changed, updating provider manager") - // loadProviderManager { providerManager in - // guard let providerManager = providerManager else { - // self.logger.log("No VPN manager found after configuration change") - // return - // } - // self.providerManager = providerManager - // } - } - - public func startTunnel() throws { - guard let providerManager = providerManager else { - throw VPNManagerError.providerManagerNotSet - } - - try providerManager.connection.startVPNTunnel() - logger.log("VPN tunnel started successfully") - } - - public func stopTunnel() throws { - guard let providerManager = providerManager else { - throw VPNManagerError.providerManagerNotSet - } - - providerManager.connection.stopVPNTunnel() - logger.log("VPN tunnel stopped successfully") - } -} diff --git a/swift/plugin/Sources/Wireguard.swift b/swift/plugin/Sources/Wireguard.swift index 42c57890..16a20d78 100644 --- a/swift/plugin/Sources/Wireguard.swift +++ b/swift/plugin/Sources/Wireguard.swift @@ -6,7 +6,6 @@ import os let appId = Bundle.main.bundleIdentifier ?? "net.defguard" let pluginAppId = "\(appId).VPNExtension" -let plugin = WireguardPlugin() let logger = Logger(subsystem: appId, category: "WireguardPlugin") /// From preferences load `NETunnelProviderManager` with a given `name. @@ -173,9 +172,7 @@ public func allTunnelStats() -> SRObjectArray { // TODO: data should contain a valid message. let data = Data() dispatchGroup.enter() - logger.info("Pre send provider message") try session.sendProviderMessage(data) { response in - logger.info("Post send provider message") if let data = response { let decoder = JSONDecoder() if let result = try? decoder.decode(Stats.self, from: data) { @@ -199,7 +196,6 @@ public func allTunnelStats() -> SRObjectArray { } semaphore.wait() - logger.info("Collected \(stats.count) stats") return SRObjectArray(stats) } diff --git a/swift/plugin/Sources/WireguardPlugin.swift b/swift/plugin/Sources/WireguardPlugin.swift deleted file mode 100644 index 357273b5..00000000 --- a/swift/plugin/Sources/WireguardPlugin.swift +++ /dev/null @@ -1,485 +0,0 @@ -import NetworkExtension -import os - -// The timeout for waiting for the tunnel status to change (e.g. when connecting or disconnecting). -let tunnelStatusTimeout: TimeInterval = 10.0 - -public class WireguardPlugin: NSObject { - private var activeTunnelData: ActiveTunnelData? - private var connectionObserver: NSObjectProtocol? - private var configurationObserver: NSObjectProtocol? - private var vpnManager: VPNManagement - private var logger = Logger( - subsystem: appId, - category: "WireguardPlugin") - - public init(vpnManager: VPNManagement? = nil) { - if let vpnManager = vpnManager { - self.logger.debug("Using provided VPN manager") - self.vpnManager = vpnManager - } else { - self.logger.debug("Creating new VPN manager instance") - self.vpnManager = VPNManager.shared - } - super.init() - } - - /// Loads the active tunnel data from the system configuration. - private func getActiveTunnelData(completion: @escaping (ActiveTunnelData?) -> Void) { - guard let providerManager = vpnManager.providerManager else { - logger.log("No VPN manager found") - return - } - - if let config = providerManager.protocolConfiguration - as? NETunnelProviderProtocol, - let configDict = config.providerConfiguration, - let activeTunnelData = try? ActiveTunnelData.from( - dictionary: configDict - ) - { - completion(activeTunnelData) - } else { - logger.log("No active tunnel data available") - completion(nil) - } - } - - /// Loads the possibly already existing VPN manager and sets up observers for VPN connection status changes if its present. - /// This is to ensure that the VPN status is observed and updated correctly when the app starts. - // private func setupVPNManager( - // completion: @escaping () -> Void - // ) { - // vpnManager.loadProviderManager { manager in - // if manager == nil { - // self.logger.log( - // "No provider manager found, the VPN status won't be observed until the VPN is started." - // ) - // } else { - // self.logger.log( - // "VPN manager loaded successfully, the VPN status will be observed and updated.") - // } - // completion() - // } - // } - - /// Sets up observers for VPN connection status changes. - private func setupVPNObservers() { - if connectionObserver != nil { - logger.log("VPN observers already set up, removing it first") - removeVPNObservers() - } - guard let providerManager = vpnManager.providerManager else { - logger.log("No provider manager found, cannot set up VPN observers") - return - } - connectionObserver = NotificationCenter.default.addObserver( - forName: .NEVPNStatusDidChange, - object: providerManager.connection, - queue: .main, - using: { notification in - self.handleVPNStatusChange() - } - ) - configurationObserver = NotificationCenter.default.addObserver( - forName: .NEVPNConfigurationChange, - object: nil, - queue: .main, - using: { notification in - self.vpnManager.handleVPNConfigurationChange() - self.handleVPNStatusChange() - } - ) - } - - private func removeVPNObservers() { - if let observer = connectionObserver { - NotificationCenter.default.removeObserver(observer) - connectionObserver = nil - } - if let observer = configurationObserver { - NotificationCenter.default.removeObserver(observer) - configurationObserver = nil - } - } - - deinit { - removeVPNObservers() - } - - /// Updates the UI status of the VPN connection. Used when the status changes asynchronously. - private func handleVPNStatusChange() { - guard let vpnStatus = vpnManager.connectionStatus else { - logger.log("Failed to get VPN status, the provider manager has not been loaded yet.") - return - } - - switch vpnStatus { - case .connected: - logger.log("Detected that the VPN has connected, emitting event.") - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase - if let activeTunnelData = activeTunnelData { - guard let data = try? encoder.encode(activeTunnelData), - let dataString = String(data: data, encoding: .utf8) - else { - logger.log("Failed to encode active tunnel data") - return - } - self.activeTunnelData = activeTunnelData - // self.emitEvent( - // event: WireguardEvent.tunnelUp, - // data: dataString - // ) - } else { - getActiveTunnelData { activeTunnelData in - guard let activeTunnelData = activeTunnelData else { - self.logger.log("No active tunnel data available") - // self.emitEvent( - // event: WireguardEvent.tunnelDown, - // data: nil - // ) - return - } - guard let data = try? encoder.encode(activeTunnelData), - let dataString = String(data: data, encoding: .utf8) - else { - self.logger.log("Failed to encode active tunnel data") - return - } - self.activeTunnelData = activeTunnelData - // self.emitEvent( - // event: WireguardEvent.tunnelUp, - // data: dataString - // ) - } - } - setupVPNObservers() - case .disconnected, .invalid: - logger.log( - "Detected that the system VPN status is disconnected. Emitting event if our state differs" - ) - // no point in emitting this event if we already agree that the tunnel is down - if activeTunnelData != nil { - if let lastError = getLastTunnelError() { - logger.log( - "Detected that the tunnel stopped due to the following error: \(lastError.rawValue, privacy: .public)" - ) - if lastError == .mfaSessionExpired { - logger.log( - "Detected that the tunnel stopped due to MFA session expiration, emitting event." - ) - // emitEvent(event: WireguardEvent.MFASessionExpired, data: nil) - } else { - logger.warning( - "Detected that the tunnel stopped due to an unknown error: \(lastError.rawValue, privacy: .public)" - ) - // emitEvent(event: WireguardEvent.tunnelDown, data: nil) - } - resetLastTunnelError() - } else { - // emitEvent(event: WireguardEvent.tunnelDown, data: nil) - } - - activeTunnelData = nil - - logger.log( - "Our state differed, emitted event to inform the frontend about stopped tunnel." - ) - } else { - logger.log("Our state did not differ, no event emitted.") - } - case .connecting: - logger.log( - "Detected that VPN is connecting, ignoring it since it is a temporary state we don't handle." - ) - case .disconnecting: - logger.log( - "Detected that VPN is disconnecting, ignoring it since it is a temporary state we don't handle." - ) - case .reasserting: - logger.log( - "Detected that VPN is reasserting, ignoring it since it is a temporary state we don't handle." - ) - @unknown default: - logger.log( - "Detected unknown VPN status: \(vpnStatus.rawValue, privacy: .public), ignoring it since it is a state we don't handle." - ) - } - } - - private func getLastTunnelError() -> TunnelStopError? { - let defaults = UserDefaults(suiteName: suiteName) - guard let lastError = defaults?.string(forKey: "lastTunnelError") else { - logger.log("No last tunnel error found in user defaults") - return nil - } - logger.log("Last tunnel error found: \(lastError, privacy: .public)") - if let error = TunnelStopError(rawValue: lastError) { - return error - } else { - logger.error( - "Last tunnel error is not a valid TunnelStopError: \(lastError, privacy: .public)") - return nil - } - } - - private func resetLastTunnelError() { - let defaults = UserDefaults(suiteName: suiteName) - defaults?.removeObject(forKey: "lastTunnelError") - } - - // private func saveConfig(config: TunnelConfiguration, result: @escaping (VPNError?) -> Void) { - // logger.info("Saving tunnel config: \(String(describing: config))") - // - // vpnManager.saveProviderManager(providerManager) { saveError in - // if let saveError = saveError { - // self.logger.log("Failed to save preferences: \(saveError, privacy: .public)") - // result( - // VPNError.saveError( - // saveError - // ) - // ) - // return - // } - // } - // } - - func startTunnel( - config: TunnelConfiguration, - completion: @escaping (VPNError?) -> Void - ) { - logger.log("Starting tunnel with config: \(String(describing: config))") - - if !config.isValidForClientConnection() { - completion(VPNError.invalidConfig) - } - - vpnManager.loadProviderManager(name: config.name) { manager in - let providerManager = manager ?? NETunnelProviderManager() - let tunnelProtocol = NETunnelProviderProtocol() - tunnelProtocol.providerBundleIdentifier = pluginAppId - // `serverAddress` must have a non-nil string value for the protocol configuration to be valid. - if let endpoint = config.peers[0].endpoint { - tunnelProtocol.serverAddress = endpoint.toString() - } else { - tunnelProtocol.serverAddress = "" - } - let configDict: [String: Any] - do { - configDict = try config.toDictionary() - } catch { - self.logger.log( - "Failed to convert config to dictionary: \(error.localizedDescription, privacy: .public)" - ) - completion( - VPNError.configurationError(error) - ) - return - } - tunnelProtocol.providerConfiguration = configDict - providerManager.protocolConfiguration = tunnelProtocol - providerManager.localizedDescription = config.name - providerManager.isEnabled = true - - if let status = self.vpnManager.connectionStatus { - if status == .connected || status == .connecting { - do { - try self.vpnManager.stopTunnel() - } catch { - self.logger.log("Failed to stop VPN tunnel: \(error, privacy: .public)") - completion( - VPNError.stopError( - error - ) - ) - return - } - self.logger.log("Stopped running VPN tunnel to update config") - self.waitForTunnelStatus( - desiredStatuses: [.disconnected, .invalid] - ) { status in - if let status = status { - self.logger.log("Timeout waiting for tunnel to disconnect") - completion( - VPNError.timeoutError( - "The tunnel disconnection has failed to complete in a specified amount of time (\(tunnelStatusTimeout) seconds). Please check your configuration and try again. Current status: \(status.rawValue)" - ) - ) - return - } - self.saveAndStartTunnel( - providerManager: providerManager, - config: config, - result: completion - ) - return - } - } - } - self.saveAndStartTunnel( - providerManager: providerManager, - config: config, - result: completion - ) - } - } - - /// Waits for the VPN connection to reach one of the desired statuses. - /// If it does not reach the desired status within the timeout, - /// it returns the current status. - private func waitForTunnelStatus( - desiredStatuses: [NEVPNStatus], - completion: @escaping (NEVPNStatus?) -> Void - ) { - let checkInterval = 0.2 - var elapsedTime = 0.0 - logger.log( - "Waiting for VPN status to change to one of: \(desiredStatuses.map { $0.rawValue })" - ) - func check() { - guard let status = vpnManager.connectionStatus else { - self.logger.log("No VPN connection status available") - completion(nil) - return - } - self.logger.log("Checking VPN status: \(status.rawValue, privacy: .public)") - if desiredStatuses.contains(status) { - self.logger.log( - "Desired VPN status reached: \(status.rawValue, privacy: .public)" - ) - completion(nil) - } else { - elapsedTime += checkInterval - if elapsedTime >= tunnelStatusTimeout { - completion(status) - } else { - DispatchQueue.main.asyncAfter( - deadline: .now() + checkInterval - ) { - check() - } - } - } - } - check() - } - - private func saveAndStartTunnel( - providerManager: NETunnelProviderManager, - config: TunnelConfiguration, - result: @escaping (VPNError?) -> Void - ) { - vpnManager.saveProviderManager(providerManager) { saveError in - if let saveError = saveError { - self.logger.log("Failed to save preferences: \(saveError, privacy: .public)") - result( - VPNError.saveError( - saveError - ) - ) - return - } - self.startVPNTunnel( - config: config, - result: result - ) - } - } - - private func closeTunnel(result: @escaping (VPNError?) -> Void) { - logger.log("Stopping tunnel") - - guard let status = vpnManager.connectionStatus else { - logger.log("No VPN connection status available") - result( - VPNError.noManager( - "No VPN connection status available. The tunnel may not be running." - ) - ) - // emitEvent(event: WireguardEvent.tunnelDown, data: nil) - return - } - - if status == .connected || status == .connecting { - removeVPNObservers() - do { - try vpnManager.stopTunnel() - } catch { - logger.log("Failed to stop VPN tunnel: \(error, privacy: .public)") - result( - VPNError.stopError(error) - ) - return - } - - waitForTunnelStatus(desiredStatuses: [.disconnected, .invalid]) { status in - if let status = status { - self.logger.log( - "Timeout waiting for tunnel to disconnect: \(status.rawValue, privacy: .public)" - ) - result( - VPNError.timeoutError( - "The tunnel disconnection has failed to complete in a specified amount of time (\(tunnelStatusTimeout) seconds). Please check your configuration and try again." - ) - ) - return - } - self.handleVPNStatusChange() - self.logger.log("VPN tunnel stopped") - result(nil) - } - } else { - logger.log("VPN tunnel is not running") - // Emit event just to update the UI if its broken - // emitEvent(event: WireguardEvent.tunnelDown, data: nil) - result(nil) - } - } - - // private func emitEvent(event: WireguardEvent, data: String?) { - // logger.log( - // "Emitting event: \(event.rawValue, privacy: .public), data: \(String(describing: data), privacy: .public)" - // ) - // guard let eventSink = eventSink else { - // logger.log("No event sink available, cannot emit event") - // return - // } - // let event: [String: Any?] = [ - // "event": event.rawValue, - // "data": data, - // ] - // eventSink(event) - // } - - private func startVPNTunnel( - config: TunnelConfiguration, - result: @escaping (VPNError?) -> Void - ) { - do { - try vpnManager.startTunnel() - // This is done because the frontend expects a blocking action to display a loading indicator. - waitForTunnelStatus(desiredStatuses: [.connected]) { status in - if status != nil { - self.logger.log("Timeout waiting for tunnel to connect.") - result( - VPNError.timeoutError( - "The tunnel connection has failed to be established in a specified amount of time. Please check your configuration and try again." - ) - ) - return - } - self.handleVPNStatusChange() - self.logger.log("VPN tunnel started successfully") - result(nil) - } - } catch { - logger.error("Failed to start VPN: \(error, privacy: .public)") - result( - VPNError.startError( - error, - ) - ) - } - } -} From 9d364cf19887b77ecd6eab89104b9ad6ab069dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Thu, 13 Nov 2025 11:18:20 +0100 Subject: [PATCH 5/8] Cleanup --- src-tauri/src/apple.rs | 19 ------------------- src-tauri/src/utils.rs | 4 ++-- swift/build.sh | 2 +- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/src-tauri/src/apple.rs b/src-tauri/src/apple.rs index 6ebaeaa9..1a8cf498 100644 --- a/src-tauri/src/apple.rs +++ b/src-tauri/src/apple.rs @@ -49,7 +49,6 @@ fn manager_for_name(name: &str) -> Option> { let handler = RcBlock::new( move |managers_ptr: *mut NSArray, error_ptr: *mut NSError| { - error!("ADAM is here"); if !error_ptr.is_null() { error!("Failed to load tunnel provider managers."); return; @@ -242,15 +241,6 @@ impl TunnelConfiguration { let provider_manager = manager_for_name(&self.name).unwrap_or_else(|| NETunnelProviderManager::new()); - if let Some(vpn_protocol) = provider_manager.protocolConfiguration() { - if let Ok(tunnel_protocol) = vpn_protocol.downcast::() { - info!("CURRENT CONFIG {tunnel_protocol:?}"); - // if let Some(config) = tunnel_protocol.providerConfiguration() { - // info!("CURRENT CONFIG {config:?}"); - // } - } - } - let tunnel_protocol = NETunnelProviderProtocol::new(); let plugin_bundle_id = NSString::from_str(PLUGIN_BUNDLE_ID); tunnel_protocol.setProviderBundleIdentifier(Some(&plugin_bundle_id)); @@ -286,15 +276,6 @@ impl TunnelConfiguration { while !spinlock.load(Ordering::Acquire) { spin_loop(); } - - // Sanity check - let descr = provider_manager.localizedDescription(); - info!("SANITY DESCR {descr:?}"); - if let Some(vpn_protocol) = provider_manager.protocolConfiguration() { - if let Ok(tunnel_protocol) = vpn_protocol.downcast::() { - info!("SAVED CONFIG {tunnel_protocol:?}"); - } - } } } } diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index e0a188c4..721c526c 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -738,7 +738,7 @@ pub(crate) async fn disconnect_interface( #[cfg(target_os = "macos")] { let result = stop_tunnel(&location.name); - error!("stop_tunnel() for location returned {result:?}"); + error!("stop_tunnel() for location {} returned {result:?}", location.name); if !result { return Err(Error::InternalError("Error from tunnel".into())); } @@ -812,7 +812,7 @@ pub(crate) async fn disconnect_interface( #[cfg(target_os = "macos")] { let result = stop_tunnel(&tunnel.name); - error!("stop_tunnel() for location returned {result:?}"); + error!("stop_tunnel() for tunnel {} returned {result:?}", tunnel.name); if !result { return Err(Error::InternalError("Error from tunnel".into())); } diff --git a/swift/build.sh b/swift/build.sh index d8475699..8189bc2e 100755 --- a/swift/build.sh +++ b/swift/build.sh @@ -48,6 +48,6 @@ popd if [ "${TAURI_ENV_DEBUG}" = 'false' ]; then CONFIG=Release else - CONFIG=Debug + CONFIG=Debug fi xcodebuild -project extension/VPNExtension.xcodeproj -target VPNExtension -configuration ${CONFIG} build From 5f7c850833e91ce54f74b61599489e1c86f251c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 14 Nov 2025 18:40:18 +0100 Subject: [PATCH 6/8] Deal with the runloop --- package.json | 18 +- pnpm-lock.yaml | 301 +++++++++++++-------------- src-tauri/Cargo.lock | 89 ++++---- src-tauri/src/bin/defguard-client.rs | 15 +- src-tauri/src/commands.rs | 19 +- src-tauri/src/utils.rs | 6 +- 6 files changed, 232 insertions(+), 216 deletions(-) diff --git a/package.json b/package.json index 86b27c72..ad7682a3 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@react-hook/resize-observer": "^2.0.2", "@stablelib/base64": "^2.0.1", "@stablelib/x25519": "^2.0.1", - "@tanstack/query-core": "^5.90.7", + "@tanstack/query-core": "^5.90.9", "@tanstack/react-virtual": "3.13.12", "@tauri-apps/api": "^2.9.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.2", @@ -97,7 +97,7 @@ "react-loading-skeleton": "^3.5.0", "react-markdown": "^10.1.0", "react-qr-code": "^2.0.18", - "react-router-dom": "^6.30.1", + "react-router-dom": "^6.30.2", "react-use-websocket": "^4.13.0", "react-virtualized-auto-sizer": "^1.0.26", "recharts": "^3.4.1", @@ -108,19 +108,19 @@ "zustand": "^5.0.8" }, "devDependencies": { - "@biomejs/biome": "^2.3.4", + "@biomejs/biome": "^2.3.5", "@hookform/devtools": "^4.4.0", "@svgr/cli": "^8.1.0", - "@tanstack/react-query": "^5.90.7", + "@tanstack/react-query": "^5.90.9", "@tanstack/react-query-devtools": "^5.90.2", "@tauri-apps/cli": "^2.9.4", "@types/file-saver": "^2.0.7", "@types/lodash-es": "^4.17.12", - "@types/node": "^24.10.0", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.2", - "@vitejs/plugin-react": "^5.1.0", - "@vitejs/plugin-react-swc": "^4.2.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.4", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "@vitejs/plugin-react-swc": "^4.2.2", "autoprefixer": "^10.4.22", "npm-run-all": "^4.1.5", "postcss": "^8.5.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9246f2a7..db0eae83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^2.0.1 version: 2.0.1 '@tanstack/query-core': - specifier: ^5.90.7 - version: 5.90.7 + specifier: ^5.90.9 + version: 5.90.9 '@tanstack/react-virtual': specifier: 3.13.12 version: 3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -103,7 +103,7 @@ importers: version: 1.0.3 html-react-parser: specifier: ^5.2.8 - version: 5.2.8(@types/react@19.2.2)(react@19.2.0) + version: 5.2.8(@types/react@19.2.4)(react@19.2.0) itertools: specifier: ^2.5.0 version: 2.5.0 @@ -115,7 +115,7 @@ importers: version: 4.17.21 merge-refs: specifier: ^2.0.0 - version: 2.0.0(@types/react@19.2.2) + version: 2.0.0(@types/react@19.2.4) millify: specifier: ^6.1.0 version: 6.1.0 @@ -154,13 +154,13 @@ importers: version: 3.5.0(react@19.2.0) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.2)(react@19.2.0) + version: 10.1.0(@types/react@19.2.4)(react@19.2.0) react-qr-code: specifier: ^2.0.18 version: 2.0.18(react@19.2.0) react-router-dom: - specifier: ^6.30.1 - version: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^6.30.2 + version: 6.30.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-use-websocket: specifier: ^4.13.0 version: 4.13.0 @@ -169,7 +169,7 @@ importers: version: 1.0.26(react-dom@19.2.0(react@19.2.0))(react@19.2.0) recharts: specifier: ^3.4.1 - version: 3.4.1(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1) + version: 3.4.1(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1) rehype-sanitize: specifier: ^6.0.0 version: 6.0.0 @@ -184,23 +184,23 @@ importers: version: 3.25.76 zustand: specifier: ^5.0.8 - version: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + version: 5.0.8(@types/react@19.2.4)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) devDependencies: '@biomejs/biome': - specifier: ^2.3.4 - version: 2.3.4 + specifier: ^2.3.5 + version: 2.3.5 '@hookform/devtools': specifier: ^4.4.0 - version: 4.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 4.4.0(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@svgr/cli': specifier: ^8.1.0 version: 8.1.0(typescript@5.9.3) '@tanstack/react-query': - specifier: ^5.90.7 - version: 5.90.7(react@19.2.0) + specifier: ^5.90.9 + version: 5.90.9(react@19.2.0) '@tanstack/react-query-devtools': specifier: ^5.90.2 - version: 5.90.2(@tanstack/react-query@5.90.7(react@19.2.0))(react@19.2.0) + version: 5.90.2(@tanstack/react-query@5.90.9(react@19.2.0))(react@19.2.0) '@tauri-apps/cli': specifier: ^2.9.4 version: 2.9.4 @@ -211,20 +211,20 @@ importers: specifier: ^4.17.12 version: 4.17.12 '@types/node': - specifier: ^24.10.0 - version: 24.10.0 + specifier: ^24.10.1 + version: 24.10.1 '@types/react': - specifier: ^19.2.2 - version: 19.2.2 + specifier: ^19.2.4 + version: 19.2.4 '@types/react-dom': - specifier: ^19.2.2 - version: 19.2.2(@types/react@19.2.2) + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.4) '@vitejs/plugin-react': - specifier: ^5.1.0 - version: 5.1.0(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) + specifier: ^5.1.1 + version: 5.1.1(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1)) '@vitejs/plugin-react-swc': - specifier: ^4.2.1 - version: 4.2.1(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1)) + specifier: ^4.2.2 + version: 4.2.2(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1)) autoprefixer: specifier: ^10.4.22 version: 10.4.22(postcss@8.5.6) @@ -251,7 +251,7 @@ importers: version: 5.9.3 vite: specifier: ^7.2.2 - version: 7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + version: 7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1) packages: @@ -342,55 +342,55 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.3.4': - resolution: {integrity: sha512-TU08LXjBHdy0mEY9APtEtZdNQQijXUDSXR7IK1i45wgoPD5R0muK7s61QcFir6FpOj/RP1+YkPx5QJlycXUU3w==} + '@biomejs/biome@2.3.5': + resolution: {integrity: sha512-HvLhNlIlBIbAV77VysRIBEwp55oM/QAjQEin74QQX9Xb259/XP/D5AGGnZMOyF1el4zcvlNYYR3AyTMUV3ILhg==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.3.4': - resolution: {integrity: sha512-w40GvlNzLaqmuWYiDU6Ys9FNhJiclngKqcGld3iJIiy2bpJ0Q+8n3haiaC81uTPY/NA0d8Q/I3Z9+ajc14102Q==} + '@biomejs/cli-darwin-arm64@2.3.5': + resolution: {integrity: sha512-fLdTur8cJU33HxHUUsii3GLx/TR0BsfQx8FkeqIiW33cGMtUD56fAtrh+2Fx1uhiCsVZlFh6iLKUU3pniZREQw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.3.4': - resolution: {integrity: sha512-3s7TLVtjJ7ni1xADXsS7x7GMUrLBZXg8SemXc3T0XLslzvqKj/dq1xGeBQ+pOWQzng9MaozfacIHdK2UlJ3jGA==} + '@biomejs/cli-darwin-x64@2.3.5': + resolution: {integrity: sha512-qpT8XDqeUlzrOW8zb4k3tjhT7rmvVRumhi2657I2aGcY4B+Ft5fNwDdZGACzn8zj7/K1fdWjgwYE3i2mSZ+vOA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.3.4': - resolution: {integrity: sha512-IruVGQRwMURivWazchiq7gKAqZSFs5so6gi0hJyxk7x6HR+iwZbO2IxNOqyLURBvL06qkIHs7Wffl6Bw30vCbQ==} + '@biomejs/cli-linux-arm64-musl@2.3.5': + resolution: {integrity: sha512-eGUG7+hcLgGnMNl1KHVZUYxahYAhC462jF/wQolqu4qso2MSk32Q+QrpN7eN4jAHAg7FUMIo897muIhK4hXhqg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.3.4': - resolution: {integrity: sha512-y7efHyyM2gYmHy/AdWEip+VgTMe9973aP7XYKPzu/j8JxnPHuSUXftzmPhkVw0lfm4ECGbdBdGD6+rLmTgNZaA==} + '@biomejs/cli-linux-arm64@2.3.5': + resolution: {integrity: sha512-u/pybjTBPGBHB66ku4pK1gj+Dxgx7/+Z0jAriZISPX1ocTO8aHh8x8e7Kb1rB4Ms0nA/SzjtNOVJ4exVavQBCw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.3.4': - resolution: {integrity: sha512-mzKFFv/w66e4/jCobFmD3kymCqG+FuWE7sVa4Yjqd9v7qt2UhXo67MSZKY9Ih18V2IwPzRKQPCw6KwdZs6AXSA==} + '@biomejs/cli-linux-x64-musl@2.3.5': + resolution: {integrity: sha512-awVuycTPpVTH/+WDVnEEYSf6nbCBHf/4wB3lquwT7puhNg8R4XvonWNZzUsfHZrCkjkLhFH/vCZK5jHatD9FEg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.3.4': - resolution: {integrity: sha512-gKfjWR/6/dfIxPJCw8REdEowiXCkIpl9jycpNVHux8aX2yhWPLjydOshkDL6Y/82PcQJHn95VCj7J+BRcE5o1Q==} + '@biomejs/cli-linux-x64@2.3.5': + resolution: {integrity: sha512-XrIVi9YAW6ye0CGQ+yax0gLfx+BFOtKaNX74n+xHWla6Cl6huUmcKNO7HPx7BiKnJUzrxXY1qYlm7xMvi08X4g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.3.4': - resolution: {integrity: sha512-5TJ6JfVez+yyupJ/iGUici2wzKf0RrSAxJhghQXtAEsc67OIpdwSKAQboemILrwKfHDi5s6mu7mX+VTCTUydkw==} + '@biomejs/cli-win32-arm64@2.3.5': + resolution: {integrity: sha512-DlBiMlBZZ9eIq4H7RimDSGsYcOtfOIfZOaI5CqsWiSlbTfqbPVfWtCf92wNzx8GNMbu1s7/g3ZZESr6+GwM/SA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.3.4': - resolution: {integrity: sha512-FGCijXecmC4IedQ0esdYNlMpx0Jxgf4zceCaMu6fkjWyjgn50ZQtMiqZZQ0Q/77yqPxvtkgZAvt5uGw0gAAjig==} + '@biomejs/cli-win32-x64@2.3.5': + resolution: {integrity: sha512-nUmR8gb6yvrKhtRgzwo/gDimPwnO5a4sCydf8ZS2kHIJhEmSmk+STsusr1LHTuM//wXppBawvSQi2xFXJCdgKQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -764,15 +764,12 @@ packages: react-redux: optional: true - '@remix-run/router@1.23.0': - resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} + '@remix-run/router@1.23.1': + resolution: {integrity: sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==} engines: {node: '>=14.0.0'} - '@rolldown/pluginutils@1.0.0-beta.43': - resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==} - - '@rolldown/pluginutils@1.0.0-beta.46': - resolution: {integrity: sha512-xMNwJo/pHkEP/mhNVnW+zUiJDle6/hxrwO0mfSJuEVRbBfgrJFuUSRoZx/nYUw5pCjrysl9OkNXCkAdih8GCnA==} + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} '@rollup/rollup-android-arm-eabi@4.53.2': resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==} @@ -1089,8 +1086,8 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - '@tanstack/query-core@5.90.7': - resolution: {integrity: sha512-6PN65csiuTNfBMXqQUxQhCNdtm1rV+9kC9YwWAIKcaxAauq3Wu7p18j3gQY3YIBJU70jT/wzCCZ2uqto/vQgiQ==} + '@tanstack/query-core@5.90.9': + resolution: {integrity: sha512-UFOCQzi6pRGeVTVlPNwNdnAvT35zugcIydqjvFUzG62dvz2iVjElmNp/hJkUoM5eqbUPfSU/GJIr/wbvD8bTUw==} '@tanstack/query-devtools@5.90.1': resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==} @@ -1101,8 +1098,8 @@ packages: '@tanstack/react-query': ^5.90.2 react: ^18 || ^19 - '@tanstack/react-query@5.90.7': - resolution: {integrity: sha512-wAHc/cgKzW7LZNFloThyHnV/AX9gTg3w5yAv0gvQHPZoCnepwqCMtzbuPbb2UvfvO32XZ46e8bPOYbfZhzVnnQ==} + '@tanstack/react-query@5.90.9': + resolution: {integrity: sha512-Zke2AaXiaSfnG8jqPZR52m8SsclKT2d9//AgE/QIzyNvbpj/Q2ln+FsZjb1j69bJZUouBvX2tg9PHirkTm8arw==} peerDependencies: react: ^18 || ^19 @@ -1295,19 +1292,19 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@24.10.0': - resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/react-dom@19.2.2': - resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.2': - resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + '@types/react@19.2.4': + resolution: {integrity: sha512-tBFxBp9Nfyy5rsmefN+WXc1JeW/j2BpBHFdLZbEVfs9wn3E3NRFxwV0pJg8M1qQAexFpvz73hJXFofV0ZAu92A==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1329,14 +1326,14 @@ packages: peerDependencies: react: '>= 16.8.0' - '@vitejs/plugin-react-swc@4.2.1': - resolution: {integrity: sha512-SIZ/XxeS2naLw4L2vVvpTyujM2OY+Rf+y6nWETqfoBrZpI3SFdyNJof3nQ8HbLhXJ1Eh9e9c0JGYC8GYPhLkCw==} + '@vitejs/plugin-react-swc@4.2.2': + resolution: {integrity: sha512-x+rE6tsxq/gxrEJN3Nv3dIV60lFflPj94c90b+NNo6n1QV1QQUTLoL0MpaOVasUZ0zqVBn7ead1B5ecx1JAGfA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4 || ^5 || ^6 || ^7 - '@vitejs/plugin-react@5.1.0': - resolution: {integrity: sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==} + '@vitejs/plugin-react@5.1.1': + resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -1389,8 +1386,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.25: - resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==} + baseline-browser-mapping@2.8.28: + resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} hasBin: true boolbase@1.0.0: @@ -1553,8 +1550,8 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.0: + resolution: {integrity: sha512-si++xzRAY9iPp60roQiFta7OFbhrgvcthrhlNAGeQptSY25uJjkfUV8OArC3KLocB8JT8ohz+qgxWCmz8RhjIg==} d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} @@ -1685,8 +1682,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.250: - resolution: {integrity: sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==} + electron-to-chromium@1.5.252: + resolution: {integrity: sha512-53uTpjtRgS7gjIxZ4qCgFdNO2q+wJt/Z8+xAvxbCqXPJrY6h7ighUkadQmNMXH96crtpa6gPFNP7BF4UBGDuaA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2089,8 +2086,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsesc@3.1.0: @@ -2521,15 +2518,15 @@ packages: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} - react-router-dom@6.30.1: - resolution: {integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==} + react-router-dom@6.30.2: + resolution: {integrity: sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - react-router@6.30.1: - resolution: {integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==} + react-router@6.30.2: + resolution: {integrity: sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' @@ -3130,39 +3127,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@biomejs/biome@2.3.4': + '@biomejs/biome@2.3.5': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.3.4 - '@biomejs/cli-darwin-x64': 2.3.4 - '@biomejs/cli-linux-arm64': 2.3.4 - '@biomejs/cli-linux-arm64-musl': 2.3.4 - '@biomejs/cli-linux-x64': 2.3.4 - '@biomejs/cli-linux-x64-musl': 2.3.4 - '@biomejs/cli-win32-arm64': 2.3.4 - '@biomejs/cli-win32-x64': 2.3.4 - - '@biomejs/cli-darwin-arm64@2.3.4': + '@biomejs/cli-darwin-arm64': 2.3.5 + '@biomejs/cli-darwin-x64': 2.3.5 + '@biomejs/cli-linux-arm64': 2.3.5 + '@biomejs/cli-linux-arm64-musl': 2.3.5 + '@biomejs/cli-linux-x64': 2.3.5 + '@biomejs/cli-linux-x64-musl': 2.3.5 + '@biomejs/cli-win32-arm64': 2.3.5 + '@biomejs/cli-win32-x64': 2.3.5 + + '@biomejs/cli-darwin-arm64@2.3.5': optional: true - '@biomejs/cli-darwin-x64@2.3.4': + '@biomejs/cli-darwin-x64@2.3.5': optional: true - '@biomejs/cli-linux-arm64-musl@2.3.4': + '@biomejs/cli-linux-arm64-musl@2.3.5': optional: true - '@biomejs/cli-linux-arm64@2.3.4': + '@biomejs/cli-linux-arm64@2.3.5': optional: true - '@biomejs/cli-linux-x64-musl@2.3.4': + '@biomejs/cli-linux-x64-musl@2.3.5': optional: true - '@biomejs/cli-linux-x64@2.3.4': + '@biomejs/cli-linux-x64@2.3.5': optional: true - '@biomejs/cli-win32-arm64@2.3.4': + '@biomejs/cli-win32-arm64@2.3.5': optional: true - '@biomejs/cli-win32-x64@2.3.4': + '@biomejs/cli-win32-x64@2.3.5': optional: true '@emotion/babel-plugin@11.13.5': @@ -3197,7 +3194,7 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0)': + '@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 @@ -3209,7 +3206,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.4 transitivePeerDependencies: - supports-color @@ -3219,22 +3216,22 @@ snapshots: '@emotion/memoize': 0.9.0 '@emotion/unitless': 0.10.0 '@emotion/utils': 1.4.2 - csstype: 3.1.3 + csstype: 3.2.0 '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0) '@emotion/serialize': 1.3.3 '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0) '@emotion/utils': 1.4.2 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.4 transitivePeerDependencies: - supports-color @@ -3359,10 +3356,10 @@ snapshots: '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 - '@hookform/devtools@4.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@hookform/devtools@4.4.0(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0) '@types/lodash': 4.17.20 little-state-machine: 4.8.1(react@19.2.0) lodash: 4.17.21 @@ -3473,7 +3470,7 @@ snapshots: '@react-hook/passive-layout-effect': 1.2.1(react@19.2.0) react: 19.2.0 - '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0)': + '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1))(react@19.2.0)': dependencies: '@standard-schema/spec': 1.0.0 '@standard-schema/utils': 0.3.0 @@ -3483,13 +3480,11 @@ snapshots: reselect: 5.1.1 optionalDependencies: react: 19.2.0 - react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) - - '@remix-run/router@1.23.0': {} + react-redux: 9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1) - '@rolldown/pluginutils@1.0.0-beta.43': {} + '@remix-run/router@1.23.1': {} - '@rolldown/pluginutils@1.0.0-beta.46': {} + '@rolldown/pluginutils@1.0.0-beta.47': {} '@rollup/rollup-android-arm-eabi@4.53.2': optional: true @@ -3761,19 +3756,19 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.90.7': {} + '@tanstack/query-core@5.90.9': {} '@tanstack/query-devtools@5.90.1': {} - '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.7(react@19.2.0))(react@19.2.0)': + '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.9(react@19.2.0))(react@19.2.0)': dependencies: '@tanstack/query-devtools': 5.90.1 - '@tanstack/react-query': 5.90.7(react@19.2.0) + '@tanstack/react-query': 5.90.9(react@19.2.0) react: 19.2.0 - '@tanstack/react-query@5.90.7(react@19.2.0)': + '@tanstack/react-query@5.90.9(react@19.2.0)': dependencies: - '@tanstack/query-core': 5.90.7 + '@tanstack/query-core': 5.90.9 react: 19.2.0 '@tanstack/react-virtual@3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': @@ -3954,19 +3949,19 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@24.10.0': + '@types/node@24.10.1': dependencies: undici-types: 7.16.0 '@types/parse-json@4.0.2': {} - '@types/react-dom@19.2.2(@types/react@19.2.2)': + '@types/react-dom@19.2.3(@types/react@19.2.4)': dependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.4 - '@types/react@19.2.2': + '@types/react@19.2.4': dependencies: - csstype: 3.1.3 + csstype: 3.2.0 '@types/unist@2.0.11': {} @@ -3983,23 +3978,23 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.0 - '@vitejs/plugin-react-swc@4.2.1(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1))': + '@vitejs/plugin-react-swc@4.2.2(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1))': dependencies: - '@rolldown/pluginutils': 1.0.0-beta.46 + '@rolldown/pluginutils': 1.0.0-beta.47 '@swc/core': 1.15.1 - vite: 7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + vite: 7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@5.1.0(vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1))': + '@vitejs/plugin-react@5.1.1(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) - '@rolldown/pluginutils': 1.0.0-beta.43 + '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1) + vite: 7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -4056,7 +4051,7 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.25: {} + baseline-browser-mapping@2.8.28: {} boolbase@1.0.0: {} @@ -4076,9 +4071,9 @@ snapshots: browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.25 + baseline-browser-mapping: 2.8.28 caniuse-lite: 1.0.30001754 - electron-to-chromium: 1.5.250 + electron-to-chromium: 1.5.252 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) @@ -4179,7 +4174,7 @@ snapshots: cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -4217,7 +4212,7 @@ snapshots: dependencies: css-tree: 2.2.1 - csstype@3.1.3: {} + csstype@3.2.0: {} d3-array@3.2.4: dependencies: @@ -4345,7 +4340,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.250: {} + electron-to-chromium@1.5.252: {} emoji-regex@8.0.0: {} @@ -4641,7 +4636,7 @@ snapshots: domhandler: 5.0.3 htmlparser2: 10.0.0 - html-react-parser@5.2.8(@types/react@19.2.2)(react@19.2.0): + html-react-parser@5.2.8(@types/react@19.2.4)(react@19.2.0): dependencies: domhandler: 5.0.3 html-dom-parser: 5.1.1 @@ -4649,7 +4644,7 @@ snapshots: react-property: 2.0.2 style-to-js: 1.1.19 optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.4 html-url-attributes@3.0.1: {} @@ -4824,7 +4819,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -4981,9 +4976,9 @@ snapshots: memorystream@0.3.1: {} - merge-refs@2.0.0(@types/react@19.2.2): + merge-refs@2.0.0(@types/react@19.2.4): optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.4 micromark-core-commonmark@2.0.3: dependencies: @@ -5329,11 +5324,11 @@ snapshots: dependencies: react: 19.2.0 - react-markdown@10.1.0(@types/react@19.2.2)(react@19.2.0): + react-markdown@10.1.0(@types/react@19.2.4)(react@19.2.0): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/react': 19.2.2 + '@types/react': 19.2.4 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 @@ -5355,27 +5350,27 @@ snapshots: qr.js: 0.0.0 react: 19.2.0 - react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 react: 19.2.0 use-sync-external-store: 1.6.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.4 redux: 5.0.1 react-refresh@0.18.0: {} - react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-router-dom@6.30.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@remix-run/router': 1.23.0 + '@remix-run/router': 1.23.1 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - react-router: 6.30.1(react@19.2.0) + react-router: 6.30.2(react@19.2.0) - react-router@6.30.1(react@19.2.0): + react-router@6.30.2(react@19.2.0): dependencies: - '@remix-run/router': 1.23.0 + '@remix-run/router': 1.23.1 react: 19.2.0 react-simple-animate@3.5.3(react-dom@19.2.0(react@19.2.0)): @@ -5399,9 +5394,9 @@ snapshots: readdirp@4.1.2: {} - recharts@3.4.1(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1): + recharts@3.4.1(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1): dependencies: - '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0) + '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1))(react@19.2.0) clsx: 2.1.1 decimal.js-light: 2.5.1 es-toolkit: 1.41.0 @@ -5410,7 +5405,7 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) react-is: 18.3.1 - react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1) reselect: 5.1.1 tiny-invariant: 1.3.3 use-sync-external-store: 1.6.0(react@19.2.0) @@ -5878,7 +5873,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@7.2.2(@types/node@24.10.0)(sass@1.92.1)(yaml@2.8.1): + vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -5887,7 +5882,7 @@ snapshots: rollup: 4.53.2 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.0 + '@types/node': 24.10.1 fsevents: 2.3.3 sass: 1.92.1 yaml: 2.8.1 @@ -5967,9 +5962,9 @@ snapshots: zod@3.25.76: {} - zustand@5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + zustand@5.0.8(@types/react@19.2.4)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 19.2.4 immer: 10.2.0 react: 19.2.0 use-sync-external-store: 1.6.0(react@19.2.0) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 348bd175..f1b5b7a1 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -117,22 +117,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -793,9 +793,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] @@ -869,9 +869,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.45" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "jobserver", @@ -1955,15 +1955,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" - -[[package]] -name = "fixedbitset" -version = "0.4.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixedbitset" @@ -2709,9 +2703,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -2778,9 +2772,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64 0.22.1", "bytes", @@ -2799,7 +2793,7 @@ dependencies = [ "tokio", "tower-service", "tracing", - "windows-registry", + "windows-registry 0.6.1", ] [[package]] @@ -4309,21 +4303,22 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petgraph" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ - "fixedbitset 0.4.2", + "fixedbitset", "indexmap 2.12.0", ] [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ - "fixedbitset 0.5.7", + "fixedbitset", + "hashbrown 0.15.5", "indexmap 2.12.0", ] @@ -6375,9 +6370,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.9.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bceb52453e507c505b330afe3398510e87f428ea42b6e76ecb6bd63b15965b5" +checksum = "9e492485dd390b35f7497401f67694f46161a2a00ffd800938d5dd3c898fb9d8" dependencies = [ "anyhow", "bytes", @@ -6427,9 +6422,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a924b6c50fe83193f0f8b14072afa7c25b7a72752a2a73d9549b463f5fe91a38" +checksum = "87d6f8cafe6a75514ce5333f115b7b1866e8e68d9672bf4ca89fc0f35697ea9d" dependencies = [ "anyhow", "cargo_toml", @@ -6449,9 +6444,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c1fe64c74cc40f90848281a90058a6db931eb400b60205840e09801ee30f190" +checksum = "b7ef707148f0755110ca54377560ab891d722de4d53297595380a748026f139f" dependencies = [ "base64 0.22.1", "brotli", @@ -6476,9 +6471,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260c5d2eb036b76206b9fca20b7be3614cfd21046c5396f7959e0e64a4b07f2f" +checksum = "71664fd715ee6e382c05345ad258d6d1d50f90cf1b58c0aa726638b33c2a075d" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -6537,7 +6532,7 @@ dependencies = [ "thiserror 2.0.17", "tracing", "url", - "windows-registry", + "windows-registry 0.5.3", "windows-result 0.3.4", ] @@ -7403,14 +7398,13 @@ dependencies = [ [[package]] name = "tree_magic_mini" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f943391d896cdfe8eec03a04d7110332d445be7df856db382dd96a730667562c" +checksum = "52fac5f7d176f7f7f7e827299ead28ef98de642c5d93a97e0cd0816d17557f19" dependencies = [ "memchr", "nom", - "once_cell", - "petgraph 0.6.5", + "petgraph 0.8.3", ] [[package]] @@ -8392,6 +8386,17 @@ dependencies = [ "windows-strings 0.4.2", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs index 3d5fabe8..4ff7c2cc 100644 --- a/src-tauri/src/bin/defguard-client.rs +++ b/src-tauri/src/bin/defguard-client.rs @@ -321,11 +321,24 @@ fn main() { // Handle shutdown. RunEvent::Exit => { debug!("Exiting the application's main event loop."); - tauri::async_runtime::block_on(async { + let handle = tauri::async_runtime::spawn(async { let _ = close_all_connections().await; // This will clean the database file, pruning write-ahead log. DB_POOL.close().await; }); + // Obj-C API needs a runtime, but at this point Tauri has closed its runtime, so + // create a temporary one. + #[cfg(target_os = "macos")] + { + use objc2_foundation::{NSDate, NSRunLoop}; + let run_loop = NSRunLoop::currentRunLoop(); + // Should be enough to quit. + let date = NSDate::dateWithTimeIntervalSinceNow(2.0); + run_loop.runUntilDate(&date); + } + tauri::async_runtime::block_on(async { + let _ = handle.await; + }); } _ => { trace!("Received event: {event:?}"); diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index ddbb027f..14e9f09c 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -17,10 +17,7 @@ const UPDATE_URL: &str = "https://pkgs.defguard.net/api/update/check"; use crate::apple::stop_tunnel; #[cfg(not(target_os = "macos"))] use crate::service::{ - proto::{ - DeleteServiceLocationsRequest, RemoveInterfaceRequest, SaveServiceLocationsRequest, - ServiceLocation, - }, + proto::{DeleteServiceLocationsRequest, RemoveInterfaceRequest, SaveServiceLocationsRequest}, utils::DAEMON_CLIENT, }; use crate::{ @@ -294,7 +291,7 @@ pub async fn save_device_config( info!("New instance {instance} created."); trace!("Created following instance: {instance:#?}"); - let locations = push_service_locations(&instance).await?; + let locations = push_service_locations(&instance, keys).await?; handle.emit(EventKey::InstanceUpdate.into(), ())?; let res: SaveDeviceConfigResponse = SaveDeviceConfigResponse { @@ -307,14 +304,20 @@ pub async fn save_device_config( } #[cfg(target_os = "macos")] -async fn push_service_locations(instance: &Instance) -> Result>, Error> { +async fn push_service_locations( + instance: &Instance, + keys: WireguardKeys, +) -> Result>, Error> { // Nothing here... yet Ok(Vec::new()) } #[cfg(not(target_os = "macos"))] -async fn push_service_locations(instance: &Instance) -> Result>, Error> { +async fn push_service_locations( + instance: &Instance, + keys: WireguardKeys, +) -> Result>, Error> { let locations = Location::find_by_instance_id(&*DB_POOL, instance.id, true).await?; trace!("Created following locations: {locations:#?}"); @@ -334,7 +337,7 @@ async fn push_service_locations(instance: &Instance) -> Result> let save_request = SaveServiceLocationsRequest { service_locations: service_locations.clone(), instance_id: instance.uuid.clone(), - private_key: keys.prvkey.clone(), + private_key: keys.prvkey, }; debug!( "Saving {} service locations to the daemon for instance {}({}).", diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index c6beb30d..2f2ef9f1 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -52,6 +52,7 @@ use crate::{ pub(crate) static DEFAULT_ROUTE_IPV4: &str = "0.0.0.0/0"; pub(crate) static DEFAULT_ROUTE_IPV6: &str = "::/0"; // Work-around MFA propagation delay. FIXME: remove once Core API is corrected. +#[cfg(target_os = "macos")] static TUNNEL_START_DELAY: Duration = Duration::from_secs(1); /// Setup client interface for `Instance`. @@ -762,7 +763,6 @@ pub(crate) async fn disconnect_interface( #[cfg(not(target_os = "macos"))] { - let mut client = DAEMON_CLIENT.clone(); let request = RemoveInterfaceRequest { interface_name, endpoint: location.endpoint.clone(), @@ -772,7 +772,7 @@ pub(crate) async fn disconnect_interface( {}...", active_connection.interface_name, location.name ); - if let Err(error) = client.remove_interface(request).await { + if let Err(error) = DAEMON_CLIENT.clone().remove_interface(request).await { let msg = if error.code() == Code::Unavailable { format!( "Couldn't remove interface {}. Background service is unavailable. \ @@ -843,7 +843,7 @@ pub(crate) async fn disconnect_interface( interface_name, endpoint: tunnel.endpoint.clone(), }; - if let Err(error) = client.remove_interface(request).await { + if let Err(error) = DAEMON_CLIENT.clone().remove_interface(request).await { error!( "Error while removing interface {}, error details: {error:?}", active_connection.interface_name From 3e756040dff5d188eaaf9c54e997d9518a951d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 14 Nov 2025 18:52:01 +0100 Subject: [PATCH 7/8] Update dependencies --- src-tauri/Cargo.lock | 2 ++ src-tauri/Cargo.toml | 3 +-- swift/boringtun | 2 +- swift/build.sh | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f1b5b7a1..26e44cc4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -669,6 +669,7 @@ dependencies = [ [[package]] name = "boringtun" version = "0.6.0" +source = "git+https://github.com/DefGuard/wireguard-rs?rev=9af5e79f60fbcf8ffa1460364d719cb1eec9f360#9af5e79f60fbcf8ffa1460364d719cb1eec9f360" dependencies = [ "aead", "base64 0.22.1", @@ -1481,6 +1482,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" version = "0.9.0" +source = "git+https://github.com/DefGuard/wireguard-rs?rev=9af5e79f60fbcf8ffa1460364d719cb1eec9f360#9af5e79f60fbcf8ffa1460364d719cb1eec9f360" dependencies = [ "base64 0.22.1", "boringtun", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 89957175..18b44c60 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -4,8 +4,7 @@ default-members = [".", "cli"] [workspace.dependencies] clap = { version = "4.5", features = ["cargo", "derive", "env"] } -# defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs", rev = "c99c0b209b19d9ce82e0f5d0d727261f8f9916b3" } -defguard_wireguard_rs = { path = "../../wireguard-rs" } +defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs", rev = "9af5e79f60fbcf8ffa1460364d719cb1eec9f360" } dirs-next = "2.0" prost = "0.14" reqwest = { version = "0.12", features = ["cookies", "json"] } diff --git a/swift/boringtun b/swift/boringtun index f47e80a9..20d92e55 160000 --- a/swift/boringtun +++ b/swift/boringtun @@ -1 +1 @@ -Subproject commit f47e80a96923733bb9ed2bd5590f882dfb1d9b95 +Subproject commit 20d92e555303ae2d7c54f759c3cb4b222eec9d54 diff --git a/swift/build.sh b/swift/build.sh index 8189bc2e..c514ccab 100755 --- a/swift/build.sh +++ b/swift/build.sh @@ -9,7 +9,7 @@ export MACOSX_DEPLOYMENT_TARGET=13.5 # Build BoringTun. -pushd ../../boringtun +pushd boringtun for TARGET in aarch64-apple-darwin x86_64-apple-darwin do From bc2fd0009a79ccfdbaa16d2b266765a239e2c488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 17 Nov 2025 09:51:13 +0100 Subject: [PATCH 8/8] Update dependences --- package.json | 8 +- pnpm-lock.yaml | 266 +++++++++++++++++++++---------------------- src-tauri/Cargo.lock | 33 ++++-- 3 files changed, 158 insertions(+), 149 deletions(-) diff --git a/package.json b/package.json index ad7682a3..cd4ae31d 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@react-hook/resize-observer": "^2.0.2", "@stablelib/base64": "^2.0.1", "@stablelib/x25519": "^2.0.1", - "@tanstack/query-core": "^5.90.9", + "@tanstack/query-core": "^5.90.10", "@tanstack/react-virtual": "3.13.12", "@tauri-apps/api": "^2.9.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.2", @@ -78,7 +78,7 @@ "fast-deep-equal": "^3.1.3", "file-saver": "^2.0.5", "get-text-width": "^1.0.3", - "html-react-parser": "^5.2.8", + "html-react-parser": "^5.2.10", "itertools": "^2.5.0", "js-base64": "^3.7.8", "lodash-es": "^4.17.21", @@ -111,13 +111,13 @@ "@biomejs/biome": "^2.3.5", "@hookform/devtools": "^4.4.0", "@svgr/cli": "^8.1.0", - "@tanstack/react-query": "^5.90.9", + "@tanstack/react-query": "^5.90.10", "@tanstack/react-query-devtools": "^5.90.2", "@tauri-apps/cli": "^2.9.4", "@types/file-saver": "^2.0.7", "@types/lodash-es": "^4.17.12", "@types/node": "^24.10.1", - "@types/react": "^19.2.4", + "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react-swc": "^4.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db0eae83..975bb0ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^2.0.1 version: 2.0.1 '@tanstack/query-core': - specifier: ^5.90.9 - version: 5.90.9 + specifier: ^5.90.10 + version: 5.90.10 '@tanstack/react-virtual': specifier: 3.13.12 version: 3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -102,8 +102,8 @@ importers: specifier: ^1.0.3 version: 1.0.3 html-react-parser: - specifier: ^5.2.8 - version: 5.2.8(@types/react@19.2.4)(react@19.2.0) + specifier: ^5.2.10 + version: 5.2.10(@types/react@19.2.5)(react@19.2.0) itertools: specifier: ^2.5.0 version: 2.5.0 @@ -115,7 +115,7 @@ importers: version: 4.17.21 merge-refs: specifier: ^2.0.0 - version: 2.0.0(@types/react@19.2.4) + version: 2.0.0(@types/react@19.2.5) millify: specifier: ^6.1.0 version: 6.1.0 @@ -154,7 +154,7 @@ importers: version: 3.5.0(react@19.2.0) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.4)(react@19.2.0) + version: 10.1.0(@types/react@19.2.5)(react@19.2.0) react-qr-code: specifier: ^2.0.18 version: 2.0.18(react@19.2.0) @@ -169,7 +169,7 @@ importers: version: 1.0.26(react-dom@19.2.0(react@19.2.0))(react@19.2.0) recharts: specifier: ^3.4.1 - version: 3.4.1(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1) + version: 3.4.1(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1) rehype-sanitize: specifier: ^6.0.0 version: 6.0.0 @@ -184,23 +184,23 @@ importers: version: 3.25.76 zustand: specifier: ^5.0.8 - version: 5.0.8(@types/react@19.2.4)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + version: 5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) devDependencies: '@biomejs/biome': specifier: ^2.3.5 version: 2.3.5 '@hookform/devtools': specifier: ^4.4.0 - version: 4.4.0(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 4.4.0(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@svgr/cli': specifier: ^8.1.0 version: 8.1.0(typescript@5.9.3) '@tanstack/react-query': - specifier: ^5.90.9 - version: 5.90.9(react@19.2.0) + specifier: ^5.90.10 + version: 5.90.10(react@19.2.0) '@tanstack/react-query-devtools': specifier: ^5.90.2 - version: 5.90.2(@tanstack/react-query@5.90.9(react@19.2.0))(react@19.2.0) + version: 5.90.2(@tanstack/react-query@5.90.10(react@19.2.0))(react@19.2.0) '@tauri-apps/cli': specifier: ^2.9.4 version: 2.9.4 @@ -214,11 +214,11 @@ importers: specifier: ^24.10.1 version: 24.10.1 '@types/react': - specifier: ^19.2.4 - version: 19.2.4 + specifier: ^19.2.5 + version: 19.2.5 '@types/react-dom': specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.4) + version: 19.2.3(@types/react@19.2.5) '@vitejs/plugin-react': specifier: ^5.1.1 version: 5.1.1(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1)) @@ -1011,68 +1011,68 @@ packages: peerDependencies: '@svgr/core': '*' - '@swc/core-darwin-arm64@1.15.1': - resolution: {integrity: sha512-vEPrVxegWIjKEz+1VCVuKRY89jhokhSmQ/YXBWLnmLj9cI08G61RTZJvdsIcjYUjjTu7NgZlYVK+b2y0fbh11g==} + '@swc/core-darwin-arm64@1.15.2': + resolution: {integrity: sha512-Ghyz4RJv4zyXzrUC1B2MLQBbppIB5c4jMZJybX2ebdEQAvryEKp3gq1kBksCNsatKGmEgXul88SETU19sMWcrw==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.15.1': - resolution: {integrity: sha512-z9QguKxE3aldvwKHHDg5OlKehasbJBF1lacn5CnN6SlrHbdwokXHFA3nIoO3Bh1Tw7bCgFtdIR4jKlTTn3kBZA==} + '@swc/core-darwin-x64@1.15.2': + resolution: {integrity: sha512-7n/PGJOcL2QoptzL42L5xFFfXY5rFxLHnuz1foU+4ruUTG8x2IebGhtwVTpaDN8ShEv2UZObBlT1rrXTba15Zw==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.15.1': - resolution: {integrity: sha512-yS2FHA8E4YeiPG9YeYk/6mKiCWuXR5RdYlCmtlGzKcjWbI4GXUVe7+p9C0M6myRt3zdj3M1knmJxk52MQA9EZQ==} + '@swc/core-linux-arm-gnueabihf@1.15.2': + resolution: {integrity: sha512-ZUQVCfRJ9wimuxkStRSlLwqX4TEDmv6/J+E6FicGkQ6ssLMWoKDy0cAo93HiWt/TWEee5vFhFaSQYzCuBEGO6A==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.15.1': - resolution: {integrity: sha512-IFrjDu7+5Y61jLsUqBVXlXutDoPBX10eEeNTjW6C1yzm+cSTE7ayiKXMIFri4gEZ4VpXS6MUgkwjxtDpIXTh+w==} + '@swc/core-linux-arm64-gnu@1.15.2': + resolution: {integrity: sha512-GZh3pYBmfnpQ+JIg+TqLuz+pM+Mjsk5VOzi8nwKn/m+GvQBsxD5ectRtxuWUxMGNG8h0lMy4SnHRqdK3/iJl7A==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.15.1': - resolution: {integrity: sha512-fKzP9mRQGbhc5QhJPIsqKNNX/jyWrZgBxmo3Nz1SPaepfCUc7RFmtcJQI5q8xAun3XabXjh90wqcY/OVyg2+Kg==} + '@swc/core-linux-arm64-musl@1.15.2': + resolution: {integrity: sha512-5av6VYZZeneiYIodwzGMlnyVakpuYZryGzFIbgu1XP8wVylZxduEzup4eP8atiMDFmIm+s4wn8GySJmYqeJC0A==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.15.1': - resolution: {integrity: sha512-ZLjMi138uTJxb+1wzo4cB8mIbJbAsSLWRNeHc1g1pMvkERPWOGlem+LEYkkzaFzCNv1J8aKcL653Vtw8INHQeg==} + '@swc/core-linux-x64-gnu@1.15.2': + resolution: {integrity: sha512-1nO/UfdCLuT/uE/7oB3EZgTeZDCIa6nL72cFEpdegnqpJVNDI6Qb8U4g/4lfVPkmHq2lvxQ0L+n+JdgaZLhrRA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.15.1': - resolution: {integrity: sha512-jvSI1IdsIYey5kOITzyajjofXOOySVitmLxb45OPUjoNojql4sDojvlW5zoHXXFePdA6qAX4Y6KbzAOV3T3ctA==} + '@swc/core-linux-x64-musl@1.15.2': + resolution: {integrity: sha512-Ksfrb0Tx310kr+TLiUOvB/I80lyZ3lSOp6cM18zmNRT/92NB4mW8oX2Jo7K4eVEI2JWyaQUAFubDSha2Q+439A==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.15.1': - resolution: {integrity: sha512-X/FcDtNrDdY9r4FcXHt9QxUqC/2FbQdvZobCKHlHe8vTSKhUHOilWl5EBtkFVfsEs4D5/yAri9e3bJbwyBhhBw==} + '@swc/core-win32-arm64-msvc@1.15.2': + resolution: {integrity: sha512-IzUb5RlMUY0r1A9IuJrQ7Tbts1wWb73/zXVXT8VhewbHGoNlBKE0qUhKMED6Tv4wDF+pmbtUJmKXDthytAvLmg==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.15.1': - resolution: {integrity: sha512-vfheiWBux8PpC87oy1cshcqzgH7alWYpnVq5jWe7xuVkjqjGGDbBUKuS84eJCdsWcVaB5EXIWLKt+11W3/BOwA==} + '@swc/core-win32-ia32-msvc@1.15.2': + resolution: {integrity: sha512-kCATEzuY2LP9AlbU2uScjcVhgnCAkRdu62vbce17Ro5kxEHxYWcugkveyBRS3AqZGtwAKYbMAuNloer9LS/hpw==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.15.1': - resolution: {integrity: sha512-n3Ppn0LSov/IdlANq+8kxHqENuJRX5XtwQqPgQsgwKIcFq22u17NKfDs9vL5PwRsEHY6Xd67pnOqQX0h4AvbuQ==} + '@swc/core-win32-x64-msvc@1.15.2': + resolution: {integrity: sha512-iJaHeYCF4jTn7OEKSa3KRiuVFIVYts8jYjNmCdyz1u5g8HRyTDISD76r8+ljEOgm36oviRQvcXaw6LFp1m0yyA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.15.1': - resolution: {integrity: sha512-s9GN3M2jA32k+StvuS9uGe4ztf5KVGBdlJMMC6LR6Ah23Lq/CWKVcC3WeQi8qaAcLd+DiddoNCNMUWymLv+wWQ==} + '@swc/core@1.15.2': + resolution: {integrity: sha512-OQm+yJdXxvSjqGeaWhP6Ia264ogifwAO7Q12uTDVYj/Ks4jBTI4JknlcjDRAXtRhqbWsfbZyK/5RtuIPyptk3w==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -1086,8 +1086,8 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - '@tanstack/query-core@5.90.9': - resolution: {integrity: sha512-UFOCQzi6pRGeVTVlPNwNdnAvT35zugcIydqjvFUzG62dvz2iVjElmNp/hJkUoM5eqbUPfSU/GJIr/wbvD8bTUw==} + '@tanstack/query-core@5.90.10': + resolution: {integrity: sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==} '@tanstack/query-devtools@5.90.1': resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==} @@ -1098,8 +1098,8 @@ packages: '@tanstack/react-query': ^5.90.2 react: ^18 || ^19 - '@tanstack/react-query@5.90.9': - resolution: {integrity: sha512-Zke2AaXiaSfnG8jqPZR52m8SsclKT2d9//AgE/QIzyNvbpj/Q2ln+FsZjb1j69bJZUouBvX2tg9PHirkTm8arw==} + '@tanstack/react-query@5.90.10': + resolution: {integrity: sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==} peerDependencies: react: ^18 || ^19 @@ -1303,8 +1303,8 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.4': - resolution: {integrity: sha512-tBFxBp9Nfyy5rsmefN+WXc1JeW/j2BpBHFdLZbEVfs9wn3E3NRFxwV0pJg8M1qQAexFpvz73hJXFofV0ZAu92A==} + '@types/react@19.2.5': + resolution: {integrity: sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1437,8 +1437,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001754: - resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + caniuse-lite@1.0.30001755: + resolution: {integrity: sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1550,8 +1550,8 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - csstype@3.2.0: - resolution: {integrity: sha512-si++xzRAY9iPp60roQiFta7OFbhrgvcthrhlNAGeQptSY25uJjkfUV8OArC3KLocB8JT8ohz+qgxWCmz8RhjIg==} + csstype@3.2.2: + resolution: {integrity: sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g==} d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} @@ -1682,8 +1682,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.252: - resolution: {integrity: sha512-53uTpjtRgS7gjIxZ4qCgFdNO2q+wJt/Z8+xAvxbCqXPJrY6h7ighUkadQmNMXH96crtpa6gPFNP7BF4UBGDuaA==} + electron-to-chromium@1.5.254: + resolution: {integrity: sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1723,8 +1723,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - es-toolkit@1.41.0: - resolution: {integrity: sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==} + es-toolkit@1.42.0: + resolution: {integrity: sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==} esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} @@ -1902,11 +1902,11 @@ packages: hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - html-dom-parser@5.1.1: - resolution: {integrity: sha512-+o4Y4Z0CLuyemeccvGN4bAO20aauB2N9tFEAep5x4OW34kV4PTarBHm6RL02afYt2BMKcr0D2Agep8S3nJPIBg==} + html-dom-parser@5.1.2: + resolution: {integrity: sha512-9nD3Rj3/FuQt83AgIa1Y3ruzspwFFA54AJbQnohXN+K6fL1/bhcDQJJY5Ne4L4A163ADQFVESd/0TLyNoV0mfg==} - html-react-parser@5.2.8: - resolution: {integrity: sha512-09WaI81tbpwhXWeMe1m9VptZVJUcigo0l59zVt+2HUIQT7+baU38/oNhllj6MKhOuGXqh0nrlwOgxbxbm6xXHw==} + html-react-parser@5.2.10: + resolution: {integrity: sha512-DjOLloguuDA+Ed7Q7PKhvMQmCl2+Yk/pfvvca68fvn15QFBbL4uHGxXwoXQ4sqS0UyuRH2lJb0S8yZCL3lvehQ==} peerDependencies: '@types/react': 0.14 || 15 || 16 || 17 || 18 || 19 react: 0.14 || 15 || 16 || 17 || 18 || 19 @@ -1937,8 +1937,8 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inline-style-parser@0.2.6: - resolution: {integrity: sha512-gtGXVaBdl5mAes3rPcMedEBm12ibjt1kDMFfheul1wUAOVEJW60voNdMVzVkfLN06O7ZaD/rxhfKgtlgtTbMjg==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} @@ -2743,11 +2743,11 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - style-to-js@1.1.19: - resolution: {integrity: sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ==} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - style-to-object@1.0.12: - resolution: {integrity: sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw==} + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} @@ -3194,7 +3194,7 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0)': + '@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 @@ -3206,7 +3206,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.4 + '@types/react': 19.2.5 transitivePeerDependencies: - supports-color @@ -3216,22 +3216,22 @@ snapshots: '@emotion/memoize': 0.9.0 '@emotion/unitless': 0.10.0 '@emotion/utils': 1.4.2 - csstype: 3.2.0 + csstype: 3.2.2 '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0) + '@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0) '@emotion/serialize': 1.3.3 '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0) '@emotion/utils': 1.4.2 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.4 + '@types/react': 19.2.5 transitivePeerDependencies: - supports-color @@ -3356,10 +3356,10 @@ snapshots: '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 - '@hookform/devtools@4.4.0(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@hookform/devtools@4.4.0(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0) + '@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0) '@types/lodash': 4.17.20 little-state-machine: 4.8.1(react@19.2.0) lodash: 4.17.21 @@ -3470,7 +3470,7 @@ snapshots: '@react-hook/passive-layout-effect': 1.2.1(react@19.2.0) react: 19.2.0 - '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1))(react@19.2.0)': + '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.5)(react@19.2.0)(redux@5.0.1))(react@19.2.0)': dependencies: '@standard-schema/spec': 1.0.0 '@standard-schema/utils': 0.3.0 @@ -3480,7 +3480,7 @@ snapshots: reselect: 5.1.1 optionalDependencies: react: 19.2.0 - react-redux: 9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.2.5)(react@19.2.0)(redux@5.0.1) '@remix-run/router@1.23.1': {} @@ -3704,51 +3704,51 @@ snapshots: transitivePeerDependencies: - typescript - '@swc/core-darwin-arm64@1.15.1': + '@swc/core-darwin-arm64@1.15.2': optional: true - '@swc/core-darwin-x64@1.15.1': + '@swc/core-darwin-x64@1.15.2': optional: true - '@swc/core-linux-arm-gnueabihf@1.15.1': + '@swc/core-linux-arm-gnueabihf@1.15.2': optional: true - '@swc/core-linux-arm64-gnu@1.15.1': + '@swc/core-linux-arm64-gnu@1.15.2': optional: true - '@swc/core-linux-arm64-musl@1.15.1': + '@swc/core-linux-arm64-musl@1.15.2': optional: true - '@swc/core-linux-x64-gnu@1.15.1': + '@swc/core-linux-x64-gnu@1.15.2': optional: true - '@swc/core-linux-x64-musl@1.15.1': + '@swc/core-linux-x64-musl@1.15.2': optional: true - '@swc/core-win32-arm64-msvc@1.15.1': + '@swc/core-win32-arm64-msvc@1.15.2': optional: true - '@swc/core-win32-ia32-msvc@1.15.1': + '@swc/core-win32-ia32-msvc@1.15.2': optional: true - '@swc/core-win32-x64-msvc@1.15.1': + '@swc/core-win32-x64-msvc@1.15.2': optional: true - '@swc/core@1.15.1': + '@swc/core@1.15.2': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.25 optionalDependencies: - '@swc/core-darwin-arm64': 1.15.1 - '@swc/core-darwin-x64': 1.15.1 - '@swc/core-linux-arm-gnueabihf': 1.15.1 - '@swc/core-linux-arm64-gnu': 1.15.1 - '@swc/core-linux-arm64-musl': 1.15.1 - '@swc/core-linux-x64-gnu': 1.15.1 - '@swc/core-linux-x64-musl': 1.15.1 - '@swc/core-win32-arm64-msvc': 1.15.1 - '@swc/core-win32-ia32-msvc': 1.15.1 - '@swc/core-win32-x64-msvc': 1.15.1 + '@swc/core-darwin-arm64': 1.15.2 + '@swc/core-darwin-x64': 1.15.2 + '@swc/core-linux-arm-gnueabihf': 1.15.2 + '@swc/core-linux-arm64-gnu': 1.15.2 + '@swc/core-linux-arm64-musl': 1.15.2 + '@swc/core-linux-x64-gnu': 1.15.2 + '@swc/core-linux-x64-musl': 1.15.2 + '@swc/core-win32-arm64-msvc': 1.15.2 + '@swc/core-win32-ia32-msvc': 1.15.2 + '@swc/core-win32-x64-msvc': 1.15.2 '@swc/counter@0.1.3': {} @@ -3756,19 +3756,19 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.90.9': {} + '@tanstack/query-core@5.90.10': {} '@tanstack/query-devtools@5.90.1': {} - '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.9(react@19.2.0))(react@19.2.0)': + '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.10(react@19.2.0))(react@19.2.0)': dependencies: '@tanstack/query-devtools': 5.90.1 - '@tanstack/react-query': 5.90.9(react@19.2.0) + '@tanstack/react-query': 5.90.10(react@19.2.0) react: 19.2.0 - '@tanstack/react-query@5.90.9(react@19.2.0)': + '@tanstack/react-query@5.90.10(react@19.2.0)': dependencies: - '@tanstack/query-core': 5.90.9 + '@tanstack/query-core': 5.90.10 react: 19.2.0 '@tanstack/react-virtual@3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': @@ -3955,13 +3955,13 @@ snapshots: '@types/parse-json@4.0.2': {} - '@types/react-dom@19.2.3(@types/react@19.2.4)': + '@types/react-dom@19.2.3(@types/react@19.2.5)': dependencies: - '@types/react': 19.2.4 + '@types/react': 19.2.5 - '@types/react@19.2.4': + '@types/react@19.2.5': dependencies: - csstype: 3.2.0 + csstype: 3.2.2 '@types/unist@2.0.11': {} @@ -3981,7 +3981,7 @@ snapshots: '@vitejs/plugin-react-swc@4.2.2(vite@7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.47 - '@swc/core': 1.15.1 + '@swc/core': 1.15.2 vite: 7.2.2(@types/node@24.10.1)(sass@1.92.1)(yaml@2.8.1) transitivePeerDependencies: - '@swc/helpers' @@ -4030,7 +4030,7 @@ snapshots: autoprefixer@10.4.22(postcss@8.5.6): dependencies: browserslist: 4.28.0 - caniuse-lite: 1.0.30001754 + caniuse-lite: 1.0.30001755 fraction.js: 5.3.4 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -4072,8 +4072,8 @@ snapshots: browserslist@4.28.0: dependencies: baseline-browser-mapping: 2.8.28 - caniuse-lite: 1.0.30001754 - electron-to-chromium: 1.5.252 + caniuse-lite: 1.0.30001755 + electron-to-chromium: 1.5.254 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) @@ -4100,7 +4100,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001754: {} + caniuse-lite@1.0.30001755: {} ccount@2.0.1: {} @@ -4212,7 +4212,7 @@ snapshots: dependencies: css-tree: 2.2.1 - csstype@3.2.0: {} + csstype@3.2.2: {} d3-array@3.2.4: dependencies: @@ -4340,7 +4340,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.252: {} + electron-to-chromium@1.5.254: {} emoji-regex@8.0.0: {} @@ -4430,7 +4430,7 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.41.0: {} + es-toolkit@1.42.0: {} esbuild@0.25.12: optionalDependencies: @@ -4615,7 +4615,7 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.19 + style-to-js: 1.1.21 unist-util-position: 5.0.0 vfile-message: 4.0.3 transitivePeerDependencies: @@ -4631,20 +4631,20 @@ snapshots: hosted-git-info@2.8.9: {} - html-dom-parser@5.1.1: + html-dom-parser@5.1.2: dependencies: domhandler: 5.0.3 htmlparser2: 10.0.0 - html-react-parser@5.2.8(@types/react@19.2.4)(react@19.2.0): + html-react-parser@5.2.10(@types/react@19.2.5)(react@19.2.0): dependencies: domhandler: 5.0.3 - html-dom-parser: 5.1.1 + html-dom-parser: 5.1.2 react: 19.2.0 react-property: 2.0.2 - style-to-js: 1.1.19 + style-to-js: 1.1.21 optionalDependencies: - '@types/react': 19.2.4 + '@types/react': 19.2.5 html-url-attributes@3.0.1: {} @@ -4671,7 +4671,7 @@ snapshots: inherits@2.0.4: {} - inline-style-parser@0.2.6: {} + inline-style-parser@0.2.7: {} internal-slot@1.1.0: dependencies: @@ -4976,9 +4976,9 @@ snapshots: memorystream@0.3.1: {} - merge-refs@2.0.0(@types/react@19.2.4): + merge-refs@2.0.0(@types/react@19.2.5): optionalDependencies: - '@types/react': 19.2.4 + '@types/react': 19.2.5 micromark-core-commonmark@2.0.3: dependencies: @@ -5324,11 +5324,11 @@ snapshots: dependencies: react: 19.2.0 - react-markdown@10.1.0(@types/react@19.2.4)(react@19.2.0): + react-markdown@10.1.0(@types/react@19.2.5)(react@19.2.0): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/react': 19.2.4 + '@types/react': 19.2.5 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 @@ -5350,13 +5350,13 @@ snapshots: qr.js: 0.0.0 react: 19.2.0 - react-redux@9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.5)(react@19.2.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 react: 19.2.0 use-sync-external-store: 1.6.0(react@19.2.0) optionalDependencies: - '@types/react': 19.2.4 + '@types/react': 19.2.5 redux: 5.0.1 react-refresh@0.18.0: {} @@ -5394,18 +5394,18 @@ snapshots: readdirp@4.1.2: {} - recharts@3.4.1(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1): + recharts@3.4.1(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1): dependencies: - '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1))(react@19.2.0) + '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.5)(react@19.2.0)(redux@5.0.1))(react@19.2.0) clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.41.0 + es-toolkit: 1.42.0 eventemitter3: 5.0.1 immer: 10.2.0 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) react-is: 18.3.1 - react-redux: 9.2.0(@types/react@19.2.4)(react@19.2.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.2.5)(react@19.2.0)(redux@5.0.1) reselect: 5.1.1 tiny-invariant: 1.3.3 use-sync-external-store: 1.6.0(react@19.2.0) @@ -5674,13 +5674,13 @@ snapshots: strip-bom@3.0.0: {} - style-to-js@1.1.19: + style-to-js@1.1.21: dependencies: - style-to-object: 1.0.12 + style-to-object: 1.0.14 - style-to-object@1.0.12: + style-to-object@1.0.14: dependencies: - inline-style-parser: 0.2.6 + inline-style-parser: 0.2.7 stylis@4.2.0: {} @@ -5962,9 +5962,9 @@ snapshots: zod@3.25.76: {} - zustand@5.0.8(@types/react@19.2.4)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + zustand@5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): optionalDependencies: - '@types/react': 19.2.4 + '@types/react': 19.2.5 immer: 10.2.0 react: 19.2.0 use-sync-external-store: 1.6.0(react@19.2.0) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 26e44cc4..3d45e8d1 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -522,9 +522,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" dependencies = [ "axum-core", "bytes", @@ -2942,9 +2942,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.8" +version = "0.25.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" dependencies = [ "bytemuck", "byteorder-lite", @@ -3666,6 +3666,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "notify-rust" version = "4.11.7" @@ -5675,9 +5684,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" dependencies = [ "base64 0.22.1", "chrono", @@ -5694,9 +5703,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" dependencies = [ "darling 0.21.3", "proc-macro2", @@ -7400,12 +7409,12 @@ dependencies = [ [[package]] name = "tree_magic_mini" -version = "3.2.1" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52fac5f7d176f7f7f7e827299ead28ef98de642c5d93a97e0cd0816d17557f19" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" dependencies = [ "memchr", - "nom", + "nom 8.0.0", "petgraph 0.8.3", ] @@ -8143,7 +8152,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]]