Compare commits

...

21 Commits

Author SHA1 Message Date
ac11f91d79 games_in_download: Mutex -> RwLock 2025-08-27 21:29:55 +02:00
8e76e8d1e2 introduce cargo vet 2025-08-27 21:21:01 +02:00
3b6fc80578 [deps] cargo update 2025-08-27 19:55:56 +02:00
8c6fd139c8 [deps] cargo update 2025-08-20 08:54:56 +02:00
66572e16ce [deps] cargo update
Updating anyhow         v1.0.98  -> v1.0.99
Updating async-trait    v0.1.88  -> v0.1.89
Updating bitflags       v2.9.1   -> v2.9.2
Updating brotli         v8.0.1   -> v8.0.2
Updating cc             v1.2.32  -> v1.2.33
Updating clap_builder   v4.5.43  -> v4.5.44
Updating clap_derive    v4.5.41  -> v4.5.45
Updating clap           v4.5.43  -> v4.5.45
Updating dlopen2        v0.7.0   -> v0.8.0
Updating glob           v0.3.2   -> v0.3.3
Updating libc           v0.2.174 -> v0.2.175
Updating objc2          v0.6.1   -> v0.6.2
Updating proc-macro2    v1.0.96  -> v1.0.101
Updating reqwest        v0.12.22 -> v0.12.23
Updating serde-untagged v0.1.7   -> v0.1.8
Updating syn            v2.0.104 -> v2.0.106
Updating tao            v0.34.0  -> v0.34.1
Updating thiserror-impl v2.0.12  -> v2.0.15
Updating thiserror      v2.0.12  -> v2.0.15
Updating uuid           v1.17.0  -> v1.18.0
2025-08-17 16:37:01 +02:00
3b19cb8b18 clippy: apply and fix new lints 2025-08-17 16:35:54 +02:00
61a41c7122 clippy: add same lints to all crates 2025-08-17 16:12:42 +02:00
cbad9389ee code: remove unnecessary else branch 2025-08-17 16:08:38 +02:00
02d84c4d84 code: better debug for install state 2025-08-17 16:07:06 +02:00
ca40a62ff8 clippy/fmt: just fix applied 2025-08-17 16:04:45 +02:00
3dcc0271b8 justfile: typical cargo commands 2025-08-17 16:04:09 +02:00
98126a72da [clippy] fixes 2025-08-12 08:44:59 +02:00
70548d2041 refactor(server): implement request handlers as methods
moved request handling logic into dedicated methods on the request handler struct for better organization and readability. this includes handlers for ping, list games, get game info, and invalid requests. game file data handler is added but not yet implemented.
2025-08-12 08:39:23 +02:00
13d97de515 cargo fmt 2025-08-12 08:38:12 +02:00
de6d39a8d7 justfile 2025-08-12 08:38:00 +02:00
32c7659702 [deps] upgrade 2025-08-12 08:37:25 +02:00
1b90a9baf3 [deps] cargo update 2025-07-11 18:21:12 +02:00
1ffab086ff [clippy] apply simple changes with --fix 2025-07-11 13:16:13 +02:00
b8329bd0b1 [feat] use mimalloc 2025-07-11 13:13:14 +02:00
24e6c8c0b5 [deps] cargo update 2025-07-11 13:12:07 +02:00
083dbc853b [deps] deno upgrade 2025-07-11 13:10:09 +02:00
20 changed files with 6051 additions and 1300 deletions

1451
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,8 @@ eyre = "0.6"
gethostname = "1" gethostname = "1"
itertools = "0.14" itertools = "0.14"
log = "0.4" log = "0.4"
mdns-sd = "0.13" mdns-sd = "0.14"
mimalloc = { version = "0.1", features = ["secure"] }
s2n-quic = { version = "1", features = ["provider-event-tracing"] } s2n-quic = { version = "1", features = ["provider-event-tracing"] }
semver = "1" semver = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View File

@@ -3,6 +3,14 @@ name = "lanspread-compat"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
todo = "warn"
unwrap_used = "warn"
[dependencies] [dependencies]
# local # local
lanspread-db = { path = "../lanspread-db" } lanspread-db = { path = "../lanspread-db" }

View File

@@ -58,6 +58,7 @@ impl From<EtiGame> for Game {
max_players: eti_game.game_maxplayers, max_players: eti_game.game_maxplayers,
version: eti_game.game_version, version: eti_game.game_version,
genre: eti_game.genre_de, genre: eti_game.genre_de,
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
size: (eti_game.game_size * 1024.0 * 1024.0 * 1024.0) as u64, size: (eti_game.game_size * 1024.0 * 1024.0 * 1024.0) as u64,
thumbnail: None, thumbnail: None,
installed: false, installed: false,

View File

@@ -61,7 +61,7 @@ impl Eq for Game {}
impl PartialOrd for Game { impl PartialOrd for Game {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.name.cmp(&other.name)) Some(self.cmp(other))
} }
} }

View File

@@ -3,6 +3,14 @@ name = "lanspread-mdns"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
todo = "warn"
unwrap_used = "warn"
[dependencies] [dependencies]
mdns-sd = { workspace = true } mdns-sd = { workspace = true }
eyre = { workspace = true } eyre = { workspace = true }

View File

@@ -1,3 +1,5 @@
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
use std::net::SocketAddr; use std::net::SocketAddr;
use eyre::bail; use eyre::bail;
@@ -59,15 +61,15 @@ pub fn discover_service(
ServiceEvent::ServiceResolved(info) => { ServiceEvent::ServiceResolved(info) => {
tracing::trace!(?info, "mdns ServiceResolved event"); tracing::trace!(?info, "mdns ServiceResolved event");
if let Some(instance_name) = instance_name { if let Some(instance_name) = instance_name
if info.get_fullname() != format!("{}.{}", instance_name, service_type) { && info.get_fullname() != format!("{instance_name}.{service_type}")
tracing::warn!( {
"Found service with wrong instance name: {} (expected: {})", tracing::warn!(
info.get_fullname(), "Found service with wrong instance name: {} (expected: {})",
instance_name, info.get_fullname(),
); instance_name,
continue; );
} continue;
} }
if let Some(address) = info.get_addresses().iter().next() { if let Some(address) = info.get_addresses().iter().next() {

View File

@@ -26,6 +26,7 @@ clap = { workspace = true }
eyre = { workspace = true } eyre = { workspace = true }
gethostname = { workspace = true } gethostname = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
mimalloc = { workspace = true }
s2n-quic = { workspace = true } s2n-quic = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
semver = { workspace = true } semver = { workspace = true }

View File

@@ -1,3 +1,8 @@
use mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
mod cli; mod cli;
mod quic; mod quic;
mod req; mod req;
@@ -11,7 +16,10 @@ use gethostname::gethostname;
use lanspread_compat::eti; use lanspread_compat::eti;
use lanspread_db::db::{Game, GameDB}; use lanspread_db::db::{Game, GameDB};
use lanspread_mdns::{ use lanspread_mdns::{
DaemonEvent, LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, DaemonEvent,
LANSPREAD_INSTANCE_NAME,
LANSPREAD_SERVICE_TYPE,
MdnsAdvertiser,
}; };
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use uuid::Uuid; use uuid::Uuid;

View File

@@ -44,63 +44,77 @@ impl RequestHandler {
Ok(()) Ok(())
} }
fn handle_ping() -> Response {
Response::Pong
}
async fn handle_list_games(&self) -> Response {
let db = self.db.read().await;
Response::ListGames(db.all_games().into_iter().cloned().collect())
}
async fn handle_get_game(&self, id: String, games_folder: &Path) -> Response {
if self.db.read().await.get_game_by_id(&id).is_none() {
tracing::error!("Game not found in DB: {id}");
return Response::GameNotFound(id);
}
let game_dir = games_folder.join(&id);
if !game_dir.exists() {
tracing::error!("Game folder does not exist: {}", game_dir.display());
return Response::GameNotFound(id);
}
let mut game_files_descs: Vec<GameFileDescription> = vec![];
for entry in WalkDir::new(&game_dir)
.into_iter()
.filter_map(std::result::Result::ok)
{
match get_relative_path(games_folder, entry.path()) {
Ok(relative_path) => match relative_path.to_str() {
Some(relative_path) => {
let game_file_description = GameFileDescription {
game_id: id.clone(),
relative_path: relative_path.to_string(),
is_dir: entry.file_type().is_dir(),
};
tracing::debug!("Found game file: {:?}", game_file_description);
game_files_descs.push(game_file_description);
}
None => {
tracing::error!("Failed to get relative path: {relative_path:?}",);
}
},
Err(e) => {
tracing::error!("Failed to get relative path: {e}");
}
}
}
Response::GetGame {
id,
file_descriptions: game_files_descs,
}
}
fn handle_get_game_file_data() -> Response {
Response::InvalidRequest(Bytes::new(), "Not implemented".to_string())
}
fn handle_invalid(data: Bytes, err_msg: String) -> Response {
Response::InvalidRequest(data, err_msg)
}
pub(crate) async fn process_request(&self, request: Request, games_folder: &Path) -> Response { pub(crate) async fn process_request(&self, request: Request, games_folder: &Path) -> Response {
match request { match request {
Request::Ping => Response::Pong, Request::Ping => RequestHandler::handle_ping(),
Request::ListGames => { Request::ListGames => self.handle_list_games().await,
let db = self.db.read().await; Request::GetGame { id } => self.handle_get_game(id, games_folder).await,
Response::ListGames(db.all_games().into_iter().cloned().collect()) Request::GetGameFileData(_) => RequestHandler::handle_get_game_file_data(),
} Request::Invalid(data, err_msg) => RequestHandler::handle_invalid(data, err_msg),
Request::GetGame { id } => {
if self.db.read().await.get_game_by_id(&id).is_none() {
tracing::error!("Game not found in DB: {id}");
return Response::GameNotFound(id);
}
let game_dir = games_folder.join(&id);
if !game_dir.exists() {
tracing::error!("Game folder does not exist: {}", game_dir.display());
return Response::GameNotFound(id);
}
let mut game_files_descs: Vec<GameFileDescription> = vec![];
for entry in WalkDir::new(&game_dir)
.into_iter()
.filter_map(std::result::Result::ok)
{
match get_relative_path(games_folder, entry.path()) {
Ok(relative_path) => match relative_path.to_str() {
Some(relative_path) => {
let game_file_description = GameFileDescription {
game_id: id.clone(),
relative_path: relative_path.to_string(),
is_dir: entry.file_type().is_dir(),
};
tracing::debug!("Found game file: {:?}", game_file_description);
game_files_descs.push(game_file_description);
}
None => {
tracing::error!("Failed to get relative path: {relative_path:?}",);
}
},
Err(e) => {
tracing::error!("Failed to get relative path: {e}");
}
}
}
Response::GetGame {
id,
file_descriptions: game_files_descs,
}
}
Request::GetGameFileData(_) => {
Response::InvalidRequest(Bytes::new(), "Not implemented".to_string())
}
Request::Invalid(data, err_msg) => Response::InvalidRequest(data, err_msg),
} }
} }
} }

View File

@@ -1,19 +1,19 @@
{ {
"version": "4", "version": "4",
"specifiers": { "specifiers": {
"npm:@tauri-apps/api@^2.3.0": "2.3.0", "npm:@tauri-apps/api@^2.6.0": "2.6.0",
"npm:@tauri-apps/cli@^2.3.1": "2.3.1", "npm:@tauri-apps/api@^2.7.0": "2.7.0",
"npm:@tauri-apps/plugin-dialog@^2.2.0": "2.2.0", "npm:@tauri-apps/cli@^2.7.1": "2.7.1",
"npm:@tauri-apps/plugin-shell@^2.2.0": "2.2.0", "npm:@tauri-apps/plugin-dialog@^2.3.2": "2.3.2",
"npm:@tauri-apps/plugin-store@^2.2.0": "2.2.0", "npm:@tauri-apps/plugin-shell@^2.3.0": "2.3.0",
"npm:@types/react-dom@^19.0.4": "19.0.4_@types+react@19.0.10", "npm:@tauri-apps/plugin-store@^2.3.0": "2.3.0",
"npm:@types/react@^19.0.10": "19.0.10", "npm:@types/react-dom@^19.1.7": "19.1.7_@types+react@19.1.9",
"npm:@types/react@^19.0.12": "19.0.12", "npm:@types/react@^19.1.9": "19.1.9",
"npm:@vitejs/plugin-react@^4.3.4": "4.3.4_vite@6.2.2_@babel+core@7.26.10", "npm:@vitejs/plugin-react@5": "5.0.0_vite@7.1.1__picomatch@4.0.3_@babel+core@7.28.0",
"npm:react-dom@19": "19.0.0_react@19.0.0", "npm:react-dom@^19.1.1": "19.1.1_react@19.1.1",
"npm:react@19": "19.0.0", "npm:react@^19.1.1": "19.1.1",
"npm:typescript@^5.8.2": "5.8.2", "npm:typescript@^5.9.2": "5.9.2",
"npm:vite@^6.2.2": "6.2.2" "npm:vite@^7.1.1": "7.1.1_picomatch@4.0.3"
}, },
"npm": { "npm": {
"@ampproject/remapping@2.3.0": { "@ampproject/remapping@2.3.0": {
@@ -23,19 +23,19 @@
"@jridgewell/trace-mapping" "@jridgewell/trace-mapping"
] ]
}, },
"@babel/code-frame@7.26.2": { "@babel/code-frame@7.27.1": {
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dependencies": [ "dependencies": [
"@babel/helper-validator-identifier", "@babel/helper-validator-identifier",
"js-tokens", "js-tokens",
"picocolors" "picocolors"
] ]
}, },
"@babel/compat-data@7.26.8": { "@babel/compat-data@7.28.0": {
"integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==" "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="
}, },
"@babel/core@7.26.10": { "@babel/core@7.28.0": {
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
"dependencies": [ "dependencies": [
"@ampproject/remapping", "@ampproject/remapping",
"@babel/code-frame", "@babel/code-frame",
@@ -54,8 +54,8 @@
"semver" "semver"
] ]
}, },
"@babel/generator@7.26.10": { "@babel/generator@7.28.0": {
"integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
"dependencies": [ "dependencies": [
"@babel/parser", "@babel/parser",
"@babel/types", "@babel/types",
@@ -64,8 +64,8 @@
"jsesc" "jsesc"
] ]
}, },
"@babel/helper-compilation-targets@7.26.5": { "@babel/helper-compilation-targets@7.27.2": {
"integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
"dependencies": [ "dependencies": [
"@babel/compat-data", "@babel/compat-data",
"@babel/helper-validator-option", "@babel/helper-validator-option",
@@ -74,15 +74,18 @@
"semver" "semver"
] ]
}, },
"@babel/helper-module-imports@7.25.9": { "@babel/helper-globals@7.28.0": {
"integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="
},
"@babel/helper-module-imports@7.27.1": {
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dependencies": [ "dependencies": [
"@babel/traverse", "@babel/traverse",
"@babel/types" "@babel/types"
] ]
}, },
"@babel/helper-module-transforms@7.26.0_@babel+core@7.26.10": { "@babel/helper-module-transforms@7.27.3_@babel+core@7.28.0": {
"integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"dependencies": [ "dependencies": [
"@babel/core", "@babel/core",
"@babel/helper-module-imports", "@babel/helper-module-imports",
@@ -90,151 +93,153 @@
"@babel/traverse" "@babel/traverse"
] ]
}, },
"@babel/helper-plugin-utils@7.26.5": { "@babel/helper-plugin-utils@7.27.1": {
"integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==" "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="
}, },
"@babel/helper-string-parser@7.25.9": { "@babel/helper-string-parser@7.27.1": {
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
}, },
"@babel/helper-validator-identifier@7.25.9": { "@babel/helper-validator-identifier@7.27.1": {
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
}, },
"@babel/helper-validator-option@7.25.9": { "@babel/helper-validator-option@7.27.1": {
"integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==" "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="
}, },
"@babel/helpers@7.26.10": { "@babel/helpers@7.28.2": {
"integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
"dependencies": [ "dependencies": [
"@babel/template", "@babel/template",
"@babel/types" "@babel/types"
] ]
}, },
"@babel/parser@7.26.10": { "@babel/parser@7.28.0": {
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"dependencies": [ "dependencies": [
"@babel/types" "@babel/types"
] ]
}, },
"@babel/plugin-transform-react-jsx-self@7.25.9_@babel+core@7.26.10": { "@babel/plugin-transform-react-jsx-self@7.27.1_@babel+core@7.28.0": {
"integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"dependencies": [ "dependencies": [
"@babel/core", "@babel/core",
"@babel/helper-plugin-utils" "@babel/helper-plugin-utils"
] ]
}, },
"@babel/plugin-transform-react-jsx-source@7.25.9_@babel+core@7.26.10": { "@babel/plugin-transform-react-jsx-source@7.27.1_@babel+core@7.28.0": {
"integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"dependencies": [ "dependencies": [
"@babel/core", "@babel/core",
"@babel/helper-plugin-utils" "@babel/helper-plugin-utils"
] ]
}, },
"@babel/template@7.26.9": { "@babel/template@7.27.2": {
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dependencies": [ "dependencies": [
"@babel/code-frame", "@babel/code-frame",
"@babel/parser", "@babel/parser",
"@babel/types" "@babel/types"
] ]
}, },
"@babel/traverse@7.26.10": { "@babel/traverse@7.28.0": {
"integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==", "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
"dependencies": [ "dependencies": [
"@babel/code-frame", "@babel/code-frame",
"@babel/generator", "@babel/generator",
"@babel/helper-globals",
"@babel/parser", "@babel/parser",
"@babel/template", "@babel/template",
"@babel/types", "@babel/types",
"debug", "debug"
"globals"
] ]
}, },
"@babel/types@7.26.10": { "@babel/types@7.28.2": {
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"dependencies": [ "dependencies": [
"@babel/helper-string-parser", "@babel/helper-string-parser",
"@babel/helper-validator-identifier" "@babel/helper-validator-identifier"
] ]
}, },
"@esbuild/aix-ppc64@0.25.1": { "@esbuild/aix-ppc64@0.25.8": {
"integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==" "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="
}, },
"@esbuild/android-arm64@0.25.1": { "@esbuild/android-arm64@0.25.8": {
"integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==" "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="
}, },
"@esbuild/android-arm@0.25.1": { "@esbuild/android-arm@0.25.8": {
"integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==" "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="
}, },
"@esbuild/android-x64@0.25.1": { "@esbuild/android-x64@0.25.8": {
"integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==" "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="
}, },
"@esbuild/darwin-arm64@0.25.1": { "@esbuild/darwin-arm64@0.25.8": {
"integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==" "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="
}, },
"@esbuild/darwin-x64@0.25.1": { "@esbuild/darwin-x64@0.25.8": {
"integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==" "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="
}, },
"@esbuild/freebsd-arm64@0.25.1": { "@esbuild/freebsd-arm64@0.25.8": {
"integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==" "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="
}, },
"@esbuild/freebsd-x64@0.25.1": { "@esbuild/freebsd-x64@0.25.8": {
"integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==" "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="
}, },
"@esbuild/linux-arm64@0.25.1": { "@esbuild/linux-arm64@0.25.8": {
"integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==" "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="
}, },
"@esbuild/linux-arm@0.25.1": { "@esbuild/linux-arm@0.25.8": {
"integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==" "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="
}, },
"@esbuild/linux-ia32@0.25.1": { "@esbuild/linux-ia32@0.25.8": {
"integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==" "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="
}, },
"@esbuild/linux-loong64@0.25.1": { "@esbuild/linux-loong64@0.25.8": {
"integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==" "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="
}, },
"@esbuild/linux-mips64el@0.25.1": { "@esbuild/linux-mips64el@0.25.8": {
"integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==" "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="
}, },
"@esbuild/linux-ppc64@0.25.1": { "@esbuild/linux-ppc64@0.25.8": {
"integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==" "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="
}, },
"@esbuild/linux-riscv64@0.25.1": { "@esbuild/linux-riscv64@0.25.8": {
"integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==" "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="
}, },
"@esbuild/linux-s390x@0.25.1": { "@esbuild/linux-s390x@0.25.8": {
"integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==" "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="
}, },
"@esbuild/linux-x64@0.25.1": { "@esbuild/linux-x64@0.25.8": {
"integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==" "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="
}, },
"@esbuild/netbsd-arm64@0.25.1": { "@esbuild/netbsd-arm64@0.25.8": {
"integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==" "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="
}, },
"@esbuild/netbsd-x64@0.25.1": { "@esbuild/netbsd-x64@0.25.8": {
"integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==" "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="
}, },
"@esbuild/openbsd-arm64@0.25.1": { "@esbuild/openbsd-arm64@0.25.8": {
"integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==" "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="
}, },
"@esbuild/openbsd-x64@0.25.1": { "@esbuild/openbsd-x64@0.25.8": {
"integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==" "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="
}, },
"@esbuild/sunos-x64@0.25.1": { "@esbuild/openharmony-arm64@0.25.8": {
"integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==" "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="
}, },
"@esbuild/win32-arm64@0.25.1": { "@esbuild/sunos-x64@0.25.8": {
"integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==" "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="
}, },
"@esbuild/win32-ia32@0.25.1": { "@esbuild/win32-arm64@0.25.8": {
"integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==" "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="
}, },
"@esbuild/win32-x64@0.25.1": { "@esbuild/win32-ia32@0.25.8": {
"integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==" "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="
}, },
"@jridgewell/gen-mapping@0.3.8": { "@esbuild/win32-x64@0.25.8": {
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="
},
"@jridgewell/gen-mapping@0.3.12": {
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dependencies": [ "dependencies": [
"@jridgewell/set-array",
"@jridgewell/sourcemap-codec", "@jridgewell/sourcemap-codec",
"@jridgewell/trace-mapping" "@jridgewell/trace-mapping"
] ]
@@ -242,117 +247,127 @@
"@jridgewell/resolve-uri@3.1.2": { "@jridgewell/resolve-uri@3.1.2": {
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
}, },
"@jridgewell/set-array@1.2.1": { "@jridgewell/sourcemap-codec@1.5.4": {
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="
}, },
"@jridgewell/sourcemap-codec@1.5.0": { "@jridgewell/trace-mapping@0.3.29": {
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
},
"@jridgewell/trace-mapping@0.3.25": {
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dependencies": [ "dependencies": [
"@jridgewell/resolve-uri", "@jridgewell/resolve-uri",
"@jridgewell/sourcemap-codec" "@jridgewell/sourcemap-codec"
] ]
}, },
"@rollup/rollup-android-arm-eabi@4.36.0": { "@rolldown/pluginutils@1.0.0-beta.30": {
"integrity": "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==" "integrity": "sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw=="
}, },
"@rollup/rollup-android-arm64@4.36.0": { "@rollup/rollup-android-arm-eabi@4.46.2": {
"integrity": "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==" "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA=="
}, },
"@rollup/rollup-darwin-arm64@4.36.0": { "@rollup/rollup-android-arm64@4.46.2": {
"integrity": "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==" "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ=="
}, },
"@rollup/rollup-darwin-x64@4.36.0": { "@rollup/rollup-darwin-arm64@4.46.2": {
"integrity": "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==" "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ=="
}, },
"@rollup/rollup-freebsd-arm64@4.36.0": { "@rollup/rollup-darwin-x64@4.46.2": {
"integrity": "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==" "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA=="
}, },
"@rollup/rollup-freebsd-x64@4.36.0": { "@rollup/rollup-freebsd-arm64@4.46.2": {
"integrity": "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==" "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg=="
}, },
"@rollup/rollup-linux-arm-gnueabihf@4.36.0": { "@rollup/rollup-freebsd-x64@4.46.2": {
"integrity": "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==" "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw=="
}, },
"@rollup/rollup-linux-arm-musleabihf@4.36.0": { "@rollup/rollup-linux-arm-gnueabihf@4.46.2": {
"integrity": "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==" "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA=="
}, },
"@rollup/rollup-linux-arm64-gnu@4.36.0": { "@rollup/rollup-linux-arm-musleabihf@4.46.2": {
"integrity": "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==" "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ=="
}, },
"@rollup/rollup-linux-arm64-musl@4.36.0": { "@rollup/rollup-linux-arm64-gnu@4.46.2": {
"integrity": "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==" "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng=="
}, },
"@rollup/rollup-linux-loongarch64-gnu@4.36.0": { "@rollup/rollup-linux-arm64-musl@4.46.2": {
"integrity": "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==" "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg=="
}, },
"@rollup/rollup-linux-powerpc64le-gnu@4.36.0": { "@rollup/rollup-linux-loongarch64-gnu@4.46.2": {
"integrity": "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==" "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA=="
}, },
"@rollup/rollup-linux-riscv64-gnu@4.36.0": { "@rollup/rollup-linux-ppc64-gnu@4.46.2": {
"integrity": "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==" "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw=="
}, },
"@rollup/rollup-linux-s390x-gnu@4.36.0": { "@rollup/rollup-linux-riscv64-gnu@4.46.2": {
"integrity": "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==" "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ=="
}, },
"@rollup/rollup-linux-x64-gnu@4.36.0": { "@rollup/rollup-linux-riscv64-musl@4.46.2": {
"integrity": "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==" "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw=="
}, },
"@rollup/rollup-linux-x64-musl@4.36.0": { "@rollup/rollup-linux-s390x-gnu@4.46.2": {
"integrity": "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==" "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA=="
}, },
"@rollup/rollup-win32-arm64-msvc@4.36.0": { "@rollup/rollup-linux-x64-gnu@4.46.2": {
"integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==" "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA=="
}, },
"@rollup/rollup-win32-ia32-msvc@4.36.0": { "@rollup/rollup-linux-x64-musl@4.46.2": {
"integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==" "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA=="
}, },
"@rollup/rollup-win32-x64-msvc@4.36.0": { "@rollup/rollup-win32-arm64-msvc@4.46.2": {
"integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==" "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g=="
}, },
"@tauri-apps/api@2.3.0": { "@rollup/rollup-win32-ia32-msvc@4.46.2": {
"integrity": "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA==" "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ=="
}, },
"@tauri-apps/cli-darwin-arm64@2.3.1": { "@rollup/rollup-win32-x64-msvc@4.46.2": {
"integrity": "sha512-TOhSdsXYt+f+asRU+Dl+Wufglj/7+CX9h8RO4hl5k7D6lR4L8yTtdhpS7btaclOMmjYC4piNfJE70GoxhOoYWw==" "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg=="
}, },
"@tauri-apps/cli-darwin-x64@2.3.1": { "@tauri-apps/api@2.6.0": {
"integrity": "sha512-LDwGg3AuBQ3aCeMAFaFwt0MSGOVFoXuXEe0z4QxQ7jZE5tdAOhKABaq4i569V5lShCgQZ6nLD/tmA5+GipvHnA==" "integrity": "sha512-hRNcdercfgpzgFrMXWwNDBN0B7vNzOzRepy6ZAmhxi5mDLVPNrTpo9MGg2tN/F7JRugj4d2aF7E1rtPXAHaetg=="
}, },
"@tauri-apps/cli-linux-arm-gnueabihf@2.3.1": { "@tauri-apps/api@2.7.0": {
"integrity": "sha512-hu3HpbbtJBvHXw5i54QHwLxOUoXWqhf7CL2YYSPOrWEEQo10NKddulP61L5gfr5z+bSSaitfLwqgTidgnaNJCA==" "integrity": "sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg=="
}, },
"@tauri-apps/cli-linux-arm64-gnu@2.3.1": { "@tauri-apps/cli-darwin-arm64@2.7.1": {
"integrity": "sha512-mEGgwkiGSKYXWHhGodo7zU9PCd2I/d6KkR+Wp1nzK+DxsCrEK6yJ5XxYLSQSDcKkM4dCxpVEPUiVMbDhmn08jg==" "integrity": "sha512-j2NXQN6+08G03xYiyKDKqbCV2Txt+hUKg0a8hYr92AmoCU8fgCjHyva/p16lGFGUG3P2Yu0xiNe1hXL9ZuRMzA=="
}, },
"@tauri-apps/cli-linux-arm64-musl@2.3.1": { "@tauri-apps/cli-darwin-x64@2.7.1": {
"integrity": "sha512-tqQkafikGfnc7ISnGjSYkbpnzJKEyO8XSa0YOXTAL3J8R5Pss5ZIZY7G8kq1mwQSR/dPVR1ZLTVXgZGuysjP8w==" "integrity": "sha512-CdYAefeM35zKsc91qIyKzbaO7FhzTyWKsE8hj7tEJ1INYpoh1NeNNyL/NSEA3Nebi5ilugioJ5tRK8ZXG8y3gw=="
}, },
"@tauri-apps/cli-linux-x64-gnu@2.3.1": { "@tauri-apps/cli-linux-arm-gnueabihf@2.7.1": {
"integrity": "sha512-I3puDJ2wGEauXlXbzIHn2etz78TaWs1cpN6zre02maHr6ZR7nf7euTCOGPhhfoMG0opA5mT/eLuYpVw648/VAA==" "integrity": "sha512-dnvyJrTA1UJxJjQ8q1N/gWomjP8Twij1BUQu2fdcT3OPpqlrbOk5R1yT0oD/721xoKNjroB5BXCsmmlykllxNg=="
}, },
"@tauri-apps/cli-linux-x64-musl@2.3.1": { "@tauri-apps/cli-linux-arm64-gnu@2.7.1": {
"integrity": "sha512-rbWiCOBuQN7tPySkUyBs914uUikE3mEUOqV/IFospvKESw4UC3G1DL5+ybfXH7Orb8/in3JpJuVzYQjo+OSbBA==" "integrity": "sha512-FtBW6LJPNRTws3qyUc294AqCWU91l/H0SsFKq6q4Q45MSS4x6wxLxou8zB53tLDGEPx3JSoPLcDaSfPlSbyujQ=="
}, },
"@tauri-apps/cli-win32-arm64-msvc@2.3.1": { "@tauri-apps/cli-linux-arm64-musl@2.7.1": {
"integrity": "sha512-PdTmUzSeTHjJuBpCV7L+V29fPhPtToU+NZU46slHKSA1aT38MiFDXBZ/6P5Zudrt9QPMfIubqnJKbK8Ivvv7Ww==" "integrity": "sha512-/HXY0t4FHkpFzjeYS5c16mlA6z0kzn5uKLWptTLTdFSnYpr8FCnOP4Sdkvm2TDQPF2ERxXtNCd+WR/jQugbGnA=="
}, },
"@tauri-apps/cli-win32-ia32-msvc@2.3.1": { "@tauri-apps/cli-linux-riscv64-gnu@2.7.1": {
"integrity": "sha512-K/Xa97kspWT4UWj3t26lL2D3QsopTAxS7kWi5kObdqtAGn3qD52qBi24FH38TdvHYz4QlnLIb30TukviCgh4gw==" "integrity": "sha512-GeW5lVI2GhhnaYckiDzstG2j2Jwlud5d2XefRGwlOK+C/bVGLT1le8MNPYK8wgRlpeK8fG1WnJJYD6Ke7YQ8bg=="
}, },
"@tauri-apps/cli-win32-x64-msvc@2.3.1": { "@tauri-apps/cli-linux-x64-gnu@2.7.1": {
"integrity": "sha512-RgwzXbP8gAno3kQEsybMtgLp6D1Z1Nec2cftryYbPTJmoMJs6e4qgtxuTSbUz5SKnHe8rGgMiFSvEGoHvbG72Q==" "integrity": "sha512-DprxKQkPxIPYwUgg+cscpv2lcIUhn2nxEPlk0UeaiV9vATxCXyytxr1gLcj3xgjGyNPlM0MlJyYaPy1JmRg1cA=="
}, },
"@tauri-apps/cli@2.3.1": { "@tauri-apps/cli-linux-x64-musl@2.7.1": {
"integrity": "sha512-xewcw/ZsCqgilTy2h7+pp2Baxoy7zLR2wXOV7SZLzkb6SshHVbm1BFAjn8iFATURRW85KLzl6wSGJ2dQHjVHqw==", "integrity": "sha512-KLlq3kOK7OUyDR757c0zQjPULpGZpLhNB0lZmZpHXvoOUcqZoCXJHh4dT/mryWZJp5ilrem5l8o9ngrDo0X1AA=="
},
"@tauri-apps/cli-win32-arm64-msvc@2.7.1": {
"integrity": "sha512-dH7KUjKkSypCeWPiainHyXoES3obS+JIZVoSwSZfKq2gWgs48FY3oT0hQNYrWveE+VR4VoR3b/F3CPGbgFvksA=="
},
"@tauri-apps/cli-win32-ia32-msvc@2.7.1": {
"integrity": "sha512-1oeibfyWQPVcijOrTg709qhbXArjX3x1MPjrmA5anlygwrbByxLBcLXvotcOeULFcnH2FYUMMLLant8kgvwE5A=="
},
"@tauri-apps/cli-win32-x64-msvc@2.7.1": {
"integrity": "sha512-D7Q9kDObutuirCNLxYQ7KAg2Xxg99AjcdYz/KuMw5HvyEPbkC9Q7JL0vOrQOrHEHxIQ2lYzFOZvKKoC2yyqXcg=="
},
"@tauri-apps/cli@2.7.1": {
"integrity": "sha512-RcGWR4jOUEl92w3uvI0h61Llkfj9lwGD1iwvDRD2isMrDhOzjeeeVn9aGzeW1jubQ/kAbMYfydcA4BA0Cy733Q==",
"dependencies": [ "dependencies": [
"@tauri-apps/cli-darwin-arm64", "@tauri-apps/cli-darwin-arm64",
"@tauri-apps/cli-darwin-x64", "@tauri-apps/cli-darwin-x64",
"@tauri-apps/cli-linux-arm-gnueabihf", "@tauri-apps/cli-linux-arm-gnueabihf",
"@tauri-apps/cli-linux-arm64-gnu", "@tauri-apps/cli-linux-arm64-gnu",
"@tauri-apps/cli-linux-arm64-musl", "@tauri-apps/cli-linux-arm64-musl",
"@tauri-apps/cli-linux-riscv64-gnu",
"@tauri-apps/cli-linux-x64-gnu", "@tauri-apps/cli-linux-x64-gnu",
"@tauri-apps/cli-linux-x64-musl", "@tauri-apps/cli-linux-x64-musl",
"@tauri-apps/cli-win32-arm64-msvc", "@tauri-apps/cli-win32-arm64-msvc",
@@ -360,22 +375,22 @@
"@tauri-apps/cli-win32-x64-msvc" "@tauri-apps/cli-win32-x64-msvc"
] ]
}, },
"@tauri-apps/plugin-dialog@2.2.0": { "@tauri-apps/plugin-dialog@2.3.2": {
"integrity": "sha512-6bLkYK68zyK31418AK5fNccCdVuRnNpbxquCl8IqgFByOgWFivbiIlvb79wpSXi0O+8k8RCSsIpOquebusRVSg==", "integrity": "sha512-cNLo9YeQSC0MF4IgXnotHsqEgJk72MBZLXmQPrLA95qTaaWiiaFQ38hIMdZ6YbGUNkr3oni3EhU+AD5jLHcdUA==",
"dependencies": [ "dependencies": [
"@tauri-apps/api" "@tauri-apps/api@2.7.0"
] ]
}, },
"@tauri-apps/plugin-shell@2.2.0": { "@tauri-apps/plugin-shell@2.3.0": {
"integrity": "sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA==", "integrity": "sha512-6GIRxO2z64uxPX4CCTuhQzefvCC0ew7HjdBhMALiGw74vFBDY95VWueAHOHgNOMV4UOUAFupyidN9YulTe5xlA==",
"dependencies": [ "dependencies": [
"@tauri-apps/api" "@tauri-apps/api@2.6.0"
] ]
}, },
"@tauri-apps/plugin-store@2.2.0": { "@tauri-apps/plugin-store@2.3.0": {
"integrity": "sha512-hJTRtuJis4w5fW1dkcgftsYxKXK0+DbAqurZ3CURHG5WkAyyZgbxpeYctw12bbzF9ZbZREXZklPq8mocCC3Sgg==", "integrity": "sha512-mre8er0nXPhyEWQzWCpUd+UnEoBQYcoA5JYlwpwOV9wcxKqlXTGfminpKsE37ic8NUb2BIZqf0QQ9/U3ib2+/A==",
"dependencies": [ "dependencies": [
"@tauri-apps/api" "@tauri-apps/api@2.6.0"
] ]
}, },
"@types/babel__core@7.20.5": { "@types/babel__core@7.20.5": {
@@ -388,8 +403,8 @@
"@types/babel__traverse" "@types/babel__traverse"
] ]
}, },
"@types/babel__generator@7.6.8": { "@types/babel__generator@7.27.0": {
"integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dependencies": [ "dependencies": [
"@babel/types" "@babel/types"
] ]
@@ -401,46 +416,41 @@
"@babel/types" "@babel/types"
] ]
}, },
"@types/babel__traverse@7.20.6": { "@types/babel__traverse@7.28.0": {
"integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"dependencies": [ "dependencies": [
"@babel/types" "@babel/types"
] ]
}, },
"@types/estree@1.0.6": { "@types/estree@1.0.8": {
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
}, },
"@types/react-dom@19.0.4_@types+react@19.0.10": { "@types/react-dom@19.1.7_@types+react@19.1.9": {
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
"dependencies": [ "dependencies": [
"@types/react@19.0.10" "@types/react"
] ]
}, },
"@types/react@19.0.10": { "@types/react@19.1.9": {
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==",
"dependencies": [ "dependencies": [
"csstype" "csstype"
] ]
}, },
"@types/react@19.0.12": { "@vitejs/plugin-react@5.0.0_vite@7.1.1__picomatch@4.0.3_@babel+core@7.28.0": {
"integrity": "sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==", "integrity": "sha512-Jx9JfsTa05bYkS9xo0hkofp2dCmp1blrKjw9JONs5BTHOvJCgLbaPSuZLGSVJW6u2qe0tc4eevY0+gSNNi0YCw==",
"dependencies": [
"csstype"
]
},
"@vitejs/plugin-react@4.3.4_vite@6.2.2_@babel+core@7.26.10": {
"integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
"dependencies": [ "dependencies": [
"@babel/core", "@babel/core",
"@babel/plugin-transform-react-jsx-self", "@babel/plugin-transform-react-jsx-self",
"@babel/plugin-transform-react-jsx-source", "@babel/plugin-transform-react-jsx-source",
"@rolldown/pluginutils",
"@types/babel__core", "@types/babel__core",
"react-refresh", "react-refresh",
"vite" "vite"
] ]
}, },
"browserslist@4.24.4": { "browserslist@4.25.2": {
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==",
"dependencies": [ "dependencies": [
"caniuse-lite", "caniuse-lite",
"electron-to-chromium", "electron-to-chromium",
@@ -448,8 +458,8 @@
"update-browserslist-db" "update-browserslist-db"
] ]
}, },
"caniuse-lite@1.0.30001706": { "caniuse-lite@1.0.30001733": {
"integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==" "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q=="
}, },
"convert-source-map@2.0.0": { "convert-source-map@2.0.0": {
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
@@ -457,17 +467,17 @@
"csstype@3.1.3": { "csstype@3.1.3": {
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
}, },
"debug@4.4.0": { "debug@4.4.1": {
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dependencies": [ "dependencies": [
"ms" "ms"
] ]
}, },
"electron-to-chromium@1.5.122": { "electron-to-chromium@1.5.199": {
"integrity": "sha512-EML1wnwkY5MFh/xUnCvY8FrhUuKzdYhowuZExZOfwJo+Zu9OsNCI23Cgl5y7awy7HrUHSwB1Z8pZX5TI34lsUg==" "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ=="
}, },
"esbuild@0.25.1": { "esbuild@0.25.8": {
"integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
"dependencies": [ "dependencies": [
"@esbuild/aix-ppc64", "@esbuild/aix-ppc64",
"@esbuild/android-arm", "@esbuild/android-arm",
@@ -490,6 +500,7 @@
"@esbuild/netbsd-x64", "@esbuild/netbsd-x64",
"@esbuild/openbsd-arm64", "@esbuild/openbsd-arm64",
"@esbuild/openbsd-x64", "@esbuild/openbsd-x64",
"@esbuild/openharmony-arm64",
"@esbuild/sunos-x64", "@esbuild/sunos-x64",
"@esbuild/win32-arm64", "@esbuild/win32-arm64",
"@esbuild/win32-ia32", "@esbuild/win32-ia32",
@@ -499,15 +510,18 @@
"escalade@3.2.0": { "escalade@3.2.0": {
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
}, },
"fdir@6.4.6_picomatch@4.0.3": {
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"dependencies": [
"picomatch"
]
},
"fsevents@2.3.3": { "fsevents@2.3.3": {
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==" "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="
}, },
"gensync@1.0.0-beta.2": { "gensync@1.0.0-beta.2": {
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
}, },
"globals@11.12.0": {
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
},
"js-tokens@4.0.0": { "js-tokens@4.0.0": {
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
@@ -535,29 +549,32 @@
"picocolors@1.1.1": { "picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
}, },
"postcss@8.5.3": { "picomatch@4.0.3": {
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
},
"postcss@8.5.6": {
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dependencies": [ "dependencies": [
"nanoid", "nanoid",
"picocolors", "picocolors",
"source-map-js" "source-map-js"
] ]
}, },
"react-dom@19.0.0_react@19.0.0": { "react-dom@19.1.1_react@19.1.1": {
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"dependencies": [ "dependencies": [
"react", "react",
"scheduler" "scheduler"
] ]
}, },
"react-refresh@0.14.2": { "react-refresh@0.17.0": {
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==" "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="
}, },
"react@19.0.0": { "react@19.1.1": {
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==" "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="
}, },
"rollup@4.36.0": { "rollup@4.46.2": {
"integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==", "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
"dependencies": [ "dependencies": [
"@rollup/rollup-android-arm-eabi", "@rollup/rollup-android-arm-eabi",
"@rollup/rollup-android-arm64", "@rollup/rollup-android-arm64",
@@ -570,8 +587,9 @@
"@rollup/rollup-linux-arm64-gnu", "@rollup/rollup-linux-arm64-gnu",
"@rollup/rollup-linux-arm64-musl", "@rollup/rollup-linux-arm64-musl",
"@rollup/rollup-linux-loongarch64-gnu", "@rollup/rollup-linux-loongarch64-gnu",
"@rollup/rollup-linux-powerpc64le-gnu", "@rollup/rollup-linux-ppc64-gnu",
"@rollup/rollup-linux-riscv64-gnu", "@rollup/rollup-linux-riscv64-gnu",
"@rollup/rollup-linux-riscv64-musl",
"@rollup/rollup-linux-s390x-gnu", "@rollup/rollup-linux-s390x-gnu",
"@rollup/rollup-linux-x64-gnu", "@rollup/rollup-linux-x64-gnu",
"@rollup/rollup-linux-x64-musl", "@rollup/rollup-linux-x64-musl",
@@ -582,8 +600,8 @@
"fsevents" "fsevents"
] ]
}, },
"scheduler@0.25.0": { "scheduler@0.26.0": {
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
}, },
"semver@6.3.1": { "semver@6.3.1": {
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
@@ -591,10 +609,17 @@
"source-map-js@1.2.1": { "source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
}, },
"typescript@5.8.2": { "tinyglobby@0.2.14_picomatch@4.0.3": {
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==" "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"dependencies": [
"fdir",
"picomatch"
]
}, },
"update-browserslist-db@1.1.3_browserslist@4.24.4": { "typescript@5.9.2": {
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="
},
"update-browserslist-db@1.1.3_browserslist@4.25.2": {
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dependencies": [ "dependencies": [
"browserslist", "browserslist",
@@ -602,13 +627,16 @@
"picocolors" "picocolors"
] ]
}, },
"vite@6.2.2": { "vite@7.1.1_picomatch@4.0.3": {
"integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", "integrity": "sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==",
"dependencies": [ "dependencies": [
"esbuild", "esbuild",
"fdir",
"fsevents", "fsevents",
"picomatch",
"postcss", "postcss",
"rollup" "rollup",
"tinyglobby"
] ]
}, },
"yallist@3.1.1": { "yallist@3.1.1": {
@@ -618,18 +646,18 @@
"workspace": { "workspace": {
"packageJson": { "packageJson": {
"dependencies": [ "dependencies": [
"npm:@tauri-apps/api@^2.3.0", "npm:@tauri-apps/api@^2.7.0",
"npm:@tauri-apps/cli@^2.3.1", "npm:@tauri-apps/cli@^2.7.1",
"npm:@tauri-apps/plugin-dialog@^2.2.0", "npm:@tauri-apps/plugin-dialog@^2.3.2",
"npm:@tauri-apps/plugin-shell@^2.2.0", "npm:@tauri-apps/plugin-shell@^2.3.0",
"npm:@tauri-apps/plugin-store@^2.2.0", "npm:@tauri-apps/plugin-store@^2.3.0",
"npm:@types/react-dom@^19.0.4", "npm:@types/react-dom@^19.1.7",
"npm:@types/react@^19.0.12", "npm:@types/react@^19.1.9",
"npm:@vitejs/plugin-react@^4.3.4", "npm:@vitejs/plugin-react@5",
"npm:react-dom@19", "npm:react-dom@^19.1.1",
"npm:react@19", "npm:react@^19.1.1",
"npm:typescript@^5.8.2", "npm:typescript@^5.9.2",
"npm:vite@^6.2.2" "npm:vite@^7.1.1"
] ]
} }
} }

View File

@@ -10,19 +10,19 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/plugin-dialog": "^2.2.0", "@tauri-apps/plugin-dialog": "^2.3.2",
"@tauri-apps/plugin-store": "^2.2.0", "@tauri-apps/plugin-store": "^2.3.0",
"react": "^19.0.0", "react": "^19.1.1",
"react-dom": "^19.0.0", "react-dom": "^19.1.1",
"@tauri-apps/api": "^2.3.0", "@tauri-apps/api": "^2.7.0",
"@tauri-apps/plugin-shell": "^2.2.0" "@tauri-apps/plugin-shell": "^2.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^19.0.12", "@types/react": "^19.1.9",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^5.0.0",
"typescript": "^5.8.2", "typescript": "^5.9.2",
"vite": "^6.2.2", "vite": "^7.1.1",
"@tauri-apps/cli": "^2.3.1" "@tauri-apps/cli": "^2.7.1"
} }
} }

View File

@@ -14,6 +14,15 @@ edition = "2024"
name = "lanspread_tauri_deno_ts_lib" name = "lanspread_tauri_deno_ts_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
todo = "warn"
unwrap_used = "warn"
needless_pass_by_value = "allow"
[build-dependencies] [build-dependencies]
tauri-build = { version = "2", features = [] } tauri-build = { version = "2", features = [] }
@@ -26,6 +35,7 @@ lanspread-mdns = { path = "../../lanspread-mdns" }
# external # external
eyre = { workspace = true } eyre = { workspace = true }
log = { workspace = true } log = { workspace = true }
mimalloc = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }

View File

@@ -1,3 +1,3 @@
fn main() { fn main() {
tauri_build::build() tauri_build::build();
} }

View File

@@ -1,485 +1,482 @@
use std::{ use std::{
collections::HashSet, collections::HashSet,
net::SocketAddr, net::SocketAddr,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use eyre::bail; use eyre::bail;
use lanspread_client::{ClientCommand, ClientEvent}; use lanspread_client::{ClientCommand, ClientEvent};
use lanspread_db::db::{Game, GameDB}; use lanspread_db::db::{Game, GameDB};
use lanspread_mdns::{LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE, discover_service}; use lanspread_mdns::{LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE, discover_service};
use tauri::{AppHandle, Emitter as _, Manager}; use tauri::{AppHandle, Emitter as _, Manager};
use tauri_plugin_shell::{ShellExt, process::Command}; use tauri_plugin_shell::{ShellExt, process::Command};
use tokio::sync::{Mutex, RwLock, mpsc::UnboundedSender}; use tokio::sync::{RwLock, mpsc::UnboundedSender};
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
struct LanSpreadState { struct LanSpreadState {
server_addr: RwLock<Option<SocketAddr>>, server_addr: RwLock<Option<SocketAddr>>,
client_ctrl: UnboundedSender<ClientCommand>, client_ctrl: UnboundedSender<ClientCommand>,
games: Arc<RwLock<GameDB>>, games: Arc<RwLock<GameDB>>,
games_in_download: Arc<Mutex<HashSet<String>>>, games_in_download: Arc<RwLock<HashSet<String>>>,
games_folder: Arc<RwLock<String>>, games_folder: Arc<RwLock<String>>,
} }
#[tauri::command] #[tauri::command]
fn request_games(state: tauri::State<LanSpreadState>) { fn request_games(state: tauri::State<LanSpreadState>) {
log::debug!("request_games"); log::debug!("request_games");
if let Err(e) = state.inner().client_ctrl.send(ClientCommand::ListGames) { if let Err(e) = state.inner().client_ctrl.send(ClientCommand::ListGames) {
log::error!("Failed to send message to client: {e:?}"); log::error!("Failed to send message to client: {e:?}");
} }
} }
#[tauri::command] #[tauri::command]
fn install_game(id: String, state: tauri::State<LanSpreadState>) -> bool { fn install_game(id: String, state: tauri::State<LanSpreadState>) -> bool {
let already_in_download = tauri::async_runtime::block_on(async { let already_in_download = tauri::async_runtime::block_on(async {
if state.inner().games_in_download.lock().await.contains(&id) { if state.inner().games_in_download.read().await.contains(&id) {
log::warn!("Game is already downloading: {id}"); log::warn!("Game is already downloading: {id}");
return true; return true;
} }
false false
}); });
if already_in_download { if already_in_download {
return false; return false;
} }
if let Err(e) = state.inner().client_ctrl.send(ClientCommand::GetGame(id)) { if let Err(e) = state.inner().client_ctrl.send(ClientCommand::GetGame(id)) {
log::error!("Failed to send message to client: {e:?}"); log::error!("Failed to send message to client: {e:?}");
} }
true true
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn run_as_admin(file: &str, params: &str, dir: &str) -> bool { fn run_as_admin(file: &str, params: &str, dir: &str) -> bool {
use std::{ffi::OsStr, os::windows::ffi::OsStrExt}; use std::{ffi::OsStr, os::windows::ffi::OsStrExt};
use windows::{Win32::UI::Shell::ShellExecuteW, core::PCWSTR}; use windows::{Win32::UI::Shell::ShellExecuteW, core::PCWSTR};
let file_wide: Vec<u16> = OsStr::new(file).encode_wide().chain(Some(0)).collect(); let file_wide: Vec<u16> = OsStr::new(file).encode_wide().chain(Some(0)).collect();
let params_wide: Vec<u16> = OsStr::new(params).encode_wide().chain(Some(0)).collect(); let params_wide: Vec<u16> = OsStr::new(params).encode_wide().chain(Some(0)).collect();
let dir_wide: Vec<u16> = OsStr::new(dir).encode_wide().chain(Some(0)).collect(); let dir_wide: Vec<u16> = OsStr::new(dir).encode_wide().chain(Some(0)).collect();
let runas_wide: Vec<u16> = OsStr::new("runas").encode_wide().chain(Some(0)).collect(); let runas_wide: Vec<u16> = OsStr::new("runas").encode_wide().chain(Some(0)).collect();
let result = unsafe { let result = unsafe {
ShellExecuteW( ShellExecuteW(
None, None,
PCWSTR::from_raw(runas_wide.as_ptr()), PCWSTR::from_raw(runas_wide.as_ptr()),
PCWSTR::from_raw(file_wide.as_ptr()), PCWSTR::from_raw(file_wide.as_ptr()),
PCWSTR::from_raw(params_wide.as_ptr()), PCWSTR::from_raw(params_wide.as_ptr()),
PCWSTR::from_raw(dir_wide.as_ptr()), PCWSTR::from_raw(dir_wide.as_ptr()),
windows::Win32::UI::WindowsAndMessaging::SW_HIDE, windows::Win32::UI::WindowsAndMessaging::SW_HIDE,
) )
}; };
(result.0 as usize) > 32 // Success if greater than 32 (result.0 as usize) > 32 // Success if greater than 32
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn run_game_windows(id: String, state: tauri::State<LanSpreadState>) { fn run_game_windows(id: String, state: tauri::State<LanSpreadState>) {
use std::fs::File; use std::fs::File;
const FIRST_START_DONE_FILE: &str = ".softlan_first_start_done"; const FIRST_START_DONE_FILE: &str = ".softlan_first_start_done";
let games_folder = let games_folder =
tauri::async_runtime::block_on(async { state.inner().games_folder.read().await.clone() }); tauri::async_runtime::block_on(async { state.inner().games_folder.read().await.clone() });
let games_folder = PathBuf::from(games_folder); let games_folder = PathBuf::from(games_folder);
if !games_folder.exists() { if !games_folder.exists() {
log::error!("games_folder {games_folder:?} does not exist"); log::error!("games_folder {games_folder:?} does not exist");
return; return;
} }
let game_path = games_folder.join(id.clone()); let game_path = games_folder.join(id.clone());
let game_setup_bin = game_path.join("game_setup.cmd"); let game_setup_bin = game_path.join("game_setup.cmd");
let game_start_bin = game_path.join("game_start.cmd"); let game_start_bin = game_path.join("game_start.cmd");
let first_start_done_file = game_path.join(FIRST_START_DONE_FILE); let first_start_done_file = game_path.join(FIRST_START_DONE_FILE);
if !first_start_done_file.exists() && game_setup_bin.exists() { if !first_start_done_file.exists() && game_setup_bin.exists() {
let result = run_as_admin( let result = run_as_admin(
"cmd.exe", "cmd.exe",
&format!( &format!(
r#"/c "{} local {} de playername""#, r#"/c "{} local {} de playername""#,
game_setup_bin.display(), game_setup_bin.display(),
&id &id
), ),
&game_path.display().to_string(), &game_path.display().to_string(),
); );
if !result { if !result {
log::error!("failed to run game_setup.cmd"); log::error!("failed to run game_setup.cmd");
return; return;
} }
if let Err(e) = File::create(&first_start_done_file) { if let Err(e) = File::create(&first_start_done_file) {
log::error!("failed to create {first_start_done_file:?}: {e}"); log::error!("failed to create {first_start_done_file:?}: {e}");
} }
} }
if game_start_bin.exists() { if game_start_bin.exists() {
let result = run_as_admin( let result = run_as_admin(
"cmd.exe", "cmd.exe",
&format!( &format!(
r#"/c "{} local {} de playername""#, r#"/c "{} local {} de playername""#,
game_start_bin.display(), game_start_bin.display(),
&id &id
), ),
&game_path.display().to_string(), &game_path.display().to_string(),
); );
if !result { if !result {
log::error!("failed to run game_start.cmd"); log::error!("failed to run game_start.cmd");
} }
} }
} }
#[tauri::command] #[tauri::command]
fn run_game(id: String, state: tauri::State<LanSpreadState>) { fn run_game(id: String, state: tauri::State<LanSpreadState>) {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
run_game_windows(id, state); run_game_windows(id, state);
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
let _ = state; let _ = state;
log::error!("run_game not implemented for this platform: id={id}"); log::error!("run_game not implemented for this platform: id={id}");
} }
} }
fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) { fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) {
if let Some(file_name) = path.file_name() { if let Some(file_name) = path.file_name()
if let Some(file_name) = file_name.to_str() { && let Some(file_name) = file_name.to_str()
if let Some(game) = game_db.get_mut_game_by_id(file_name) { && let Some(game) = game_db.get_mut_game_by_id(file_name)
if installed { {
log::debug!("Game is installed: {game}"); if installed {
} else { log::debug!("Set {game} to installed");
log::error!("Game is missing: {game}"); } else {
} log::debug!("Set {game} to uninstalled");
game.installed = installed; }
} game.installed = installed;
} }
} }
}
#[tauri::command]
#[tauri::command] fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
fn update_game_directory(app_handle: tauri::AppHandle, path: String) { log::info!("update_game_directory: {path}");
log::info!("update_game_directory: {path}");
app_handle
app_handle .state::<LanSpreadState>()
.state::<LanSpreadState>() .client_ctrl
.client_ctrl .send(ClientCommand::SetGameDir(path.clone()))
.send(ClientCommand::SetGameDir(path.clone())) .expect("Failed to send ClientCommand: SetGameDir");
.unwrap();
{
{ tauri::async_runtime::block_on(async {
tauri::async_runtime::block_on(async { let mut games_folder = app_handle
let mut games_folder = app_handle .state::<LanSpreadState>()
.state::<LanSpreadState>() .inner()
.inner() .games_folder
.games_folder .write()
.write() .await;
.await;
games_folder.clone_from(&path);
*games_folder = path.clone(); });
}); }
}
let path = PathBuf::from(path);
let path = PathBuf::from(path); if !path.exists() {
if !path.exists() { log::error!("game dir {} does not exist", path.display());
log::error!("game dir {path:?} does not exist"); }
}
let entries = match path.read_dir() {
let entries = match path.read_dir() { Ok(entries) => entries,
Ok(entries) => entries, Err(e) => {
Err(e) => { log::error!("Failed to read game dir: {e}");
log::error!("Failed to read game dir: {e}"); return;
return; }
} };
};
tauri::async_runtime::spawn(async move {
tauri::async_runtime::spawn(async move { let mut game_db = app_handle
let mut game_db = app_handle .state::<LanSpreadState>()
.state::<LanSpreadState>() .inner()
.inner() .games
.games .write()
.write() .await;
.await;
// Reset all games to uninstalled
// Reset all games to uninstalled game_db.set_all_uninstalled();
game_db.set_all_uninstalled();
// update game_db with installed games from real game directory
// update game_db with installed games from real game directory entries.into_iter().for_each(|entry| {
entries.into_iter().for_each(|entry| { if let Ok(entry) = entry
if let Ok(entry) = entry { && let Ok(path_type) = entry.file_type()
if let Ok(path_type) = entry.file_type() { && path_type.is_dir()
if path_type.is_dir() { {
let path = entry.path(); let path = entry.path();
if path.join("version.ini").exists() { if path.join("version.ini").exists() {
set_game_install_state_from_path(&mut game_db, &path, true); set_game_install_state_from_path(&mut game_db, &path, true);
} }
} }
} });
}
}); if let Err(e) = app_handle.emit("games-list-updated", Some(game_db.all_games())) {
log::error!("Failed to emit games-list-updated event: {e}");
if let Err(e) = app_handle.emit("games-list-updated", Some(game_db.all_games())) { }
log::error!("Failed to emit games-list-updated event: {e}"); });
} }
});
} async fn find_server(app: AppHandle) {
log::info!("Looking for server...");
async fn find_server(app: AppHandle) {
log::info!("Looking for server..."); loop {
match discover_service(LANSPREAD_SERVICE_TYPE, Some(LANSPREAD_INSTANCE_NAME)) {
loop { Ok(server_addr) => {
match discover_service(LANSPREAD_SERVICE_TYPE, Some(LANSPREAD_INSTANCE_NAME)) { log::info!("Found server at {server_addr}");
Ok(server_addr) => { let state: tauri::State<LanSpreadState> = app.state();
log::info!("Found server at {server_addr}"); *state.server_addr.write().await = Some(server_addr);
let state: tauri::State<LanSpreadState> = app.state(); state
*state.server_addr.write().await = Some(server_addr); .client_ctrl
state .send(ClientCommand::ServerAddr(server_addr))
.client_ctrl .expect("Failed to send ClientCommand: ServerAddr");
.send(ClientCommand::ServerAddr(server_addr)) request_games(state);
.unwrap(); break;
request_games(state); }
break; Err(e) => {
} log::warn!("Failed to find server: {e} - retrying...");
Err(e) => { }
log::warn!("Failed to find server: {e} - retrying..."); }
} }
} }
}
} async fn update_game_db(games: Vec<Game>, app: AppHandle) {
for game in &games {
async fn update_game_db(games: Vec<Game>, app: AppHandle) { log::trace!("client event ListGames iter: {game:?}");
for game in &games { }
log::trace!("client event ListGames iter: {game:?}");
} let state = app.state::<LanSpreadState>();
let state = app.state::<LanSpreadState>(); // Store games list
*state.games.write().await = GameDB::from(games.clone());
// Store games list
*state.games.write().await = GameDB::from(games.clone()); // Tell Frontend about new games list
if let Err(e) = app.emit("games-list-updated", Some(games)) {
// Tell Frontend about new games list log::error!("Failed to emit games-list-updated event: {e}");
if let Err(e) = app.emit("games-list-updated", Some(games)) { } else {
log::error!("Failed to emit games-list-updated event: {e}"); log::info!("Emitted games-list-updated event");
} else { }
log::info!("Emitted games-list-updated event"); }
}
} fn add_final_slash(path: &str) -> String {
#[cfg(target_os = "windows")]
fn add_final_slash(path: &str) -> String { const SLASH_CHAR: char = '\\';
#[cfg(target_os = "windows")]
const SLASH_CHAR: char = '\\'; #[cfg(not(target_os = "windows"))]
const SLASH_CHAR: char = '/';
#[cfg(not(target_os = "windows"))]
const SLASH_CHAR: char = '/'; if path.ends_with(SLASH_CHAR) {
path.to_string()
if path.ends_with(SLASH_CHAR) { } else {
path.to_string() format!("{path}{SLASH_CHAR}")
} else { }
format!("{path}{SLASH_CHAR}") }
}
} async fn do_unrar(sidecar: Command, rar_file: &Path, dest_dir: &Path) -> eyre::Result<()> {
if let Ok(()) = std::fs::create_dir_all(dest_dir) {
async fn do_unrar(sidecar: Command, rar_file: &Path, dest_dir: &Path) -> eyre::Result<()> { if let Ok(rar_file) = rar_file.canonicalize() {
if let Ok(()) = std::fs::create_dir_all(dest_dir) { if let Ok(dest_dir) = dest_dir.canonicalize() {
if let Ok(rar_file) = rar_file.canonicalize() { let dest_dir = dest_dir
if let Ok(dest_dir) = dest_dir.canonicalize() { .to_str()
let dest_dir = dest_dir .ok_or_else(|| eyre::eyre!("failed to get str of dest_dir"))?;
.to_str()
.ok_or_else(|| eyre::eyre!("failed to get str of dest_dir"))?; log::info!(
"unrar game: {} to {}",
log::info!( rar_file.canonicalize()?.display(),
"unrar game: {} to {}", dest_dir
rar_file.canonicalize()?.display(), );
dest_dir
); let out = sidecar
.arg("x") // extract files
let out = sidecar .arg(rar_file.canonicalize()?)
.arg("x") // extract files .arg("-y") // Assume Yes on all queries
.arg(rar_file.canonicalize()?) .arg("-o") // Set overwrite mode
.arg("-y") // Assume Yes on all queries .arg(add_final_slash(dest_dir))
.arg("-o") // Set overwrite mode .output()
.arg(add_final_slash(dest_dir)) .await?;
.output()
.await?; if !out.status.success() {
log::error!("unrar stderr: {}", String::from_utf8_lossy(&out.stderr));
if !out.status.success() { }
log::error!("unrar stderr: {}", String::from_utf8_lossy(&out.stderr));
} return Ok(());
}
return Ok(()); log::error!("dest_dir canonicalize failed: {}", dest_dir.display());
} else { } else {
log::error!("dest_dir canonicalize failed: {:?}", &dest_dir); log::error!("rar_file canonicalize failed: {}", rar_file.display());
} }
} else { }
log::error!("rar_file canonicalize failed: {:?}", &rar_file);
} bail!("failed to create directory: {dest_dir:?}");
} else { }
log::error!("failed to create dest_dir: {:?}", &dest_dir);
} async fn unpack_game(id: &str, sidecar: Command, games_folder: String) {
let game_path = PathBuf::from(games_folder).join(id);
bail!("failed to create directory: {dest_dir:?}"); let eti_rar = game_path.join(format!("{id}.eti"));
} let local_path = game_path.join("local");
async fn unpack_game(id: &str, sidecar: Command, games_folder: String) { if let Err(e) = do_unrar(sidecar, &eti_rar, &local_path).await {
let game_path = PathBuf::from(games_folder).join(id); log::error!("{} -> {}: {e}", eti_rar.display(), local_path.display());
let eti_rar = game_path.join(format!("{id}.eti")); }
let local_path = game_path.join("local"); }
if let Err(e) = do_unrar(sidecar, &eti_rar, &local_path).await { #[allow(clippy::too_many_lines)]
log::error!("{eti_rar:?} -> {local_path:?}: {e}"); #[allow(clippy::missing_panics_doc)]
} #[cfg_attr(mobile, tauri::mobile_entry_point)]
} pub fn run() {
let tauri_logger_builder = tauri_plugin_log::Builder::new()
#[cfg_attr(mobile, tauri::mobile_entry_point)] .clear_targets()
pub fn run() { .target(tauri_plugin_log::Target::new(
let tauri_logger_builder = tauri_plugin_log::Builder::new() tauri_plugin_log::TargetKind::Stdout,
.clear_targets() ))
.target(tauri_plugin_log::Target::new( .level(log::LevelFilter::Info)
tauri_plugin_log::TargetKind::Stdout, .level_for("mdns_sd::service_daemon", log::LevelFilter::Off);
))
.level(log::LevelFilter::Info) // channel to pass commands to the client
.level_for("mdns_sd::service_daemon", log::LevelFilter::Off); let (tx_client_control, rx_client_control) =
tokio::sync::mpsc::unbounded_channel::<ClientCommand>();
// channel to pass commands to the client
let (tx_client_control, rx_client_control) = // channel to receive events from the client
tokio::sync::mpsc::unbounded_channel::<ClientCommand>(); let (tx_client_event, mut rx_client_event) =
tokio::sync::mpsc::unbounded_channel::<ClientEvent>();
// channel to receive events from the client
let (tx_client_event, mut rx_client_event) = let lanspread_state = LanSpreadState {
tokio::sync::mpsc::unbounded_channel::<ClientEvent>(); server_addr: RwLock::new(None),
client_ctrl: tx_client_control,
let lanspread_state = LanSpreadState { games: Arc::new(RwLock::new(GameDB::empty())),
server_addr: RwLock::new(None), games_in_download: Arc::new(RwLock::new(HashSet::new())),
client_ctrl: tx_client_control, games_folder: Arc::new(RwLock::new(String::new())),
games: Arc::new(RwLock::new(GameDB::empty())), };
games_in_download: Arc::new(Mutex::new(HashSet::new())),
games_folder: Arc::new(RwLock::new("".to_string())), tauri::Builder::default()
}; .plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
tauri::Builder::default() .plugin(tauri_logger_builder.build())
.plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_dialog::init()) .invoke_handler(tauri::generate_handler![
.plugin(tauri_logger_builder.build()) request_games,
.plugin(tauri_plugin_shell::init()) install_game,
.invoke_handler(tauri::generate_handler![ run_game,
request_games, update_game_directory
install_game, ])
run_game, .manage(lanspread_state)
update_game_directory .setup(|app| {
]) let app_handle = app.handle().clone();
.manage(lanspread_state) // discover server
.setup(|app| { tauri::async_runtime::spawn(async move { find_server(app_handle).await });
let app_handle = app.handle().clone(); tauri::async_runtime::spawn(async move {
// discover server lanspread_client::run(rx_client_control, tx_client_event).await
tauri::async_runtime::spawn(async move { find_server(app_handle).await }); });
tauri::async_runtime::spawn(async move {
lanspread_client::run(rx_client_control, tx_client_event).await let app_handle = app.handle().clone();
}); tauri::async_runtime::spawn(async move {
while let Some(event) = rx_client_event.recv().await {
let app_handle = app.handle().clone(); match event {
tauri::async_runtime::spawn(async move { ClientEvent::ListGames(games) => {
while let Some(event) = rx_client_event.recv().await { log::info!("ClientEvent::ListGames received");
match event { update_game_db(games, app_handle.clone()).await;
ClientEvent::ListGames(games) => { }
log::info!("ClientEvent::ListGames received"); ClientEvent::GotGameFiles { id, file_descriptions } => {
update_game_db(games, app_handle.clone()).await; log::info!("ClientEvent::GotGameFiles received");
}
ClientEvent::GotGameFiles { id, file_descriptions } => { if let Err(e) = app_handle.emit(
log::info!("ClientEvent::GotGameFiles received"); "game-download-pre",
Some(id.clone()),
if let Err(e) = app_handle.emit( ) {
"game-download-pre", log::error!("ClientEvent::GotGameFiles: Failed to emit game-download-pre event: {e}");
Some(id.clone()), }
) {
log::error!("ClientEvent::GotGameFiles: Failed to emit game-download-pre event: {e}"); app_handle
} .state::<LanSpreadState>()
.inner()
app_handle .client_ctrl
.state::<LanSpreadState>() .send(ClientCommand::DownloadGameFiles{
.inner() id,
.client_ctrl file_descriptions,
.send(ClientCommand::DownloadGameFiles{ })
id, .expect("Failed to send ClientCommand: DownloadGameFiles");
file_descriptions,
}) }
.unwrap(); ClientEvent::DownloadGameFilesBegin { id } => {
log::info!("ClientEvent::DownloadGameFilesBegin received");
}
ClientEvent::DownloadGameFilesBegin { id } => { app_handle
log::info!("ClientEvent::DownloadGameFilesBegin received"); .state::<LanSpreadState>()
.inner()
app_handle .games_in_download
.state::<LanSpreadState>() .write()
.inner() .await
.games_in_download .insert(id.clone());
.lock()
.await if let Err(e) = app_handle.emit("game-download-begin", Some(id)) {
.insert(id.clone()); log::error!("ClientEvent::DownloadGameFilesBegin: Failed to emit game-download-begin event: {e}");
}
if let Err(e) = app_handle.emit("game-download-begin", Some(id)) { }
log::error!("ClientEvent::DownloadGameFilesBegin: Failed to emit game-download-begin event: {e}"); ClientEvent::DownloadGameFilesFinished { id } => {
} log::info!("ClientEvent::DownloadGameFilesFinished received");
} if let Err(e) = app_handle.emit("game-download-finished", Some(id.clone())) {
ClientEvent::DownloadGameFilesFinished { id } => { log::error!("ClientEvent::DownloadGameFilesFinished: Failed to emit game-download-finished event: {e}");
log::info!("ClientEvent::DownloadGameFilesFinished received"); }
if let Err(e) = app_handle.emit("game-download-finished", Some(id.clone())) {
log::error!("ClientEvent::DownloadGameFilesFinished: Failed to emit game-download-finished event: {e}"); app_handle
} .state::<LanSpreadState>()
.inner()
app_handle .games_in_download
.state::<LanSpreadState>() .write()
.inner() .await
.games_in_download .remove(&id.clone());
.lock()
.await
.remove(&id.clone()); let games_folder = app_handle
.state::<LanSpreadState>()
.inner()
let games_folder = app_handle .games_folder
.state::<LanSpreadState>() .read()
.inner() .await
.games_folder .clone();
.read()
.await if let Ok(sidecar) = app_handle.shell().sidecar("unrar") {
.clone(); unpack_game(&id, sidecar, games_folder).await;
if let Ok(sidecar) = app_handle.shell().sidecar("unrar") { log::info!("ClientEvent::UnpackGameFinished received");
unpack_game(&id, sidecar, games_folder).await; if let Err(e) = app_handle.emit("game-unpack-finished", Some(id.clone())) {
log::error!("ClientEvent::UnpackGameFinished: Failed to emit game-unpack-finished event: {e}");
log::info!("ClientEvent::UnpackGameFinished received"); }
if let Err(e) = app_handle.emit("game-unpack-finished", Some(id.clone())) { }
log::error!("ClientEvent::UnpackGameFinished: Failed to emit game-unpack-finished event: {e}"); }
} ClientEvent::DownloadGameFilesFailed { id } => {
} log::warn!("ClientEvent::DownloadGameFilesFailed received");
}
ClientEvent::DownloadGameFilesFailed { id } => { if let Err(e) = app_handle.emit("game-download-failed", Some(id.clone())) {
log::warn!("ClientEvent::DownloadGameFilesFailed received"); log::error!("Failed to emit game-download-failed event: {e}");
}
if let Err(e) = app_handle.emit("game-download-failed", Some(id.clone())) {
log::error!("Failed to emit game-download-failed event: {e}"); app_handle
} .state::<LanSpreadState>()
.inner()
app_handle .games_in_download
.state::<LanSpreadState>() .write()
.inner() .await
.games_in_download .remove(&id.clone());
.lock() },
.await }
.remove(&id.clone()); }
}, });
}
} Ok(())
}); })
.run(tauri::generate_context!())
Ok(()) .expect("error while running tauri application");
}) }
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -1,6 +1,11 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
fn main() { fn main() {
lanspread_tauri_deno_ts_lib::run() lanspread_tauri_deno_ts_lib::run();
} }

22
justfile Normal file
View File

@@ -0,0 +1,22 @@
export RUSTFLAGS := "-C target-cpu=native"
server:
cargo build -p lanspread-server
client:
cargo tauri dev
fmt:
cargo +nightly fmt
_fix:
cargo fix
cargo clippy --fix
fix: _fix fmt
clippy:
cargo clippy
clean:
cargo clean

7
supply-chain/audits.toml Normal file
View File

@@ -0,0 +1,7 @@
# cargo-vet audits file
[[audits.windows-link]]
who = "ddidderr <ddidderr@paul.network>"
criteria = "safe-to-deploy"
delta = "0.1.1 -> 0.1.3"

2184
supply-chain/config.toml Normal file

File diff suppressed because it is too large Load Diff

1982
supply-chain/imports.lock Normal file

File diff suppressed because it is too large Load Diff