diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index 297a220..c554d90 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -318,6 +318,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -346,10 +363,39 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "percent-encoding", "time", "version_check", ] +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -373,7 +419,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", "foreign-types", "libc", @@ -386,7 +432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -399,6 +445,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -506,6 +561,18 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + [[package]] name = "dbus" version = "0.9.11" @@ -635,6 +702,15 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dom_query" version = "0.27.0" @@ -721,6 +797,15 @@ 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" @@ -1034,8 +1119,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1057,8 +1144,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -1209,6 +1299,25 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1298,6 +1407,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -1321,6 +1431,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots 1.0.9", ] [[package]] @@ -1341,9 +1452,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1744,6 +1857,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -1759,6 +1878,12 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "markup5ever" version = "0.38.0" @@ -2178,8 +2303,10 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-http", "tauri-plugin-process", "tauri-plugin-updater", + "tauri-plugin-websocket", ] [[package]] @@ -2330,6 +2457,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -2398,6 +2534,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + [[package]] name = "quick-xml" version = "0.39.4" @@ -2407,6 +2559,62 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -2428,6 +2636,61 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "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.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "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 = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -2503,6 +2766,49 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.9", +] + [[package]] name = "reqwest" version = "0.13.4" @@ -2616,6 +2922,7 @@ version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ + "web-time", "zeroize", ] @@ -2625,7 +2932,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni 0.22.4", "log", @@ -2663,6 +2970,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" @@ -2745,7 +3058,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2885,6 +3198,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.21.0" @@ -2948,6 +3273,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2955,7 +3291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -3137,6 +3473,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3157,6 +3504,27 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3178,7 +3546,7 @@ checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ "bitflags 2.13.0", "block2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dbus", @@ -3268,7 +3636,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.13.4", "serde", "serde_json", "serde_repr", @@ -3367,6 +3735,54 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-fs" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de22eef34fd78c0da050e748710edd50bf127e651d02ea1b2bfada1523cc5c51" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-plugin-http" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7241a0c762649be8fba7dd4cc84684d0e409f26b335a978ef4dd5fe78da74ce6" +dependencies = [ + "bytes", + "cookie_store", + "data-url", + "http", + "regex", + "reqwest 0.12.28", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "tokio", + "url", + "urlpattern", +] + [[package]] name = "tauri-plugin-process" version = "2.3.1" @@ -3393,7 +3809,7 @@ dependencies = [ "minisign-verify", "osakit", "percent-encoding", - "reqwest", + "reqwest 0.13.4", "rustls", "semver", "serde", @@ -3410,6 +3826,26 @@ dependencies = [ "zip", ] +[[package]] +name = "tauri-plugin-websocket" +version = "2.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca243c7f0bf935cd81123e07f82188ccb919b19fbfc74518b947eedc4619bbb" +dependencies = [ + "futures-util", + "http", + "log", + "rand 0.9.5", + "rustls", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite", +] + [[package]] name = "tauri-runtime" version = "2.11.3" @@ -3639,9 +4075,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -3652,6 +4100,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -3877,6 +4341,24 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", +] + [[package]] name = "typeid" version = "1.0.3" @@ -4141,6 +4623,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web_atoms" version = "0.2.5" @@ -4206,6 +4698,24 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.38.2" @@ -4391,6 +4901,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -4820,6 +5341,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "zerofrom" version = "0.1.8" diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index ad8145a..1012772 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -22,6 +22,17 @@ serde_json = "1" # Auto-update: prompt the operator, download a signed update, relaunch. tauri-plugin-updater = "2" tauri-plugin-process = "2" +# HTTP client for the SPA's API/WS calls to the local Fastify server. The window +# runs at tauri://localhost, which WebKitGTK treats as a secure origin — a plain +# http://127.0.0.1:3000 fetch() from inside it is blocked as mixed content (a +# long-standing WebKit limitation, not fixable via CSP). Routing through this +# plugin sends the request via Tauri's Rust side instead of the webview's own +# fetch, sidestepping the browser mixed-content check entirely. +tauri-plugin-http = "2" +# Same mixed-content problem as above, but for the live-feed WebSocket +# (ws://127.0.0.1:3000 from the secure tauri://localhost origin) — HTTP and WS +# are separate browser checks, so this needs its own plugin. +tauri-plugin-websocket = "2" [features] # Used by `tauri dev`/CLI for hot-reload of the Rust side. diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json index afb8be5..e3fbaa5 100644 --- a/apps/desktop/src-tauri/capabilities/default.json +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -6,6 +6,14 @@ "permissions": [ "core:default", "updater:default", - "process:default" + "process:default", + "websocket:default", + { + "identifier": "http:default", + "allow": [ + { "url": "http://127.0.0.1:3000" }, + { "url": "http://localhost:3000" } + ] + } ] } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 48f17e5..605cf80 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -16,6 +16,13 @@ pub fn run() { // endpoint + signing pubkey live in tauri.conf.json. .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_process::init()) + // Routes the SPA's fetch()/WS calls to the local Fastify server through + // Tauri's native HTTP client — see the Cargo.toml comment on why the + // webview's own fetch() can't reach http://127.0.0.1:3000 directly. + .plugin(tauri_plugin_http::init()) + // Live-feed WebSocket — same mixed-content reason as the HTTP plugin + // above, but WS needs its own plugin (separate browser check). + .plugin(tauri_plugin_websocket::init()) .run(tauri::generate_context!()) .expect("error while running the Parking System desktop shell"); } diff --git a/apps/web/package.json b/apps/web/package.json index c5e998a..f3b9f02 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -18,8 +18,10 @@ "@radix-ui/react-tabs": "^1.1.15", "@tanstack/react-query": "^5.101.0", "@tanstack/react-router": "^1.170.16", + "@tauri-apps/plugin-http": "^2.5.2", "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-updater": "^2.10.1", + "@tauri-apps/plugin-websocket": "^2.3.0", "i18next": "^26.3.1", "react": "19.2.7", "react-dom": "19.2.7", diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts index eb5ecfb..b43083e 100644 --- a/apps/web/src/api.ts +++ b/apps/web/src/api.ts @@ -6,7 +6,7 @@ // wiki/entities/local-jwt-auth.md. import { logFailedRequest } from "./lib/logger.js"; -import { apiUrl } from "./lib/origin.js"; +import { apiUrl, platformFetch } from "./lib/origin.js"; import type { AppLogRecord, ValidationLine, ValidationMode } from "@parking/shared"; const CSRF_COOKIE = "parking_csrf"; @@ -28,7 +28,7 @@ export async function apiFetch(path: string, init: RequestInit = {}): Promise const csrf = readCookie(CSRF_COOKIE); if (csrf) headers.set(CSRF_HEADER, csrf); } - const res = await fetch(apiUrl(path), { ...init, headers, credentials: "include" }); + const res = await platformFetch(apiUrl(path), { ...init, headers, credentials: "include" }); if (!res.ok) { const msg = (await res.json().catch(() => ({}))) as { error?: string; problems?: string[]; [k: string]: unknown }; const error = msg.error ?? `${path}: ${res.status}`; diff --git a/apps/web/src/lib/logger.ts b/apps/web/src/lib/logger.ts index 261f1d4..b577ee1 100644 --- a/apps/web/src/lib/logger.ts +++ b/apps/web/src/lib/logger.ts @@ -14,6 +14,7 @@ // high-signal sources (failed requests, uncaught errors) are always captured. import { LOG_LEVEL_ORDER, type ClientLogInput, type LogLevel } from "@parking/shared"; +import { apiUrl, platformFetch } from "./origin.js"; const ENDPOINT = "/api/logs"; const FLUSH_MS = 4000; @@ -76,7 +77,7 @@ async function flush(): Promise { const headers: Record = { "content-type": "application/json" }; const csrf = readCookie(CSRF_COOKIE); if (csrf) headers[CSRF_HEADER] = csrf; - await fetch(ENDPOINT, { + await platformFetch(apiUrl(ENDPOINT), { method: "POST", headers, credentials: "include", @@ -90,7 +91,10 @@ async function flush(): Promise { } } -/** Best-effort synchronous flush on page hide (sendBeacon survives unload). */ +/** Best-effort synchronous flush on page hide (sendBeacon survives unload). Browser + * only — sendBeacon is a native browser API with no Tauri-HTTP-plugin equivalent, + * so this drops silently in the desktop shell (unload is rare there; the regular + * 4s-interval flush above covers the common case). */ function flushBeacon(): void { if (queue.length === 0) return; const entries = queue.splice(0, queue.length); @@ -99,7 +103,7 @@ function flushBeacon(): void { // sendBeacon can't set the CSRF header; the server accepts the ingest for any // signed-in session (cookie sent automatically). If CSRF later guards it strictly, // this path degrades to "lost on unload" — acceptable for diagnostics. - navigator.sendBeacon(ENDPOINT, blob); + navigator.sendBeacon(apiUrl(ENDPOINT), blob); } catch { /* ignore */ } diff --git a/apps/web/src/lib/origin.ts b/apps/web/src/lib/origin.ts index fd053ca..41d947b 100644 --- a/apps/web/src/lib/origin.ts +++ b/apps/web/src/lib/origin.ts @@ -10,6 +10,15 @@ // Keep this the SINGLE source for the backend origin — api.ts and the live-feed // WebSocket both read it, so the web app and the desktop shell stay identical // except for this one build-time value. +// +// platformFetch(): WebKitGTK treats tauri://localhost as a SECURE origin, so a +// plain http://127.0.0.1:3000 fetch() from inside it is blocked as mixed +// content (a WebKit limitation — CSP's connect-src does NOT override this; +// found 2026-09-03 as "Load failed" on every desktop request). Inside Tauri we +// dynamically import @tauri-apps/plugin-http's fetch, which routes the request +// through Tauri's native side instead of the webview's own fetch, sidestepping +// the check entirely. Browser build never imports the plugin (dynamic import, +// same pattern as desktop-updater.ts). /** Backend HTTP origin, no trailing slash. Empty string = same-origin/relative. */ export const API_BASE: string = (import.meta.env.VITE_API_BASE ?? "").replace(/\/$/, ""); @@ -28,3 +37,22 @@ export function wsUrl(path: string): string { const proto = window.location.protocol === "https:" ? "wss:" : "ws:"; return `${proto}//${window.location.host}${path}`; } + +/** True when running inside the Tauri webview (not a normal browser). */ +function inTauri(): boolean { + return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; +} + +/** + * fetch(), but routed through @tauri-apps/plugin-http inside the desktop + * shell (see the file header for why the webview's own fetch can't reach + * the local backend). Same signature as the global fetch; a plain pass- + * through in the browser. + */ +export async function platformFetch(input: RequestInfo | URL, init?: RequestInit): Promise { + if (inTauri()) { + const { fetch: tauriFetch } = await import("@tauri-apps/plugin-http"); + return tauriFetch(input, init); + } + return fetch(input, init); +} diff --git a/apps/web/src/lib/platform-ws.ts b/apps/web/src/lib/platform-ws.ts new file mode 100644 index 0000000..25dcc4b --- /dev/null +++ b/apps/web/src/lib/platform-ws.ts @@ -0,0 +1,102 @@ +// Desktop-only WebSocket adapter. +// +// WebKitGTK treats tauri://localhost as a SECURE origin, so a plain +// ws://127.0.0.1:3000 connection from inside it is blocked as mixed content — +// same root cause as the HTTP fetch() issue (see origin.ts's platformFetch), +// but WS is a separate browser check with its own plugin +// (@tauri-apps/plugin-websocket), which routes the connection through Tauri's +// native side instead of the webview's own WebSocket. +// +// That plugin's API is async/listener-based, not the synchronous +// onopen/onmessage/onclose event surface use-live-feed.ts is written against +// (and has already been hardened for — reconnect backoff, StrictMode +// double-invoke, cleanup). Rather than rewrite that hook around a different +// API shape, this adapter presents the same native-WebSocket-like interface +// use-live-feed.ts already expects, so that hook needs no changes at all. +// +// Browser build: plain pass-through to the real WebSocket (this file's +// createPlatformSocket is only called from inside inTauri() callers). + +export interface PlatformSocket { + onopen: (() => void) | null; + onmessage: ((ev: { data: string }) => void) | null; + onclose: (() => void) | null; + onerror: (() => void) | null; + close(): void; +} + +class NativeSocketAdapter implements PlatformSocket { + onopen: (() => void) | null = null; + onmessage: ((ev: { data: string }) => void) | null = null; + onclose: (() => void) | null = null; + onerror: (() => void) | null = null; + #sock: WebSocket; + + constructor(url: string) { + this.#sock = new WebSocket(url); + this.#sock.onopen = () => this.onopen?.(); + this.#sock.onmessage = (ev) => this.onmessage?.({ data: ev.data as string }); + this.#sock.onclose = () => this.onclose?.(); + this.#sock.onerror = () => this.onerror?.(); + } + + close(): void { + this.#sock.close(); + } +} + +class TauriSocketAdapter implements PlatformSocket { + onopen: (() => void) | null = null; + onmessage: ((ev: { data: string }) => void) | null = null; + onclose: (() => void) | null = null; + onerror: (() => void) | null = null; + #closed = false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + #conn: any = null; + + constructor(url: string) { + void this.#connect(url); + } + + async #connect(url: string): Promise { + try { + const { default: TauriWebSocket } = await import("@tauri-apps/plugin-websocket"); + if (this.#closed) return; // close() called before connect resolved + const conn = await TauriWebSocket.connect(url); + if (this.#closed) { + void conn.disconnect(); + return; + } + this.#conn = conn; + conn.addListener((msg: { type: string; data: unknown }) => { + if (msg.type === "Text") { + this.onmessage?.({ data: msg.data as string }); + } else if (msg.type === "Close") { + this.onclose?.(); + } + // Binary/Ping/Pong: the server protocol here is text-JSON only (see + // routes/ws.ts) — nothing else is expected. + }); + this.onopen?.(); + } catch { + this.onerror?.(); + this.onclose?.(); + } + } + + close(): void { + this.#closed = true; + void this.#conn?.disconnect(); + } +} + +/** True when running inside the Tauri webview (not a normal browser). */ +function inTauri(): boolean { + return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; +} + +/** Open a live-feed socket, routed through the Tauri WebSocket plugin inside the + * desktop shell (mixed-content workaround), or the native WebSocket in a browser. */ +export function createPlatformSocket(url: string): PlatformSocket { + return inTauri() ? new TauriSocketAdapter(url) : new NativeSocketAdapter(url); +} diff --git a/apps/web/src/lib/use-live-feed.ts b/apps/web/src/lib/use-live-feed.ts index 940435a..1e97a87 100644 --- a/apps/web/src/lib/use-live-feed.ts +++ b/apps/web/src/lib/use-live-feed.ts @@ -4,6 +4,7 @@ import type { DeviceStatus, LedgerEvent, Occupancy } from "../api.js"; import { qk } from "./query.js"; import { useLiveStore, type LaneStatus, type LanePresence } from "./live-store.js"; import { wsUrl } from "./origin.js"; +import { createPlatformSocket, type PlatformSocket } from "./platform-ws.js"; // Booth WebSocket client. Opens ONE socket to /api/ws and turns server pushes into // (a) live-store updates for the ticker/occupancy and (b) Query cache invalidations @@ -36,7 +37,7 @@ export function useLiveFeed(enabled: boolean = true): void { useLiveStore(); // Hold the socket + reconnect timer across renders; guard against StrictMode // double-invoke and unmount. - const sockRef = useRef(null); + const sockRef = useRef(null); const retryRef = useRef(0); const closedRef = useRef(false); @@ -50,7 +51,7 @@ export function useLiveFeed(enabled: boolean = true): void { const connect = () => { if (closedRef.current) return; setStatus(retryRef.current === 0 ? "connecting" : "connecting"); - const sock = new WebSocket(wsUrl("/api/ws")); + const sock = createPlatformSocket(wsUrl("/api/ws")); sockRef.current = sock; sock.onopen = () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64973d3..e1a25da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,12 +108,18 @@ importers: '@tanstack/react-router': specifier: ^1.170.16 version: 1.170.16(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tauri-apps/plugin-http': + specifier: ^2.5.2 + version: 2.6.0 '@tauri-apps/plugin-process': specifier: ^2.3.1 version: 2.3.1 '@tauri-apps/plugin-updater': specifier: ^2.10.1 version: 2.10.1 + '@tauri-apps/plugin-websocket': + specifier: ^2.3.0 + version: 2.4.3 i18next: specifier: ^26.3.1 version: 26.3.1(typescript@6.0.3) @@ -1577,12 +1583,18 @@ packages: engines: {node: '>= 10'} hasBin: true + '@tauri-apps/plugin-http@2.6.0': + resolution: {integrity: sha512-QYXwbGb4hQ9/8Riv/ejU/kPFFnbBIrBcWwV1LIXv2xBKfoj8lkWfGkd9pkCSsBI/pljPtz+IPqfrE3t3bVl3mg==} + '@tauri-apps/plugin-process@2.3.1': resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==} '@tauri-apps/plugin-updater@2.10.1': resolution: {integrity: sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==} + '@tauri-apps/plugin-websocket@2.4.3': + resolution: {integrity: sha512-c85ykljg6AzY6Zw4KpYsEBaLirjPIs6m8xxC6hZcdwAchckaBU368US+oSsa5B43PjSLukjwD5vOOqzYYnswWA==} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -4089,6 +4101,10 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.11.3 '@tauri-apps/cli-win32-x64-msvc': 2.11.3 + '@tauri-apps/plugin-http@2.6.0': + dependencies: + '@tauri-apps/api': 2.11.1 + '@tauri-apps/plugin-process@2.3.1': dependencies: '@tauri-apps/api': 2.11.1 @@ -4097,6 +4113,10 @@ snapshots: dependencies: '@tauri-apps/api': 2.11.1 + '@tauri-apps/plugin-websocket@2.4.3': + dependencies: + '@tauri-apps/api': 2.11.1 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.7 diff --git a/wiki/decisions/desktop-shell-tauri.md b/wiki/decisions/desktop-shell-tauri.md index c99c301..1fdd410 100644 --- a/wiki/decisions/desktop-shell-tauri.md +++ b/wiki/decisions/desktop-shell-tauri.md @@ -140,6 +140,30 @@ Per the user's choices — the operator **keeps OS access** (no fullscreen lockd - **Right-click:** the context menu is blocked in **prod only** (`apps/web/src/lib/kiosk.ts`, guarded on `import.meta.env.PROD`); dev keeps right-click + devtools. Applies to both the browser prod build and the desktop build (same SPA). +- **Mixed content blocks http(s)/ws(s) from the webview — fixed 2026-09-03.** Even with + `VITE_API_BASE` correctly set (below), login still failed with WebKit's generic `"Load failed"`. + Root cause is a separate, deeper issue: WebKitGTK treats `tauri://localhost` as a **secure + origin**, so a plain `http://127.0.0.1:3000` `fetch()` — or a `ws://127.0.0.1:3000` WebSocket — + from inside it is blocked as **mixed content**, a long-standing WebKit limitation + ([bugs.webkit.org #171934](https://bugs.webkit.org/show_bug.cgi?id=171934)). `connect-src` in the + CSP does **not** override this — it's a different browser security layer entirely, so the request + never even reaches the network layer to be diagnosable via server logs. **Fix:** two Tauri plugins + route the SPA's traffic through Tauri's native (Rust) side instead of the webview's own + fetch/WebSocket, which sidesteps the check entirely: + - **`tauri-plugin-http`** — `apps/web/src/lib/origin.ts`'s `platformFetch()` dynamically imports + `@tauri-apps/plugin-http`'s `fetch` (a genuine drop-in for the standard Fetch API) inside Tauri, + plain `fetch` in the browser. `api.ts` and `logger.ts` both call `platformFetch` instead of the + global `fetch` now. + - **`tauri-plugin-websocket`** — NOT a drop-in (async `connect()`/listener-callback API, not + `onopen`/`onmessage`/sync `send`/`close`). `apps/web/src/lib/platform-ws.ts` adapts it behind + the same native-`WebSocket`-shaped interface `use-live-feed.ts` already expects (hardened for + reconnect backoff + StrictMode double-invoke), so that hook needed zero changes. + - Capability grants: `apps/desktop/src-tauri/capabilities/default.json` adds `websocket:default` + and a scoped `http:default` (`allow: [{url: "http://127.0.0.1:3000"}, {url: + "http://localhost:3000"}]`) — deny-by-default, matching the CSP's existing allowlist. + - `logger.ts`'s `flushBeacon()` (page-hide `navigator.sendBeacon`) is a native browser API with no + Tauri equivalent — it still drops silently in the desktop shell on unload. Accepted: the regular + 4s-interval flush (now fixed, routes through `platformFetch`) covers the common case. - **`VITE_API_BASE` — desktop vs. browser (regression found + fixed 2026-09-03):** `apps/web/.env.production` (committed, shared by both builds) sets `VITE_API_BASE=` (empty) — this is correct for the **browser/booth** build (Fastify same-origin, stays relative) since commit diff --git a/wiki/log.md b/wiki/log.md index b3175de..8d5f1be 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -2767,3 +2767,15 @@ zero diagnostic trail. Fixed release.yml to sed-patch tauri.conf.json's version right before building (checked-in value is now dev-only, never hand-maintained for releases), and split desktop-updater.ts's catch so a real post-accept failure logs instead of vanishing. Full detail on [[desktop-shell-tauri]]. + +## [2026-09-03] fix | Desktop login "Load failed": WebKit mixed-content, not CORS/CSP + +After fixing VITE_API_BASE, login still failed with WebKit's generic "Load failed" — a raw browser +fetch() rejection with no server-side trace, since the request never reached the network. Root +cause: WebKitGTK treats tauri://localhost as a secure origin, so http://127.0.0.1:3000 (and +ws://127.0.0.1:3000) from inside it is blocked as mixed content — a known WebKit limitation, NOT +fixable via CSP connect-src. Fixed by routing both through Tauri plugins that use the native (Rust) +HTTP/WS client instead of the webview's own: tauri-plugin-http (a genuine fetch() drop-in, wired +into api.ts/logger.ts via a new platformFetch() in origin.ts) and tauri-plugin-websocket (NOT a +drop-in — async/listener API — adapted behind a native-WebSocket-shaped interface in the new +platform-ws.ts so use-live-feed.ts needed no changes). Full detail on [[desktop-shell-tauri]].