diff --git a/.clippy.toml b/.clippy.toml index 587456d..352cad8 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,9 +1,9 @@ # Clippy configuration for OpenCade # See https://doc.rust-lang.org/clippy/configuration.html # This file is read by `cargo clippy` at workspace root. -# Rust 1.78 reads this dotfile directly; keep a single config to avoid ambiguity. +# Rust 1.98 reads this dotfile directly; keep a single config to avoid ambiguity. -msrv = "1.78" +msrv = "1.98" cognitive-complexity-threshold = 30 too-many-arguments-threshold = 7 type-complexity-threshold = 250 diff --git a/.env.example b/.env.example index 353a372..5fe6ab2 100644 --- a/.env.example +++ b/.env.example @@ -13,3 +13,6 @@ OPENCADE_ENV=production # POSTGRES_USER=opencade # POSTGRES_PASSWORD=opencade # POSTGRES_DB=opencade +# RFC 8489 server as a numeric IP:port. Both variables should identify the same service. +# OPENCADE_STUN_SERVER=203.0.113.10:3478 +# VITE_STUN_SERVER=203.0.113.10:3478 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9aff81..70bd52f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.78" + toolchain: "1.98" components: rustfmt, clippy - name: Cache cargo registry + target @@ -60,7 +60,7 @@ jobs: - name: Setup Node.js for protocol drift check uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: pnpm - name: Install protocol formatter @@ -88,7 +88,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: pnpm - name: Install dependencies @@ -136,7 +136,7 @@ jobs: - name: Install Rust MSRV uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.78" + toolchain: "1.98" components: rustfmt, clippy - name: Cache Cargo @@ -157,6 +157,7 @@ jobs: path: | target/release/opencade-match-probe.exe target/release/opencade-match-verify.exe + target/release/opencade-alpha-summary.exe if-no-files-found: error retention-days: 14 diff --git a/.gitignore b/.gitignore index 4ff7ddc..d10f9c4 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ out/ # ── Tauri ──────────────────────────────────────────────────── apps/client/src-tauri/target/ +apps/client/src-tauri/gen/schemas/ apps/client/dist/ apps/client/bundle/ diff --git a/AGENTS.md b/AGENTS.md index 9b9308e..ffd618f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ OpenCade — open-source arcade netplay platform, clean-room alternative to prop ## Architecture & Data Flow - **System:** `apps/client` (Tauri) ↔ `apps/server` (Axum) over HTTPS/WS ` /ws` → P2P/relay between peers. Server never sees game inputs except when relaying as fallback. -- **Client:** Tauri 1.x hosts React routes `Games | Lobbies | Friends | Servers | Settings`. Rust core owns `fs` (ROM scan under `emulator//ROMs/`), `process` (spawn via `tauri::api::process::Command` with arg escaping, no shell), `diag` (Network Test). State: TanStack Query + Zustand, WS client with typed `packages/protocol` discriminated unions and reconnect backoff. +- **Client:** Tauri 2.x hosts React routes `Games | Lobbies | Friends | Servers | Settings`. Rust core owns `fs` (ROM scan under `emulator//ROMs/`), `process` (safe direct process spawning with arg escaping, no shell), `diag` (Network Test). State: TanStack Query + Zustand, WS client with typed `packages/protocol` discriminated unions and reconnect backoff. - **Server:** Single Axum monolith + `postgres:16-alpine` (no Redis in MVP). Routes `POST /api/v1/auth/*`, `GET /api/v1/games`, `GET /api/v1/lobbies/:game`, `POST /api/v1/rooms` + `.../:id/{accept,decline,cancel}`, WS `/ws` with versioned envelope `{type,version,request_id,timestamp,payload}` (`presence.update`, `chat.message`, `challenge.*`, `session.offer/answer/candidate`, `room.*`). In-memory presence Hub, Postgres for durable state. - **Networking:** Signaling relayed verbatim through server; client-driven UDP hole-punch → STUN hint (`GET /servers` returns `stun:host:port` when configured) → WS relay fallback (in-process `relay` module, future `services/relay` binary). Room states `WAITING → CHALLENGING → CONNECTING → PLAYING → FINISHED|CANCELLED`. Latency `rtt_ms/loss/jitter` via `presence.update`. - **Reference only:** `docs/reference-fightcade-install.md` describes the opaque PyInstaller launcher (`emulator/fcade.exe`/`frm.exe` → `fightcade/launcher.py`) and `fbneo-training-mode` Lua surface — not used at runtime. @@ -31,7 +31,7 @@ OpenCade — open-source arcade netplay platform, clean-room alternative to prop ## Development Commands ```bash -# prerequisites: Rust 1.78+, pnpm 9+, Docker, Postgres 16 +# prerequisites: Rust 1.98+, Node 24+, pnpm 11+, Docker, Postgres 16 pnpm install # workspace install (apps/*, packages/*) pnpm -C apps/client tauri dev # Vite + Tauri dev (Windows) pnpm -C apps/client build # TS check + Vite build @@ -80,9 +80,9 @@ pnpm tauri dev -- --log opencade # client logs to %APPDATA%/OpenCade/logs/openc ## Runtime/Tooling Preferences -- **Runtime:** Rust 1.78+ (MSRV), Tauri 1.x (WebView2 on Windows), Node 20+ with **pnpm 9** (not npm/yarn), Postgres 16-alpine (sqlx compile-time checked). No Bun. No Electron. +- **Runtime:** Rust 1.98+ (MSRV), Tauri 2.x (WebView2 on Windows), Node 24+ with **pnpm 11** (not npm/yarn), Postgres 16-alpine (sqlx compile-time checked). No Bun. No Electron. - **Package manager:** `pnpm` at root (`pnpm-workspace.yaml` covers `apps/*`, `packages/*`, `adapters/*`, `services/*`) — `pnpm install` only, commit `pnpm-lock.yaml`. -- **Build:** `cargo build --workspace`, `pnpm -C apps/client build`, `tauri build` (MSI/NSIS on Windows). Docker multi-stage `rust:1.78 → debian:bookworm-slim`. +- **Build:** `cargo build --workspace`, `pnpm -C apps/client build`, `tauri build` (MSI/NSIS on Windows). Docker multi-stage `rust:1.98-bookworm → debian:bookworm-slim`. - **Env:** `DATABASE_URL=postgres://opencade:opencade@db:5432/opencade`, `SESSION_SECRET` (32B CSPRNG), `RUST_LOG=info,opencade_server=debug`. Never commit `.env` (see `.env.example`). - **OS:** Windows 10/11 primary (Tauri), Linux/macOS viable via same stack — no `.lnk` shortcuts, use `tauri::path`. - **Tooling constraints:** keep `disableDevTools` off in dev, on in prod via Tauri `tauri.conf.json > build > devPath`. No `shell` permission in `tauri.conf.json`; use `process` allowlist. diff --git a/Cargo.lock b/Cargo.lock index eb8228f..8575d25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -34,21 +34,10 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - [[package]] name = "atoi" version = "2.0.0" @@ -72,14 +61,14 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "axum" -version = "0.7.9" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ - "async-trait", "axum-core", "base64", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", @@ -92,16 +81,15 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", - "sha1", + "sha1 0.10.7", "sync_wrapper", "tokio", - "tokio-tungstenite", - "tower 0.5.3", + "tokio-tungstenite 0.29.0", + "tower", "tower-layer", "tower-service", "tracing", @@ -109,19 +97,17 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -136,9 +122,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" @@ -155,7 +141,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -167,6 +153,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -201,6 +196,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -215,11 +221,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "const-oid" -version = "0.9.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "core-foundation-sys" @@ -236,11 +248,20 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -276,13 +297,32 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "dashmap" -version = "5.5.3" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", + "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", @@ -296,26 +336,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] -name = "der" -version = "0.7.10" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", ] [[package]] name = "digest" -version = "0.10.7" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer", + "block-buffer 0.12.1", "const-oid", - "crypto-common", - "subtle", + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", ] [[package]] @@ -346,18 +397,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "etcetera" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -384,9 +434,9 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -395,9 +445,9 @@ dependencies = [ [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" @@ -475,7 +525,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -536,10 +586,22 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -548,22 +610,28 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -580,29 +648,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", + "digest 0.11.3", ] [[package]] @@ -650,6 +709,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.11.0" @@ -709,24 +777,118 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "2.7.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.17.1", ] [[package]] @@ -751,9 +913,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -761,29 +920,11 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libredox" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.9.2", -] - [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "pkg-config", "vcpkg", @@ -795,6 +936,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + [[package]] name = "lock_api" version = "0.4.14" @@ -806,9 +953,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "matchers" @@ -821,18 +968,18 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest", + "digest 0.11.3", ] [[package]] @@ -855,7 +1002,7 @@ checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -864,42 +1011,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", + "windows-sys", ] [[package]] @@ -909,7 +1021,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -924,7 +1035,7 @@ version = "0.1.0" dependencies = [ "opencade-emulator-sdk", "serde", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -932,7 +1043,7 @@ name = "opencade-emulator-sdk" version = "0.1.0" dependencies = [ "serde", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -943,7 +1054,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror 1.0.69", + "thiserror", "toml", ] @@ -956,8 +1067,9 @@ dependencies = [ "opencade-protocol", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "tokio", + "uuid", ] [[package]] @@ -996,17 +1108,18 @@ dependencies = [ "chrono", "dashmap", "futures-util", + "hex", "opencade-protocol", + "rand_core 0.6.4", "regex", "serde", "serde_json", - "serial_test", - "sha2", + "sha2 0.11.0", "sqlx", - "thiserror 1.0.69", + "thiserror", "tokio", - "tokio-tungstenite", - "tower 0.4.13", + "tokio-tungstenite 0.30.0", + "tower", "tower-http", "tracing", "tracing-subscriber", @@ -1018,7 +1131,7 @@ name = "opencade-shared" version = "0.1.0" dependencies = [ "serde", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -1045,7 +1158,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -1057,72 +1170,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.34" @@ -1130,10 +1193,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] -name = "plain" -version = "0.2.3" +name = "potential_utf" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] [[package]] name = "ppv-lite86" @@ -1168,25 +1234,41 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" -version = "0.8.7" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "libc", "rand_chacha", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", ] [[package]] @@ -1199,19 +1281,25 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "rand_core" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "bitflags", + "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "redox_syscall" -version = "0.9.2" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c93da5bb2c5d4e6c0ef7abeead62c89169a0a4882bfb83ac892f2423aea2fe" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -1245,26 +1333,6 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rustix" version = "1.1.4" @@ -1275,7 +1343,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1323,7 +1391,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1352,11 +1420,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1372,50 +1440,47 @@ dependencies = [ ] [[package]] -name = "serial_test" -version = "3.5.0" +name = "sha1" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ - "futures-executor", - "futures-util", - "log", - "once_cell", - "parking_lot", - "serial_test_derive", + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] -name = "serial_test_derive" -version = "3.5.0" +name = "sha1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] -name = "sha1" -version = "0.10.7" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1443,16 +1508,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] - [[package]] name = "slab" version = "0.4.12" @@ -1475,7 +1530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1487,21 +1542,11 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1512,12 +1557,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64", "bytes", + "cfg-if", "chrono", "crc", "crossbeam-queue", @@ -1527,18 +1573,17 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", - "thiserror 2.0.20", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -1548,9 +1593,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", @@ -1561,78 +1606,63 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck", "hex", - "once_cell", "proc-macro2", "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "thiserror", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64", "bitflags", "byteorder", "bytes", "chrono", "crc", - "digest", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", "log", - "md-5", - "memchr", - "once_cell", "percent-encoding", - "rand", - "rsa", "serde", - "sha1", - "sha2", - "smallvec", + "sha1 0.11.0", + "sha2 0.11.0", "sqlx-core", - "stringprep", - "thiserror 2.0.20", + "thiserror", "tracing", "uuid", - "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64", @@ -1648,20 +1678,18 @@ dependencies = [ "hex", "hkdf", "hmac", - "home", "itoa", "log", "md-5", "memchr", - "once_cell", - "rand", + "rand 0.10.2", "serde", "serde_json", - "sha2", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.20", + "thiserror", "tracing", "uuid", "whoami", @@ -1669,13 +1697,14 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "chrono", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -1685,14 +1714,19 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", - "thiserror 2.0.20", + "thiserror", "tracing", "url", "uuid", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "stringprep" version = "0.1.5" @@ -1723,9 +1757,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -1738,6 +1772,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -1745,10 +1790,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1760,33 +1805,13 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.20", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] @@ -1797,7 +1822,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1809,6 +1834,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.12.0" @@ -1838,7 +1873,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1849,7 +1884,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1865,71 +1900,66 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.24.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.29.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17a073bfed563fa236697a068031408a93cd9522e08abf9933ead3e73411bd71" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.30.0", ] [[package]] name = "toml" -version = "0.8.23" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_writer" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", -] +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tower" @@ -1949,15 +1979,15 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.5.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233" dependencies = [ "bitflags", "bytes", "http", "http-body", - "http-body-util", + "percent-encoding", "pin-project-lite", "tower-layer", "tower-service", @@ -2053,21 +2083,21 @@ dependencies = [ [[package]] name = "ts-rs" -version = "9.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b44017f9f875786e543595076374b9ef7d13465a518dd93d6ccdbf5b432dde8c" +checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8" dependencies = [ "chrono", - "thiserror 1.0.69", + "thiserror", "ts-rs-macros", "uuid", ] [[package]] name = "ts-rs-macros" -version = "9.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130" +checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa" dependencies = [ "proc-macro2", "quote", @@ -2077,20 +2107,34 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.24.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1 0.10.7", + "thiserror", +] + +[[package]] +name = "tungstenite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22" dependencies = [ - "byteorder", "bytes", "data-encoding", "http", "httparse", "log", - "rand", - "sha1", - "thiserror 1.0.69", - "utf-8", + "rand 0.10.2", + "sha1 0.11.0", + "thiserror", ] [[package]] @@ -2128,29 +2172,32 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "url" -version = "2.5.0" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] -name = "utf-8" -version = "0.7.6" +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.8.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" dependencies = [ - "getrandom 0.2.17", - "serde", + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", ] [[package]] @@ -2186,12 +2233,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -2239,13 +2280,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "winapi-util" @@ -2253,7 +2290,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -2315,24 +2352,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -2343,167 +2362,120 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "winnow" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "writeable" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "yoke" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "yoke-derive" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +name = "zerocopy" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "zerocopy-derive" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "zerofrom" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] [[package]] -name = "winnow" -version = "0.7.15" +name = "zerofrom-derive" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ - "memchr", + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", ] [[package]] -name = "wit-bindgen" -version = "0.57.1" +name = "zerotrie" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] -name = "zerocopy" -version = "0.8.56" +name = "zerovec" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ - "zerocopy-derive", + "yoke", + "zerofrom", + "zerovec-derive", ] [[package]] -name = "zerocopy-derive" -version = "0.8.56" +name = "zerovec-derive" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] -[[package]] -name = "zeroize" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" - [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 99f2b22..7b53925 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,8 @@ members = [ resolver = "2" [workspace.package] -edition = "2021" +edition = "2024" version = "0.1.0" license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" diff --git a/README.md b/README.md index 9b20036..78e3fa5 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ > **Open-source arcade netplay — a clean-room, community-owned alternative for low-latency rollback matchmaking and emulation.** -OpenCade is a monorepo for a modern arcade netplay platform: Rust server (Axum + PostgreSQL), Tauri + React + TypeScript desktop client, and a pluggable emulator adapter SDK. The repository now contains an executable **Proof of Match** control plane, deterministic mock-adapter data plane, safe local FBNeo launch boundary, and LAN UDP transport. FBNeo netplay, NAT traversal, and relay fallback remain explicitly unproven and are not advertised as implemented. +OpenCade is a monorepo for a modern arcade netplay platform: Rust server (Axum + PostgreSQL), Tauri + React + TypeScript desktop client, and a pluggable emulator adapter SDK. The repository now contains an executable **Proof of Match** control plane, deterministic mock-adapter data plane, safe local FBNeo launch boundary, direct UDP transport, and an evidence-gated STUN/hole-punch path. FBNeo netplay, physical cross-NAT results, behavior classification, and relay fallback remain explicitly unproven and are not advertised as implemented. ## Support — keep it community-owned -OpenCade exists to replace proprietary Fightcade with a community-owned, self-hostable, and fully auditable alternative. Proof-of-Match is done — lobby, signaling, and deterministic transport run today. What remains is the hard part: NAT traversal, relay fallback, and the emulator seam — expensive, unglamorous systems work that determines whether matches actually connect. +OpenCade exists to replace proprietary Fightcade with a community-owned, self-hostable, and fully auditable alternative. Proof-of-Match is done — lobby, signaling, deterministic transport, STUN discovery, and bounded hole punching run today. What remains is physical network evidence, relay fallback, and the emulator seam — expensive, unglamorous systems work that determines whether matches actually connect. **[☕ Buy Me a Coffee — https://buymeacoffee.com/zendevve](https://buymeacoffee.com/zendevve)** — one coffee keeps the relay and adapter work open. No paywall, no premium. @@ -24,7 +24,7 @@ OpenCade exists to replace proprietary Fightcade with a community-owned, self-ho - Docker + Docker Compose (server + Postgres) - Rust stable + `sqlx-cli` (server) -- Node.js 20+ + pnpm 9+ (client) +- Node.js 24+ + pnpm 11+ (client) - Rust + Tauri prerequisites ([tauri.app/start/prerequisites](https://tauri.app/start/prerequisites)) ### 1. Server (Docker Compose) @@ -141,7 +141,9 @@ High-level: `Client (Tauri)` ↔ `Server (Axum REST + authenticated WebSocket)` - **Server:** auth (Argon2id), hashed sessions, games, server hints, lobbies, durable challenges, rooms/matches, and authenticated WebSocket signaling (`offer`/`answer`/`candidate`). -- **Networking:** deterministic in-memory and connected UDP transports are implemented; hole punching, STUN, and relay fallback are deferred until two-machine LAN evidence exists. +- **Networking:** deterministic in-memory and connected UDP transports, RFC 8489 reflexive-address + discovery, and bounded nonce-bound hole punching are implemented. Cone/symmetric behavior + classification and relay fallback remain deferred pending physical campaign evidence. - **Client:** login/register, games, lobby challenges, match state, local availability scan, diagnostics, and redacted report export; Rust core owns process spawn, filesystem validation, and diagnostics. - **Emulator SDK:** trait-based adapters with safe process launch (no shell injection, argument escaping), ROM validation, and game-definition TOML. diff --git a/adapters/fbneo/Cargo.toml b/adapters/fbneo/Cargo.toml index 8c6ad37..5bf252b 100644 --- a/adapters/fbneo/Cargo.toml +++ b/adapters/fbneo/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "opencade-adapter-fbneo" version = "0.1.0" -edition = "2021" +edition = "2024" description = "OpenCade FBNeo emulator adapter (placeholder)" license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" [dependencies] -serde = { version = "1", features = ["derive"] } -thiserror = "1" +serde = { version = "1.0.229", features = ["derive"] } +thiserror = "2.0.20" opencade-emulator-sdk = { path = "../../packages/emulator-sdk" } diff --git a/adapters/fbneo/src/lib.rs b/adapters/fbneo/src/lib.rs index 2f5a423..0b85150 100644 --- a/adapters/fbneo/src/lib.rs +++ b/adapters/fbneo/src/lib.rs @@ -1,6 +1,6 @@ use opencade_emulator_sdk::{ - spawn_validated, AdapterCapabilities, AdapterError, DetectedEmulator, EmulatorAdapter, - StdProcessLauncher, ValidationReport, + AdapterCapabilities, AdapterError, DetectedEmulator, EmulatorAdapter, NetplayReadiness, + StdProcessLauncher, ValidationReport, spawn_validated, }; use std::path::{Path, PathBuf}; use std::process::Child; @@ -45,7 +45,7 @@ impl EmulatorAdapter for FbneoAdapter { fn capabilities(&self) -> AdapterCapabilities { AdapterCapabilities { local_play: true, - netplay: false, + netplay: NetplayReadiness::BlockedNoPublicInterface, } } @@ -156,7 +156,11 @@ mod tests { .expect("validation"), ValidationReport::valid() ); - assert!(!adapter.capabilities().netplay); + assert_eq!( + adapter.capabilities().netplay, + NetplayReadiness::BlockedNoPublicInterface + ); + assert!(!adapter.capabilities().supports_netplay()); std::fs::remove_dir_all(root).expect("remove fixture"); } diff --git a/apps/client/package.json b/apps/client/package.json index 92e3a02..17b0632 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -13,19 +13,19 @@ }, "dependencies": { "@opencade/protocol": "workspace:*", - "@tanstack/react-query": "^5.51.1", - "@tauri-apps/api": "^1.6.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "zustand": "^4.5.2" + "@tanstack/react-query": "^5.102.0", + "@tauri-apps/api": "^2.11.1", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "zustand": "^5.0.15" }, "devDependencies": { - "@tauri-apps/cli": "^1.6.0", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "^5.5.0", - "vite": "^5.4.0", - "vitest": "^1.6.0" + "@tauri-apps/cli": "^2.11.4", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.1.0", + "typescript": "^7.0.2", + "vite": "^8.2.2", + "vitest": "^4.1.11" } } diff --git a/apps/client/src-tauri/Cargo.lock b/apps/client/src-tauri/Cargo.lock index 7a933fd..381b5f5 100644 --- a/apps/client/src-tauri/Cargo.lock +++ b/apps/client/src-tauri/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -49,39 +49,38 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "atk" -version = "0.15.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" dependencies = [ "atk-sys", - "bitflags 1.3.2", "glib", "libc", ] [[package]] name = "atk-sys" -version = "0.15.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.2.2", + "system-deps", ] [[package]] -name = "autocfg" -version = "1.5.1" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "base64" -version = "0.13.1" +name = "autocfg" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -95,6 +94,21 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -106,12 +120,9 @@ name = "bitflags" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +dependencies = [ + "serde_core", +] [[package]] name = "block-buffer" @@ -122,11 +133,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -135,22 +155,21 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.3" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[package]] -name = "bstr" -version = "1.13.1" +name = "bs58" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "memchr", - "serde_core", + "tinyvec", ] [[package]] @@ -176,39 +195,75 @@ name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] [[package]] name = "cairo-rs" -version = "0.15.12" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.1", "cairo-sys-rs", "glib", "libc", - "thiserror", + "once_cell", + "thiserror 1.0.69", ] [[package]] name = "cairo-sys-rs" -version = "0.15.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" dependencies = [ "glib-sys", "libc", - "system-deps 6.2.2", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.20", ] [[package]] name = "cargo_toml" -version = "0.15.3" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ "serde", - "toml 0.7.8", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -238,15 +293,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "cfg-expr" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" -dependencies = [ - "smallvec", -] - [[package]] name = "cfg-expr" version = "0.15.8" @@ -274,45 +320,9 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", + "windows-link 0.2.1", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "combine" version = "4.6.7" @@ -324,16 +334,20 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.4.0" +name = "cookie" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "time", + "version_check", +] [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -347,11 +361,11 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.1", "core-foundation", "core-graphics-types", "foreign-types", @@ -360,11 +374,11 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.1", "core-foundation", "libc", ] @@ -380,9 +394,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" dependencies = [ "cfg-if", ] @@ -396,25 +410,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.22" @@ -433,19 +428,15 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.27.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", + "itoa", + "phf", "smallvec", - "syn 1.0.109", ] [[package]] @@ -460,19 +451,25 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.9" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" dependencies = [ - "quote", - "syn 2.0.119", + "ctor-proc-macro", + "dtor", ] +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "darling" -version = "0.20.11" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -480,11 +477,10 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.11" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", @@ -494,32 +490,81 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.11" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", "syn 2.0.119", ] +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] + [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", - "serde", + "serde_core", ] [[package]] name = "derive_more" -version = "0.99.20" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case", "proc-macro2", "quote", "rustc_version", @@ -537,31 +582,95 @@ dependencies = [ ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "dirs" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "cfg-if", - "dirs-sys-next", + "dirs-sys", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "dirs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", + "option-ext", "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", "winapi", ] [[package]] -name = "dispatch" -version = "0.2.0" +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] [[package]] name = "dtoa" @@ -578,22 +687,43 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "embed-resource" -version = "2.5.2" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d506610004cfc74a6f5ee7e8c632b355de5eca1f03ee5e5e0ec11b77d4eb3d61" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.8.23", + "toml 1.1.4+spec-1.1.0", "vswhom", "winreg", ] @@ -604,21 +734,23 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + [[package]] name = "errno" version = "0.3.14" @@ -655,18 +787,8 @@ dependencies = [ ] [[package]] -name = "filetime" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" -dependencies = [ - "cfg-if", - "libc", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.11" +name = "find-msvc-tools" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" @@ -680,35 +802,44 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fluent-uri" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ + "foreign-types-macros", "foreign-types-shared", ] +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "foreign-types-shared" -version = "0.1.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" @@ -719,16 +850,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures-channel" version = "0.3.34" @@ -769,9 +890,15 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + [[package]] name = "futures-task" version = "0.3.34" @@ -785,28 +912,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "gdk" -version = "0.15.4" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" dependencies = [ - "bitflags 1.3.2", "cairo-rs", "gdk-pixbuf", "gdk-sys", @@ -818,35 +938,35 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.15.11" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" dependencies = [ - "bitflags 1.3.2", "gdk-pixbuf-sys", "gio", "glib", "libc", + "once_cell", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.15.10" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 6.2.2", + "system-deps", ] [[package]] name = "gdk-sys" -version = "0.15.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -856,47 +976,48 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.2.2", + "system-deps", ] [[package]] name = "gdkwayland-sys" -version = "0.15.3" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" dependencies = [ "gdk-sys", "glib-sys", "gobject-sys", "libc", "pkg-config", - "system-deps 6.2.2", + "system-deps", ] [[package]] -name = "gdkx11-sys" -version = "0.15.1" +name = "gdkx11" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" dependencies = [ - "gdk-sys", - "glib-sys", + "gdk", + "gdkx11-sys", + "gio", + "glib", "libc", - "system-deps 6.2.2", "x11", ] [[package]] -name = "generator" -version = "0.7.5" +name = "gdkx11-sys" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" dependencies = [ - "cc", + "gdk-sys", + "glib-sys", "libc", - "log", - "rustversion", - "windows 0.48.0", + "system-deps", + "x11", ] [[package]] @@ -911,99 +1032,115 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.2.17" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "r-efi 6.0.0", ] [[package]] name = "gio" -version = "0.15.12" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" dependencies = [ - "bitflags 1.3.2", "futures-channel", "futures-core", "futures-io", + "futures-util", "gio-sys", "glib", "libc", "once_cell", - "thiserror", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", ] [[package]] name = "gio-sys" -version = "0.15.10" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.2.2", + "system-deps", "winapi", ] [[package]] name = "glib" -version = "0.15.12" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.1", "futures-channel", "futures-core", "futures-executor", "futures-task", + "futures-util", + "gio-sys", "glib-macros", "glib-sys", "gobject-sys", "libc", + "memchr", "once_cell", "smallvec", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "glib-macros" -version = "0.15.13" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" dependencies = [ - "anyhow", "heck 0.4.1", - "proc-macro-crate", + "proc-macro-crate 2.0.2", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", ] [[package]] name = "glib-sys" -version = "0.15.10" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" dependencies = [ "libc", - "system-deps 6.2.2", + "system-deps", ] [[package]] @@ -1012,38 +1149,24 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" -[[package]] -name = "globset" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - [[package]] name = "gobject-sys" -version = "0.15.10" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" dependencies = [ "glib-sys", "libc", - "system-deps 6.2.2", + "system-deps", ] [[package]] name = "gtk" -version = "0.15.5" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" dependencies = [ "atk", - "bitflags 1.3.2", "cairo-rs", "field-offset", "futures-channel", @@ -1054,16 +1177,15 @@ dependencies = [ "gtk-sys", "gtk3-macros", "libc", - "once_cell", "pango", "pkg-config", ] [[package]] name = "gtk-sys" -version = "0.15.3" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" dependencies = [ "atk-sys", "cairo-sys-rs", @@ -1074,21 +1196,20 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.2.2", + "system-deps", ] [[package]] name = "gtk3-macros" -version = "0.15.6" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" dependencies = [ - "anyhow", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", ] [[package]] @@ -1099,18 +1220,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" - -[[package]] -name = "heck" -version = "0.3.3" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1132,34 +1244,95 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "html5ever" -version = "0.26.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ "log", - "mac", "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", ] [[package]] name = "http" -version = "0.2.12" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", - "fnv", - "itoa 1.0.18", + "itoa", ] [[package]] -name = "http-range" +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] [[package]] name = "iana-time-zone" @@ -1173,7 +1346,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -1187,56 +1360,122 @@ dependencies = [ [[package]] name = "ico" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" dependencies = [ "byteorder", - "png", + "png 0.17.16", ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "icu_collections" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_locale_core" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "ignore" -version = "0.4.22" +name = "icu_normalizer" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", ] [[package]] -name = "image" -version = "0.24.9" +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-traits", + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1252,38 +1491,30 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.17.1", "serde", + "serde_core", ] [[package]] name = "infer" -version = "0.13.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" dependencies = [ "cfb", ] [[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "itoa" -version = "0.4.8" +name = "ipnet" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "itoa" @@ -1293,9 +1524,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "javascriptcore-rs" -version = "0.16.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" dependencies = [ "bitflags 1.3.2", "glib", @@ -1304,53 +1535,108 @@ dependencies = [ [[package]] name = "javascriptcore-rs-sys" -version = "0.4.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 5.0.0", + "system-deps", ] [[package]] -name = "jni" -version = "0.20.0" +name = "jiff" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" dependencies = [ - "cesu8", - "combine", - "jni-sys 0.3.1", + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", "log", - "thiserror", - "walkdir", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "jiff-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" dependencies = [ - "jni-sys 0.4.1", + "defmt", ] [[package]] -name = "jni-sys" -version = "0.4.1" +name = "jiff-static" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" dependencies = [ - "jni-sys-macros", + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "jni-sys-macros" -version = "0.4.1" +name = "jiff-tzdb" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", "syn 2.0.119", @@ -1369,38 +1655,35 @@ dependencies = [ [[package]] name = "json-patch" -version = "2.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" dependencies = [ "jsonptr", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "jsonptr" -version = "0.4.7" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" dependencies = [ - "fluent-uri", "serde", "serde_json", ] [[package]] -name = "kuchikiki" -version = "0.8.2" +name = "keyboard-types" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "cssparser", - "html5ever", - "indexmap 1.9.3", - "matches", - "selectors", + "bitflags 2.13.1", + "serde", + "unicode-segmentation", ] [[package]] @@ -1410,95 +1693,93 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libc" -version = "0.2.189" +name = "libappindicator" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] [[package]] -name = "libredox" -version = "0.1.20" +name = "libappindicator-sys" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ - "libc", + "gtk-sys", + "libloading", + "once_cell", ] [[package]] -name = "line-wrap" -version = "0.2.0" +name = "libc" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "libdbus-sys" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] [[package]] -name = "linux-raw-sys" -version = "0.12.1" +name = "libloading" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] [[package]] -name = "lock_api" -version = "0.4.14" +name = "libredox" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" dependencies = [ - "scopeguard", + "libc", ] [[package]] -name = "log" -version = "0.4.33" +name = "litemap" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] -name = "loom" -version = "0.5.6" +name = "lock_api" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", + "scopeguard", ] [[package]] -name = "mac" -version = "0.1.1" +name = "log" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", - "string_cache", - "string_cache_codegen", "tendril", + "web_atoms", ] [[package]] @@ -1510,12 +1791,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "memchr" version = "2.8.3" @@ -1531,6 +1806,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1548,34 +1829,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", "windows-sys 0.61.2", ] [[package]] name = "ndk" -version = "0.6.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.1", "jni-sys 0.3.1", + "log", "ndk-sys", "num_enum", - "thiserror", + "raw-window-handle", + "thiserror 1.0.69", ] -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - [[package]] name = "ndk-sys" -version = "0.3.0" +version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ "jni-sys 0.3.1", ] @@ -1586,12 +1884,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1603,9 +1895,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-traits" @@ -1618,51 +1910,219 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.11" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", ] [[package]] -name = "objc" -version = "0.2.7" +name = "objc2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ - "malloc_buf", - "objc_exception", + "objc2-encode", + "objc2-exception-helper", ] [[package]] -name = "objc_exception" -version = "0.1.2" +name = "objc2-app-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "cc", + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] -name = "objc_id" +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "objc", + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] @@ -1677,7 +2137,7 @@ version = "0.1.0" dependencies = [ "opencade-emulator-sdk", "serde", - "thiserror", + "thiserror 2.0.20", ] [[package]] @@ -1687,6 +2147,7 @@ dependencies = [ "opencade-adapter-fbneo", "opencade-emulator-sdk", "opencade-networking", + "opencade-protocol", "serde", "serde_json", "tauri", @@ -1702,7 +2163,7 @@ name = "opencade-emulator-sdk" version = "0.1.0" dependencies = [ "serde", - "thiserror", + "thiserror 2.0.20", ] [[package]] @@ -1714,8 +2175,9 @@ dependencies = [ "opencade-protocol", "serde", "serde_json", - "thiserror", + "thiserror 2.0.20", "tokio", + "uuid", ] [[package]] @@ -1729,13 +2191,19 @@ dependencies = [ "uuid", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "pango" -version = "0.15.10" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" dependencies = [ - "bitflags 1.3.2", + "gio", "glib", "libc", "once_cell", @@ -1744,14 +2212,14 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.15.10" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.2.2", + "system-deps", ] [[package]] @@ -1774,7 +2242,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1785,106 +2253,43 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_macros", + "phf_shared", + "serde", ] [[package]] name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.7", + "phf_generator", + "phf_shared", ] [[package]] name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.7", -] - -[[package]] -name = "phf_macros" -version = "0.8.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", + "fastrand", + "phf_shared", ] [[package]] name = "phf_macros" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator", + "phf_shared", "proc-macro2", "quote", "syn 2.0.119", @@ -1892,29 +2297,11 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "siphasher 1.0.3", + "siphasher", ] [[package]] @@ -1931,13 +2318,12 @@ checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "plist" -version = "1.6.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ - "base64 0.21.7", - "indexmap 2.7.1", - "line-wrap", + "base64 0.22.1", + "indexmap 2.14.0", "quick-xml", "serde", "time", @@ -1957,20 +2343,48 @@ dependencies = [ ] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "png" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "portable-atomic-util" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ - "zerocopy", + "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1987,6 +2401,25 @@ dependencies = [ "toml_edit 0.19.15", ] +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2011,12 +2444,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.107" @@ -2028,9 +2455,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] @@ -2045,110 +2472,61 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.7.3" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "rand" -version = "0.8.7" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" +name = "raw-window-handle" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] -name = "rand_core" -version = "0.5.1" +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "getrandom 0.1.16", + "bitflags 2.13.1", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "redox_users" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", + "libredox", + "thiserror 2.0.20", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "ref-cast" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ - "rand_core 0.5.1", + "ref-cast-impl", ] [[package]] -name = "rand_pcg" -version = "0.2.1" +name = "ref-cast-impl" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror", + "proc-macro2", + "quote", + "syn 3.0.4", ] [[package]] @@ -2181,38 +2559,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] -name = "rustc_version" -version = "0.4.1" +name = "reqwest" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "semver", + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", ] [[package]] -name = "rustix" -version = "0.38.44" +name = "rustc-hash" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] -name = "rustix" -version = "1.1.4" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "semver", ] [[package]] @@ -2231,10 +2623,55 @@ dependencies = [ ] [[package]] -name = "scoped-tls" -version = "1.0.1" +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] [[package]] name = "scopeguard" @@ -2244,22 +2681,21 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "selectors" -version = "0.22.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.1", "cssparser", "derive_more", - "fxhash", "log", - "matches", - "phf 0.8.0", - "phf_codegen 0.8.0", + "new_debug_unreachable", + "phf", + "phf_codegen", "precomputed-hash", + "rustc-hash", "servo_arc", "smallvec", - "thin-slice", ] [[package]] @@ -2282,6 +2718,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -2299,7 +2747,18 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -2308,8 +2767,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap 2.7.1", - "itoa 1.0.18", + "itoa", "memchr", "serde", "serde_core", @@ -2324,7 +2782,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -2336,19 +2794,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_with" -version = "3.8.1" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" dependencies = [ "base64 0.22.1", + "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", - "serde", - "serde_derive", + "indexmap 2.14.0", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -2356,9 +2826,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.1" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" dependencies = [ "darling", "proc-macro2", @@ -2390,11 +2860,10 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.1.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" dependencies = [ - "nodrop", "stable_deref_trait", ] @@ -2440,12 +2909,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.3" @@ -2475,31 +2938,51 @@ dependencies = [ ] [[package]] -name = "soup2" -version = "0.2.1" +name = "softbuffer" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" dependencies = [ - "bitflags 1.3.2", + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", "gio", "glib", "libc", - "once_cell", - "soup2-sys", + "soup3-sys", ] [[package]] -name = "soup2-sys" -version = "0.2.0" +name = "soup3-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" dependencies = [ - "bitflags 1.3.2", "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 5.0.0", + "system-deps", ] [[package]] @@ -2508,36 +2991,26 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - [[package]] name = "string_cache" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.11.3", + "phf_shared", "precomputed-hash", - "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator", + "phf_shared", "proc-macro2", "quote", ] @@ -2548,6 +3021,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "swift-rs" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45c444e496845d3f2a351146bff59aae4975b2280238df1dfaa0c7d1846f38e" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "syn" version = "1.0.109" @@ -2555,7 +3039,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] @@ -2572,9 +3055,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -2582,16 +3065,23 @@ dependencies = [ ] [[package]] -name = "system-deps" -version = "5.0.0" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ - "cfg-expr 0.9.1", - "heck 0.3.3", - "pkg-config", - "toml 0.5.11", - "version-compare 0.0.11", + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -2600,57 +3090,50 @@ version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "cfg-expr 0.15.8", + "cfg-expr", "heck 0.5.0", "pkg-config", - "toml 0.8.23", - "version-compare 0.2.1", + "toml 0.8.2", + "version-compare", ] [[package]] name = "tao" -version = "0.16.11" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf915e6c7112402f7b88a064cfbd264f851052df07fdc3a2abd3038b0cc434a" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "cc", - "cocoa", + "bitflags 2.13.1", + "block2", "core-foundation", "core-graphics", "crossbeam-channel", - "dispatch", - "gdk", - "gdk-pixbuf", - "gdk-sys", + "dbus", + "dispatch2", + "dlopen2", + "dpi", "gdkwayland-sys", "gdkx11-sys", - "gio", - "glib", - "glib-sys", "gtk", - "image", - "instant", "jni", - "lazy_static", "libc", "log", "ndk", - "ndk-context", "ndk-sys", - "objc", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", "once_cell", "parking_lot", - "png", + "percent-encoding", "raw-window-handle", - "scopeguard", - "serde", "tao-macros", "unicode-segmentation", - "uuid", - "windows 0.39.0", - "windows-implement 0.39.0", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", "x11-dl", ] @@ -2665,17 +3148,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "tar" -version = "0.4.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" -dependencies = [ - "filetime", - "libc", - "xattr", -] - [[package]] name = "target-lexicon" version = "0.12.16" @@ -2684,64 +3156,68 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "1.8.3" +version = "2.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae1f57c291a6ab8e1d2e6b8ad0a35ff769c9925deb8a89de85425ff08762d0c" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" dependencies = [ "anyhow", - "cocoa", - "dirs-next", + "bytes", + "cookie", + "dirs", "dunce", "embed_plist", - "encoding_rs", - "flate2", - "futures-util", - "getrandom 0.2.17", - "glib", + "getrandom 0.3.4", "glob", "gtk", "heck 0.5.0", "http", - "ignore", + "jni", + "libc", "log", - "objc", - "once_cell", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", "percent-encoding", "plist", - "rand 0.8.7", "raw-window-handle", - "semver", + "reqwest", "serde", "serde_json", "serde_repr", "serialize-to-javascript", - "state", - "tar", + "swift-rs", + "tauri-build", "tauri-macros", "tauri-runtime", "tauri-runtime-wry", "tauri-utils", - "tempfile", - "thiserror", + "thiserror 2.0.20", "tokio", + "tray-icon", "url", - "uuid", "webkit2gtk", "webview2-com", - "windows 0.39.0", + "window-vibrancy", + "windows", ] [[package]] name = "tauri-build" -version = "1.5.6" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db08694eec06f53625cfc6fff3a363e084e5e9a238166d2989996413c346453" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" dependencies = [ "anyhow", "cargo_toml", - "dirs-next", + "dirs", + "glob", "heck 0.5.0", "json-patch", + "schemars 0.8.22", "semver", "serde", "serde_json", @@ -2752,145 +3228,152 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "1.4.6" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53438d78c4a037ffe5eafa19e447eea599bedfb10844cb08ec53c2471ac3ac3f" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "brotli", "ico", "json-patch", "plist", - "png", + "png 0.17.16", "proc-macro2", "quote", "semver", "serde", "serde_json", "sha2", + "syn 2.0.119", "tauri-utils", - "thiserror", + "thiserror 2.0.20", "time", + "url", "uuid", "walkdir", ] [[package]] name = "tauri-macros" -version = "1.4.7" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233988ac08c1ed3fe794cd65528d48d8f7ed4ab3895ca64cdaa6ad4d00c45c0b" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", "tauri-codegen", "tauri-utils", ] [[package]] name = "tauri-runtime" -version = "0.14.6" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8066855882f00172935e3fa7d945126580c34dcbabab43f5d4f0c2398a67d47b" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" dependencies = [ + "cookie", + "dpi", "gtk", "http", - "http-range", - "rand 0.8.7", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", - "thiserror", + "thiserror 2.0.20", "url", - "uuid", + "webkit2gtk", "webview2-com", - "windows 0.39.0", + "windows", ] [[package]] name = "tauri-runtime-wry" -version = "0.14.11" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce361fec1e186705371f1c64ae9dd2a3a6768bc530d0a2d5e75a634bb416ad4d" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" dependencies = [ - "cocoa", "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", "percent-encoding", - "rand 0.8.7", "raw-window-handle", + "softbuffer", + "tao", "tauri-runtime", "tauri-utils", - "uuid", + "url", "webkit2gtk", "webview2-com", - "windows 0.39.0", + "windows", "wry", ] [[package]] name = "tauri-utils" -version = "1.6.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c357952645e679de02cd35007190fcbce869b93ffc61b029f33fe02648453774" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" dependencies = [ + "anyhow", "brotli", + "cargo_metadata", "ctor", + "dom_query", "dunce", "glob", - "heck 0.5.0", - "html5ever", + "http", "infer", "json-patch", - "kuchikiki", "log", "memchr", - "phf 0.11.3", + "phf", + "plist", "proc-macro2", "quote", + "regex", + "schemars 0.8.22", "semver", "serde", + "serde-untagged", "serde_json", "serde_with", - "thiserror", + "swift-rs", + "thiserror 2.0.20", + "toml 1.1.4+spec-1.1.0", "url", + "urlpattern", + "uuid", "walkdir", - "windows-version", ] [[package]] name = "tauri-winres" -version = "0.1.1" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" dependencies = [ + "dunce", "embed-resource", - "toml 0.7.8", -] - -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix 0.38.44", - "windows-sys 0.52.0", + "toml 1.1.4+spec-1.1.0", ] [[package]] name = "tendril" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" dependencies = [ - "futf", - "mac", - "utf-8", + "new_debug_unreachable", ] [[package]] @@ -2903,18 +3386,21 @@ dependencies = [ ] [[package]] -name = "thin-slice" -version = "0.1.1" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.20", ] [[package]] @@ -2928,6 +3414,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "thread_local" version = "1.1.10" @@ -2939,35 +3436,44 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa 1.0.18", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.12.0" @@ -3008,83 +3514,186 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] name = "toml" -version = "0.5.11" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", ] [[package]] name = "toml" -version = "0.7.8" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", ] [[package]] name = "toml" -version = "0.8.23" +version = "1.1.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.27", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ + "indexmap 2.14.0", "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", ] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "tower-http" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", + "bitflags 2.13.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "tower-layer" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow 0.7.15", -] +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] -name = "toml_write" -version = "0.1.2" +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -3147,23 +3756,51 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tray-icon" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ts-rs" -version = "9.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b44017f9f875786e543595076374b9ef7d13465a518dd93d6ccdbf5b432dde8c" +checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8" dependencies = [ "chrono", - "thiserror", + "thiserror 2.0.20", "ts-rs-macros", "uuid", ] [[package]] name = "ts-rs-macros" -version = "9.0.1" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130" +checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa" dependencies = [ "proc-macro2", "quote", @@ -3171,6 +3808,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.20.1" @@ -3178,58 +3821,99 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "unicode-bidi" -version = "0.3.18" +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" [[package]] -name = "unicode-ident" -version = "1.0.24" +name = "unic-common" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" [[package]] -name = "unicode-normalization" -version = "0.1.25" +name = "unic-ucd-ident" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" dependencies = [ - "tinyvec", + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", ] +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "url" -version = "2.5.0" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] -name = "utf-8" -version = "0.7.6" +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.8.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" dependencies = [ - "getrandom 0.2.17", - "serde", + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", ] [[package]] @@ -3238,12 +3922,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "version-compare" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" - [[package]] name = "version-compare" version = "0.2.1" @@ -3287,10 +3965,13 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +name = "want" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] [[package]] name = "wasi" @@ -3298,6 +3979,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -3311,6 +4001,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.127" @@ -3343,11 +4043,46 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + [[package]] name = "webkit2gtk" -version = "0.18.2" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -3363,20 +4098,18 @@ dependencies = [ "javascriptcore-rs", "libc", "once_cell", - "soup2", + "soup3", "webkit2gtk-sys", ] [[package]] name = "webkit2gtk-sys" -version = "0.18.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" dependencies = [ - "atk-sys", "bitflags 1.3.2", "cairo-sys-rs", - "gdk-pixbuf-sys", "gdk-sys", "gio-sys", "glib-sys", @@ -3384,48 +4117,45 @@ dependencies = [ "gtk-sys", "javascriptcore-rs-sys", "libc", - "pango-sys", "pkg-config", - "soup2-sys", - "system-deps 6.2.2", + "soup3-sys", + "system-deps", ] [[package]] name = "webview2-com" -version = "0.19.1" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.39.0", - "windows-implement 0.39.0", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", ] [[package]] name = "webview2-com-macros" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", ] [[package]] name = "webview2-com-sys" -version = "0.19.0" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ - "regex", - "serde", - "serde_json", - "thiserror", - "windows 0.39.0", - "windows-bindgen", - "windows-metadata", + "thiserror 2.0.20", + "windows", + "windows-core 0.61.2", ] [[package]] @@ -3460,36 +4190,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.39.0" +name = "window-vibrancy" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" dependencies = [ - "windows-implement 0.39.0", - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", ] [[package]] name = "windows" -version = "0.48.0" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-targets 0.48.5", + "windows-core 0.61.2", ] [[package]] -name = "windows-bindgen" -version = "0.39.0" +name = "windows-core" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-metadata", - "windows-tokens", + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -3498,21 +4245,22 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", + "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] -name = "windows-implement" -version = "0.39.0" +name = "windows-future" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "syn 1.0.109", - "windows-tokens", + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", ] [[package]] @@ -3537,6 +4285,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" @@ -3544,10 +4298,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-metadata" -version = "0.39.0" +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] [[package]] name = "windows-result" @@ -3555,34 +4322,34 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] name = "windows-strings" -version = "0.5.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.48.5", + "windows-link 0.2.1", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.42.2", ] [[package]] @@ -3600,22 +4367,22 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -3635,10 +4402,13 @@ dependencies = [ ] [[package]] -name = "windows-tokens" -version = "0.39.0" +name = "windows-threading" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] [[package]] name = "windows-version" @@ -3646,14 +4416,14 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -3663,15 +4433,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.39.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -3681,15 +4445,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -3705,15 +4463,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.39.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -3723,15 +4475,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -3741,9 +4487,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -3753,15 +4499,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.39.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -3783,56 +4523,80 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.52.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + [[package]] name = "wry" -version = "0.24.12" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2a144c3ab5e83e04724bc8e67cea552ffae413185fda459fafdae173fd985d" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" dependencies = [ - "base64 0.13.1", - "block", - "cocoa", - "core-graphics", + "base64 0.22.1", + "block2", + "cookie", "crossbeam-channel", + "dirs", + "dom_query", + "dpi", "dunce", - "gdk", - "gio", - "glib", + "gdkx11", "gtk", - "html5ever", "http", - "kuchikiki", + "javascriptcore-rs", + "jni", "libc", - "log", - "objc", - "objc_id", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", "once_cell", - "serde", - "serde_json", + "percent-encoding", + "raw-window-handle", "sha2", - "soup2", - "tao", - "thiserror", + "soup3", + "tao-macros", + "thiserror 2.0.20", "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.39.0", - "windows-implement 0.39.0", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", ] [[package]] @@ -3857,33 +4621,80 @@ dependencies = [ ] [[package]] -name = "xattr" -version = "1.6.1" +name = "yoke" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "libc", - "rustix 1.1.4", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", ] [[package]] -name = "zerocopy" -version = "0.8.56" +name = "zerofrom" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ - "zerocopy-derive", + "zerofrom-derive", ] [[package]] -name = "zerocopy-derive" -version = "0.8.56" +name = "zerofrom-derive" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", ] [[package]] diff --git a/apps/client/src-tauri/Cargo.toml b/apps/client/src-tauri/Cargo.toml index 6562f93..2221107 100644 --- a/apps/client/src-tauri/Cargo.toml +++ b/apps/client/src-tauri/Cargo.toml @@ -1,27 +1,28 @@ [package] name = "opencade-client" version = "0.1.0" -edition = "2021" +edition = "2024" description = "OpenCade desktop client (Tauri + Rust backend)" authors = ["OpenCade Contributors"] license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" [build-dependencies] -tauri-build = { version = "1.5", features = [] } +tauri-build = { version = "2.6.3", features = [] } [dependencies] -tauri = { version = "1.6", features = [] } -tokio = { version = "1", features = ["full"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" +tauri = { version = "2.11.5", features = [] } +tokio = { version = "1.53.1", features = ["full"] } +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" opencade-adapter-fbneo = { path = "../../../adapters/fbneo" } opencade-emulator-sdk = { path = "../../../packages/emulator-sdk" } opencade-networking = { path = "../../../packages/networking" } -uuid = { version = "1", features = ["v4"] } +opencade-protocol = { path = "../../../packages/protocol" } +uuid = { version = "1.25.0", features = ["v4"] } [features] default = ["custom-protocol"] diff --git a/apps/client/src-tauri/capabilities/migrated.json b/apps/client/src-tauri/capabilities/migrated.json new file mode 100644 index 0000000..f7a6e76 --- /dev/null +++ b/apps/client/src-tauri/capabilities/migrated.json @@ -0,0 +1,7 @@ +{ + "identifier": "migrated", + "description": "permissions that were migrated from v1", + "local": true, + "windows": ["main"], + "permissions": ["core:default"] +} diff --git a/apps/client/src-tauri/src/commands/diag.rs b/apps/client/src-tauri/src/commands/diag.rs index 4d141cc..7289df1 100644 --- a/apps/client/src-tauri/src/commands/diag.rs +++ b/apps/client/src-tauri/src/commands/diag.rs @@ -1,37 +1,8 @@ +use opencade_networking::{NatMapping, UdpPeer, discover_reflexive_address}; use serde::Serialize; +use std::net::{Ipv4Addr, SocketAddr}; use std::time::{Duration, Instant}; -#[allow(dead_code)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Default)] -#[serde(rename_all = "lowercase")] -pub enum NatType { - Open, - Cone, - Symmetric, - Blocked, - #[default] - Unknown, -} - -impl NatType { - #[must_use] - pub fn as_str(self) -> &'static str { - match self { - Self::Open => "open", - Self::Cone => "cone", - Self::Symmetric => "symmetric", - Self::Blocked => "blocked", - Self::Unknown => "unknown", - } - } -} - -impl std::fmt::Display for NatType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - #[derive(Debug, Serialize)] pub struct NetworkDiagnostics { pub nat: String, @@ -42,58 +13,76 @@ pub struct NetworkDiagnostics { pub stun_reachable: bool, } -#[must_use] -pub fn classify_nat(rtt_ok: bool, stun_ok: bool) -> &'static str { - let _ = rtt_ok; - if stun_ok { - NatType::Cone.as_str() - } else { - NatType::Unknown.as_str() - } +async fn configured_stun_server() -> Option { + let endpoint = match std::env::var("OPENCADE_STUN_SERVER") { + Ok(endpoint) => endpoint, + Err(_) => { + let host = std::env::var("STUN_HOST").ok()?; + let port = std::env::var("STUN_PORT") + .ok() + .and_then(|value| value.parse::().ok()) + .unwrap_or(3478); + format!("{host}:{port}") + } + }; + tokio::net::lookup_host(endpoint).await.ok()?.next() } #[tauri::command] pub async fn network_test() -> NetworkDiagnostics { let started = Instant::now(); + let server = std::env::var("OPENCADE_SERVER_ADDR").unwrap_or_else(|_| "127.0.0.1:8080".into()); let relay_reachable = matches!( tokio::time::timeout( Duration::from_secs(1), - tokio::net::TcpStream::connect("127.0.0.1:8080"), + tokio::net::TcpStream::connect(server), ) .await, Ok(Ok(_)) ); - let rtt_ms = if relay_reachable { - Some(started.elapsed().as_millis() as u64) - } else { - None - }; - let rtt_ok = relay_reachable; - let stun_host = - std::env::var("STUN_HOST").unwrap_or_else(|_| "stun.opencade.local:3478".to_string()); - let stun_reachable = matches!( - tokio::time::timeout( - Duration::from_millis(200), - tokio::net::TcpStream::connect(&stun_host), - ) - .await, - Ok(Ok(_)) - ); - - let nat = classify_nat(rtt_ok, stun_reachable).to_string(); - - // Loss and jitter are measured over a longer window in the full networking stack; - // for this lightweight TCP probe they are reported as zero but the fields are - // kept stable for the UI contract. - let loss = 0.0_f32; - let jitter_ms = 0.0_f32; + let stun_server = configured_stun_server().await; + let (nat, stun_rtt_ms, stun_reachable) = match stun_server { + None => ("unknown".to_string(), None, false), + Some(stun_server) => { + let stun_started = Instant::now(); + match UdpPeer::bind_unconnected(SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0))).await { + Ok(peer) => match discover_reflexive_address( + &peer, + stun_server, + Duration::from_millis(1_500), + ) + .await + { + Ok(observation) => ( + match observation.mapping { + NatMapping::Open => "open", + NatMapping::Mapped => "mapped", + } + .to_string(), + Some( + stun_started + .elapsed() + .as_millis() + .try_into() + .unwrap_or(u64::MAX), + ), + true, + ), + Err(_) => ("blocked".to_string(), None, false), + }, + Err(_) => ("blocked".to_string(), None, false), + } + } + }; + let relay_rtt_ms = + relay_reachable.then(|| started.elapsed().as_millis().try_into().unwrap_or(u64::MAX)); NetworkDiagnostics { nat, - rtt_ms, - loss, - jitter_ms, + rtt_ms: stun_rtt_ms.or(relay_rtt_ms), + loss: 0.0, + jitter_ms: 0.0, relay_reachable, stun_reachable, } diff --git a/apps/client/src-tauri/src/commands/match_probe.rs b/apps/client/src-tauri/src/commands/match_probe.rs index 2c70cd5..d0e9ad4 100644 --- a/apps/client/src-tauri/src/commands/match_probe.rs +++ b/apps/client/src-tauri/src/commands/match_probe.rs @@ -1,11 +1,15 @@ use opencade_emulator_sdk::{MatchDescriptor, PeerRole, TransportKind}; -use opencade_networking::{run_match_probe, MatchProbeConfig, MatchProbeReport, UdpPeer}; +use opencade_networking::{ + HolePunchConfig, MatchProbeConfig, MatchProbeReport, UdpPeer, discover_reflexive_address, + punch_hole, run_match_probe, +}; +use opencade_protocol::{MatchCandidateKind, NatMappingState}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; -use tokio::sync::{watch, Mutex}; +use tokio::sync::{Mutex, watch}; use uuid::Uuid; const MAX_ACTIVE_PROBES: usize = 8; @@ -31,6 +35,8 @@ struct PreparedProbe { #[derive(Debug, Clone, Serialize)] pub struct MatchEndpointCandidate { pub endpoint: SocketAddr, + pub reflexive_endpoint: Option, + pub nat: NatMappingState, pub nonce: String, } @@ -39,6 +45,7 @@ pub struct ReserveMatchProbeRequest { pub room_id: String, pub advertised_host: Option, pub bind_port: Option, + pub stun_server: Option, } #[derive(Debug, Deserialize)] @@ -49,6 +56,7 @@ pub struct RunMatchProbeRequest { pub peer_user_id: String, pub role: PeerRole, pub peer_endpoint: SocketAddr, + pub peer_reflexive_endpoint: Option, pub peer_nonce: String, #[serde(default = "default_frame_count")] pub frame_count: u64, @@ -89,8 +97,27 @@ pub async fn reserve_match_probe( Some(_) => return Err("advertised_host must not be unspecified".into()), None => discover_local_ipv4()?, }; + let endpoint = SocketAddr::from((host, port)); + let stun = match request.stun_server { + Some(server) => { + match discover_reflexive_address(&peer, server, Duration::from_millis(1_500)).await { + Ok(observation) => Some(observation.reflexive_endpoint), + Err(error) => { + tracing::warn!(%error, %server, "STUN discovery failed; retaining host candidate"); + None + } + } + } + None => None, + }; let candidate = MatchEndpointCandidate { - endpoint: SocketAddr::from((host, port)), + endpoint, + reflexive_endpoint: stun.filter(|reflexive| *reflexive != endpoint), + nat: match stun { + Some(reflexive) if reflexive == endpoint => NatMappingState::Open, + Some(_) => NatMappingState::Mapped, + None => NatMappingState::Unknown, + }, nonce: Uuid::new_v4().to_string(), }; let mut probes = state.probes.lock().await; @@ -143,6 +170,29 @@ pub async fn run_reserved_match_probe( prepared }; let probe = async move { + let session_key = combined_session_key(&prepared.candidate.nonce, &request.peer_nonce)?; + let mut peer_candidates = vec![request.peer_endpoint]; + if let Some(reflexive) = request.peer_reflexive_endpoint { + peer_candidates.push(reflexive); + } + let punch_config = HolePunchConfig::new( + request.room_id.clone(), + session_key.clone(), + peer_candidates, + 3, + Duration::from_millis(500), + ) + .map_err(|error| error.to_string())?; + let (peer, punch) = punch_hole(prepared.peer, &punch_config) + .await + .map_err(|error| error.to_string())?; + let candidate_kind = if request.peer_reflexive_endpoint == Some(punch.selected_peer) + && request.peer_reflexive_endpoint != Some(request.peer_endpoint) + { + MatchCandidateKind::Reflexive + } else { + MatchCandidateKind::Host + }; let descriptor = MatchDescriptor { room_id: request.room_id, game_id: request.game_id, @@ -151,10 +201,9 @@ pub async fn run_reserved_match_probe( role: request.role, transport: TransportKind::DirectUdp, local_endpoint: prepared.candidate.endpoint, - peer_endpoint: request.peer_endpoint, + peer_endpoint: punch.selected_peer, input_delay_frames: 2, }; - let session_key = combined_session_key(&prepared.candidate.nonce, &request.peer_nonce)?; let config = MatchProbeConfig::new( descriptor, session_key, @@ -162,14 +211,13 @@ pub async fn run_reserved_match_probe( Duration::from_millis(request.timeout_ms), ) .map_err(|error| error.to_string())?; - let peer = prepared - .peer - .connect(request.peer_endpoint) + let mut report = run_match_probe(&peer, &config) .await .map_err(|error| error.to_string())?; - run_match_probe(&peer, &config) - .await - .map_err(|error| error.to_string()) + report.nat = prepared.candidate.nat; + report.candidate = candidate_kind; + report.punch_attempts = punch.attempts; + Ok(report) }; let result = cancel_or_complete(probe, cancel_rx).await; remove_running_probe(&state, &room_id, run_id).await; @@ -194,7 +242,8 @@ pub async fn cancel_match_probe( } async fn cancel_probe(state: &MatchProbeState, room_id: &str) { - if let Some(MatchProbeSlot::Running { cancel, .. }) = state.probes.lock().await.remove(room_id) { + if let Some(MatchProbeSlot::Running { cancel, .. }) = state.probes.lock().await.remove(room_id) + { let _ = cancel.send(true); } } @@ -285,7 +334,8 @@ mod tests { async fn cancellation_interrupts_pending_probe() { let (cancel_tx, cancel_rx) = watch::channel(false); cancel_tx.send(true).expect("receiver remains active"); - let result = cancel_or_complete(std::future::pending::>(), cancel_rx).await; + let result = + cancel_or_complete(std::future::pending::>(), cancel_rx).await; assert_eq!(result, Err("match probe cancelled".into())); } diff --git a/apps/client/src-tauri/src/commands/process.rs b/apps/client/src-tauri/src/commands/process.rs index f0b2bd9..871c727 100644 --- a/apps/client/src-tauri/src/commands/process.rs +++ b/apps/client/src-tauri/src/commands/process.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use std::path::PathBuf; use std::process::Child; use std::sync::Mutex; +use tauri::Manager; #[derive(Default)] pub struct ProcessState { @@ -11,10 +12,10 @@ pub struct ProcessState { } pub fn fbneo_root(app: &tauri::AppHandle) -> Result { - app.path_resolver() + app.path() .resource_dir() .map(|root| root.join("emulator").join("fbneo")) - .ok_or_else(|| "application resource directory is unavailable".into()) + .map_err(|_| "application resource directory is unavailable".into()) } #[tauri::command] diff --git a/apps/client/src-tauri/tauri.conf.json b/apps/client/src-tauri/tauri.conf.json index 5416d3d..9dabd42 100644 --- a/apps/client/src-tauri/tauri.conf.json +++ b/apps/client/src-tauri/tauri.conf.json @@ -2,46 +2,38 @@ "build": { "beforeDevCommand": "pnpm dev", "beforeBuildCommand": "pnpm build", - "devPath": "http://localhost:1420", - "distDir": "../dist" + "frontendDist": "../dist", + "devUrl": "http://localhost:1420" }, - "package": { - "productName": "OpenCade", - "version": "0.1.0" + "bundle": { + "active": true, + "category": "Game", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] }, - "tauri": { - "allowlist": { - "all": false, - "shell": { - "all": false, - "execute": false, - "open": false, - "sidecar": false - } - }, - "bundle": { - "active": true, - "category": "Game", - "identifier": "com.opencade.client", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - }, - "security": { - "csp": "default-src 'self'; connect-src 'self' http://localhost:8080 ws://localhost:8080; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'" - }, + "productName": "OpenCade", + "mainBinaryName": "OpenCade", + "version": "0.1.0", + "identifier": "com.opencade.client", + "plugins": {}, + "app": { "windows": [ { "fullscreen": false, "height": 720, "resizable": true, "title": "OpenCade", - "width": 1280 + "width": 1280, + "useHttpsScheme": true } - ] + ], + "security": { + "csp": "default-src 'self'; connect-src ipc: http://ipc.localhost 'self' http://localhost:8080 ws://localhost:8080; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'" + } } } diff --git a/apps/client/src/lib/diagnostics.ts b/apps/client/src/lib/diagnostics.ts index c738322..8600265 100644 --- a/apps/client/src/lib/diagnostics.ts +++ b/apps/client/src/lib/diagnostics.ts @@ -1,4 +1,4 @@ -import { invoke } from "@tauri-apps/api/tauri"; +import { invoke } from "@tauri-apps/api/core"; export type NatType = "open" | "cone" | "symmetric" | "blocked" | "unknown"; diff --git a/apps/client/src/lib/match.test.ts b/apps/client/src/lib/match.test.ts index e75826f..6665218 100644 --- a/apps/client/src/lib/match.test.ts +++ b/apps/client/src/lib/match.test.ts @@ -16,11 +16,31 @@ describe("parseMatchEndpoint", () => { parseMatchEndpoint({ room_id: "room-1", endpoint: "192.168.1.20:42000", + reflexive_endpoint: "203.0.113.9:52000", + nat: "mapped", nonce: "nonce-1", }) ).toEqual({ room_id: "room-1", endpoint: "192.168.1.20:42000", + reflexive_endpoint: "203.0.113.9:52000", + nat: "mapped", + nonce: "nonce-1", + }); + }); + + it("defaults traversal evidence from an older version-1 peer", () => { + expect( + parseMatchEndpoint({ + room_id: "room-1", + endpoint: "192.168.1.20:42000", + nonce: "nonce-1", + }) + ).toEqual({ + room_id: "room-1", + endpoint: "192.168.1.20:42000", + reflexive_endpoint: null, + nat: "unknown", nonce: "nonce-1", }); }); @@ -31,6 +51,15 @@ describe("parseMatchEndpoint", () => { parseMatchEndpoint({ room_id: "room-1", endpoint: 42000, nonce: "nonce-1" }) ).toBeUndefined(); expect(parseMatchEndpoint({ room_id: "room-1", endpoint: "127.0.0.1:1" })).toBeUndefined(); + expect( + parseMatchEndpoint({ + room_id: "room-1", + endpoint: "127.0.0.1:1", + reflexive_endpoint: null, + nat: "symmetric", + nonce: "nonce-1", + }) + ).toBeUndefined(); }); }); diff --git a/apps/client/src/lib/match.ts b/apps/client/src/lib/match.ts index 2369f40..406cf61 100644 --- a/apps/client/src/lib/match.ts +++ b/apps/client/src/lib/match.ts @@ -8,11 +8,27 @@ export function parseMatchEndpoint(payload: unknown): MatchEndpointPayload | und if (typeof payload !== "object" || payload === null) return undefined; const roomId = Reflect.get(payload, "room_id"); const endpoint = Reflect.get(payload, "endpoint"); + const reflexiveEndpoint = Reflect.get(payload, "reflexive_endpoint"); + const nat = Reflect.get(payload, "nat"); const nonce = Reflect.get(payload, "nonce"); - if (typeof roomId !== "string" || typeof endpoint !== "string" || typeof nonce !== "string") { + if ( + typeof roomId !== "string" || + typeof endpoint !== "string" || + (reflexiveEndpoint !== undefined && + reflexiveEndpoint !== null && + typeof reflexiveEndpoint !== "string") || + (nat !== undefined && nat !== "unknown" && nat !== "open" && nat !== "mapped") || + typeof nonce !== "string" + ) { return undefined; } - return { room_id: roomId, endpoint, nonce }; + return { + room_id: roomId, + endpoint, + reflexive_endpoint: reflexiveEndpoint ?? null, + nat: nat ?? "unknown", + nonce, + }; } export function parseMatchCompletion(payload: unknown): MatchProbeCompletedPayload | undefined { diff --git a/apps/client/src/lib/native.ts b/apps/client/src/lib/native.ts index 853f90a..25a4e3b 100644 --- a/apps/client/src/lib/native.ts +++ b/apps/client/src/lib/native.ts @@ -1,12 +1,17 @@ -import { invoke } from "@tauri-apps/api/tauri"; +import { invoke } from "@tauri-apps/api/core"; export type GameAvailability = { game_id: string; available: boolean; warnings: string[] }; export type NetworkDiagnostics = { - nat: "unknown" | "cone" | "symmetric"; + nat: "unknown" | "open" | "mapped" | "blocked"; rtt_ms: number | null; relay_reachable: boolean; }; -export type MatchEndpointCandidate = { endpoint: string; nonce: string }; +export type MatchEndpointCandidate = { + endpoint: string; + reflexive_endpoint?: string; + nat: "unknown" | "open" | "mapped"; + nonce: string; +}; export type MatchProbeReport = { room_id: string; local_user_id: string; @@ -17,6 +22,9 @@ export type MatchProbeReport = { frames_received: number; transcript_checksum: string; elapsed_ms: number; + nat: "unknown" | "open" | "mapped"; + candidate: "host" | "reflexive"; + punch_attempts: number; }; export function isDesktopRuntime(): boolean { @@ -50,7 +58,10 @@ export async function runNetworkTest(): Promise { export async function reserveMatchProbe(roomId: string): Promise { if (!isDesktopRuntime()) throw new Error("LAN match probe requires the desktop client"); return invoke("reserve_match_probe", { - request: { room_id: roomId }, + request: { + room_id: roomId, + stun_server: import.meta.env.VITE_STUN_SERVER || undefined, + }, }); } @@ -61,6 +72,7 @@ export async function runReservedMatchProbe(request: { peer_user_id: string; role: "host" | "guest"; peer_endpoint: string; + peer_reflexive_endpoint?: string; peer_nonce: string; frame_count?: number; timeout_ms?: number; diff --git a/apps/client/src/lib/report.test.ts b/apps/client/src/lib/report.test.ts index db6f12a..c5d81e3 100644 --- a/apps/client/src/lib/report.test.ts +++ b/apps/client/src/lib/report.test.ts @@ -12,6 +12,9 @@ describe("buildMatchReport", () => { frames_received: 60, transcript_checksum: "0123456789abcdef", elapsed_ms: 240, + nat: "mapped" as const, + candidate: "reflexive" as const, + punch_attempts: 2, }; it("contains room correlation without identities, credentials, or local paths", () => { @@ -57,6 +60,9 @@ describe("buildMatchReport", () => { frames_received: 60, transcript_checksum: "0123456789abcdef", elapsed_ms: 240, + nat: "mapped", + candidate: "reflexive", + punch_attempts: 2, }); expect(JSON.stringify(report.probe)).not.toContain("user_id"); expect(JSON.stringify(report.probe)).not.toContain("nonce"); diff --git a/apps/client/src/lib/report.ts b/apps/client/src/lib/report.ts index 7067082..0559f91 100644 --- a/apps/client/src/lib/report.ts +++ b/apps/client/src/lib/report.ts @@ -17,6 +17,9 @@ export function buildMatchReport( frames_received: probe.frames_received, transcript_checksum: probe.transcript_checksum, elapsed_ms: probe.elapsed_ms, + nat: probe.nat, + candidate: probe.candidate, + punch_attempts: probe.punch_attempts, }, client: { platform: typeof navigator === "undefined" ? "unknown" : navigator.platform, diff --git a/apps/client/src/lib/useLanMatchProbe.ts b/apps/client/src/lib/useLanMatchProbe.ts index 1a0d872..60c472e 100644 --- a/apps/client/src/lib/useLanMatchProbe.ts +++ b/apps/client/src/lib/useLanMatchProbe.ts @@ -76,6 +76,8 @@ export function useLanMatchProbe({ { room_id: roomId, endpoint: candidate.endpoint, + reflexive_endpoint: candidate.reflexive_endpoint ?? null, + nat: candidate.nat, nonce: candidate.nonce, }, () => cancelled || probeAttempt.current !== attempt, @@ -126,6 +128,7 @@ export function useLanMatchProbe({ peer_user_id: participants.peerUserId, role: participants.role, peer_endpoint: peerEndpoint.endpoint, + peer_reflexive_endpoint: peerEndpoint.reflexive_endpoint ?? undefined, peer_nonce: peerEndpoint.nonce, }); if (cancelled || probeAttempt.current !== attempt) return; diff --git a/apps/client/vite.config.ts b/apps/client/vite.config.ts index dbbbd64..aae6c6e 100644 --- a/apps/client/vite.config.ts +++ b/apps/client/vite.config.ts @@ -7,8 +7,8 @@ export default defineConfig({ server: { strictPort: true }, envPrefix: ["VITE_", "TAURI_"], build: { - target: process.env.TAURI_PLATFORM === "windows" ? "chrome105" : "safari13", - minify: !process.env.TAURI_DEBUG ? "esbuild" : false, + target: "baseline-widely-available", + minify: !process.env.TAURI_DEBUG ? "oxc" : false, sourcemap: !!process.env.TAURI_DEBUG, }, }); diff --git a/apps/server/Cargo.toml b/apps/server/Cargo.toml index d1908a6..225d8b3 100644 --- a/apps/server/Cargo.toml +++ b/apps/server/Cargo.toml @@ -1,33 +1,34 @@ [package] name = "opencade-server" version = "0.1.0" -edition = "2021" +edition = "2024" description = "OpenCade server (Axum + PostgreSQL)" authors = ["OpenCade Contributors"] license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" [dependencies] -axum = { version = "0.7", features = ["ws", "json"] } -tokio = { version = "1", features = ["full"] } -tower = { version = "0.4", features = ["util"] } -tower-http = { version = "0.5", features = ["cors", "trace"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -sqlx = { version = "0.8.6", default-features = false, features = ["runtime-tokio", "postgres", "chrono", "uuid", "migrate", "macros"] } -argon2 = "0.5" -chrono = { version = "0.4", features = ["serde"] } -uuid = { version = "1", features = ["v4", "serde"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -regex = "1" -sha2 = "0.10" -dashmap = "5" -thiserror = "1" +axum = { version = "0.8.9", features = ["ws", "json"] } +tokio = { version = "1.53.1", features = ["full"] } +tower = { version = "0.5.3", features = ["util"] } +tower-http = { version = "0.7.0", features = ["cors", "trace"] } +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +sqlx = { version = "0.9.0", default-features = false, features = ["runtime-tokio", "postgres", "chrono", "uuid", "migrate", "macros"] } +argon2 = "0.5.3" +rand_core = { version = "0.6.4", features = ["getrandom"] } +chrono = { version = "0.4.45", features = ["serde"] } +uuid = { version = "1.25.0", features = ["v4", "serde"] } +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +regex = "1.13.1" +sha2 = "0.11.0" +hex = "0.4.3" +dashmap = "6.2.1" +thiserror = "2.0.20" opencade-protocol = { path = "../../packages/protocol" } [dev-dependencies] -futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } -serial_test = "3" -tokio-tungstenite = "0.24" +futures-util = { version = "0.3.34", default-features = false, features = ["sink", "std"] } +tokio-tungstenite = "0.30.0" diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index 1f9f046..93dabc5 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # --- builder --------------------------------------------------------------- -FROM rust:1.78-bookworm AS builder +FROM rust:1.98-bookworm AS builder WORKDIR /app # Copy workspace manifests first for layer caching. @@ -14,10 +14,11 @@ COPY packages/emulator-sdk/Cargo.toml packages/emulator-sdk/Cargo.toml COPY packages/shared/Cargo.toml packages/shared/Cargo.toml COPY packages/game-definitions/Cargo.toml packages/game-definitions/Cargo.toml COPY adapters/fbneo/Cargo.toml adapters/fbneo/Cargo.toml +COPY services/relay/Cargo.toml services/relay/Cargo.toml # Create stub sources so `cargo build` can resolve the workspace graph. RUN mkdir -p apps/server/src packages/protocol/src packages/networking/src \ - packages/emulator-sdk/src packages/shared/src packages/game-definitions/src adapters/fbneo/src \ + packages/emulator-sdk/src packages/shared/src packages/game-definitions/src adapters/fbneo/src services/relay/src \ && echo "fn main() {}" > apps/server/src/main.rs \ && echo "" > packages/protocol/src/lib.rs \ && echo "" > packages/networking/src/lib.rs \ @@ -25,6 +26,7 @@ RUN mkdir -p apps/server/src packages/protocol/src packages/networking/src \ && echo "" > packages/shared/src/lib.rs \ && echo "" > packages/game-definitions/src/lib.rs \ && echo "" > adapters/fbneo/src/lib.rs \ + && echo "fn main() {}" > services/relay/src/main.rs \ && cargo build --release --locked # Copy the real sources (invalidates cache only when source changes). diff --git a/apps/server/src/authn.rs b/apps/server/src/authn.rs index 2926a1a..dd06a20 100644 --- a/apps/server/src/authn.rs +++ b/apps/server/src/authn.rs @@ -1,6 +1,5 @@ use argon2::password_hash::rand_core::{OsRng, RngCore}; use axum::{ - async_trait, extract::FromRequestParts, http::{header, request::Parts}, }; @@ -20,7 +19,7 @@ pub struct AuthUser { pub fn hash_token(token: &str) -> String { let mut hasher = Sha256::new(); hasher.update(token.as_bytes()); - format!("{:x}", hasher.finalize()) + hex::encode(hasher.finalize()) } pub fn generate_session_token() -> String { @@ -80,7 +79,6 @@ fn bearer_token(parts: &Parts) -> Result<&str, AppError> { .ok_or_else(|| AppError::Unauthorized("missing bearer token".into())) } -#[async_trait] impl FromRequestParts for AuthUser { type Rejection = AppError; diff --git a/apps/server/src/config.rs b/apps/server/src/config.rs index bfb1d59..5b54682 100644 --- a/apps/server/src/config.rs +++ b/apps/server/src/config.rs @@ -27,28 +27,31 @@ pub struct Config { impl Config { pub fn from_env() -> Result { - let database_url = env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://opencade:opencade@localhost:5432/opencade".to_string()); - let session_secret = env::var("SESSION_SECRET") - .unwrap_or_else(|_| "dev-session-secret-change-me".to_string()); - let rust_log = env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()); - let port = match env::var("PORT") { - Ok(value) => value + Self::from_lookup(|key| env::var(key).ok()) + } + + fn from_lookup(mut lookup: impl FnMut(&str) -> Option) -> Result { + let database_url = lookup("DATABASE_URL") + .unwrap_or_else(|| "postgres://opencade:opencade@localhost:5432/opencade".to_string()); + let session_secret = + lookup("SESSION_SECRET").unwrap_or_else(|| "dev-session-secret-change-me".to_string()); + let rust_log = lookup("RUST_LOG").unwrap_or_else(|| "info".to_string()); + let port = match lookup("PORT") { + Some(value) => value .parse::() .ok() .filter(|port| *port > 0) .ok_or(ConfigError::InvalidPort)?, - Err(_) => 8080, + None => 8080, }; - let production = env::var("OPENCADE_ENV") - .map(|value| value.eq_ignore_ascii_case("production")) - .unwrap_or(false); + let production = + lookup("OPENCADE_ENV").is_some_and(|value| value.eq_ignore_ascii_case("production")); if production && session_secret.len() < 32 { return Err(ConfigError::WeakProductionSecret); } - let allowed_origins = env::var("ALLOWED_ORIGINS") - .unwrap_or_else(|_| "http://localhost:1420,tauri://localhost".to_string()) + let allowed_origins = lookup("ALLOWED_ORIGINS") + .unwrap_or_else(|| "http://localhost:1420,tauri://localhost".to_string()) .split(',') .map(str::trim) .filter(|origin| !origin.is_empty()) @@ -58,17 +61,16 @@ impl Config { return Err(ConfigError::MissingAllowedOrigins); } - let stun_host = env::var("STUN_HOST").unwrap_or_else(|_| "stun.opencade.local".to_string()); - let stun_port = match env::var("STUN_PORT") { - Ok(value) => value + let stun_host = lookup("STUN_HOST").unwrap_or_else(|| "stun.opencade.local".to_string()); + let stun_port = match lookup("STUN_PORT") { + Some(value) => value .parse::() .ok() .filter(|p| *p > 0) .ok_or(ConfigError::InvalidStunPort)?, - Err(_) => 3478, + None => 3478, }; - let relay_url = env::var("RELAY_URL") - .ok() + let relay_url = lookup("RELAY_URL") .map(|v| v.trim().to_owned()) .filter(|v| !v.is_empty()); @@ -103,42 +105,25 @@ impl Config { #[cfg(test)] mod tests { use super::*; - use serial_test::serial; - - const KEYS: [&str; 9] = [ - "DATABASE_URL", - "SESSION_SECRET", - "RUST_LOG", - "PORT", - "OPENCADE_ENV", - "ALLOWED_ORIGINS", - "STUN_HOST", - "STUN_PORT", - "RELAY_URL", - ]; - - fn clear_env() { - for key in KEYS { - env::remove_var(key); - } + use std::collections::HashMap; + + fn load_config(values: &[(&str, &str)]) -> Result { + let values = values.iter().copied().collect::>(); + Config::from_lookup(|key| values.get(key).map(|value| (*value).to_string())) } #[test] - #[serial] fn defaults_are_safe_for_local_development() { - clear_env(); - let config = Config::from_env().expect("development defaults should be valid"); + let config = load_config(&[]).expect("development defaults should be valid"); assert_eq!(config.port, 8080); assert!(!config.production); assert!(config.allowed_origins.contains(&"tauri://localhost".into())); assert_eq!(config.stun_host, "stun.opencade.local"); assert_eq!(config.stun_port, 3478); assert_eq!(config.relay_url, None); - clear_env(); } #[test] - #[serial] fn for_test_uses_deterministic_stun_defaults() { let config = Config::for_test(); assert_eq!(config.stun_host, "127.0.0.1"); @@ -147,82 +132,69 @@ mod tests { } #[test] - #[serial] fn rejects_invalid_port_instead_of_hiding_configuration_error() { - clear_env(); - env::set_var("PORT", "invalid"); - assert_eq!(Config::from_env(), Err(ConfigError::InvalidPort)); - clear_env(); + assert_eq!( + load_config(&[("PORT", "invalid")]), + Err(ConfigError::InvalidPort) + ); } #[test] - #[serial] fn rejects_invalid_stun_port() { - clear_env(); - env::set_var("STUN_PORT", "not_a_port"); - assert_eq!(Config::from_env(), Err(ConfigError::InvalidStunPort)); - clear_env(); - // zero is also invalid - env::set_var("STUN_PORT", "0"); - assert_eq!(Config::from_env(), Err(ConfigError::InvalidStunPort)); - clear_env(); + assert_eq!( + load_config(&[("STUN_PORT", "not_a_port")]), + Err(ConfigError::InvalidStunPort) + ); + assert_eq!( + load_config(&[("STUN_PORT", "0")]), + Err(ConfigError::InvalidStunPort) + ); } #[test] - #[serial] fn rejects_weak_production_secret() { - clear_env(); - env::set_var("OPENCADE_ENV", "production"); - env::set_var("SESSION_SECRET", "weak"); - assert_eq!(Config::from_env(), Err(ConfigError::WeakProductionSecret)); - clear_env(); + assert_eq!( + load_config(&[("OPENCADE_ENV", "production"), ("SESSION_SECRET", "weak")]), + Err(ConfigError::WeakProductionSecret) + ); } #[test] - #[serial] fn parses_explicit_origins() { - clear_env(); - env::set_var( + let config = load_config(&[( "ALLOWED_ORIGINS", "https://one.example, https://two.example", - ); - let config = Config::from_env().expect("explicit origins should parse"); + )]) + .expect("explicit origins should parse"); assert_eq!( config.allowed_origins, vec!["https://one.example", "https://two.example"] ); - clear_env(); } #[test] - #[serial] fn parses_stun_and_relay_env() { - clear_env(); - env::set_var("STUN_HOST", "stun.example.com"); - env::set_var("STUN_PORT", "3479"); - env::set_var("RELAY_URL", "wss://relay.example.com/relay"); - let config = Config::from_env().expect("stun and relay should parse"); + let config = load_config(&[ + ("STUN_HOST", "stun.example.com"), + ("STUN_PORT", "3479"), + ("RELAY_URL", "wss://relay.example.com/relay"), + ]) + .expect("stun and relay should parse"); assert_eq!(config.stun_host, "stun.example.com"); assert_eq!(config.stun_port, 3479); assert_eq!( config.relay_url, Some("wss://relay.example.com/relay".into()) ); - clear_env(); } #[test] - #[serial] fn relay_url_absent_when_not_set_or_empty() { - clear_env(); - let config = Config::from_env().expect("defaults"); + let config = load_config(&[]).expect("defaults"); assert_eq!(config.relay_url, None); - env::set_var("RELAY_URL", ""); - let config = Config::from_env().expect("empty relay should be None"); + let config = load_config(&[("RELAY_URL", "")]).expect("empty relay should be None"); assert_eq!(config.relay_url, None); - env::set_var("RELAY_URL", " "); - let config = Config::from_env().expect("whitespace relay should be None"); + let config = load_config(&[("RELAY_URL", " ")]).expect("whitespace relay should be None"); assert_eq!(config.relay_url, None); - clear_env(); } } diff --git a/apps/server/src/error.rs b/apps/server/src/error.rs index df9577a..25f1c76 100644 --- a/apps/server/src/error.rs +++ b/apps/server/src/error.rs @@ -2,12 +2,12 @@ //! responses using the versioned [`Envelope`] format. use axum::{ + Json, http::StatusCode, response::{IntoResponse, Response}, - Json, }; use opencade_protocol::Envelope; -use serde_json::{json, Value}; +use serde_json::{Value, json}; /// Application-level errors. /// diff --git a/apps/server/src/health.rs b/apps/server/src/health.rs index 7f80dc7..d3538c5 100644 --- a/apps/server/src/health.rs +++ b/apps/server/src/health.rs @@ -1,6 +1,6 @@ -use axum::{extract::State, http::StatusCode, Json}; +use axum::{Json, extract::State, http::StatusCode}; use opencade_protocol::{Envelope, PROTOCOL_VERSION}; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use crate::state::AppState; diff --git a/apps/server/src/lib.rs b/apps/server/src/lib.rs index fafa4d4..83ff874 100644 --- a/apps/server/src/lib.rs +++ b/apps/server/src/lib.rs @@ -8,9 +8,9 @@ pub mod state; pub mod ws; use axum::{ - http::{header, HeaderValue, Method}, - routing::{get, post}, Router, + http::{HeaderValue, Method, header}, + routing::{get, post}, }; use tower_http::{ cors::{AllowOrigin, CorsLayer}, @@ -31,35 +31,44 @@ pub fn build_app(state: AppState) -> Router { .route("/api/v1/auth/logout", post(routes::auth::logout)) .route("/api/v1/auth/me", get(routes::auth::me)) .route("/api/v1/games", get(routes::games::list_games)) - .route("/api/v1/games/:id", get(routes::games::get_game)) + .route("/api/v1/games/{id}", get(routes::games::get_game)) .route("/api/v1/servers", get(routes::servers::list_servers)) .route( "/api/v1/challenges", get(routes::challenges::list_incoming).post(routes::challenges::create_challenge), ) .route( - "/api/v1/challenges/:id/accept", + "/api/v1/challenges/{id}/accept", post(routes::challenges::accept_challenge), ) .route( - "/api/v1/challenges/:id/decline", + "/api/v1/challenges/{id}/decline", post(routes::challenges::decline_challenge), ) .route( - "/api/v1/challenges/:id/cancel", + "/api/v1/challenges/{id}/cancel", post(routes::challenges::cancel_challenge), ) - .route("/api/v1/lobbies/:game_id", get(routes::lobbies::get_lobby)) + .route("/api/v1/lobbies/{game_id}", get(routes::lobbies::get_lobby)) .route("/api/v1/rooms", post(routes::rooms::create_room)) - .route("/api/v1/rooms/:id", get(routes::rooms::get_room)) - .route("/api/v1/rooms/:id/accept", post(routes::rooms::accept_room)) + .route("/api/v1/rooms/{id}", get(routes::rooms::get_room)) + .route( + "/api/v1/rooms/{id}/accept", + post(routes::rooms::accept_room), + ) .route( - "/api/v1/rooms/:id/decline", + "/api/v1/rooms/{id}/decline", post(routes::rooms::decline_room), ) - .route("/api/v1/rooms/:id/cancel", post(routes::rooms::cancel_room)) - .route("/api/v1/rooms/:id/start", post(routes::rooms::start_room)) - .route("/api/v1/rooms/:id/finish", post(routes::rooms::finish_room)) + .route( + "/api/v1/rooms/{id}/cancel", + post(routes::rooms::cancel_room), + ) + .route("/api/v1/rooms/{id}/start", post(routes::rooms::start_room)) + .route( + "/api/v1/rooms/{id}/finish", + post(routes::rooms::finish_room), + ) .route("/ws", get(ws::ws_handler)) .with_state(state) .layer(cors) diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index 421721b..e210af4 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -1,4 +1,4 @@ -use opencade_server::{build_app, shutdown_signal, AppState, Config}; +use opencade_server::{AppState, Config, build_app, shutdown_signal}; use sqlx::postgres::PgPoolOptions; use std::net::SocketAddr; use tracing::info; diff --git a/apps/server/src/routes/auth.rs b/apps/server/src/routes/auth.rs index 4e1a760..b4578a8 100644 --- a/apps/server/src/routes/auth.rs +++ b/apps/server/src/routes/auth.rs @@ -1,15 +1,15 @@ -use argon2::password_hash::{rand_core::OsRng, SaltString}; +use argon2::password_hash::{SaltString, rand_core::OsRng}; use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; -use axum::{extract::State, http::StatusCode, Json}; +use axum::{Json, extract::State, http::StatusCode}; use opencade_protocol::Envelope; use regex::Regex; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use sqlx::Row; use uuid::Uuid; use crate::{ - authn::{generate_session_token, hash_token, AuthUser}, + authn::{AuthUser, generate_session_token, hash_token}, error::AppError, state::AppState, }; diff --git a/apps/server/src/routes/challenges.rs b/apps/server/src/routes/challenges.rs index ee2179c..7ba655a 100644 --- a/apps/server/src/routes/challenges.rs +++ b/apps/server/src/routes/challenges.rs @@ -1,14 +1,14 @@ -use axum::{extract::Path, extract::State, http::StatusCode, Json}; +use axum::{Json, extract::Path, extract::State, http::StatusCode}; use opencade_protocol::Envelope; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use sqlx::{Postgres, Row, Transaction}; use uuid::Uuid; use crate::{ authn::AuthUser, error::AppError, - room_state::{to_database, transition, RoomEvent}, + room_state::{RoomEvent, to_database, transition}, state::AppState, }; diff --git a/apps/server/src/routes/games.rs b/apps/server/src/routes/games.rs index 17e3be6..0ef10c8 100644 --- a/apps/server/src/routes/games.rs +++ b/apps/server/src/routes/games.rs @@ -1,6 +1,6 @@ -use axum::{extract::Path, extract::State, Json}; +use axum::{Json, extract::Path, extract::State}; use opencade_protocol::Envelope; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use sqlx::Row; use crate::{authn::AuthUser, error::AppError, state::AppState}; diff --git a/apps/server/src/routes/lobbies.rs b/apps/server/src/routes/lobbies.rs index 8f82a0f..cdf1cc9 100644 --- a/apps/server/src/routes/lobbies.rs +++ b/apps/server/src/routes/lobbies.rs @@ -1,7 +1,7 @@ -use axum::{extract::Path, extract::State, Json}; +use axum::{Json, extract::Path, extract::State}; use opencade_protocol::Envelope; use serde::Serialize; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use sqlx::Row; use crate::{authn::AuthUser, error::AppError, state::AppState}; diff --git a/apps/server/src/routes/rooms.rs b/apps/server/src/routes/rooms.rs index 6eff427..0fce0f6 100644 --- a/apps/server/src/routes/rooms.rs +++ b/apps/server/src/routes/rooms.rs @@ -1,14 +1,14 @@ -use axum::{extract::Path, extract::State, http::StatusCode, Json}; +use axum::{Json, extract::Path, extract::State, http::StatusCode}; use opencade_protocol::{Envelope, RoomPayload, RoomState}; use serde::Deserialize; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use sqlx::{Postgres, Row, Transaction}; use uuid::Uuid; use crate::{ authn::AuthUser, error::AppError, - room_state::{from_database, to_database, transition, RoomEvent}, + room_state::{RoomEvent, from_database, to_database, transition}, state::AppState, }; diff --git a/apps/server/src/routes/servers.rs b/apps/server/src/routes/servers.rs index 8dbab6c..a8b9af6 100644 --- a/apps/server/src/routes/servers.rs +++ b/apps/server/src/routes/servers.rs @@ -1,6 +1,6 @@ -use axum::{extract::State, Json}; +use axum::{Json, extract::State}; use opencade_protocol::Envelope; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use sqlx::Row; use crate::{authn::AuthUser, error::AppError, state::AppState}; diff --git a/apps/server/src/state.rs b/apps/server/src/state.rs index f658af0..7f519e4 100644 --- a/apps/server/src/state.rs +++ b/apps/server/src/state.rs @@ -57,7 +57,7 @@ impl AppState { tracing::error!(%user_id, %message_type, "failed to serialize websocket notification"); return; }; - if let Err(error) = target.try_send(Message::Text(text)) { + if let Err(error) = target.try_send(Message::Text(text.into())) { tracing::warn!(%error, %user_id, %message_type, "websocket notification queue unavailable"); } } diff --git a/apps/server/src/ws.rs b/apps/server/src/ws.rs index a7d4b01..a61a9d8 100644 --- a/apps/server/src/ws.rs +++ b/apps/server/src/ws.rs @@ -1,18 +1,17 @@ use axum::{ extract::{ - ws::{close_code, CloseFrame, Message, WebSocket, WebSocketUpgrade}, State, + ws::{CloseFrame, Message, WebSocket, WebSocketUpgrade, close_code}, }, - http::{header, HeaderMap}, + http::{HeaderMap, header}, response::{IntoResponse, Response}, }; use opencade_protocol::{ - is_supported_version, Envelope, MatchEndpointPayload, MatchProbeCompletedPayload, - PROTOCOL_VERSION, + Envelope, MatchEndpointPayload, MatchProbeCompletedPayload, PROTOCOL_VERSION, + is_supported_version, }; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use sqlx::Row; -use std::borrow::Cow; use std::collections::VecDeque; use std::time::{Duration, Instant}; use tokio::sync::mpsc; @@ -20,7 +19,7 @@ use tracing::{info, warn}; use uuid::Uuid; use crate::{ - authn::{authenticate_token, AuthUser}, + authn::{AuthUser, authenticate_token}, error::AppError, state::AppState, }; @@ -84,7 +83,7 @@ pub async fn handle_socket(mut socket: WebSocket, state: AppState, user: AuthUse if let Some(previous) = state.ws_hub.insert(user_key.clone(), sender) { let _ = previous.try_send(Message::Close(Some(CloseFrame { code: close_code::NORMAL, - reason: Cow::Borrowed("replaced by a newer connection"), + reason: "replaced by a newer connection".into(), }))); } @@ -252,6 +251,9 @@ fn validate_match_endpoint(envelope: &Envelope) -> Result<(), ()> { .endpoint .parse::() .map_err(|_| ())?; + if let Some(reflexive) = candidate.reflexive_endpoint { + reflexive.parse::().map_err(|_| ())?; + } Uuid::parse_str(&candidate.nonce).map_err(|_| ())?; Ok(()) } @@ -305,7 +307,7 @@ async fn relay_to_room_members( for member_id in member_ids.into_iter().filter(|id| *id != sender_id) { if let Some(target) = state.ws_hub.get(&member_id.to_string()) { target - .try_send(Message::Text(original_text.to_string())) + .try_send(Message::Text(original_text.to_string().into())) .map_err(|error| { warn!(%error, user_id = %member_id, %room_id, "signaling queue unavailable"); RelayError::PeerUnavailable @@ -367,7 +369,10 @@ async fn send_error( async fn send_envelope(socket: &mut WebSocket, envelope: Envelope) -> Result<(), ()> { let text = serde_json::to_string(&envelope).map_err(|_| ())?; - socket.send(Message::Text(text)).await.map_err(|_| ()) + socket + .send(Message::Text(text.into())) + .await + .map_err(|_| ()) } #[cfg(test)] diff --git a/apps/server/tests/api_flow.rs b/apps/server/tests/api_flow.rs index 7ef2db3..c9a2fee 100644 --- a/apps/server/tests/api_flow.rs +++ b/apps/server/tests/api_flow.rs @@ -1,12 +1,12 @@ use axum::{ - body::{to_bytes, Body}, - http::{header, Method, Request, StatusCode}, Router, + body::{Body, to_bytes}, + http::{Method, Request, StatusCode, header}, }; use futures_util::{SinkExt, StreamExt}; use opencade_protocol::Envelope; -use opencade_server::{build_app, AppState, Config}; -use serde_json::{json, Value}; +use opencade_server::{AppState, Config, build_app}; +use serde_json::{Value, json}; use sqlx::PgPool; use tokio_tungstenite::tungstenite::client::IntoClientRequest; use tokio_tungstenite::{connect_async, tungstenite::Message}; @@ -219,7 +219,7 @@ async fn authenticated_users_create_and_accept_a_room(pool: PgPool) { "payload": { "room_id": room_id, "sdp": "v=0\\r\\n" } }); host_socket - .send(Message::Text(offer.to_string())) + .send(Message::Text(offer.to_string().into())) .await .expect("send offer"); let relayed = guest_socket @@ -254,7 +254,7 @@ async fn authenticated_users_create_and_accept_a_room(pool: PgPool) { } }); host_socket - .send(Message::Text(endpoint.to_string())) + .send(Message::Text(endpoint.to_string().into())) .await .expect("send endpoint"); let relayed_endpoint = guest_socket @@ -292,7 +292,7 @@ async fn authenticated_users_create_and_accept_a_room(pool: PgPool) { } }); guest_socket - .send(Message::Text(completion.to_string())) + .send(Message::Text(completion.to_string().into())) .await .expect("send completion"); let relayed_completion = host_socket diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6a5b6e6..4ca0fab 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -627,16 +627,21 @@ Peer A Server (/ws) Peer B All three types carry `envelope.version = 1` and are validated: `roomId` must exist, sender must be a `room_members` row, `sdp`/`candidate` are opaque strings (length-capped, no execution). -### 10.2 NAT traversal — direct UDP → hole-punch → STUN → WS relay fallback +### 10.2 NAT traversal — host/reflexive candidates and UDP hole punching -`packages/networking/src/nat.ts` (and Rust side for Tauri diagnostics) implements the ordered fallback: +`packages/networking/src/{stun,traversal}.rs` implements the current direct path: -1. **Direct UDP** — try peer's advertised `host:port` from signaling. -2. **Hole punching** — simultaneous `UDP` sends to each peer's reflexive address; 3 attempts, 500 ms apart. -3. **STUN** — query configured STUN server (`stun:stun.opencade.local:3478` in dev, public STUN in prod) to learn reflexive address; re-attempt. -4. **WS relay fallback** — keep `/ws` open; server forwards frames in-process. In MVP this _is_ the relay. `services/relay` (opencade-relay) is a placeholder WS echo that will become a real TURN relay post-MVP without protocol change. +1. Reserve one UDP socket before signaling. +2. If configured, send an RFC 8489 Binding request from that same socket to learn its reflexive + address. +3. Exchange host/reflexive candidates and a nonce through the authenticated room WebSocket. +4. Send room/session-bound punch packets to at most eight candidates, 3 attempts and 500 ms + apart. Accept only a known source with matching credentials, then connect the reserved socket. -The client reports `natType` (`open | cone | symmetric | blocked`) from the STUN binding response. UI shows `Network Test` with this value. +A single Binding server cannot honestly distinguish cone from symmetric behavior. Diagnostics and +reports therefore use `unknown | open | mapped | blocked`: `open` means the reflexive address equals +the advertised host address, while `mapped` only proves translation occurred. RFC 5780 behavior +discovery and a relay fallback remain deferred until physical campaign evidence justifies them. ### 10.3 Latency — RTT / loss / jitter @@ -875,7 +880,7 @@ Archival example with explicit relay (when enabled) listens on `3478`/`3478/udp` ### 16.2 Dockerfiles -- `apps/server/Dockerfile` — `rust:1.78-slim` builder → `debian:bookworm-slim` runtime, `sqlx migrate run` on start. +- `apps/server/Dockerfile` — `rust:1.98-bookworm` builder → `debian:bookworm-slim` runtime, `sqlx migrate run` on start. - `services/relay/Dockerfile` — same pattern, binary `opencade-relay`. No `docker-compose.override.yml` in repo; devs create it locally if needed. diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index 48f7218..c2f6452 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -21,17 +21,25 @@ Updated 2026-08-24. 60-frame transcripts and machine-readable reports. - One canonical, privacy-minimized desktop/CLI evidence format, a fail-closed paired-report verifier, and CI-built Windows LAN alpha tools. -- NAT traversal fallback (direct UDP → hole-punch → STUN → WS/relay) via `packages/networking` `NatTraversal`/`FallbackOrder`, STUN hint in `GET /servers` (`stun:host:port`), and latency metrics (EWMA RTT alpha=0.2, loss, jitter) over 30 samples. -- Standalone `opencade-relay` service (Axum WS relay on `/relay` with `GET /health`/`ready`, room bucket forwarding, envelope validation, graceful shutdown) wired in `docker-compose.yml` on `3478` and `3478/udp` with dedicated Dockerfile. -- Desktop `diagnose_network` reports `nat`, `rtt_ms`, `loss`, `jitter_ms`, `relay_reachable`, `stun_reachable` with wired Tauri command and typed `apps/client/src/lib/diagnostics.ts` wrapper. -- PostgreSQL, WebSocket, lifecycle, safe-launch, mock-match, UDP, NAT, relay, two-process, TypeScript, and MSRV +- RFC 8489 reflexive-address discovery and bounded authenticated UDP hole punching on the same + reserved socket, with host/reflexive candidate evidence in redacted reports. +- A fail-closed campaign summarizer that derives the 8-of-10 alpha gate and compatibility matrix + from paired reports. +- A standalone `opencade-relay` service with health/readiness routes and bounded room forwarding, + plus server-provided STUN hints and 30-sample latency metrics. +- Desktop network diagnostics backed by real RFC 8489 Binding when a STUN endpoint is configured, + with typed RTT, loss, jitter, relay-reachability, and STUN-reachability fields. +- PostgreSQL, WebSocket, lifecycle, safe-launch, mock-match, UDP, NAT, relay, two-process, + TypeScript, and MSRV checks in CI. ## Deliberately not claimed -- FBNeo netplay. The adapter reports `netplay: false` until a public documented interface or an - original clean-room bridge satisfies ADR 0001. -- Symmetric-NAT traversal beyond classification, TURN allocation, and production STUN/TURN deployment. +- FBNeo netplay. The adapter reports `BlockedNoPublicInterface`; ADR 0002 records the public-source + feasibility result and exit criteria. +- Cone/symmetric NAT behavior classification and proven relay fallback. One RFC 8489 Binding response is + deliberately reported only as `open`, `mapped`, or `unknown`. +- TURN allocation and production STUN/TURN deployment. - Production packaging/signing, friends/chat/rankings/replays, or a public MVP release. - Ten two-machine LAN matches and a 20-person community alpha; these require external testers and real Windows hosts. Use `docs/alpha/LAN_TEST.md` to collect that evidence. diff --git a/docs/NEXT_IMPLEMENTATIONS.md b/docs/NEXT_IMPLEMENTATIONS.md index 21b294e..9cb9b4d 100644 --- a/docs/NEXT_IMPLEMENTATIONS.md +++ b/docs/NEXT_IMPLEMENTATIONS.md @@ -8,6 +8,11 @@ > implemented. See `docs/IMPLEMENTATION_STATUS.md` for the verified result and the remaining > real-hardware/NAT validation work. The original review is retained as the decision record. +> **Traversal update (2026-08-24):** RFC 8489 discovery, bounded nonce-bound hole punching, and +> automated 8-of-10 campaign aggregation are now implemented. Physical Windows evidence remains +> the gate; cone/symmetric classification and relay fallback remain deferred. The sequencing below +> is retained as the original pre-evidence plan. + ## Recommendation: build the executable Proof of Match The next implementation should be one narrow, deterministic vertical slice: @@ -239,7 +244,7 @@ the product claim or select a different externally managed emulator before conti 5. Add `--migrate` and make the container run migrations before serving. 6. Make `/health` liveness-only and `/ready` verify Postgres without returning raw DB errors. 7. Remove Tauri `shell-open`, set a restrictive CSP, and enforce production session-secret checks. -8. Add CI checks for `pnpm -r test`, typecheck, `docker compose config`, and Rust 1.78 MSRV. +8. Add CI checks for `pnpm -r test`, typecheck, `docker compose config`, and Rust 1.98 MSRV. **Exit:** a clean database migrates, five real games are returned, bad DB state fails visibly, and router integration tests exercise the same app that `main` serves. diff --git a/docs/adr/0002-fbneo-netplay-feasibility.md b/docs/adr/0002-fbneo-netplay-feasibility.md new file mode 100644 index 0000000..c840ba6 --- /dev/null +++ b/docs/adr/0002-fbneo-netplay-feasibility.md @@ -0,0 +1,44 @@ +# ADR 0002: Keep standalone FBNeo netplay blocked + +- Status: accepted +- Date: 2026-08-24 + +## Context + +OpenCade needs a documented, automatable emulator seam that accepts a peer/session endpoint and +provides deterministic input/transcript evidence. Local game launch is insufficient. + +The public FBNeo material reviewed for this decision says: + +- The official README describes netplay as a feature and lists the Libretro port. +- The official UI documentation exposes “Play via Kaillera.” +- The official command-line reference documents game/load/list/resolution options, but no endpoint, + session, input-stream, or headless netplay contract. +- Open issue #2319 asks whether native standalone netplay is planned and identifies FBNeo-through- + RetroArch as the currently known path; it does not establish a standalone API. + +Sources: + +- https://github.com/finalburnneo/FBNeo/blob/master/README.md +- https://github.com/finalburnneo/FBNeo/wiki/Command-Line +- https://github.com/finalburnneo/FBNeo/wiki/menu_game +- https://github.com/finalburnneo/FBNeo/issues/2319 + +## Decision + +The standalone FBNeo adapter remains local-play only and reports +`NetplayReadiness::BlockedNoPublicInterface`. OpenCade will not automate UI dialogs, depend on an +undocumented Kaillera wire contract, or copy behavior from proprietary launchers. + +RetroArch/Libretro is a separate future adapter candidate, not an implicit fallback. It requires an +independent process-boundary and license review; OpenCade must not link incompatible emulator code. + +## Exit criteria + +Change this decision only when one of these clean-room seams exists: + +1. FBNeo publishes stable endpoint/session command-line arguments or an API; or +2. an original, independently specified sidecar is accepted upstream and exposes the required seam. + +Either path must additionally pass a two-peer deterministic 60-frame transcript test, fail closed on +version mismatch, preserve safe process argument handling, and satisfy the repository license gate. diff --git a/docs/alpha/LAN_TEST.md b/docs/alpha/LAN_TEST.md index 443dc15..3fc56bd 100644 --- a/docs/alpha/LAN_TEST.md +++ b/docs/alpha/LAN_TEST.md @@ -5,26 +5,32 @@ direct UDP frame transport; it does not claim FBNeo netplay support. ## Before the session -1. On the host, set a 32-byte-or-longer `SESSION_SECRET` and run - `docker compose up --build -d`. +1. For same-LAN tests, set a 32-byte-or-longer `SESSION_SECRET` on the host and run + `docker compose up --build -d`. For different-NAT tests, deploy the same server behind a + publicly reachable HTTPS/WSS endpoint; do not expose a developer laptop directly. 2. Confirm `GET /health` and `GET /ready` both return HTTP 200. -3. Point both clients at the host's LAN URL with `VITE_API_URL` and allow TCP 8080 through the host - firewall. -4. Run `pnpm -C apps/client tauri dev` on both Windows machines. -5. Use fixture-free local ROM scanning only; never attach ROMs or emulator binaries to reports. +3. Point both clients at the LAN server or deployed public control-plane URL with `VITE_API_URL`. + For LAN, allow TCP 8080 through the host firewall. +4. For the different-NAT phase, set `VITE_STUN_SERVER=:3478` on both clients. The server + must implement RFC 8489 Binding. DNS names are deliberately not resolved by the alpha client. +5. Run `pnpm -C apps/client tauri dev` on both Windows machines. +6. Use fixture-free local ROM scanning only; never attach ROMs or emulator binaries to reports. ## Scenario 1. Register two separate users and select the same game. 2. Keep both lobby screens open until each user is visible. 3. User A sends a challenge; user B accepts it. -4. Confirm both clients reach `connecting`. Each desktop client reserves a UDP port and exchanges a - nonce-bound `match.endpoint` candidate through the authenticated WebSocket. -5. Wait for both clients to report `Direct UDP verified`, with 60 received frames and the same +4. Confirm both clients reach `connecting`. Each desktop client reserves one UDP socket, optionally + discovers its reflexive address through STUN on that same socket, then exchanges host/reflexive + candidates and a nonce through the authenticated WebSocket. +5. The clients send bounded, room/session-bound punch packets to the advertised candidates. + Unknown sources and mismatched credentials are ignored. +6. Wait for both clients to report `Direct UDP verified`, with 60 received frames and the same transcript checksum. The host then transitions the room to `playing` and `finished`. -6. If either side reports a firewall or timeout error, allow the advertised UDP port through the +7. If either side reports a firewall or timeout error, allow the advertised UDP port through the firewall and select `Retry LAN probe` on both clients. -7. Export the redacted report from each client. Copy both reports to one machine and verify them: +8. Export the redacted report from each client. Copy both reports to one machine and verify them: ```bash opencade-match-verify host-report.json guest-report.json @@ -65,3 +71,17 @@ cargo build -p opencade-networking --bins --release --locked Record each attempt in `MATCH_REPORT_TEMPLATE.md`. Do not schedule relay work until ten LAN attempts have at least an 80% connection-and-completion rate or show one concentrated, fixable failure. + +## Campaign gate + +Run five same-LAN and five different-NAT attempts, storing exactly two JSON reports per room in one +directory. Then derive the gate and compatibility matrix: + +```bash +opencade-alpha-summary ./campaign-reports +``` + +Exit 0 means at least 8 of 10 rooms passed. Exit 1 means the evidence was readable but the gate did +not pass; exit 2 means the input was invalid. A single Binding response only proves `open` (the +mapped address equals the advertised host address) or `mapped`; it does not claim cone/symmetric NAT +classification. That requires RFC 5780 behavior discovery and remains deferred. diff --git a/docs/alpha/reports/README.md b/docs/alpha/reports/README.md index 5d7bec7..8ac858d 100644 --- a/docs/alpha/reports/README.md +++ b/docs/alpha/reports/README.md @@ -19,12 +19,14 @@ Result 10/10 on 2026-08-24 (see CI). Physical LAN requires 2× Windows 10/11 on the same subnet, `docker compose up --build -d`, `VITE_API_URL=http://:8080`, firewall TCP 8080 + UDP probe ports. Follow `docs/alpha/LAN_TEST.md`, save each attempt as `attempt-01-host.json` and -`attempt-01-guest.json`, then verify every pair: +`attempt-01-guest.json`, then summarize the directory: ```bash -opencade-match-verify attempt-01-host.json attempt-01-guest.json +opencade-alpha-summary . ``` -Pass is at least 8 verified attempts out of 10. Do not fabricate reports—they must come from real +The summary pairs reports by room ID, verifies each pair, and emits a compatibility matrix by game, +platform, observed mapping, and selected candidate. Pass is at least 8 verified attempts out of 10. +Do not fabricate reports—they must come from real two-machine runs. Until then, this gate is `HALT: physical LAN not available in this single-box environment—local 10/10 + evidence tooling ready`. diff --git a/package.json b/package.json index ca44e11..872b8ef 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "opencade", "private": true, "version": "0.1.0", - "packageManager": "pnpm@9.15.9", + "packageManager": "pnpm@11.23.0", "description": "OpenCade monorepo root", "scripts": { "format:check": "prettier --check .", @@ -12,6 +12,6 @@ "test": "pnpm -r --if-present test" }, "devDependencies": { - "prettier": "^3.3.3" + "prettier": "^3.9.6" } } diff --git a/packages/emulator-sdk/Cargo.toml b/packages/emulator-sdk/Cargo.toml index 6d2f9ec..722565d 100644 --- a/packages/emulator-sdk/Cargo.toml +++ b/packages/emulator-sdk/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "opencade-emulator-sdk" version = "0.1.0" -edition = "2021" +edition = "2024" description = "OpenCade emulator adapter SDK — trait for pluggable emulator backends" license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" [dependencies] -serde = { version = "1", features = ["derive"] } -thiserror = "1" +serde = { version = "1.0.229", features = ["derive"] } +thiserror = "2.0.20" diff --git a/packages/emulator-sdk/src/lib.rs b/packages/emulator-sdk/src/lib.rs index b87170e..14595c5 100644 --- a/packages/emulator-sdk/src/lib.rs +++ b/packages/emulator-sdk/src/lib.rs @@ -169,7 +169,19 @@ impl MatchDescriptor { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct AdapterCapabilities { pub local_play: bool, - pub netplay: bool, + pub netplay: NetplayReadiness, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NetplayReadiness { + Supported, + BlockedNoPublicInterface, +} + +impl AdapterCapabilities { + pub fn supports_netplay(self) -> bool { + self.netplay == NetplayReadiness::Supported + } } /// Pluggable emulator backend. @@ -192,7 +204,7 @@ pub trait EmulatorAdapter: Send + Sync { /// Validate and prepare a match before starting the emulator process. fn prepare_match(&self, descriptor: &MatchDescriptor) -> Result<(), AdapterError> { descriptor.validate()?; - if !self.capabilities().netplay { + if !self.capabilities().supports_netplay() { return Err(AdapterError::MatchPreparation(format!( "adapter '{}' does not provide netplay", self.id() @@ -232,7 +244,7 @@ impl EmulatorAdapter for MockAdapter { fn capabilities(&self) -> AdapterCapabilities { AdapterCapabilities { local_play: false, - netplay: true, + netplay: NetplayReadiness::Supported, } } @@ -361,15 +373,17 @@ mod tests { spec.args.last(), Some(&rom.canonicalize().expect("canonical rom").into_os_string()) ); - assert!(spawn_validated( - &RecordingLauncher, - &executable, - &emulator_root, - &outside, - &rom_root, - &[], - ) - .is_err()); + assert!( + spawn_validated( + &RecordingLauncher, + &executable, + &emulator_root, + &outside, + &rom_root, + &[], + ) + .is_err() + ); std::fs::remove_dir_all(fixture).expect("remove fixture"); } diff --git a/packages/game-definitions/Cargo.toml b/packages/game-definitions/Cargo.toml index 2505563..9797ccd 100644 --- a/packages/game-definitions/Cargo.toml +++ b/packages/game-definitions/Cargo.toml @@ -1,18 +1,18 @@ [package] name = "opencade-game-definitions" version = "0.1.0" -edition = "2021" +edition = "2024" description = "OpenCade declarative game definitions (TOML) — schema_version 1" license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" [dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" -toml = "0.8" -thiserror = "1" -regex = "1" +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +toml = "1.1.4" +thiserror = "2.0.20" +regex = "1.13.1" [dev-dependencies] -tempfile = "3" +tempfile = "3.27.0" diff --git a/packages/game-definitions/package.json b/packages/game-definitions/package.json index 30d3000..34d7a3f 100644 --- a/packages/game-definitions/package.json +++ b/packages/game-definitions/package.json @@ -17,8 +17,8 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { - "typescript": "^5.5.0", - "vitest": "^1.6.0" + "typescript": "^7.0.2", + "vitest": "^4.1.11" }, "license": "Apache-2.0" } diff --git a/packages/game-definitions/src/lib.rs b/packages/game-definitions/src/lib.rs index 3ef646b..1ce1255 100644 --- a/packages/game-definitions/src/lib.rs +++ b/packages/game-definitions/src/lib.rs @@ -1,8 +1,8 @@ pub mod loader; pub use loader::{ - load_all_from_dir, load_from_path, load_from_str, GameDefError, GameDefinition, LaunchConfig, - Metadata, ValidationConfig, + GameDefError, GameDefinition, LaunchConfig, Metadata, ValidationConfig, load_all_from_dir, + load_from_path, load_from_str, }; #[cfg(test)] diff --git a/packages/networking/Cargo.toml b/packages/networking/Cargo.toml index 020cf75..1e7d5a1 100644 --- a/packages/networking/Cargo.toml +++ b/packages/networking/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "opencade-networking" version = "0.1.0" -edition = "2021" +edition = "2024" description = "OpenCade networking — signaling, NAT traversal, relay (placeholder for M1)" license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" [dependencies] -chrono = "0.4" +chrono = "0.4.45" opencade-emulator-sdk = { path = "../emulator-sdk" } opencade-protocol = { path = "../protocol" } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -thiserror = "1" -tokio = { version = "1", features = ["macros", "net", "rt", "sync", "time"] } +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +thiserror = "2.0.20" +tokio = { version = "1.53.1", features = ["macros", "net", "rt", "sync", "time"] } +uuid = { version = "1.25.0", features = ["v4"] } [dev-dependencies] diff --git a/packages/networking/src/bin/opencade-alpha-summary.rs b/packages/networking/src/bin/opencade-alpha-summary.rs new file mode 100644 index 0000000..33d5899 --- /dev/null +++ b/packages/networking/src/bin/opencade-alpha-summary.rs @@ -0,0 +1,71 @@ +use opencade_networking::{read_match_report, summarize_match_reports}; +use std::path::{Path, PathBuf}; +use std::{env, fs, process}; + +const MAX_CAMPAIGN_REPORTS: usize = 100; + +fn main() { + match run() { + Ok(gate_passed) if gate_passed => {} + Ok(_) => process::exit(1), + Err(message) => { + eprintln!("{message}"); + process::exit(2); + } + } +} + +fn run() -> Result { + let mut args = env::args_os().skip(1); + let directory = args.next().ok_or_else(usage)?; + if args.next().is_some() { + return Err(usage()); + } + + let mut paths = report_paths(Path::new(&directory))?; + paths.sort(); + if paths.len() > MAX_CAMPAIGN_REPORTS { + return Err(format!( + "campaign contains {} JSON reports; maximum is {MAX_CAMPAIGN_REPORTS}", + paths.len() + )); + } + + let mut reports = Vec::with_capacity(paths.len()); + for path in paths { + let filename = path + .file_name() + .and_then(|value| value.to_str()) + .unwrap_or(""); + let report = read_match_report(&path) + .map_err(|error| format!("{filename}: {} ({})", error, error.code()))?; + reports.push(report); + } + let summary = summarize_match_reports(&reports); + let output = serde_json::to_string_pretty(&summary) + .map_err(|_| "failed to serialize campaign summary".to_string())?; + println!("{output}"); + Ok(summary.gate_passed) +} + +fn report_paths(directory: &Path) -> Result, String> { + let entries = + fs::read_dir(directory).map_err(|_| "campaign directory is unreadable".to_string())?; + let mut paths = Vec::new(); + for entry in entries { + let entry = entry.map_err(|_| "campaign directory is unreadable".to_string())?; + let path = entry.path(); + if path.is_file() + && path + .extension() + .is_some_and(|extension| extension == "json") + { + paths.push(path); + } + } + Ok(paths) +} + +fn usage() -> String { + "usage: opencade-alpha-summary REPORT_DIRECTORY".into() +} diff --git a/packages/networking/src/bin/opencade-match-probe.rs b/packages/networking/src/bin/opencade-match-probe.rs index 04f6d90..6977eb4 100644 --- a/packages/networking/src/bin/opencade-match-probe.rs +++ b/packages/networking/src/bin/opencade-match-probe.rs @@ -1,9 +1,9 @@ use chrono::Utc; use opencade_emulator_sdk::{MatchDescriptor, PeerRole, TransportKind}; -use opencade_networking::{run_match_probe, MatchProbeConfig, UdpPeer}; +use opencade_networking::{MatchProbeConfig, UdpPeer, run_match_probe}; use opencade_protocol::{ - MatchReport, MatchReportClient, MatchReportProbe, MatchReportRole, MatchReportRoom, - MatchReportTransport, RoomState, MATCH_REPORT_SCHEMA_VERSION, + MATCH_REPORT_SCHEMA_VERSION, MatchReport, MatchReportClient, MatchReportProbe, MatchReportRole, + MatchReportRoom, MatchReportTransport, RoomState, }; use std::env; use std::net::SocketAddr; @@ -124,6 +124,9 @@ async fn main() -> Result<(), Box> { frames_received: u32::try_from(probe.frames_received).unwrap_or(u32::MAX), transcript_checksum: probe.transcript_checksum, elapsed_ms: u32::try_from(probe.elapsed_ms).unwrap_or(u32::MAX), + nat: Some(probe.nat), + candidate: Some(probe.candidate), + punch_attempts: Some(probe.punch_attempts), }, client: MatchReportClient { platform: std::env::consts::OS.into(), diff --git a/packages/networking/src/bin/opencade-match-verify.rs b/packages/networking/src/bin/opencade-match-verify.rs index 5515b33..3d901ca 100644 --- a/packages/networking/src/bin/opencade-match-verify.rs +++ b/packages/networking/src/bin/opencade-match-verify.rs @@ -1,12 +1,9 @@ -use opencade_networking::verify_match_reports; +use opencade_networking::{read_match_report, verify_match_reports}; use opencade_protocol::MatchReport; use serde::Serialize; -use std::fs::File; -use std::io::Read; +use std::path::Path; use std::{env, process}; -const MAX_REPORT_BYTES: u64 = 64 * 1024; - #[derive(Debug, Serialize)] struct Failure { verified: bool, @@ -58,42 +55,8 @@ fn run() -> Result { } fn read_report(path: &str, label: &'static str) -> Result { - let file = File::open(path).map_err(|_| { - ( - Failure::new( - "report_unreadable", - format!("could not read {label} report"), - ), - 2, - ) - })?; - let mut bytes = Vec::new(); - file.take(MAX_REPORT_BYTES + 1) - .read_to_end(&mut bytes) - .map_err(|_| { - ( - Failure::new( - "report_unreadable", - format!("could not read {label} report"), - ), - 2, - ) - })?; - if bytes.len() as u64 > MAX_REPORT_BYTES { - return Err(( - Failure::new("report_too_large", format!("{label} report exceeds 64 KiB")), - 2, - )); - } - serde_json::from_slice(&bytes).map_err(|_| { - ( - Failure::new( - "report_invalid", - format!("{label} report is not a canonical OpenCade match report"), - ), - 2, - ) - }) + read_match_report(Path::new(path)) + .map_err(|error| (Failure::new(error.code(), format!("{label} {error}")), 2)) } fn usage_failure() -> (Failure, i32) { diff --git a/packages/networking/src/lib.rs b/packages/networking/src/lib.rs index bdd969f..1ba3feb 100644 --- a/packages/networking/src/lib.rs +++ b/packages/networking/src/lib.rs @@ -7,13 +7,21 @@ pub mod nat; mod probe; mod report; +mod stun; +mod traversal; pub use latency::LatencyMetrics; pub use nat::{ - next_transport, transport_for_nat, FallbackOrder, NatTraversal, NatType, FALLBACK_ORDER, + FALLBACK_ORDER, FallbackOrder, NatTraversal, NatType, next_transport, transport_for_nat, }; -pub use probe::{run_match_probe, MatchProbeConfig, MatchProbeReport, MAX_PROBE_FRAMES}; -pub use report::{verify_match_reports, MatchVerification, ReportVerificationError}; +pub use probe::{MAX_PROBE_FRAMES, MatchProbeConfig, MatchProbeReport, run_match_probe}; +pub use report::{ + ALPHA_MATCH_FRAMES, AlphaCampaignFailure, AlphaCampaignSummary, CompatibilityResult, + MAX_REPORT_BYTES, MatchVerification, ReportReadError, ReportVerificationError, + read_match_report, summarize_match_reports, verify_match_reports, +}; +pub use stun::{NatMapping, StunObservation, discover_reflexive_address}; +pub use traversal::{HolePunchConfig, HolePunchReport, punch_hole}; pub const MAX_INPUT_BYTES: usize = 256; pub const INPUT_QUEUE_CAPACITY: usize = 120; const MAX_DATAGRAM_BYTES: usize = 2_048; @@ -71,6 +79,12 @@ pub enum TransportError { received_frames: u64, expected_frames: u64, }, + #[error("STUN discovery timed out")] + StunTimeout, + #[error("invalid STUN response: {0}")] + InvalidStunResponse(String), + #[error("UDP hole punch timed out after {attempts} attempts")] + HolePunchTimeout { attempts: u8 }, } /// Connected UDP transport for LAN proof runs. Authentication and endpoint negotiation remain in diff --git a/packages/networking/src/probe.rs b/packages/networking/src/probe.rs index d6eb7e0..f128c51 100644 --- a/packages/networking/src/probe.rs +++ b/packages/networking/src/probe.rs @@ -1,5 +1,6 @@ -use super::{InputFrame, TransportError, UdpPeer, MAX_INPUT_BYTES}; +use super::{InputFrame, MAX_INPUT_BYTES, TransportError, UdpPeer}; use opencade_emulator_sdk::{MatchDescriptor, PeerRole, TransportKind}; +use opencade_protocol::{MatchCandidateKind, NatMappingState}; use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; @@ -19,6 +20,11 @@ pub(super) enum ProbePacket { session_key: String, frame: InputFrame, }, + HolePunch { + version: u8, + room_id: String, + session_key: String, + }, } #[derive(Debug, Clone)] @@ -80,6 +86,9 @@ pub struct MatchProbeReport { pub frames_received: u64, pub transcript_checksum: String, pub elapsed_ms: u128, + pub nat: NatMappingState, + pub candidate: MatchCandidateKind, + pub punch_attempts: u8, } /// Runs a bounded deterministic frame exchange over a connected UDP peer. @@ -125,7 +134,9 @@ pub async fn run_match_probe( } Ok(Err(error)) => return Err(error), Ok(Ok(packet)) => { - let remote = validated_remote_frame(packet, config)?; + let Some(remote) = validated_remote_frame(packet, config)? else { + continue; + }; if remote.frame < frame_number { let previous = deterministic_frame( remote.frame, @@ -164,6 +175,9 @@ pub async fn run_match_probe( frames_received: config.frame_count, transcript_checksum: format!("{checksum:016x}"), elapsed_ms: started.elapsed().as_millis(), + nat: NatMappingState::Unknown, + candidate: MatchCandidateKind::Host, + punch_attempts: 0, }) } @@ -191,12 +205,15 @@ async fn send_probe_frame( fn validated_remote_frame( packet: ProbePacket, config: &MatchProbeConfig, -) -> Result { +) -> Result, TransportError> { let ProbePacket::Input { room_id, session_key, frame, - } = packet; + } = packet + else { + return Ok(None); + }; if room_id != config.descriptor.room_id || session_key != config.session_key || frame.player_id != config.descriptor.peer_user_id @@ -204,7 +221,7 @@ fn validated_remote_frame( { return Err(TransportError::PeerMismatch); } - Ok(frame) + Ok(Some(frame)) } async fn linger_for_peer( @@ -233,7 +250,9 @@ async fn linger_for_peer( } Ok(Err(error)) => return Err(error), Ok(Ok(packet)) => { - let remote = validated_remote_frame(packet, config)?; + let Some(remote) = validated_remote_frame(packet, config)? else { + continue; + }; let response = deterministic_frame( remote.frame.min(final_frame_number), &config.descriptor.local_user_id, diff --git a/packages/networking/src/report.rs b/packages/networking/src/report.rs index deb32f2..6630160 100644 --- a/packages/networking/src/report.rs +++ b/packages/networking/src/report.rs @@ -1,9 +1,47 @@ use opencade_protocol::{ - MatchReport, MatchReportTransport, RoomState, MATCH_REPORT_SCHEMA_VERSION, + MATCH_REPORT_SCHEMA_VERSION, MatchCandidateKind, MatchReport, MatchReportTransport, + NatMappingState, RoomState, }; use serde::Serialize; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::File; +use std::io::Read; +use std::path::Path; pub const ALPHA_MATCH_FRAMES: u32 = 60; +pub const MAX_REPORT_BYTES: u64 = 64 * 1024; + +#[derive(Debug, thiserror::Error)] +pub enum ReportReadError { + #[error("report could not be read")] + Unreadable, + #[error("report exceeds 64 KiB")] + TooLarge, + #[error("report is not a canonical OpenCade match report")] + Invalid, +} + +impl ReportReadError { + pub fn code(&self) -> &'static str { + match self { + Self::Unreadable => "report_unreadable", + Self::TooLarge => "report_too_large", + Self::Invalid => "report_invalid", + } + } +} + +pub fn read_match_report(path: &Path) -> Result { + let file = File::open(path).map_err(|_| ReportReadError::Unreadable)?; + let mut bytes = Vec::new(); + file.take(MAX_REPORT_BYTES + 1) + .read_to_end(&mut bytes) + .map_err(|_| ReportReadError::Unreadable)?; + if bytes.len() as u64 > MAX_REPORT_BYTES { + return Err(ReportReadError::TooLarge); + } + serde_json::from_slice(&bytes).map_err(|_| ReportReadError::Invalid) +} #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct MatchVerification { @@ -16,8 +54,39 @@ pub struct MatchVerification { pub transcript_checksum: String, } +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct AlphaCampaignFailure { + pub room_id: String, + pub code: &'static str, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct CompatibilityResult { + pub game_id: String, + pub platform: String, + pub nat: &'static str, + pub candidate: &'static str, + pub attempts: usize, + pub verified: usize, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct AlphaCampaignSummary { + pub schema_version: u8, + pub reports: usize, + pub attempts: usize, + pub verified: usize, + pub failed: usize, + pub success_rate: f64, + pub gate_passed: bool, + pub failures: Vec, + pub compatibility: Vec, +} + #[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum ReportVerificationError { + #[error("room has {reports} reports; exactly two are required")] + PairSize { reports: usize }, #[error("{report} report uses unsupported schema version {version}")] UnsupportedSchema { report: &'static str, version: u8 }, #[error("{report} report does not describe a finished room")] @@ -43,6 +112,7 @@ pub enum ReportVerificationError { impl ReportVerificationError { pub fn code(&self) -> &'static str { match self { + Self::PairSize { .. } => "report_pair_invalid", Self::UnsupportedSchema { .. } => "schema_unsupported", Self::RoomNotFinished { .. } => "room_not_finished", Self::EmptyCorrelation { .. } => "correlation_missing", @@ -57,6 +127,100 @@ impl ReportVerificationError { } } +pub fn summarize_match_reports(reports: &[MatchReport]) -> AlphaCampaignSummary { + let mut rooms: BTreeMap<&str, Vec<&MatchReport>> = BTreeMap::new(); + for report in reports { + rooms.entry(&report.room.id).or_default().push(report); + } + + let mut verified = 0; + let mut failures = Vec::new(); + let mut compatibility: BTreeMap<(String, String, &'static str, &'static str), (usize, usize)> = + BTreeMap::new(); + + for (room_id, pair) in &rooms { + let result = if pair.len() == 2 { + verify_match_reports(pair[0], pair[1]) + } else { + Err(ReportVerificationError::PairSize { + reports: pair.len(), + }) + }; + let is_verified = result.is_ok(); + if is_verified { + verified += 1; + } else if let Err(error) = result { + failures.push(AlphaCampaignFailure { + room_id: (*room_id).to_string(), + code: error.code(), + }); + } + + let mut seen = BTreeSet::new(); + for report in pair { + let key = ( + report.room.game_id.clone(), + report.client.platform.clone(), + nat_label(report.probe.nat), + candidate_label(report.probe.candidate), + ); + if seen.insert(key.clone()) { + let counts = compatibility.entry(key).or_default(); + counts.0 += 1; + if is_verified { + counts.1 += 1; + } + } + } + } + + let attempts = rooms.len(); + let success_rate = if attempts == 0 { + 0.0 + } else { + verified as f64 / attempts as f64 + }; + AlphaCampaignSummary { + schema_version: 1, + reports: reports.len(), + attempts, + verified, + failed: attempts - verified, + success_rate, + gate_passed: attempts >= 10 && success_rate >= 0.8, + failures, + compatibility: compatibility + .into_iter() + .map( + |((game_id, platform, nat, candidate), (attempts, verified))| CompatibilityResult { + game_id, + platform, + nat, + candidate, + attempts, + verified, + }, + ) + .collect(), + } +} + +fn nat_label(value: Option) -> &'static str { + match value { + Some(NatMappingState::Open) => "open", + Some(NatMappingState::Mapped) => "mapped", + Some(NatMappingState::Unknown) | None => "unknown", + } +} + +fn candidate_label(value: Option) -> &'static str { + match value { + Some(MatchCandidateKind::Host) => "host", + Some(MatchCandidateKind::Reflexive) => "reflexive", + None => "unknown", + } +} + pub fn verify_match_reports( first: &MatchReport, second: &MatchReport, @@ -148,6 +312,9 @@ mod tests { frames_received: ALPHA_MATCH_FRAMES, transcript_checksum: "0376c2e852f4fd25".into(), elapsed_ms: 240, + nat: Some(opencade_protocol::NatMappingState::Mapped), + candidate: Some(opencade_protocol::MatchCandidateKind::Reflexive), + punch_attempts: Some(2), }, client: MatchReportClient { platform: "windows".into(), @@ -205,4 +372,29 @@ mod tests { }) ); } + + #[test] + fn campaign_gate_accepts_eight_of_ten_verified_attempts() { + let mut reports = Vec::new(); + for attempt in 0..10 { + let room_id = format!("room-{attempt}"); + let mut host = report(MatchReportRole::Host); + host.room.id.clone_from(&room_id); + let mut guest = report(MatchReportRole::Guest); + guest.room.id = room_id; + if attempt >= 8 { + guest.probe.transcript_checksum = "aaaaaaaaaaaaaaaa".into(); + } + reports.extend([host, guest]); + } + + let summary = summarize_match_reports(&reports); + assert_eq!(summary.attempts, 10); + assert_eq!(summary.verified, 8); + assert_eq!(summary.failed, 2); + assert_eq!(summary.success_rate, 0.8); + assert!(summary.gate_passed); + assert_eq!(summary.compatibility.len(), 1); + assert_eq!(summary.compatibility[0].verified, 8); + } } diff --git a/packages/networking/src/stun.rs b/packages/networking/src/stun.rs new file mode 100644 index 0000000..1c56200 --- /dev/null +++ b/packages/networking/src/stun.rs @@ -0,0 +1,274 @@ +use crate::{TransportError, UdpPeer}; +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::time::Duration; +use uuid::Uuid; + +const BINDING_REQUEST: u16 = 0x0001; +const BINDING_SUCCESS: u16 = 0x0101; +const MAGIC_COOKIE: u32 = 0x2112_A442; +const MAPPED_ADDRESS: u16 = 0x0001; +const XOR_MAPPED_ADDRESS: u16 = 0x0020; +const HEADER_BYTES: usize = 20; +const MAX_STUN_BYTES: usize = 2_048; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum NatMapping { + Open, + Mapped, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct StunObservation { + pub local_endpoint: SocketAddr, + pub reflexive_endpoint: SocketAddr, + pub mapping: NatMapping, +} + +/// Uses the already-reserved match socket for an RFC 8489 Binding transaction. Reusing the socket +/// is essential: a reflexive address learned from a throwaway socket is not the match mapping. +pub async fn discover_reflexive_address( + peer: &UdpPeer, + server: SocketAddr, + timeout: Duration, +) -> Result { + if server.ip().is_unspecified() || server.port() == 0 || timeout.is_zero() { + return Err(TransportError::InvalidConfiguration( + "STUN server and timeout must be usable".into(), + )); + } + let local_endpoint = peer.local_addr()?; + let transaction_id = transaction_id(); + let request = binding_request(transaction_id); + peer.socket + .send_to(&request, server) + .await + .map_err(super::map_udp_error)?; + + let deadline = tokio::time::Instant::now() + timeout; + let mut buffer = [0_u8; MAX_STUN_BYTES]; + loop { + let remaining = deadline.saturating_duration_since(tokio::time::Instant::now()); + if remaining.is_zero() { + return Err(TransportError::StunTimeout); + } + let received = tokio::time::timeout(remaining, peer.socket.recv_from(&mut buffer)) + .await + .map_err(|_| TransportError::StunTimeout)? + .map_err(super::map_udp_error)?; + if received.1 != server { + continue; + } + let reflexive_endpoint = parse_binding_response(&buffer[..received.0], transaction_id)?; + let mapping = if reflexive_endpoint == local_endpoint { + NatMapping::Open + } else { + NatMapping::Mapped + }; + return Ok(StunObservation { + local_endpoint, + reflexive_endpoint, + mapping, + }); + } +} + +fn transaction_id() -> [u8; 12] { + let uuid = Uuid::new_v4(); + let mut transaction_id = [0_u8; 12]; + transaction_id.copy_from_slice(&uuid.as_bytes()[..12]); + transaction_id +} + +fn binding_request(transaction_id: [u8; 12]) -> [u8; HEADER_BYTES] { + let mut request = [0_u8; HEADER_BYTES]; + request[..2].copy_from_slice(&BINDING_REQUEST.to_be_bytes()); + request[4..8].copy_from_slice(&MAGIC_COOKIE.to_be_bytes()); + request[8..].copy_from_slice(&transaction_id); + request +} + +fn parse_binding_response( + response: &[u8], + transaction_id: [u8; 12], +) -> Result { + if response.len() < HEADER_BYTES { + return invalid("response is shorter than the STUN header"); + } + if u16::from_be_bytes([response[0], response[1]]) != BINDING_SUCCESS { + return invalid("response is not a Binding success response"); + } + if u32::from_be_bytes([response[4], response[5], response[6], response[7]]) != MAGIC_COOKIE { + return invalid("magic cookie does not match RFC 8489"); + } + if response[8..20] != transaction_id { + return invalid("transaction id does not match the request"); + } + let message_length = usize::from(u16::from_be_bytes([response[2], response[3]])); + if message_length % 4 != 0 { + return invalid("declared message length is not 32-bit aligned"); + } + let message_end = HEADER_BYTES + .checked_add(message_length) + .filter(|end| *end <= response.len()) + .ok_or_else(|| { + TransportError::InvalidStunResponse("declared message length is invalid".into()) + })?; + + let mut offset = HEADER_BYTES; + let mut mapped = None; + while offset + 4 <= message_end { + let attribute_type = u16::from_be_bytes([response[offset], response[offset + 1]]); + let attribute_length = usize::from(u16::from_be_bytes([ + response[offset + 2], + response[offset + 3], + ])); + let value_start = offset + 4; + let value_end = value_start + .checked_add(attribute_length) + .filter(|end| *end <= message_end) + .ok_or_else(|| { + TransportError::InvalidStunResponse("attribute length is invalid".into()) + })?; + if attribute_type == XOR_MAPPED_ADDRESS { + return parse_address(&response[value_start..value_end], Some(transaction_id)); + } + if attribute_type == MAPPED_ADDRESS { + mapped = Some(parse_address(&response[value_start..value_end], None)?); + } + offset = value_end + .checked_add((4 - attribute_length % 4) % 4) + .ok_or_else(|| { + TransportError::InvalidStunResponse("attribute padding overflowed".into()) + })?; + } + mapped.ok_or_else(|| { + TransportError::InvalidStunResponse("response contains no mapped address".into()) + }) +} + +fn parse_address( + value: &[u8], + xor_transaction_id: Option<[u8; 12]>, +) -> Result { + if value.len() < 8 || value[0] != 0 { + return invalid("mapped address attribute is malformed"); + } + let encoded_port = u16::from_be_bytes([value[2], value[3]]); + let port = xor_transaction_id + .map(|_| encoded_port ^ (MAGIC_COOKIE >> 16) as u16) + .unwrap_or(encoded_port); + let cookie = MAGIC_COOKIE.to_be_bytes(); + let ip = match value[1] { + 0x01 if value.len() == 8 => { + let mut octets = [0_u8; 4]; + octets.copy_from_slice(&value[4..8]); + if xor_transaction_id.is_some() { + for (octet, mask) in octets.iter_mut().zip(cookie) { + *octet ^= mask; + } + } + IpAddr::V4(Ipv4Addr::from(octets)) + } + 0x02 if value.len() == 20 => { + let mut octets = [0_u8; 16]; + octets.copy_from_slice(&value[4..20]); + if let Some(transaction_id) = xor_transaction_id { + for (octet, mask) in octets + .iter_mut() + .zip(cookie.into_iter().chain(transaction_id)) + { + *octet ^= mask; + } + } + IpAddr::V6(Ipv6Addr::from(octets)) + } + _ => return invalid("mapped address family or length is unsupported"), + }; + Ok(SocketAddr::new(ip, port)) +} + +fn invalid(message: &str) -> Result { + Err(TransportError::InvalidStunResponse(message.into())) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn xor_mapped_response(transaction_id: [u8; 12], address: SocketAddr) -> Vec { + let mut response = Vec::from(binding_request(transaction_id)); + response[..2].copy_from_slice(&BINDING_SUCCESS.to_be_bytes()); + response[2..4].copy_from_slice(&12_u16.to_be_bytes()); + response.extend_from_slice(&XOR_MAPPED_ADDRESS.to_be_bytes()); + response.extend_from_slice(&8_u16.to_be_bytes()); + response.extend_from_slice(&[0, 1]); + response.extend_from_slice(&(address.port() ^ (MAGIC_COOKIE >> 16) as u16).to_be_bytes()); + let IpAddr::V4(ip) = address.ip() else { + panic!("IPv4 fixture"); + }; + for (octet, mask) in ip.octets().into_iter().zip(MAGIC_COOKIE.to_be_bytes()) { + response.push(octet ^ mask); + } + response + } + + #[test] + fn parses_xor_mapped_ipv4_response() { + let transaction_id = [7_u8; 12]; + let address: SocketAddr = "203.0.113.9:45678".parse().expect("address"); + let response = xor_mapped_response(transaction_id, address); + + assert_eq!( + parse_binding_response(&response, transaction_id).expect("binding response"), + address + ); + } + + #[tokio::test] + async fn binding_transaction_uses_the_reserved_match_socket() { + let server = tokio::net::UdpSocket::bind("127.0.0.1:0") + .await + .expect("mock STUN server"); + let server_address = server.local_addr().expect("server address"); + let responder = tokio::spawn(async move { + let mut request = [0_u8; HEADER_BYTES]; + let (_, source) = server + .recv_from(&mut request) + .await + .expect("Binding request"); + let transaction_id: [u8; 12] = request[8..20].try_into().expect("transaction id"); + let response = xor_mapped_response(transaction_id, source); + server + .send_to(&response, source) + .await + .expect("Binding response"); + source + }); + let peer = UdpPeer::bind_unconnected("127.0.0.1:0".parse().expect("peer address")) + .await + .expect("reserved peer"); + let reserved_address = peer.local_addr().expect("reserved address"); + let observation = + discover_reflexive_address(&peer, server_address, Duration::from_millis(500)) + .await + .expect("STUN observation"); + + assert_eq!(responder.await.expect("responder"), reserved_address); + assert_eq!(observation.reflexive_endpoint, reserved_address); + assert_eq!(observation.mapping, NatMapping::Open); + } + + #[test] + fn rejects_wrong_transaction_and_truncated_attributes() { + let transaction_id = [7_u8; 12]; + let response = binding_request(transaction_id); + assert!(parse_binding_response(&response, [8_u8; 12]).is_err()); + + let mut truncated = Vec::from(response); + truncated[..2].copy_from_slice(&BINDING_SUCCESS.to_be_bytes()); + truncated[2..4].copy_from_slice(&8_u16.to_be_bytes()); + assert!(parse_binding_response(&truncated, transaction_id).is_err()); + } +} diff --git a/packages/networking/src/traversal.rs b/packages/networking/src/traversal.rs new file mode 100644 index 0000000..0e6e4f8 --- /dev/null +++ b/packages/networking/src/traversal.rs @@ -0,0 +1,295 @@ +use crate::{TransportError, UdpPeer}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::net::SocketAddr; +use std::time::Duration; + +const MAX_PUNCH_CANDIDATES: usize = 8; +const MAX_PUNCH_BYTES: usize = 1_024; + +#[derive(Debug, Clone)] +pub struct HolePunchConfig { + room_id: String, + session_key: String, + peer_candidates: Vec, + attempts: u8, + interval: Duration, +} + +impl HolePunchConfig { + pub fn new( + room_id: impl Into, + session_key: impl Into, + peer_candidates: Vec, + attempts: u8, + interval: Duration, + ) -> Result { + let room_id = room_id.into(); + let session_key = session_key.into(); + let mut seen = HashSet::new(); + let peer_candidates = peer_candidates + .into_iter() + .filter(|candidate| seen.insert(*candidate)) + .collect::>(); + if room_id.trim().is_empty() + || room_id.len() > 128 + || session_key.trim().is_empty() + || session_key.len() > 128 + { + return Err(TransportError::InvalidConfiguration( + "hole punch requires bounded room and session identifiers".into(), + )); + } + if peer_candidates.is_empty() + || peer_candidates.len() > MAX_PUNCH_CANDIDATES + || peer_candidates + .iter() + .any(|candidate| candidate.ip().is_unspecified() || candidate.port() == 0) + { + return Err(TransportError::InvalidConfiguration(format!( + "hole punch requires 1 to {MAX_PUNCH_CANDIDATES} usable peer candidates" + ))); + } + if attempts == 0 || attempts > 10 || interval.is_zero() { + return Err(TransportError::InvalidConfiguration( + "hole punch requires 1 to 10 attempts and a non-zero interval".into(), + )); + } + Ok(Self { + room_id, + session_key, + peer_candidates, + attempts, + interval, + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct HolePunchReport { + pub selected_peer: SocketAddr, + pub attempts: u8, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum PunchPacket { + HolePunch { + version: u8, + room_id: String, + session_key: String, + }, +} + +/// Simultaneously sends nonce-bound packets to every advertised peer candidate, then connects +/// the reserved socket to the first valid responder. The operation is strictly bounded. +pub async fn punch_hole( + peer: UdpPeer, + config: &HolePunchConfig, +) -> Result<(UdpPeer, HolePunchReport), TransportError> { + let packet = serde_json::to_vec(&PunchPacket::HolePunch { + version: 1, + room_id: config.room_id.clone(), + session_key: config.session_key.clone(), + }) + .map_err(|error| TransportError::Serialization(error.to_string()))?; + let candidates = config + .peer_candidates + .iter() + .copied() + .collect::>(); + let mut buffer = [0_u8; MAX_PUNCH_BYTES]; + + for attempt in 1..=config.attempts { + for candidate in &config.peer_candidates { + match peer.socket.send_to(&packet, candidate).await { + Ok(_) => {} + Err(error) if error.kind() == std::io::ErrorKind::ConnectionRefused => {} + Err(error) => return Err(super::map_udp_error(error)), + } + } + let deadline = tokio::time::Instant::now() + config.interval; + loop { + let remaining = deadline.saturating_duration_since(tokio::time::Instant::now()); + if remaining.is_zero() { + break; + } + let received = match tokio::time::timeout(remaining, peer.socket.recv_from(&mut buffer)) + .await + { + Err(_) => break, + Ok(Err(error)) if matches!(error.kind(), std::io::ErrorKind::ConnectionRefused) => { + continue; + } + Ok(Err(error)) => return Err(super::map_udp_error(error)), + Ok(Ok(received)) => received, + }; + if !candidates.contains(&received.1) { + continue; + } + let Ok(candidate_packet) = serde_json::from_slice::(&buffer[..received.0]) + else { + continue; + }; + let PunchPacket::HolePunch { + version, + room_id, + session_key, + } = candidate_packet; + if version != 1 || room_id != config.room_id || session_key != config.session_key { + continue; + } + peer.socket + .send_to(&packet, received.1) + .await + .map_err(super::map_udp_error)?; + let connected = peer.connect(received.1).await?; + return Ok(( + connected, + HolePunchReport { + selected_peer: received.1, + attempts: attempt, + }, + )); + } + } + Err(TransportError::HolePunchTimeout { + attempts: config.attempts, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{MatchProbeConfig, run_match_probe}; + use opencade_emulator_sdk::{MatchDescriptor, PeerRole, TransportKind}; + + #[tokio::test] + async fn loopback_peers_punch_and_select_each_other() { + let host = UdpPeer::bind_unconnected("127.0.0.1:0".parse().expect("host bind")) + .await + .expect("host peer"); + let guest = UdpPeer::bind_unconnected("127.0.0.1:0".parse().expect("guest bind")) + .await + .expect("guest peer"); + let host_address = host.local_addr().expect("host address"); + let guest_address = guest.local_addr().expect("guest address"); + let host_config = HolePunchConfig::new( + "room-1", + "session-1", + vec![guest_address], + 3, + Duration::from_millis(100), + ) + .expect("host config"); + let guest_config = HolePunchConfig::new( + "room-1", + "session-1", + vec![host_address], + 3, + Duration::from_millis(100), + ) + .expect("guest config"); + + let (host_result, guest_result) = tokio::join!( + punch_hole(host, &host_config), + punch_hole(guest, &guest_config) + ); + assert_eq!( + host_result.expect("host punch").1.selected_peer, + guest_address + ); + assert_eq!( + guest_result.expect("guest punch").1.selected_peer, + host_address + ); + } + + #[tokio::test] + async fn punched_peers_continue_into_the_deterministic_probe() { + let host = UdpPeer::bind_unconnected("127.0.0.1:0".parse().expect("host bind")) + .await + .expect("host peer"); + let guest = UdpPeer::bind_unconnected("127.0.0.1:0".parse().expect("guest bind")) + .await + .expect("guest peer"); + let host_address = host.local_addr().expect("host address"); + let guest_address = guest.local_addr().expect("guest address"); + let host_punch = HolePunchConfig::new( + "room-1", + "session-1", + vec![guest_address], + 3, + Duration::from_millis(100), + ) + .expect("host punch config"); + let guest_punch = HolePunchConfig::new( + "room-1", + "session-1", + vec![host_address], + 3, + Duration::from_millis(100), + ) + .expect("guest punch config"); + let (host_result, guest_result) = tokio::join!( + punch_hole(host, &host_punch), + punch_hole(guest, &guest_punch) + ); + let (host, _) = host_result.expect("host punch"); + let (guest, _) = guest_result.expect("guest punch"); + + let config = |role, local_user: &str, peer_user: &str, local, remote| { + MatchProbeConfig::new( + MatchDescriptor { + room_id: "room-1".into(), + game_id: "sfiii3".into(), + local_user_id: local_user.into(), + peer_user_id: peer_user.into(), + role, + transport: TransportKind::DirectUdp, + local_endpoint: local, + peer_endpoint: remote, + input_delay_frames: 2, + }, + "session-1", + 60, + Duration::from_secs(3), + ) + .expect("probe config") + }; + let host_config = config(PeerRole::Host, "host", "guest", host_address, guest_address); + let guest_config = config( + PeerRole::Guest, + "guest", + "host", + guest_address, + host_address, + ); + let (host_report, guest_report) = tokio::join!( + run_match_probe(&host, &host_config), + run_match_probe(&guest, &guest_config) + ); + assert_eq!( + host_report.expect("host probe").transcript_checksum, + guest_report.expect("guest probe").transcript_checksum + ); + } + + #[test] + fn rejects_unbounded_or_unusable_candidates() { + assert!( + HolePunchConfig::new("room", "session", Vec::new(), 3, Duration::from_millis(500)) + .is_err() + ); + assert!( + HolePunchConfig::new( + "room", + "session", + vec!["0.0.0.0:0".parse().expect("unspecified")], + 3, + Duration::from_millis(500) + ) + .is_err() + ); + } +} diff --git a/packages/networking/tests/report_verifier.rs b/packages/networking/tests/report_verifier.rs index 8f15502..425af5f 100644 --- a/packages/networking/tests/report_verifier.rs +++ b/packages/networking/tests/report_verifier.rs @@ -1,7 +1,7 @@ use chrono::{TimeZone, Utc}; use opencade_protocol::{ - MatchReport, MatchReportClient, MatchReportProbe, MatchReportRole, MatchReportRoom, - MatchReportTransport, RoomState, MATCH_REPORT_SCHEMA_VERSION, + MATCH_REPORT_SCHEMA_VERSION, MatchReport, MatchReportClient, MatchReportProbe, MatchReportRole, + MatchReportRoom, MatchReportTransport, RoomState, }; use std::fs; use std::path::PathBuf; @@ -27,6 +27,9 @@ fn report(role: MatchReportRole) -> MatchReport { frames_received: 60, transcript_checksum: "0376c2e852f4fd25".into(), elapsed_ms: 240, + nat: Some(opencade_protocol::NatMappingState::Mapped), + candidate: Some(opencade_protocol::MatchCandidateKind::Reflexive), + punch_attempts: Some(2), }, client: MatchReportClient { platform: "windows".into(), @@ -105,3 +108,41 @@ fn verifier_cli_returns_machine_readable_success_and_failure() { fs::remove_dir_all(directory).expect("remove fixture directory"); } + +#[test] +fn summary_cli_derives_eight_of_ten_campaign_gate() { + let directory = fixture_dir(); + fs::create_dir(&directory).expect("fixture directory"); + + for attempt in 0..10 { + let room_id = format!("room-{attempt}"); + let mut host = report(MatchReportRole::Host); + host.room.id.clone_from(&room_id); + let mut guest = report(MatchReportRole::Guest); + guest.room.id = room_id; + if attempt >= 8 { + guest.probe.transcript_checksum = "aaaaaaaaaaaaaaaa".into(); + } + for (role, report) in [("host", host), ("guest", guest)] { + let path = directory.join(format!("attempt-{attempt:02}-{role}.json")); + fs::write(path, serde_json::to_vec(&report).expect("report JSON")) + .expect("campaign report"); + } + } + + let result = Command::new(env!("CARGO_BIN_EXE_opencade-alpha-summary")) + .arg(&directory) + .output() + .expect("run campaign summary"); + assert!(result.status.success()); + let output: serde_json::Value = + serde_json::from_slice(&result.stdout).expect("summary output JSON"); + assert_eq!(output["attempts"], 10); + assert_eq!(output["verified"], 8); + assert_eq!(output["success_rate"], 0.8); + assert_eq!(output["gate_passed"], true); + assert_eq!(output["compatibility"][0]["attempts"], 10); + assert_eq!(output["compatibility"][0]["verified"], 8); + + fs::remove_dir_all(directory).expect("remove fixture directory"); +} diff --git a/packages/protocol/Cargo.toml b/packages/protocol/Cargo.toml index 62f87e2..a5881aa 100644 --- a/packages/protocol/Cargo.toml +++ b/packages/protocol/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "opencade-protocol" version = "0.1.0" -edition = "2021" +edition = "2024" description = "OpenCade versioned protocol envelope (shared client/server)" license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" [dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" -chrono = { version = "0.4", features = ["serde"] } -uuid = { version = "1", features = ["v4", "serde"] } -ts-rs = { version = "9", features = ["serde-compat", "chrono-impl", "uuid-impl"] } +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +chrono = { version = "0.4.45", features = ["serde"] } +uuid = { version = "1.25.0", features = ["v4", "serde"] } +ts-rs = { version = "12.0.1", features = ["serde-compat", "chrono-impl", "uuid-impl"] } diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 036f285..5b1a2b1 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -18,8 +18,8 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { - "typescript": "^5.5.0", - "vitest": "^1.6.0" + "typescript": "^7.0.2", + "vitest": "^4.1.11" }, "license": "Apache-2.0" } diff --git a/packages/protocol/src/generated/MatchCandidateKind.ts b/packages/protocol/src/generated/MatchCandidateKind.ts new file mode 100644 index 0000000..78f705b --- /dev/null +++ b/packages/protocol/src/generated/MatchCandidateKind.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Candidate selected by the bounded direct-UDP traversal attempt. + */ +export type MatchCandidateKind = "host" | "reflexive"; diff --git a/packages/protocol/src/generated/MatchEndpointPayload.ts b/packages/protocol/src/generated/MatchEndpointPayload.ts index 2ca8c87..d1eacc2 100644 --- a/packages/protocol/src/generated/MatchEndpointPayload.ts +++ b/packages/protocol/src/generated/MatchEndpointPayload.ts @@ -1,6 +1,13 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { NatMappingState } from "./NatMappingState"; /** * Direct-UDP endpoint candidate exchanged between authenticated room members. */ -export type MatchEndpointPayload = { room_id: string; endpoint: string; nonce: string }; +export type MatchEndpointPayload = { + room_id: string; + endpoint: string; + reflexive_endpoint: string | null; + nat: NatMappingState; + nonce: string; +}; diff --git a/packages/protocol/src/generated/MatchReportProbe.ts b/packages/protocol/src/generated/MatchReportProbe.ts index d468658..9a74243 100644 --- a/packages/protocol/src/generated/MatchReportProbe.ts +++ b/packages/protocol/src/generated/MatchReportProbe.ts @@ -1,6 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { MatchCandidateKind } from "./MatchCandidateKind"; import type { MatchReportRole } from "./MatchReportRole"; import type { MatchReportTransport } from "./MatchReportTransport"; +import type { NatMappingState } from "./NatMappingState"; /** * Deterministic transport evidence from one side of an alpha match. @@ -12,4 +14,7 @@ export type MatchReportProbe = { frames_received: number; transcript_checksum: string; elapsed_ms: number; + nat: NatMappingState | null; + candidate: MatchCandidateKind | null; + punch_attempts: number | null; }; diff --git a/packages/protocol/src/generated/NatMappingState.ts b/packages/protocol/src/generated/NatMappingState.ts new file mode 100644 index 0000000..e1cbe44 --- /dev/null +++ b/packages/protocol/src/generated/NatMappingState.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Honest result of one STUN mapping observation. Cone/symmetric classification needs RFC 5780 + * behavior discovery and is deliberately not inferred from a single Binding transaction. + */ +export type NatMappingState = "unknown" | "open" | "mapped"; diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index df5d457..7c5f371 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -5,6 +5,8 @@ export type { PresencePayload, ChatPayload, MatchEndpointPayload, + MatchCandidateKind, + NatMappingState, MatchProbeCompletedPayload, MatchReport, MatchReportClient, diff --git a/packages/protocol/src/lib.rs b/packages/protocol/src/lib.rs index f25af96..7920170 100644 --- a/packages/protocol/src/lib.rs +++ b/packages/protocol/src/lib.rs @@ -168,9 +168,34 @@ pub struct SessionCandidatePayload { pub struct MatchEndpointPayload { pub room_id: String, pub endpoint: String, + #[serde(default)] + pub reflexive_endpoint: Option, + #[serde(default)] + pub nat: NatMappingState, pub nonce: String, } +/// Honest result of one STUN mapping observation. Cone/symmetric classification needs RFC 5780 +/// behavior discovery and is deliberately not inferred from a single Binding transaction. +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, TS)] +#[ts(export, export_to = "../src/generated/NatMappingState.ts")] +#[serde(rename_all = "snake_case")] +pub enum NatMappingState { + #[default] + Unknown, + Open, + Mapped, +} + +/// Candidate selected by the bounded direct-UDP traversal attempt. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, TS)] +#[ts(export, export_to = "../src/generated/MatchCandidateKind.ts")] +#[serde(rename_all = "snake_case")] +pub enum MatchCandidateKind { + Host, + Reflexive, +} + /// Result of a completed direct-UDP proof, relayed to the other room member. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[ts(export, export_to = "../src/generated/MatchProbeCompletedPayload.ts")] @@ -217,6 +242,9 @@ pub struct MatchReportProbe { pub frames_received: u32, pub transcript_checksum: String, pub elapsed_ms: u32, + pub nat: Option, + pub candidate: Option, + pub punch_attempts: Option, } /// Non-sensitive producer metadata used to diagnose platform-specific alpha failures. @@ -359,6 +387,8 @@ mod tests { let payload = MatchEndpointPayload { room_id: "room-1".into(), endpoint: "192.168.1.20:42000".into(), + reflexive_endpoint: Some("203.0.113.9:52000".into()), + nat: NatMappingState::Mapped, nonce: "8a1110d5-8dd2-4ad2-9c88-ad9768bc4905".into(), }; let encoded = serde_json::to_string(&Envelope::new("match.endpoint", payload.clone())) diff --git a/packages/protocol/src/messages.ts b/packages/protocol/src/messages.ts index 9efe685..cb4909c 100644 --- a/packages/protocol/src/messages.ts +++ b/packages/protocol/src/messages.ts @@ -1,6 +1,8 @@ import type { ChallengePayload } from "./generated/ChallengePayload.js"; import type { ChatPayload } from "./generated/ChatPayload.js"; import type { MatchEndpointPayload } from "./generated/MatchEndpointPayload.js"; +import type { MatchCandidateKind } from "./generated/MatchCandidateKind.js"; +import type { NatMappingState } from "./generated/NatMappingState.js"; import type { MatchProbeCompletedPayload } from "./generated/MatchProbeCompletedPayload.js"; import type { MatchReport } from "./generated/MatchReport.js"; import type { MatchReportClient } from "./generated/MatchReportClient.js"; @@ -16,6 +18,8 @@ export type { ChallengePayload, ChatPayload, MatchEndpointPayload, + MatchCandidateKind, + NatMappingState, MatchProbeCompletedPayload, MatchReport, MatchReportClient, diff --git a/packages/shared/Cargo.toml b/packages/shared/Cargo.toml index d3369c3..2cead71 100644 --- a/packages/shared/Cargo.toml +++ b/packages/shared/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "opencade-shared" version = "0.1.0" -edition = "2021" +edition = "2024" description = "OpenCade shared utilities (placeholder)" license = "Apache-2.0" repository = "https://github.com/opencade/opencade" -rust-version = "1.78" +rust-version = "1.98" [dependencies] -serde = { version = "1", features = ["derive"] } -thiserror = "1" +serde = { version = "1.0.229", features = ["derive"] } +thiserror = "2.0.20" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25a621a..63ce1c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ importers: .: devDependencies: prettier: - specifier: ^3.3.3 + specifier: ^3.9.6 version: 3.9.6 apps/client: @@ -18,767 +18,738 @@ importers: specifier: workspace:* version: link:../../packages/protocol '@tanstack/react-query': - specifier: ^5.51.1 - version: 5.101.4(react@18.3.1) + specifier: ^5.102.0 + version: 5.102.0(react@19.2.8) '@tauri-apps/api': - specifier: ^1.6.0 - version: 1.6.0 + specifier: ^2.11.1 + version: 2.11.1 react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.2.8 + version: 19.2.8 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.2.8 + version: 19.2.8(react@19.2.8) zustand: - specifier: ^4.5.2 - version: 4.5.7(@types/react@18.3.31)(react@18.3.1) + specifier: ^5.0.15 + version: 5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)) devDependencies: '@tauri-apps/cli': - specifier: ^1.6.0 - version: 1.6.3 + specifier: ^2.11.4 + version: 2.11.4 '@types/react': - specifier: ^18.3.3 - version: 18.3.31 + specifier: ^19.2.18 + version: 19.2.18 '@types/react-dom': - specifier: ^18.3.0 - version: 18.3.7(@types/react@18.3.31) + specifier: ^19.2.4 + version: 19.2.4(@types/react@19.2.18) '@vitejs/plugin-react': - specifier: ^4.3.1 - version: 4.3.1(vite@5.4.21) + specifier: ^6.1.0 + version: 6.1.0(vite@8.2.2(esbuild@0.28.2)) typescript: - specifier: ^5.5.0 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite: - specifier: ^5.4.0 - version: 5.4.21 + specifier: ^8.2.2 + version: 8.2.2(esbuild@0.28.2) vitest: - specifier: ^1.6.0 - version: 1.6.1 + specifier: ^4.1.11 + version: 4.1.11(vite@8.2.2(esbuild@0.28.2)) packages/game-definitions: devDependencies: typescript: - specifier: ^5.5.0 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vitest: - specifier: ^1.6.0 - version: 1.6.1 + specifier: ^4.1.11 + version: 4.1.11(vite@8.2.2(esbuild@0.28.2)) packages/protocol: devDependencies: typescript: - specifier: ^5.5.0 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vitest: - specifier: ^1.6.0 - version: 1.6.1 + specifier: ^4.1.11 + version: 4.1.11(vite@8.2.2(esbuild@0.28.2)) packages: - '@babel/code-frame@7.29.7': - resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.7': - resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.7': - resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.8': - resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.29.7': - resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.29.7': - resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.29.7': - resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.29.7': - resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.29.7': - resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.29.7': - resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.7': - resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.8': - resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.29.7': - resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.29.7': - resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.29.7': - resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.8': - resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.8': - resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} - engines: {node: '>=6.9.0'} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} cpu: [x64] os: [win32] - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@oxc-project/types@0.146.0': + resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==} - '@napi-rs/lzma-linux-x64-gnu@1.5.1': - resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} - engines: {node: ^22.20 || ^24.12 || >=25} - cpu: [x64] - os: [linux] - - '@rollup/rollup-android-arm-eabi@4.62.5': - resolution: {integrity: sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==} + '@rolldown/binding-android-arm-eabi@1.2.5': + resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.62.5': - resolution: {integrity: sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==} + '@rolldown/binding-android-arm64@1.2.5': + resolution: {integrity: sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.62.5': - resolution: {integrity: sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==} + '@rolldown/binding-darwin-arm64@1.2.5': + resolution: {integrity: sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.62.5': - resolution: {integrity: sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==} + '@rolldown/binding-darwin-x64@1.2.5': + resolution: {integrity: sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.62.5': - resolution: {integrity: sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.62.5': - resolution: {integrity: sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==} + '@rolldown/binding-freebsd-x64@1.2.5': + resolution: {integrity: sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.62.5': - resolution: {integrity: sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.62.5': - resolution: {integrity: sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.5': + resolution: {integrity: sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.62.5': - resolution: {integrity: sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==} + '@rolldown/binding-linux-arm64-gnu@1.2.5': + resolution: {integrity: sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.62.5': - resolution: {integrity: sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==} + '@rolldown/binding-linux-arm64-musl@1.2.5': + resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.62.5': - resolution: {integrity: sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-loong64-musl@4.62.5': - resolution: {integrity: sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.62.5': - resolution: {integrity: sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==} + '@rolldown/binding-linux-ppc64-gnu@1.2.5': + resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.62.5': - resolution: {integrity: sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.62.5': - resolution: {integrity: sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.62.5': - resolution: {integrity: sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.62.5': - resolution: {integrity: sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==} + '@rolldown/binding-linux-s390x-gnu@1.2.5': + resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.62.5': - resolution: {integrity: sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==} + '@rolldown/binding-linux-x64-gnu@1.2.5': + resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.62.5': - resolution: {integrity: sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==} + '@rolldown/binding-linux-x64-musl@1.2.5': + resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] - '@rollup/rollup-openbsd-x64@4.62.5': - resolution: {integrity: sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.62.5': - resolution: {integrity: sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==} + '@rolldown/binding-openharmony-arm64@1.2.5': + resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.62.5': - resolution: {integrity: sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==} + '@rolldown/binding-win32-arm64-msvc@1.2.5': + resolution: {integrity: sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.62.5': - resolution: {integrity: sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.62.5': - resolution: {integrity: sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==} + '@rolldown/binding-win32-x64-msvc@1.2.5': + resolution: {integrity: sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.62.5': - resolution: {integrity: sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==} - cpu: [x64] - os: [win32] + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@sinclair/typebox@0.27.12': - resolution: {integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@tanstack/query-core@5.101.4': - resolution: {integrity: sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==} + '@tanstack/query-core@5.102.0': + resolution: {integrity: sha512-tvBzr11Q7StuMCEsIJdqX8TAWt6WZIzfw/yrSAjObZDerwTTPeCxeLXN2R8ZSn4ZxFpQV819Xn8QmoO+vtnDvw==} - '@tanstack/react-query@5.101.4': - resolution: {integrity: sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==} + '@tanstack/react-query@5.102.0': + resolution: {integrity: sha512-0GyVyEcGt9M7jHPCua16hNVtstUXE2R4HsnNubFYcDs7DJaLzh1dSiXsADDU/cNn/SqrLfa0vRKyjUmbx4rZLA==} peerDependencies: react: ^18 || ^19 - '@tauri-apps/api@1.6.0': - resolution: {integrity: sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==} - engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} + '@tauri-apps/api@2.11.1': + resolution: {integrity: sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==} - '@tauri-apps/cli-darwin-arm64@1.6.3': - resolution: {integrity: sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==} + '@tauri-apps/cli-darwin-arm64@2.11.4': + resolution: {integrity: sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tauri-apps/cli-darwin-x64@1.6.3': - resolution: {integrity: sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==} + '@tauri-apps/cli-darwin-x64@2.11.4': + resolution: {integrity: sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tauri-apps/cli-linux-arm-gnueabihf@1.6.3': - resolution: {integrity: sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==} + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': + resolution: {integrity: sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tauri-apps/cli-linux-arm64-gnu@1.6.3': - resolution: {integrity: sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==} + '@tauri-apps/cli-linux-arm64-gnu@2.11.4': + resolution: {integrity: sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] - '@tauri-apps/cli-linux-arm64-musl@1.6.3': - resolution: {integrity: sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==} + '@tauri-apps/cli-linux-arm64-musl@2.11.4': + resolution: {integrity: sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] + + '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': + resolution: {integrity: sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] - '@tauri-apps/cli-linux-x64-gnu@1.6.3': - resolution: {integrity: sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==} + '@tauri-apps/cli-linux-x64-gnu@2.11.4': + resolution: {integrity: sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] - '@tauri-apps/cli-linux-x64-musl@1.6.3': - resolution: {integrity: sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==} + '@tauri-apps/cli-linux-x64-musl@2.11.4': + resolution: {integrity: sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] - '@tauri-apps/cli-win32-arm64-msvc@1.6.3': - resolution: {integrity: sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==} + '@tauri-apps/cli-win32-arm64-msvc@2.11.4': + resolution: {integrity: sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tauri-apps/cli-win32-ia32-msvc@1.6.3': - resolution: {integrity: sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==} + '@tauri-apps/cli-win32-ia32-msvc@2.11.4': + resolution: {integrity: sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@tauri-apps/cli-win32-x64-msvc@1.6.3': - resolution: {integrity: sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==} + '@tauri-apps/cli-win32-x64-msvc@2.11.4': + resolution: {integrity: sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tauri-apps/cli@1.6.3': - resolution: {integrity: sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==} + '@tauri-apps/cli@2.11.4': + resolution: {integrity: sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==} engines: {node: '>= 10'} hasBin: true - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - - '@types/react-dom@18.3.7': - resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + '@types/react-dom@19.2.4': + resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} peerDependencies: - '@types/react': ^18.0.0 + '@types/react': ^19.2.0 - '@types/react@18.3.31': - resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} - '@vitejs/plugin-react@4.3.1': - resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] - '@vitest/expect@1.6.1': - resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] - '@vitest/runner@1.6.1': - resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] - '@vitest/snapshot@1.6.1': - resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] - '@vitest/spy@1.6.1': - resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] - '@vitest/utils@1.6.1': - resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] - acorn-walk@8.3.5: - resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} - engines: {node: '>=0.4.0'} + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] - acorn@8.18.0: - resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} - engines: {node: '>=0.4.0'} - hasBin: true + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] - baseline-browser-mapping@2.11.17: - resolution: {integrity: sha512-KAUDn1OSS0fmPlGO+NOUMRcOQ/b/shUBH3OgkG73mPgdf+JD/BQ6fHboGxNOxnUmlwcq+lLq3dTkayRPuSfXwg==} - engines: {node: '>=6.0.0'} - hasBin: true + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] - browserslist@4.28.8: - resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] - caniuse-lite@1.0.30001809: - resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] - chai@4.5.0: - resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} - engines: {node: '>=4'} + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@vitejs/plugin-react@6.1.0': + resolution: {integrity: sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.145.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + oxc-transform-react: + optional: true - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} peerDependencies: - supports-color: '*' + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: - supports-color: + msw: + optional: true + vite: optional: true - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} - engines: {node: '>=6'} + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - electron-to-chromium@1.5.412: - resolution: {integrity: sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==} + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - hasBin: true - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] - local-pkg@0.5.1: - resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} - engines: {node: '>=14'} + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.18: resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - node-releases@2.0.53: - resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} - engines: {node: '>=18'} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} postcss@8.5.26: resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} @@ -789,58 +760,26 @@ packages: engines: {node: '>=14'} hasBin: true - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} peerDependencies: - react: ^18.3.1 - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} + react: ^19.2.8 - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} engines: {node: '>=0.10.0'} - rollup@4.62.5: - resolution: {integrity: sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + rolldown@1.2.5: + resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -848,74 +787,61 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - - strip-literal@2.1.1: - resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} - engines: {node: '>=14.0.0'} - - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} - engines: {node: '>=14.0.0'} - - type-detect@4.1.0: - resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} - engines: {node: '>=4'} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} - update-browserslist-db@1.3.1: - resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - vite-node@1.6.1: - resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} + vite@8.2.2: + resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 || ^0.5.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true - less: + '@vitejs/devtools': optional: true - lightningcss: + esbuild: + optional: true + jiti: + optional: true + less: optional: true sass: optional: true @@ -927,24 +853,44 @@ packages: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true - vitest@1.6.1: - resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.1 - '@vitest/ui': 1.6.1 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@opentelemetry/api': + optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': optional: true '@vitest/ui': optional: true @@ -953,30 +899,19 @@ packages: jsdom: optional: true - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yocto-queue@1.2.2: - resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} - engines: {node: '>=12.20'} - - zustand@4.5.7: - resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} - engines: {node: '>=12.7.0'} + zustand@5.0.15: + resolution: {integrity: sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==} + engines: {node: '>=12.20.0'} peerDependencies: - '@types/react': '>=16.8' + '@types/react': '>=18.0.0' immer: '>=9.0.6' - react: '>=16.8' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' peerDependenciesMeta: '@types/react': optional: true @@ -984,617 +919,438 @@ packages: optional: true react: optional: true + use-sync-external-store: + optional: true snapshots: - '@babel/code-frame@7.29.7': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.7': {} - - '@babel/core@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.8': - dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.29.7': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.8 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.29.7': {} - - '@babel/helper-module-imports@7.29.7': - dependencies: - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.29.7': {} - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/helper-validator-option@7.29.7': {} - - '@babel/helpers@7.29.7': - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - - '@babel/parser@7.29.8': - dependencies: - '@babel/types': 7.29.8 - - '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/template@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - - '@babel/traverse@7.29.8': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.8': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@esbuild/aix-ppc64@0.21.5': + '@esbuild/aix-ppc64@0.28.2': optional: true - '@esbuild/android-arm64@0.21.5': + '@esbuild/android-arm64@0.28.2': optional: true - '@esbuild/android-arm@0.21.5': + '@esbuild/android-arm@0.28.2': optional: true - '@esbuild/android-x64@0.21.5': + '@esbuild/android-x64@0.28.2': optional: true - '@esbuild/darwin-arm64@0.21.5': + '@esbuild/darwin-arm64@0.28.2': optional: true - '@esbuild/darwin-x64@0.21.5': + '@esbuild/darwin-x64@0.28.2': optional: true - '@esbuild/freebsd-arm64@0.21.5': + '@esbuild/freebsd-arm64@0.28.2': optional: true - '@esbuild/freebsd-x64@0.21.5': + '@esbuild/freebsd-x64@0.28.2': optional: true - '@esbuild/linux-arm64@0.21.5': + '@esbuild/linux-arm64@0.28.2': optional: true - '@esbuild/linux-arm@0.21.5': + '@esbuild/linux-arm@0.28.2': optional: true - '@esbuild/linux-ia32@0.21.5': + '@esbuild/linux-ia32@0.28.2': optional: true - '@esbuild/linux-loong64@0.21.5': + '@esbuild/linux-loong64@0.28.2': optional: true - '@esbuild/linux-mips64el@0.21.5': + '@esbuild/linux-mips64el@0.28.2': optional: true - '@esbuild/linux-ppc64@0.21.5': + '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-riscv64@0.21.5': + '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/linux-s390x@0.21.5': + '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/linux-x64@0.21.5': + '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/netbsd-x64@0.21.5': + '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/openbsd-x64@0.21.5': + '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/sunos-x64@0.21.5': + '@esbuild/openbsd-arm64@0.28.2': optional: true - '@esbuild/win32-arm64@0.21.5': + '@esbuild/openbsd-x64@0.28.2': optional: true - '@esbuild/win32-ia32@0.21.5': + '@esbuild/openharmony-arm64@0.28.2': optional: true - '@esbuild/win32-x64@0.21.5': + '@esbuild/sunos-x64@0.28.2': optional: true - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.12 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + '@esbuild/win32-arm64@0.28.2': + optional: true - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@esbuild/win32-ia32@0.28.2': + optional: true - '@jridgewell/resolve-uri@3.1.2': {} + '@esbuild/win32-x64@0.28.2': + optional: true '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@oxc-project/types@0.146.0': {} - '@napi-rs/lzma-linux-x64-gnu@1.5.1': + '@rolldown/binding-android-arm-eabi@1.2.5': optional: true - '@rollup/rollup-android-arm-eabi@4.62.5': + '@rolldown/binding-android-arm64@1.2.5': optional: true - '@rollup/rollup-android-arm64@4.62.5': + '@rolldown/binding-darwin-arm64@1.2.5': optional: true - '@rollup/rollup-darwin-arm64@4.62.5': + '@rolldown/binding-darwin-x64@1.2.5': optional: true - '@rollup/rollup-darwin-x64@4.62.5': + '@rolldown/binding-freebsd-x64@1.2.5': optional: true - '@rollup/rollup-freebsd-arm64@4.62.5': + '@rolldown/binding-linux-arm-gnueabihf@1.2.5': optional: true - '@rollup/rollup-freebsd-x64@4.62.5': + '@rolldown/binding-linux-arm64-gnu@1.2.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.62.5': + '@rolldown/binding-linux-arm64-musl@1.2.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.62.5': + '@rolldown/binding-linux-ppc64-gnu@1.2.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.62.5': + '@rolldown/binding-linux-s390x-gnu@1.2.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.62.5': + '@rolldown/binding-linux-x64-gnu@1.2.5': optional: true - '@rollup/rollup-linux-loong64-gnu@4.62.5': + '@rolldown/binding-linux-x64-musl@1.2.5': optional: true - '@rollup/rollup-linux-loong64-musl@4.62.5': + '@rolldown/binding-openharmony-arm64@1.2.5': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.62.5': + '@rolldown/binding-win32-arm64-msvc@1.2.5': optional: true - '@rollup/rollup-linux-ppc64-musl@4.62.5': + '@rolldown/binding-win32-x64-msvc@1.2.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.62.5': + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@tanstack/query-core@5.102.0': {} + + '@tanstack/react-query@5.102.0(react@19.2.8)': + dependencies: + '@tanstack/query-core': 5.102.0 + react: 19.2.8 + + '@tauri-apps/api@2.11.1': {} + + '@tauri-apps/cli-darwin-arm64@2.11.4': optional: true - '@rollup/rollup-linux-riscv64-musl@4.62.5': + '@tauri-apps/cli-darwin-x64@2.11.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.62.5': + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.62.5': + '@tauri-apps/cli-linux-arm64-gnu@2.11.4': optional: true - '@rollup/rollup-linux-x64-musl@4.62.5': + '@tauri-apps/cli-linux-arm64-musl@2.11.4': optional: true - '@rollup/rollup-openbsd-x64@4.62.5': + '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': optional: true - '@rollup/rollup-openharmony-arm64@4.62.5': + '@tauri-apps/cli-linux-x64-gnu@2.11.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.62.5': + '@tauri-apps/cli-linux-x64-musl@2.11.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.62.5': + '@tauri-apps/cli-win32-arm64-msvc@2.11.4': optional: true - '@rollup/rollup-win32-x64-gnu@4.62.5': + '@tauri-apps/cli-win32-ia32-msvc@2.11.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.62.5': + '@tauri-apps/cli-win32-x64-msvc@2.11.4': optional: true - '@sinclair/typebox@0.27.12': {} + '@tauri-apps/cli@2.11.4': + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 2.11.4 + '@tauri-apps/cli-darwin-x64': 2.11.4 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.4 + '@tauri-apps/cli-linux-arm64-gnu': 2.11.4 + '@tauri-apps/cli-linux-arm64-musl': 2.11.4 + '@tauri-apps/cli-linux-riscv64-gnu': 2.11.4 + '@tauri-apps/cli-linux-x64-gnu': 2.11.4 + '@tauri-apps/cli-linux-x64-musl': 2.11.4 + '@tauri-apps/cli-win32-arm64-msvc': 2.11.4 + '@tauri-apps/cli-win32-ia32-msvc': 2.11.4 + '@tauri-apps/cli-win32-x64-msvc': 2.11.4 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} - '@tanstack/query-core@5.101.4': {} + '@types/estree@1.0.9': {} - '@tanstack/react-query@5.101.4(react@18.3.1)': + '@types/react-dom@19.2.4(@types/react@19.2.18)': dependencies: - '@tanstack/query-core': 5.101.4 - react: 18.3.1 + '@types/react': 19.2.18 - '@tauri-apps/api@1.6.0': {} + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 - '@tauri-apps/cli-darwin-arm64@1.6.3': + '@typescript/typescript-aix-ppc64@7.0.2': optional: true - '@tauri-apps/cli-darwin-x64@1.6.3': + '@typescript/typescript-darwin-arm64@7.0.2': optional: true - '@tauri-apps/cli-linux-arm-gnueabihf@1.6.3': + '@typescript/typescript-darwin-x64@7.0.2': optional: true - '@tauri-apps/cli-linux-arm64-gnu@1.6.3': + '@typescript/typescript-freebsd-arm64@7.0.2': optional: true - '@tauri-apps/cli-linux-arm64-musl@1.6.3': + '@typescript/typescript-freebsd-x64@7.0.2': optional: true - '@tauri-apps/cli-linux-x64-gnu@1.6.3': + '@typescript/typescript-linux-arm64@7.0.2': optional: true - '@tauri-apps/cli-linux-x64-musl@1.6.3': + '@typescript/typescript-linux-arm@7.0.2': optional: true - '@tauri-apps/cli-win32-arm64-msvc@1.6.3': + '@typescript/typescript-linux-loong64@7.0.2': optional: true - '@tauri-apps/cli-win32-ia32-msvc@1.6.3': + '@typescript/typescript-linux-mips64el@7.0.2': optional: true - '@tauri-apps/cli-win32-x64-msvc@1.6.3': + '@typescript/typescript-linux-ppc64@7.0.2': optional: true - '@tauri-apps/cli@1.6.3': - dependencies: - semver: 7.8.5 - optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 1.6.3 - '@tauri-apps/cli-darwin-x64': 1.6.3 - '@tauri-apps/cli-linux-arm-gnueabihf': 1.6.3 - '@tauri-apps/cli-linux-arm64-gnu': 1.6.3 - '@tauri-apps/cli-linux-arm64-musl': 1.6.3 - '@tauri-apps/cli-linux-x64-gnu': 1.6.3 - '@tauri-apps/cli-linux-x64-musl': 1.6.3 - '@tauri-apps/cli-win32-arm64-msvc': 1.6.3 - '@tauri-apps/cli-win32-ia32-msvc': 1.6.3 - '@tauri-apps/cli-win32-x64-msvc': 1.6.3 - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.8 + '@typescript/typescript-linux-s390x@7.0.2': + optional: true - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 + '@typescript/typescript-linux-x64@7.0.2': + optional: true - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.8 + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true - '@types/estree@1.0.9': {} + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true - '@types/prop-types@15.7.15': {} + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true - '@types/react-dom@18.3.7(@types/react@18.3.31)': - dependencies: - '@types/react': 18.3.31 + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true - '@types/react@18.3.31': - dependencies: - '@types/prop-types': 15.7.15 - csstype: 3.2.3 + '@typescript/typescript-sunos-x64@7.0.2': + optional: true - '@vitejs/plugin-react@4.3.1(vite@5.4.21)': - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.4.21 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-win32-arm64@7.0.2': + optional: true - '@vitest/expect@1.6.1': + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@vitejs/plugin-react@6.1.0(vite@8.2.2(esbuild@0.28.2))': dependencies: - '@vitest/spy': 1.6.1 - '@vitest/utils': 1.6.1 - chai: 4.5.0 + '@rolldown/pluginutils': 1.0.1 + vite: 8.2.2(esbuild@0.28.2) - '@vitest/runner@1.6.1': + '@vitest/expect@4.1.11': dependencies: - '@vitest/utils': 1.6.1 - p-limit: 5.0.0 - pathe: 1.1.2 + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + chai: 6.2.2 + tinyrainbow: 3.1.1 - '@vitest/snapshot@1.6.1': + '@vitest/mocker@4.1.11(vite@8.2.2(esbuild@0.28.2))': dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 magic-string: 0.30.21 - pathe: 1.1.2 - pretty-format: 29.7.0 + optionalDependencies: + vite: 8.2.2(esbuild@0.28.2) - '@vitest/spy@1.6.1': + '@vitest/pretty-format@4.1.11': dependencies: - tinyspy: 2.2.1 + tinyrainbow: 3.1.1 - '@vitest/utils@1.6.1': + '@vitest/runner@4.1.11': dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + '@vitest/utils': 4.1.11 + pathe: 2.0.3 - acorn-walk@8.3.5: + '@vitest/snapshot@4.1.11': dependencies: - acorn: 8.18.0 - - acorn@8.18.0: {} - - ansi-styles@5.2.0: {} - - assertion-error@1.1.0: {} + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pathe: 2.0.3 - baseline-browser-mapping@2.11.17: {} + '@vitest/spy@4.1.11': {} - browserslist@4.28.8: + '@vitest/utils@4.1.11': dependencies: - baseline-browser-mapping: 2.11.17 - caniuse-lite: 1.0.30001809 - electron-to-chromium: 1.5.412 - node-releases: 2.0.53 - update-browserslist-db: 1.3.1(browserslist@4.28.8) - - cac@6.7.14: {} - - caniuse-lite@1.0.30001809: {} + '@vitest/pretty-format': 4.1.11 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 - chai@4.5.0: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.1.0 - - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 + assertion-error@2.0.1: {} - confbox@0.1.8: {} + chai@6.2.2: {} convert-source-map@2.0.0: {} - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - csstype@3.2.3: {} - debug@4.4.3: - dependencies: - ms: 2.1.3 + detect-libc@2.1.2: {} - deep-eql@4.1.4: - dependencies: - type-detect: 4.1.0 - - diff-sequences@29.6.3: {} + es-module-lexer@2.3.2: {} - electron-to-chromium@1.5.412: {} - - esbuild@0.21.5: + esbuild@0.28.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - - escalade@3.2.0: {} + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + optional: true estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 - execa@8.0.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 + expect-type@1.4.0: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 fsevents@2.3.3: optional: true - gensync@1.0.0-beta.2: {} - - get-func-name@2.0.2: {} - - get-stream@8.0.1: {} + lightningcss-android-arm64@1.33.0: + optional: true - human-signals@5.0.0: {} + lightningcss-darwin-arm64@1.33.0: + optional: true - is-stream@3.0.0: {} + lightningcss-darwin-x64@1.33.0: + optional: true - isexe@2.0.0: {} + lightningcss-freebsd-x64@1.33.0: + optional: true - js-tokens@4.0.0: {} + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true - js-tokens@9.0.1: {} + lightningcss-linux-arm64-gnu@1.33.0: + optional: true - jsesc@3.1.0: {} + lightningcss-linux-arm64-musl@1.33.0: + optional: true - json5@2.2.3: {} + lightningcss-linux-x64-gnu@1.33.0: + optional: true - local-pkg@0.5.1: - dependencies: - mlly: 1.8.2 - pkg-types: 1.3.1 + lightningcss-linux-x64-musl@1.33.0: + optional: true - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 + lightningcss-win32-arm64-msvc@1.33.0: + optional: true - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 + lightningcss-win32-x64-msvc@1.33.0: + optional: true - lru-cache@5.1.1: + lightningcss@1.33.0: dependencies: - yallist: 3.1.1 + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - merge-stream@2.0.0: {} - - mimic-fn@4.0.0: {} - - mlly@1.8.2: - dependencies: - acorn: 8.18.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 - - ms@2.1.3: {} - nanoid@3.3.18: {} - node-releases@2.0.53: {} - - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - - p-limit@5.0.0: - dependencies: - yocto-queue: 1.2.2 - - path-key@3.1.1: {} - - path-key@4.0.0: {} - - pathe@1.1.2: {} + obug@2.1.4: {} pathe@2.0.3: {} - pathval@1.1.1: {} - picocolors@1.1.1: {} - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 + picomatch@4.0.5: {} postcss@8.5.26: dependencies: @@ -1604,184 +1360,126 @@ snapshots: prettier@3.9.6: {} - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - - react-dom@18.3.1(react@18.3.1): + react-dom@19.2.8(react@19.2.8): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 - - react-is@18.3.1: {} + react: 19.2.8 + scheduler: 0.27.0 - react-refresh@0.14.2: {} - - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react@19.2.8: {} - rollup@4.62.5: + rolldown@1.2.5: dependencies: - '@types/estree': 1.0.9 + '@oxc-project/types': 0.146.0 + '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@napi-rs/lzma-linux-x64-gnu': 1.5.1 - '@rollup/rollup-android-arm-eabi': 4.62.5 - '@rollup/rollup-android-arm64': 4.62.5 - '@rollup/rollup-darwin-arm64': 4.62.5 - '@rollup/rollup-darwin-x64': 4.62.5 - '@rollup/rollup-freebsd-arm64': 4.62.5 - '@rollup/rollup-freebsd-x64': 4.62.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.62.5 - '@rollup/rollup-linux-arm-musleabihf': 4.62.5 - '@rollup/rollup-linux-arm64-gnu': 4.62.5 - '@rollup/rollup-linux-arm64-musl': 4.62.5 - '@rollup/rollup-linux-loong64-gnu': 4.62.5 - '@rollup/rollup-linux-loong64-musl': 4.62.5 - '@rollup/rollup-linux-ppc64-gnu': 4.62.5 - '@rollup/rollup-linux-ppc64-musl': 4.62.5 - '@rollup/rollup-linux-riscv64-gnu': 4.62.5 - '@rollup/rollup-linux-riscv64-musl': 4.62.5 - '@rollup/rollup-linux-s390x-gnu': 4.62.5 - '@rollup/rollup-linux-x64-gnu': 4.62.5 - '@rollup/rollup-linux-x64-musl': 4.62.5 - '@rollup/rollup-openbsd-x64': 4.62.5 - '@rollup/rollup-openharmony-arm64': 4.62.5 - '@rollup/rollup-win32-arm64-msvc': 4.62.5 - '@rollup/rollup-win32-ia32-msvc': 4.62.5 - '@rollup/rollup-win32-x64-gnu': 4.62.5 - '@rollup/rollup-win32-x64-msvc': 4.62.5 - fsevents: 2.3.3 - - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 - - semver@6.3.1: {} - - semver@7.8.5: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} + '@rolldown/binding-android-arm-eabi': 1.2.5 + '@rolldown/binding-android-arm64': 1.2.5 + '@rolldown/binding-darwin-arm64': 1.2.5 + '@rolldown/binding-darwin-x64': 1.2.5 + '@rolldown/binding-freebsd-x64': 1.2.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.5 + '@rolldown/binding-linux-arm64-gnu': 1.2.5 + '@rolldown/binding-linux-arm64-musl': 1.2.5 + '@rolldown/binding-linux-ppc64-gnu': 1.2.5 + '@rolldown/binding-linux-s390x-gnu': 1.2.5 + '@rolldown/binding-linux-x64-gnu': 1.2.5 + '@rolldown/binding-linux-x64-musl': 1.2.5 + '@rolldown/binding-openharmony-arm64': 1.2.5 + '@rolldown/binding-win32-arm64-msvc': 1.2.5 + '@rolldown/binding-win32-x64-msvc': 1.2.5 + + scheduler@0.27.0: {} siginfo@2.0.0: {} - signal-exit@4.1.0: {} - source-map-js@1.2.1: {} stackback@0.0.2: {} - std-env@3.10.0: {} - - strip-final-newline@3.0.0: {} - - strip-literal@2.1.1: - dependencies: - js-tokens: 9.0.1 + std-env@4.2.0: {} tinybench@2.9.0: {} - tinypool@0.8.4: {} - - tinyspy@2.2.1: {} - - type-detect@4.1.0: {} + tinyexec@1.3.0: {} - typescript@5.9.3: {} - - ufo@1.6.4: {} - - update-browserslist-db@1.3.1(browserslist@4.28.8): + tinyglobby@0.2.17: dependencies: - browserslist: 4.28.8 - escalade: 3.2.0 - picocolors: 1.1.1 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 - use-sync-external-store@1.6.0(react@18.3.1): - dependencies: - react: 18.3.1 + tinyrainbow@3.1.1: {} - vite-node@1.6.1: - dependencies: - cac: 6.7.14 - debug: 4.4.3 - pathe: 1.1.2 - picocolors: 1.1.1 - vite: 5.4.21 - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite@5.4.21: + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + use-sync-external-store@1.6.0(react@19.2.8): + dependencies: + react: 19.2.8 + optional: true + + vite@8.2.2(esbuild@0.28.2): dependencies: - esbuild: 0.21.5 + lightningcss: 1.33.0 + picomatch: 4.0.5 postcss: 8.5.26 - rollup: 4.62.5 + rolldown: 1.2.5 + tinyglobby: 0.2.17 optionalDependencies: + esbuild: 0.28.2 fsevents: 2.3.3 - vitest@1.6.1: - dependencies: - '@vitest/expect': 1.6.1 - '@vitest/runner': 1.6.1 - '@vitest/snapshot': 1.6.1 - '@vitest/spy': 1.6.1 - '@vitest/utils': 1.6.1 - acorn-walk: 8.3.5 - chai: 4.5.0 - debug: 4.4.3 - execa: 8.0.1 - local-pkg: 0.5.1 + vitest@4.1.11(vite@8.2.2(esbuild@0.28.2)): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.2.2(esbuild@0.28.2)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 magic-string: 0.30.21 - pathe: 1.1.2 - picocolors: 1.1.1 - std-env: 3.10.0 - strip-literal: 2.1.1 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 tinybench: 2.9.0 - tinypool: 0.8.4 - vite: 5.4.21 - vite-node: 1.6.1 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 8.2.2(esbuild@0.28.2) why-is-node-running: 2.3.0 transitivePeerDependencies: - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - which@2.0.2: - dependencies: - isexe: 2.0.0 + - msw why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - yallist@3.1.1: {} - - yocto-queue@1.2.2: {} - - zustand@4.5.7(@types/react@18.3.31)(react@18.3.1): - dependencies: - use-sync-external-store: 1.6.0(react@18.3.1) + zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)): optionalDependencies: - '@types/react': 18.3.31 - react: 18.3.1 + '@types/react': 19.2.18 + react: 19.2.8 + use-sync-external-store: 1.6.0(react@19.2.8) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1d5eb26..fe57c2a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,3 +3,5 @@ packages: - "packages/*" - "adapters/*" - "services/*" +allowBuilds: + esbuild: true diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..4ecc04c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.98.0" +components = ["clippy", "rustfmt"] +profile = "minimal" diff --git a/rustfmt.toml b/rustfmt.toml index 11e2b82..a7ed036 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -edition = "2021" +edition = "2024" max_width = 100 hard_tabs = false tab_spaces = 4 diff --git a/services/relay/Cargo.toml b/services/relay/Cargo.toml index 0c9ad2a..82592c1 100644 --- a/services/relay/Cargo.toml +++ b/services/relay/Cargo.toml @@ -1,20 +1,20 @@ [package] name = "opencade-relay" version = "0.1.0" -edition = "2021" -rust-version = "1.78" +edition = "2024" +rust-version = "1.98" license = "Apache-2.0" description = "OpenCade relay service (WS TURN-like fallback)" repository = "https://github.com/opencade/opencade" [dependencies] -axum = { version = "0.7", features = ["ws"] } -tokio = { version = "1", features = ["full"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -dashmap = "5" -futures = "0.3" +axum = { version = "0.8.9", features = ["ws"] } +tokio = { version = "1.53.1", features = ["full"] } +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.23", features = ["env-filter", "json"] } +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +dashmap = "6.2.1" +futures = "0.3.34" opencade-protocol = { path = "../../packages/protocol" } -uuid = { version = "1", features = ["v4"] } +uuid = { version = "1.25.0", features = ["v4"] } diff --git a/services/relay/Dockerfile b/services/relay/Dockerfile index 23ec0a4..4a73ca1 100644 --- a/services/relay/Dockerfile +++ b/services/relay/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # --- builder --------------------------------------------------------------- -FROM rust:1.78-slim AS builder +FROM rust:1.98-bookworm AS builder WORKDIR /app # Copy workspace manifests for caching @@ -26,7 +26,7 @@ RUN mkdir -p apps/server/src packages/protocol/src packages/networking/src \ && echo "" > packages/game-definitions/src/lib.rs \ && echo "" > adapters/fbneo/src/lib.rs \ && echo "fn main() {}" > services/relay/src/main.rs \ - && cargo build --release --locked -p opencade-relay || true + && cargo build --release --locked -p opencade-relay # Copy real sources COPY . . diff --git a/services/relay/src/main.rs b/services/relay/src/main.rs index d42c26f..3dc01aa 100644 --- a/services/relay/src/main.rs +++ b/services/relay/src/main.rs @@ -1,23 +1,23 @@ use axum::{ + Json, Router, extract::{ - ws::{Message, WebSocket, WebSocketUpgrade}, Query, State, + ws::{Message, WebSocket, WebSocketUpgrade}, }, http::StatusCode, response::IntoResponse, routing::get, - Json, Router, }; use dashmap::DashMap; use futures::{SinkExt, StreamExt}; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use std::{collections::HashMap, env, net::SocketAddr, sync::Arc}; use tokio::sync::mpsc; use tracing::{info, warn}; use tracing_subscriber::EnvFilter; use uuid::Uuid; -use opencade_protocol::{is_supported_version, Envelope}; +use opencade_protocol::{Envelope, is_supported_version}; #[derive(Debug, Clone)] struct Config { @@ -105,17 +105,16 @@ async fn relay_ws_handler( fn extract_room_id_from_text(text: &str, fallback: &str) -> String { if let Ok(value) = serde_json::from_str::(text) { - if let Some(payload) = value.get("payload") { - if let Some(room) = payload.get("room_id").and_then(|v| v.as_str()) { - if !room.trim().is_empty() { - return room.to_string(); - } - } + if let Some(payload) = value.get("payload") + && let Some(room) = payload.get("room_id").and_then(|v| v.as_str()) + && !room.trim().is_empty() + { + return room.to_string(); } - if let Some(room) = value.get("room_id").and_then(|v| v.as_str()) { - if !room.trim().is_empty() { - return room.to_string(); - } + if let Some(room) = value.get("room_id").and_then(|v| v.as_str()) + && !room.trim().is_empty() + { + return room.to_string(); } } fallback.to_string() @@ -175,7 +174,7 @@ async fn handle_socket(socket: WebSocket, state: SharedState, initial_room: Stri "message": format!("unsupported version: {}", envelope.version), "request_id": envelope.request_id, }); - let _ = tx.send(Message::Text(err.to_string())); + let _ = tx.send(Message::Text(err.to_string().into())); continue; } if let Err(e) = envelope.validate() { @@ -186,7 +185,7 @@ async fn handle_socket(socket: WebSocket, state: SharedState, initial_room: Stri "message": e, "request_id": envelope.request_id, }); - let _ = tx.send(Message::Text(err.to_string())); + let _ = tx.send(Message::Text(err.to_string().into())); continue; } let target_room = envelope