diff --git a/rustfmt.toml b/rustfmt.toml index 32a9786f..5e9e2206 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,7 @@ edition = "2018" + +# Widths +chain_width = 100 +max_width = 100 +single_line_if_else_max_width = 100 +fn_call_width = 100 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 62f7e2ad..bbaa745d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -403,31 +403,30 @@ dependencies = [ ] [[package]] -name = "axum" -version = "0.6.20" +name = "aws-lc-rs" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "cdd82dba44d209fddb11c190e0a94b78651f95299598e472215667417a03ff1d" dependencies = [ - "async-trait", - "axum-core 0.3.4", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "itoa 1.0.11", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower", - "tower-layer", - "tower-service", + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7a4168111d7eb622a31b214057b8509c0a7e1794f44c546d742330dc793972" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", ] [[package]] @@ -437,7 +436,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", - "axum-core 0.4.3", + "axum-core", "bytes", "futures-util", "http 1.1.0", @@ -457,23 +456,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-core" version = "0.4.3" @@ -527,6 +509,29 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.72", + "which", +] + [[package]] name = "bit_field" version = "0.10.2" @@ -803,6 +808,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfb" version = "0.7.3" @@ -857,6 +871,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.5", +] + [[package]] name = "clipboard-win" version = "5.4.0" @@ -866,6 +891,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.25.0" @@ -1410,7 +1444,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.5", ] [[package]] @@ -1806,6 +1840,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -2335,6 +2375,30 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 1.1.0", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.1.0", +] + [[package]] name = "heck" version = "0.4.1" @@ -2534,6 +2598,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-proxy2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9043b7b23fb0bc4a1c7014c27b50a4fc42cc76206f71d34fc0dfe5b28ddc3faf" +dependencies = [ + "bytes", + "futures-util", + "headers", + "http 1.1.0", + "hyper 1.4.1", + "hyper-tls", + "hyper-util", + "native-tls", + "pin-project-lite", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -2545,22 +2629,24 @@ dependencies = [ "hyper 0.14.30", "log", "rustls 0.21.12", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", ] [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", "hyper 1.4.1", "hyper-util", + "log", "rustls 0.23.12", + "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -2611,9 +2697,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -2624,7 +2710,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -2989,6 +3074,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lebe" version = "0.5.2" @@ -3015,7 +3106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] @@ -3046,6 +3137,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libm" version = "0.2.8" @@ -3248,6 +3349,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "muda" version = "0.15.1" @@ -4202,22 +4309,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.6" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", - "prost-derive 0.12.6", -] - -[[package]] -name = "prost" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" -dependencies = [ - "bytes", - "prost-derive 0.13.1", + "prost-derive", ] [[package]] @@ -4234,8 +4331,8 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.13.1", - "prost-types 0.13.1", + "prost", + "prost-types", "regex", "syn 2.0.72", "tempfile", @@ -4243,22 +4340,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.72", -] - -[[package]] -name = "prost-derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools 0.13.0", @@ -4269,24 +4353,24 @@ dependencies = [ [[package]] name = "prost-reflect" -version = "0.12.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057237efdb71cf4b3f9396302a3d6599a92fa94063ba537b66130980ea9909f3" +checksum = "4b7535b02f0e5efe3e1dbfcb428be152226ed0c66cad9541f2274c8ba8d4cd40" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "once_cell", - "prost 0.12.6", + "prost", "prost-reflect-derive", - "prost-types 0.12.6", + "prost-types", "serde", "serde-value", ] [[package]] name = "prost-reflect-derive" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172da1212c02be2c94901440cb27183cd92bff00ebacca5c323bf7520b8f9c04" +checksum = "f4fce6b22f15cc8d8d400a2b98ad29202b33bd56c7d9ddd815bc803a807ecb65" dependencies = [ "proc-macro2", "quote", @@ -4295,20 +4379,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.6" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ - "prost 0.12.6", -] - -[[package]] -name = "prost-types" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" -dependencies = [ - "prost 0.13.1", + "prost", ] [[package]] @@ -4390,7 +4465,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.0.0", "rustls 0.23.12", "socket2", "thiserror", @@ -4407,7 +4482,7 @@ dependencies = [ "bytes", "rand 0.8.5", "ring", - "rustc-hash", + "rustc-hash 2.0.0", "rustls 0.23.12", "slab", "thiserror", @@ -4713,7 +4788,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.4.1", - "hyper-rustls 0.27.2", + "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", "ipnet", @@ -4893,6 +4968,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.0.0" @@ -4939,6 +5020,8 @@ version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -4959,6 +5042,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -5000,6 +5096,7 @@ version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -5200,9 +5297,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -5230,9 +5327,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -5401,6 +5498,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -6557,9 +6660,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -6640,40 +6743,13 @@ dependencies = [ [[package]] name = "tonic" -version = "0.10.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", - "axum 0.6.20", - "base64 0.21.7", - "bytes", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "hyper-timeout 0.4.1", - "percent-encoding", - "pin-project", - "prost 0.12.6", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38659f4a91aba8598d27821589f5db7dddd94601e7a01b1e485a50e5484c7401" -dependencies = [ - "async-stream", - "async-trait", - "axum 0.7.5", + "axum", "base64 0.22.1", "bytes", "h2 0.4.5", @@ -6685,7 +6761,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost 0.13.1", + "prost", "socket2", "tokio", "tokio-stream", @@ -6710,15 +6786,15 @@ dependencies = [ [[package]] name = "tonic-reflection" -version = "0.10.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa37c513df1339d197f4ba21d28c918b9ef1ac1768265f11ecb6b7f1cba1b76" +checksum = "878d81f52e7fcfd80026b7fdb6a9b578b3c3653ba987f87f0dce4b64043cba27" dependencies = [ - "prost 0.12.6", - "prost-types 0.12.6", + "prost", + "prost-types", "tokio", "tokio-stream", - "tonic 0.10.2", + "tonic", ] [[package]] @@ -7368,6 +7444,18 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "whoami" version = "1.5.1" @@ -7896,20 +7984,23 @@ version = "0.1.0" dependencies = [ "anyhow", "dunce", - "hyper 0.14.30", - "hyper-rustls 0.24.2", + "hyper 1.4.1", + "hyper-proxy2", + "hyper-rustls 0.27.3", + "hyper-util", "log", "md5", - "prost 0.12.6", + "prost", "prost-reflect", - "prost-types 0.12.6", + "prost-types", + "rustls 0.23.12", "serde", "serde_json", "tauri", "tauri-plugin-shell", "tokio", "tokio-stream", - "tonic 0.10.2", + "tonic", "tonic-reflection", "uuid", ] @@ -7943,7 +8034,7 @@ dependencies = [ "dunce", "log", "path-slash", - "prost 0.13.1", + "prost", "rand 0.8.5", "regex", "reqwest", @@ -7953,7 +8044,7 @@ dependencies = [ "tauri-plugin-shell", "thiserror", "tokio", - "tonic 0.12.1", + "tonic", "tonic-build", "ts-rs", "yaak_models", diff --git a/src-tauri/migrations/20241012181547_proxy-setting.sql b/src-tauri/migrations/20241012181547_proxy-setting.sql new file mode 100644 index 00000000..57e4371e --- /dev/null +++ b/src-tauri/migrations/20241012181547_proxy-setting.sql @@ -0,0 +1 @@ +ALTER TABLE settings ADD COLUMN proxy TEXT; diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index 28f644fd..f62561a5 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -14,7 +14,7 @@ use http::{HeaderMap, HeaderName, HeaderValue}; use log::{debug, error, warn}; use mime_guess::Mime; use reqwest::redirect::Policy; -use reqwest::{multipart, Url}; +use reqwest::{multipart, Proxy, Url}; use reqwest::{Method, Response}; use serde_json::Value; use tauri::{Manager, Runtime, WebviewWindow}; @@ -25,10 +25,11 @@ use tokio::sync::watch::Receiver; use tokio::sync::{oneshot, Mutex}; use yaak_models::models::{ Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader, - HttpResponseState, + HttpResponseState, ProxySetting, ProxySettingAuth, }; use yaak_models::queries::{ - get_http_response, get_workspace, update_response_if_id, upsert_cookie_jar, + get_http_response, get_or_create_settings, get_workspace, update_response_if_id, + upsert_cookie_jar, }; use yaak_plugin_runtime::events::{RenderPurpose, WindowContext}; @@ -40,9 +41,9 @@ pub async fn send_http_request( cookie_jar: Option, cancelled_rx: &mut Receiver, ) -> Result { - let workspace = get_workspace(window, &request.workspace_id) - .await - .expect("Failed to get Workspace"); + let workspace = + get_workspace(window, &request.workspace_id).await.expect("Failed to get Workspace"); + let settings = get_or_create_settings(window).await; let cb = PluginTemplateCallback::new( window.app_handle(), &WindowContext::from_window(window), @@ -61,6 +62,7 @@ pub async fn send_http_request( if !url_string.starts_with("http://") && !url_string.starts_with("https://") { url_string = format!("http://{}", url_string); } + debug!("Sending request to {url_string}"); let mut client_builder = reqwest::Client::builder() .redirect(match workspace.setting_follow_redirects { @@ -75,6 +77,31 @@ pub async fn send_http_request( .danger_accept_invalid_certs(!workspace.setting_validate_certificates) .tls_info(true); + match settings.proxy { + Some(ProxySetting::Disabled) => client_builder = client_builder.no_proxy(), + Some(ProxySetting::Enabled { http, https, auth }) => { + debug!("Using proxy http={http} https={https}"); + let mut proxy = Proxy::custom(move |url| { + let http = if http.is_empty() { None } else { Some(http.to_owned()) }; + let https = if https.is_empty() { None } else { Some(https.to_owned()) }; + let proxy_url = match (url.scheme(), http, https) { + ("http", Some(proxy_url), _) => Some(proxy_url), + ("https", _, Some(proxy_url)) => Some(proxy_url), + _ => None, + }; + proxy_url + }); + + if let Some(ProxySettingAuth { user, password }) = auth { + debug!("Using proxy auth"); + proxy = proxy.basic_auth(user.as_str(), password.as_str()); + } + + client_builder = client_builder.proxy(proxy); + } + None => {} // Nothing to do for this one, as it is the default + } + // Add cookie store if specified let maybe_cookie_manager = match cookie_jar.clone() { Some(cj) => { @@ -196,16 +223,8 @@ pub async fn send_http_request( let a = rendered_request.authentication; if b == "basic" { - let username = a - .get("username") - .unwrap_or(empty_value) - .as_str() - .unwrap_or_default(); - let password = a - .get("password") - .unwrap_or(empty_value) - .as_str() - .unwrap_or_default(); + let username = a.get("username").unwrap_or(empty_value).as_str().unwrap_or_default(); + let password = a.get("password").unwrap_or(empty_value).as_str().unwrap_or_default(); let auth = format!("{username}:{password}"); let encoded = BASE64_STANDARD.encode(auth); @@ -214,11 +233,7 @@ pub async fn send_http_request( HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(), ); } else if b == "bearer" { - let token = a - .get("token") - .unwrap_or(empty_value) - .as_str() - .unwrap_or_default(); + let token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or_default(); headers.insert( "Authorization", HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), @@ -232,10 +247,7 @@ pub async fn send_http_request( let query = get_str_h(&request_body, "query"); let variables = get_str_h(&request_body, "variables"); let body = if variables.trim().is_empty() { - format!( - r#"{{"query":{}}}"#, - serde_json::to_string(query).unwrap_or_default() - ) + format!(r#"{{"query":{}}}"#, serde_json::to_string(query).unwrap_or_default()) } else { format!( r#"{{"query":{},"variables":{variables}}}"#, @@ -326,9 +338,8 @@ pub async fn send_http_request( Mime::from_str("application/octet-stream").unwrap(); let mime = mime_guess::from_path(file_path.clone()).first_or(default_mime); - part = part - .mime_str(mime.essence_str()) - .map_err(|e| e.to_string())?; + part = + part.mime_str(mime.essence_str()).map_err(|e| e.to_string())?; } // Set file path if not empty @@ -359,6 +370,7 @@ pub async fn send_http_request( let sendable_req = match request_builder.build() { Ok(r) => r, Err(e) => { + warn!("Failed to build request builder {e:?}"); return Ok(response_err(&*response.lock().await, e.to_string(), window).await); } }; @@ -392,9 +404,7 @@ pub async fn send_http_request( let response_headers = v.headers().clone(); let dir = window.app_handle().path().app_data_dir().unwrap(); let base_dir = dir.join("responses"); - create_dir_all(base_dir.clone()) - .await - .expect("Failed to create responses dir"); + create_dir_all(base_dir.clone()).await.expect("Failed to create responses dir"); let body_path = if response_id.is_empty() { base_dir.join(response_id.clone()) } else { @@ -509,7 +519,8 @@ pub async fn send_http_request( } } Err(e) => { - response_err(&*response.lock().await, e.to_string(), &window).await; + warn!("Failed to execute request {e}"); + response_err(&*response.lock().await, format!("{e} → {e:?}"), &window).await; } }; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a1b87e1e..b3c60227 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -596,6 +596,7 @@ async fn cmd_grpc_go( stream.into_inner() } Some(Err(e)) => { + warn!("GRPC stream error {e:?}"); upsert_grpc_event( &w, &(match e.status { @@ -648,7 +649,7 @@ async fn cmd_grpc_go( &w, &GrpcEvent { content: "Connection complete".to_string(), - status: Some(Code::Unavailable as i32), + status: Some(Code::Ok as i32), metadata: metadata_to_map(trailers), event_type: GrpcEventType::ConnectionEnd, ..base_event.clone() @@ -1164,7 +1165,7 @@ async fn response_err( error: String, w: &WebviewWindow, ) -> HttpResponse { - warn!("Failed to send request: {}", error); + warn!("Failed to send request: {error:?}"); let mut response = response.clone(); response.state = HttpResponseState::Closed; response.error = Some(error.clone()); diff --git a/src-tauri/yaak_grpc/Cargo.toml b/src-tauri/yaak_grpc/Cargo.toml index a8f2ae39..2e54cafc 100644 --- a/src-tauri/yaak_grpc/Cargo.toml +++ b/src-tauri/yaak_grpc/Cargo.toml @@ -4,21 +4,24 @@ version = "0.1.0" edition = "2021" [dependencies] -tonic = "0.10.2" -prost = "0.12" -tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "fs"] } -tonic-reflection = "0.10.2" -tokio-stream = "0.1.14" -prost-types = "0.12.3" -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" -prost-reflect = { version = "0.12.0", features = ["serde", "derive"] } -log = "0.4.20" anyhow = "1.0.79" -hyper = { version = "0.14" } -hyper-rustls = { version = "0.24.0", features = ["http2"] } -uuid = { version = "1.7.0", features = ["v4"] } +dunce = "1.0.4" +hyper = { version = "1.4.1" } +hyper-proxy2 = { version = "0.1.0" } +hyper-rustls = { version = "0.27.3", features = ["http2", "rustls-native-certs"] } +hyper-util = { version = "0.1.9" } +log = "0.4.20" +md5 = "0.7.0" +prost = "0.13.3" +prost-reflect = { version = "0.14.2", features = ["serde", "derive"] } +prost-types = "0.13.3" +rustls = { version = "0.23.12" , features = ["ring"]} +serde = { version = "1.0.210", features = ["derive"] } +serde_json = "1.0.113" tauri = { workspace = true } tauri-plugin-shell = { workspace = true } -md5 = "0.7.0" -dunce = "1.0.4" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "fs"] } +tokio-stream = "0.1.14" +tonic = "0.12.3" +tonic-reflection = "0.12.3" +uuid = { version = "1.7.0", features = ["v4"] } diff --git a/src-tauri/yaak_grpc/src/manager.rs b/src-tauri/yaak_grpc/src/manager.rs index 4c54bb70..fc0ce3a4 100644 --- a/src-tauri/yaak_grpc/src/manager.rs +++ b/src-tauri/yaak_grpc/src/manager.rs @@ -1,10 +1,9 @@ use std::collections::BTreeMap; use std::path::PathBuf; use std::str::FromStr; - -use hyper::client::HttpConnector; -use hyper::Client; use hyper_rustls::HttpsConnector; +use hyper_util::client::legacy::Client; +use hyper_util::client::legacy::connect::HttpConnector; pub use prost_reflect::DynamicMessage; use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor}; use serde_json::Deserializer; @@ -28,7 +27,7 @@ pub struct GrpcConnection { pub uri: Uri, } -#[derive(Default)] +#[derive(Default, Debug)] pub struct StreamError { pub message: String, pub status: Option, @@ -54,19 +53,14 @@ impl From for StreamError { impl GrpcConnection { pub fn service(&self, service: &str) -> Result { - let service = self - .pool - .get_service_by_name(service) - .ok_or("Failed to find service")?; + let service = self.pool.get_service_by_name(service).ok_or("Failed to find service")?; Ok(service) } pub fn method(&self, service: &str, method: &str) -> Result { let service = self.service(service)?; - let method = service - .methods() - .find(|m| m.name() == method) - .ok_or("Failed to find method")?; + let method = + service.methods().find(|m| m.name() == method).ok_or("Failed to find method")?; Ok(method) } @@ -132,13 +126,10 @@ impl GrpcConnection { let path = method_desc_to_path(method); let codec = DynamicCodec::new(method.clone()); client.ready().await.unwrap(); - client - .client_streaming(req, path, codec) - .await - .map_err(|e| StreamError { - message: e.message().to_string(), - status: Some(e), - }) + client.client_streaming(req, path, codec).await.map_err(|e| StreamError { + message: e.message().to_string(), + status: Some(e), + }) } pub async fn server_streaming( @@ -197,8 +188,7 @@ impl GrpcHandle { fill_pool_from_files(&self.app_handle, proto_files).await }?; - self.pools - .insert(make_pool_key(id, uri, proto_files), pool.clone()); + self.pools.insert(make_pool_key(id, uri, proto_files), pool.clone()); Ok(()) } @@ -211,9 +201,7 @@ impl GrpcHandle { // Ensure reflection is up-to-date self.reflect(id, uri, proto_files).await?; - let pool = self - .get_pool(id, uri, proto_files) - .ok_or("Failed to get pool".to_string())?; + let pool = self.get_pool(id, uri, proto_files).ok_or("Failed to get pool".to_string())?; Ok(self.services_from_pool(&pool)) } @@ -249,9 +237,7 @@ impl GrpcHandle { proto_files: &Vec, ) -> Result { self.reflect(id, uri, proto_files).await?; - let pool = self - .get_pool(id, uri, proto_files) - .ok_or("Failed to get pool")?; + let pool = self.get_pool(id, uri, proto_files).ok_or("Failed to get pool")?; let uri = uri_from_str(uri)?; let conn = get_transport(); diff --git a/src-tauri/yaak_grpc/src/proto.rs b/src-tauri/yaak_grpc/src/proto.rs index b5dc9552..4370710a 100644 --- a/src-tauri/yaak_grpc/src/proto.rs +++ b/src-tauri/yaak_grpc/src/proto.rs @@ -1,16 +1,16 @@ -use std::env::temp_dir; -use std::ops::Deref; -use std::path::PathBuf; -use std::str::FromStr; - use anyhow::anyhow; -use hyper::client::HttpConnector; -use hyper::Client; use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; +use hyper_util::client::legacy::connect::HttpConnector; +use hyper_util::client::legacy::Client; +use hyper_util::rt::TokioExecutor; use log::{debug, warn}; use prost::Message; use prost_reflect::{DescriptorPool, MethodDescriptor}; use prost_types::{FileDescriptorProto, FileDescriptorSet}; +use std::env::temp_dir; +use std::ops::Deref; +use std::path::PathBuf; +use std::str::FromStr; use tauri::path::BaseDirectory; use tauri::{AppHandle, Manager}; use tauri_plugin_shell::ShellExt; @@ -20,10 +20,10 @@ use tonic::body::BoxBody; use tonic::codegen::http::uri::PathAndQuery; use tonic::transport::Uri; use tonic::Request; -use tonic_reflection::pb::server_reflection_client::ServerReflectionClient; -use tonic_reflection::pb::server_reflection_request::MessageRequest; -use tonic_reflection::pb::server_reflection_response::MessageResponse; -use tonic_reflection::pb::ServerReflectionRequest; +use tonic_reflection::pb::v1::server_reflection_client::ServerReflectionClient; +use tonic_reflection::pb::v1::server_reflection_request::MessageRequest; +use tonic_reflection::pb::v1::server_reflection_response::MessageResponse; +use tonic_reflection::pb::v1::ServerReflectionRequest; pub async fn fill_pool_from_files( app_handle: &AppHandle, @@ -38,9 +38,8 @@ pub async fn fill_pool_from_files( .expect("failed to resolve protoc include directory"); // HACK: Remove UNC prefix for Windows paths - let global_import_dir = dunce::simplified(global_import_dir.as_path()) - .to_string_lossy() - .to_string(); + let global_import_dir = + dunce::simplified(global_import_dir.as_path()).to_string_lossy().to_string(); let desc_path = dunce::simplified(desc_path.as_path()); let mut args = vec![ @@ -89,12 +88,9 @@ pub async fn fill_pool_from_files( let bytes = fs::read(desc_path).await.map_err(|e| e.to_string())?; let fdp = FileDescriptorSet::decode(bytes.deref()).map_err(|e| e.to_string())?; - pool.add_file_descriptor_set(fdp) - .map_err(|e| e.to_string())?; + pool.add_file_descriptor_set(fdp).map_err(|e| e.to_string())?; - fs::remove_file(desc_path) - .await - .map_err(|e| e.to_string())?; + fs::remove_file(desc_path).await.map_err(|e| e.to_string())?; Ok(pool) } @@ -114,16 +110,34 @@ pub async fn fill_pool_from_reflection(uri: &Uri) -> Result Client, BoxBody> { - let connector = HttpsConnectorBuilder::new().with_native_roots(); - let connector = connector.https_or_http().enable_http2().wrap_connector({ - let mut http_connector = HttpConnector::new(); - http_connector.enforce_http(false); - http_connector - }); - Client::builder() - .pool_max_idle_per_host(0) - .http2_only(true) - .build(connector) + if let Err(_) = rustls::crypto::ring::default_provider().install_default() { + warn!("Default certs already installed"); + } + + let connector = HttpsConnectorBuilder::new() + .with_native_roots() + .unwrap() + .https_or_http() + .enable_http2() + .wrap_connector({ + let mut http_connector = HttpConnector::new(); + http_connector.enforce_http(false); + http_connector + + // TODO: Figure out how to make proxy work. We'll need to run the following on every request: + // if let Some(headers) = proxy.http_headers(&uri) { + // req.headers_mut().extend(headers.clone().into_iter()); + // } + // This means we need to move this connection logic next to where the req is built + + // let proxy_uri = "http://localhost:9090".parse().unwrap(); + // let proxy = Proxy::new(Intercept::All, proxy_uri); + // let mut proxy_connector = ProxyConnector::unsecured(http_connector); + // proxy_connector.add_proxy(proxy); + // proxy_connector + }); + + Client::builder(TokioExecutor::new()).http2_only(true).build(connector) } async fn list_services( @@ -137,11 +151,7 @@ async fn list_services( _ => panic!("Expected a ListServicesResponse variant"), }; - Ok(list_services_response - .service - .iter() - .map(|s| s.name.clone()) - .collect::>()) + Ok(list_services_response.service.iter().map(|s| s.name.clone()).collect::>()) } async fn file_descriptor_set_from_service_name( @@ -157,10 +167,7 @@ async fn file_descriptor_set_from_service_name( { Ok(resp) => resp, Err(e) => { - warn!( - "Error fetching file descriptor for service {}: {}", - service_name, e - ); + warn!("Error fetching file descriptor for service {}: {}", service_name, e); return; } }; @@ -178,8 +185,7 @@ async fn file_descriptor_set_from_service_name( file_descriptor_set_by_filename(&dep_name, pool, client).await; } - pool.add_file_descriptor_proto(fdp) - .expect("add file descriptor proto"); + pool.add_file_descriptor_proto(fdp).expect("add file descriptor proto"); } } @@ -208,8 +214,7 @@ async fn file_descriptor_set_by_filename( for fd in file_descriptor_response.file_descriptor_proto { let fdp = FileDescriptorProto::decode(fd.deref()).unwrap(); - pool.add_file_descriptor_proto(fdp) - .expect("add file descriptor proto"); + pool.add_file_descriptor_proto(fdp).expect("add file descriptor proto"); } } diff --git a/src-tauri/yaak_models/bindings/models.ts b/src-tauri/yaak_models/bindings/models.ts index 0f25155c..f1afec54 100644 --- a/src-tauri/yaak_models/bindings/models.ts +++ b/src-tauri/yaak_models/bindings/models.ts @@ -44,6 +44,10 @@ export type KeyValue = { model: "key_value", createdAt: string, updatedAt: strin export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, }; -export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, telemetry: boolean, theme: string, themeDark: string, themeLight: string, updateChannel: string, }; +export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, } | { "type": "disabled" }; + +export type ProxySettingAuth = { user: string, password: string, }; + +export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, telemetry: boolean, theme: string, themeDark: string, themeLight: string, updateChannel: string, proxy: ProxySetting | null, }; export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, variables: Array, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; diff --git a/src-tauri/yaak_models/src/models.rs b/src-tauri/yaak_models/src/models.rs index 09f7fae2..9382ba82 100644 --- a/src-tauri/yaak_models/src/models.rs +++ b/src-tauri/yaak_models/src/models.rs @@ -6,6 +6,26 @@ use serde_json::Value; use std::collections::BTreeMap; use ts_rs::TS; +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase", tag = "type")] +#[ts(export, export_to = "models.ts")] +pub enum ProxySetting { + Enabled { + http: String, + https: String, + auth: Option, + }, + Disabled, +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export, export_to = "models.ts")] +pub struct ProxySettingAuth { + pub user: String, + pub password: String, +} + #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "models.ts")] @@ -27,6 +47,7 @@ pub struct Settings { pub theme_dark: String, pub theme_light: String, pub update_channel: String, + pub proxy: Option, } #[derive(Iden)] @@ -44,6 +65,7 @@ pub enum SettingsIden { InterfaceFontSize, InterfaceScale, OpenWorkspaceNewWindow, + Proxy, Telemetry, Theme, ThemeDark, @@ -55,22 +77,24 @@ impl<'s> TryFrom<&Row<'s>> for Settings { type Error = rusqlite::Error; fn try_from(r: &Row<'s>) -> Result { + let proxy: Option = r.get("proxy")?; Ok(Settings { id: r.get("id")?, model: r.get("model")?, created_at: r.get("created_at")?, updated_at: r.get("updated_at")?, - theme: r.get("theme")?, appearance: r.get("appearance")?, + editor_font_size: r.get("editor_font_size")?, + editor_soft_wrap: r.get("editor_soft_wrap")?, + interface_font_size: r.get("interface_font_size")?, + interface_scale: r.get("interface_scale")?, + open_workspace_new_window: r.get("open_workspace_new_window")?, + proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }), + telemetry: r.get("telemetry")?, + theme: r.get("theme")?, theme_dark: r.get("theme_dark")?, theme_light: r.get("theme_light")?, update_channel: r.get("update_channel")?, - interface_font_size: r.get("interface_font_size")?, - interface_scale: r.get("interface_scale")?, - editor_font_size: r.get("editor_font_size")?, - editor_soft_wrap: r.get("editor_soft_wrap")?, - telemetry: r.get("telemetry")?, - open_workspace_new_window: r.get("open_workspace_new_window")?, }) } } @@ -431,7 +455,7 @@ pub struct HttpResponseHeader { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[serde( rename_all = "snake_case")] +#[serde(rename_all = "snake_case")] #[ts(export, export_to = "models.ts")] pub enum HttpResponseState { Initialized, @@ -618,7 +642,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcRequest { } #[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[serde( rename_all = "snake_case")] +#[serde(rename_all = "snake_case")] #[ts(export, export_to = "models.ts")] pub enum GrpcConnectionState { Initialized, @@ -626,7 +650,7 @@ pub enum GrpcConnectionState { Closed, } -impl Default for GrpcConnectionState{ +impl Default for GrpcConnectionState { fn default() -> Self { Self::Initialized } @@ -911,7 +935,7 @@ impl ModelType { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase", untagged)] -#[ts(export, export_to="models.ts")] +#[ts(export, export_to = "models.ts")] pub enum AnyModel { CookieJar(CookieJar), Environment(Environment), diff --git a/src-tauri/yaak_models/src/queries.rs b/src-tauri/yaak_models/src/queries.rs index 5f6b2cbb..5fec7cc8 100644 --- a/src-tauri/yaak_models/src/queries.rs +++ b/src-tauri/yaak_models/src/queries.rs @@ -11,6 +11,7 @@ use crate::models::{ use crate::plugin::SqliteConnection; use log::{debug, error}; use rand::distributions::{Alphanumeric, DistString}; +use rusqlite::OptionalExtension; use sea_query::ColumnRef::Asterisk; use sea_query::Keyword::CurrentTimestamp; use sea_query::{Cond, Expr, OnConflict, Order, Query, SqliteQueryBuilder}; @@ -117,9 +118,7 @@ pub async fn set_key_value_raw( .returning_all() .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db - .prepare(sql.as_str()) - .expect("Failed to prepare KeyValue upsert"); + let mut stmt = db.prepare(sql.as_str()).expect("Failed to prepare KeyValue upsert"); let kv = stmt .query_row(&*params.as_params(), |row| row.try_into()) .expect("Failed to upsert KeyValue"); @@ -143,8 +142,7 @@ pub async fn get_key_value_raw( ) .build_rusqlite(SqliteQueryBuilder); - db.query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()) - .ok() + db.query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok() } pub async fn list_workspaces(mgr: &impl Manager) -> Result> { @@ -365,11 +363,7 @@ pub async fn upsert_grpc_request( request.service.as_ref().map(|s| s.as_str()).into(), request.method.as_ref().map(|s| s.as_str()).into(), request.message.as_str().into(), - request - .authentication_type - .as_ref() - .map(|s| s.as_str()) - .into(), + request.authentication_type.as_ref().map(|s| s.as_str()).into(), serde_json::to_string(&request.authentication)?.into(), serde_json::to_string(&request.metadata)?.into(), ]) @@ -434,10 +428,7 @@ pub async fn upsert_grpc_connection( ) -> Result { let connections = list_http_responses_for_request(window, connection.request_id.as_str(), None).await?; - for c in connections - .iter() - .skip(MAX_GRPC_CONNECTIONS_PER_REQUEST - 1) - { + for c in connections.iter().skip(MAX_GRPC_CONNECTIONS_PER_REQUEST - 1) { debug!("Deleting old grpc connection {}", c.id); delete_grpc_connection(window, c.id.as_str()).await?; } @@ -664,7 +655,7 @@ pub async fn list_grpc_events( .from(GrpcEventIden::Table) .cond_where(Expr::col(GrpcEventIden::ConnectionId).eq(connection_id)) .column(Asterisk) - .order_by(GrpcEventIden::CreatedAt, Order::Desc) + .order_by(GrpcEventIden::CreatedAt, Order::Asc) .build_rusqlite(SqliteQueryBuilder); let mut stmt = db.prepare(sql.as_str())?; let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; @@ -757,7 +748,7 @@ pub async fn delete_environment( const SETTINGS_ID: &str = "default"; -async fn get_settings(mgr: &impl Manager) -> Result { +async fn get_settings(mgr: &impl Manager) -> Result> { let dbm = &*mgr.state::(); let db = dbm.0.lock().await.get().unwrap(); @@ -767,13 +758,15 @@ async fn get_settings(mgr: &impl Manager) -> Result { .cond_where(Expr::col(SettingsIden::Id).eq(SETTINGS_ID)) .build_rusqlite(SqliteQueryBuilder); let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) + Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?) } pub async fn get_or_create_settings(mgr: &impl Manager) -> Settings { - if let Ok(settings) = get_settings(mgr).await { - return settings; - } + match get_settings(mgr).await { + Ok(Some(settings)) => return settings, + Ok(None) => (), + Err(e) => panic!("Failed to get settings {e:?}"), + }; let dbm = &*mgr.state::(); let db = dbm.0.lock().await.get().unwrap(); @@ -785,11 +778,8 @@ pub async fn get_or_create_settings(mgr: &impl Manager) -> Settin .returning_all() .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db - .prepare(sql.as_str()) - .expect("Failed to prepare Settings insert"); - stmt.query_row(&*params.as_params(), |row| row.try_into()) - .expect("Failed to insert Settings") + let mut stmt = db.prepare(sql.as_str()).expect("Failed to prepare Settings insert"); + stmt.query_row(&*params.as_params(), |row| row.try_into()).expect("Failed to insert Settings") } pub async fn update_settings( @@ -805,39 +795,23 @@ pub async fn update_settings( .values([ (SettingsIden::Id, "default".into()), (SettingsIden::CreatedAt, CurrentTimestamp.into()), - ( - SettingsIden::Appearance, - settings.appearance.as_str().into(), - ), + (SettingsIden::Appearance, settings.appearance.as_str().into()), (SettingsIden::ThemeDark, settings.theme_dark.as_str().into()), - ( - SettingsIden::ThemeLight, - settings.theme_light.as_str().into(), - ), - ( - SettingsIden::UpdateChannel, - settings.update_channel.as_str().into(), - ), - ( - SettingsIden::InterfaceFontSize, - settings.interface_font_size.into(), - ), - ( - SettingsIden::InterfaceScale, - settings.interface_scale.into(), - ), - ( - SettingsIden::EditorFontSize, - settings.editor_font_size.into(), - ), - ( - SettingsIden::EditorSoftWrap, - settings.editor_soft_wrap.into(), - ), + (SettingsIden::ThemeLight, settings.theme_light.as_str().into()), + (SettingsIden::UpdateChannel, settings.update_channel.into()), + (SettingsIden::InterfaceFontSize, settings.interface_font_size.into()), + (SettingsIden::InterfaceScale, settings.interface_scale.into()), + (SettingsIden::EditorFontSize, settings.editor_font_size.into()), + (SettingsIden::EditorSoftWrap, settings.editor_soft_wrap.into()), (SettingsIden::Telemetry, settings.telemetry.into()), + (SettingsIden::OpenWorkspaceNewWindow, settings.open_workspace_new_window.into()), ( - SettingsIden::OpenWorkspaceNewWindow, - settings.open_workspace_new_window.into(), + SettingsIden::Proxy, + (match settings.proxy { + None => None, + Some(p) => Some(serde_json::to_string(&p)?), + }) + .into(), ), ]) .returning_all() @@ -1308,10 +1282,7 @@ pub async fn create_http_response( elapsed.into(), elapsed_headers.into(), url.into(), - serde_json::to_value(state)? - .as_str() - .unwrap_or_default() - .into(), + serde_json::to_value(state)?.as_str().unwrap_or_default().into(), status.into(), status_reason.into(), content_length.into(), @@ -1391,32 +1362,15 @@ pub async fn update_http_response( HttpResponseIden::StatusReason, response.status_reason.as_ref().map(|s| s.as_str()).into(), ), - ( - HttpResponseIden::ContentLength, - response.content_length.into(), - ), - ( - HttpResponseIden::BodyPath, - response.body_path.as_ref().map(|s| s.as_str()).into(), - ), - ( - HttpResponseIden::Error, - response.error.as_ref().map(|s| s.as_str()).into(), - ), + (HttpResponseIden::ContentLength, response.content_length.into()), + (HttpResponseIden::BodyPath, response.body_path.as_ref().map(|s| s.as_str()).into()), + (HttpResponseIden::Error, response.error.as_ref().map(|s| s.as_str()).into()), ( HttpResponseIden::Headers, - serde_json::to_string(&response.headers) - .unwrap_or_default() - .into(), - ), - ( - HttpResponseIden::Version, - response.version.as_ref().map(|s| s.as_str()).into(), - ), - ( - HttpResponseIden::State, - serde_json::to_value(&response.state)?.as_str().into(), + serde_json::to_string(&response.headers).unwrap_or_default().into(), ), + (HttpResponseIden::Version, response.version.as_ref().map(|s| s.as_str()).into()), + (HttpResponseIden::State, serde_json::to_value(&response.state)?.as_str().into()), ( HttpResponseIden::RemoteAddr, response.remote_addr.as_ref().map(|s| s.as_str()).into(), diff --git a/src-web/components/Settings/Settings.tsx b/src-web/components/Settings/Settings.tsx index 7ae66668..a60ac4ce 100644 --- a/src-web/components/Settings/Settings.tsx +++ b/src-web/components/Settings/Settings.tsx @@ -10,6 +10,7 @@ import { HeaderSize } from '../HeaderSize'; import { SettingsAppearance } from './SettingsAppearance'; import { SettingsGeneral } from './SettingsGeneral'; import { SettingsPlugins } from './SettingsPlugins'; +import {SettingsProxy} from "./SettingsProxy"; interface Props { hide?: () => void; @@ -17,11 +18,12 @@ interface Props { enum Tab { General = 'general', + Proxy = 'proxy', Appearance = 'appearance', Plugins = 'plugins', } -const tabs = [Tab.General, Tab.Appearance, Tab.Plugins]; +const tabs = [Tab.General, Tab.Appearance, Tab.Proxy, Tab.Plugins]; export default function Settings({ hide }: Props) { const osInfo = useOsInfo(); @@ -78,6 +80,9 @@ export default function Settings({ hide }: Props) { + + + ); diff --git a/src-web/components/Settings/SettingsGeneral.tsx b/src-web/components/Settings/SettingsGeneral.tsx index 6a260ab4..d68d5509 100644 --- a/src-web/components/Settings/SettingsGeneral.tsx +++ b/src-web/components/Settings/SettingsGeneral.tsx @@ -27,12 +27,13 @@ export function SettingsGeneral() { } return ( - +
{ + if (v === 'automatic') { + updateSettings.mutate({ proxy: undefined }); + } else if (v === 'enabled') { + updateSettings.mutate({ + proxy: { + type: 'enabled', + http: '', + https: '', + auth: { user: '', password: '' }, + }, + }); + } else { + updateSettings.mutate({ proxy: { type: 'disabled' } }); + } + }} + options={[ + { label: 'Automatic Proxy Detection', value: 'automatic' }, + { label: 'Custom Proxy Configuration', value: 'enabled' }, + { label: 'No Proxy', value: 'disabled' }, + ]} + /> + {settings.proxy?.type === 'enabled' && ( + + + { + const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : ''; + const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null; + updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } }); + }} + /> + { + const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : ''; + const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null; + updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } }); + }} + /> + + + { + const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : ''; + const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : ''; + const auth = enabled ? { user: '', password: '' } : null; + updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } }); + }} + /> + + {settings.proxy.auth != null && ( + + { + const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : ''; + const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : ''; + const password = + settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.password ?? '') : ''; + const auth = { user, password }; + updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } }); + }} + /> + { + const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : ''; + const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : ''; + const user = + settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.user ?? '') : ''; + const auth = { user, password }; + updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } }); + }} + /> + + )} + + )} + + ); +} diff --git a/src-web/components/core/Banner.tsx b/src-web/components/core/Banner.tsx index 5d6cc86e..7074d6ee 100644 --- a/src-web/components/core/Banner.tsx +++ b/src-web/components/core/Banner.tsx @@ -14,6 +14,7 @@ export function Banner({ children, className, color = 'secondary' }: Props) { className={classNames( className, `x-theme-banner--${color}`, + 'whitespace-pre-wrap', 'border border-dashed border-border-subtle bg-surface', 'italic px-3 py-2 rounded select-auto cursor-text', 'overflow-x-auto text-text', diff --git a/src-web/components/core/PlainInput.tsx b/src-web/components/core/PlainInput.tsx index c2b37b92..a91a3eca 100644 --- a/src-web/components/core/PlainInput.tsx +++ b/src-web/components/core/PlainInput.tsx @@ -93,7 +93,7 @@ export const PlainInput = forwardRef(function htmlFor={id} className={classNames( labelClassName, - 'text-text-subtle whitespace-nowrap', + 'text-text-subtle whitespace-nowrap flex-shrink-0', hideLabel && 'sr-only', )} >