mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-11-13 12:13:42 +01:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a34d0a46d | ||
|
|
35058cc3be | ||
|
|
1f0541f379 | ||
|
|
f0e767d77e | ||
|
|
075fcaf2c4 |
No files matched your search
Generated
+122
-140
@@ -109,7 +109,7 @@ checksum = "f6f39be698127218cca460cb624878c9aa4e2b47dba3b277963d2bf00bad263b"
|
||||
dependencies = [
|
||||
"android_log-sys",
|
||||
"env_filter 0.1.3",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -211,7 +211,7 @@ checksum = "aeb9553e62312c8756a5979254fc6d7da8bbeb5986a1e17037fab932f7037917"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"image",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
@@ -304,9 +304,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.52"
|
||||
version = "0.4.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ad47a740fb906fffdd891e8b6d5fde2511600f7bfaebd68939fa08a14999459"
|
||||
checksum = "9d5755640120b24961f8231e05892e4a4f7e0d04e8aeca161508c9c397946970"
|
||||
dependencies = [
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
@@ -676,9 +676,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
||||
dependencies = [
|
||||
"objc2 0.6.5",
|
||||
]
|
||||
@@ -1186,9 +1186,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "compression-codecs"
|
||||
version = "0.4.47"
|
||||
version = "0.4.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84f840cff84d10085260e460023934d0af699e71212b09b2a41452e8bd9cda43"
|
||||
checksum = "cae36683101b2899f48bd31f8db9839437356188e3b1ff23096e73dab09ba8fe"
|
||||
dependencies = [
|
||||
"brotli 9.1.1",
|
||||
"compression-core",
|
||||
@@ -1334,7 +1334,7 @@ dependencies = [
|
||||
"document-features",
|
||||
"idna",
|
||||
"indexmap 2.14.2",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
@@ -1441,12 +1441,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core_detect"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
|
||||
|
||||
[[package]]
|
||||
name = "countme"
|
||||
version = "3.0.1"
|
||||
@@ -2013,7 +2007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.5",
|
||||
]
|
||||
@@ -2223,16 +2217,11 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.44"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89253175e704ab869b04f9db224d38630e0211a53841a79181deae0889e169f5"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core_detect",
|
||||
"multiversion_no_op",
|
||||
"rustversion",
|
||||
"scopeguard",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2274,7 +2263,7 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"regex 1.13.1",
|
||||
]
|
||||
|
||||
@@ -2284,7 +2273,7 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"regex 1.13.1",
|
||||
]
|
||||
|
||||
@@ -2298,7 +2287,7 @@ dependencies = [
|
||||
"anstyle",
|
||||
"env_filter 2.0.0",
|
||||
"jiff",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2359,7 +2348,7 @@ name = "eventsource-client"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/yaakapp/rust-eventsource-client#60e0e3ac5038149c4778dc4979b09b152214f9a8"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"pin-project",
|
||||
]
|
||||
|
||||
@@ -2450,7 +2439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2495,9 +2484,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.11"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "626d4928326b646e70f65ed36fc7cf9f206bdb68a0019ee7c6b4709da4e084c4"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.9.1",
|
||||
@@ -2979,7 +2968,7 @@ dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
@@ -3237,7 +3226,7 @@ version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"mac",
|
||||
"markup5ever 0.14.1",
|
||||
"match_token",
|
||||
@@ -3249,7 +3238,7 @@ version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"markup5ever 0.38.0",
|
||||
]
|
||||
|
||||
@@ -3421,7 +3410,7 @@ dependencies = [
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
@@ -3831,7 +3820,7 @@ dependencies = [
|
||||
"defmt",
|
||||
"jiff-core",
|
||||
"jiff-static",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
@@ -3844,7 +3833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3869,7 +3858,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-sys 0.3.0",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
@@ -3885,7 +3874,7 @@ dependencies = [
|
||||
"combine",
|
||||
"jni-macros",
|
||||
"jni-sys 0.4.1",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.21",
|
||||
"walkdir",
|
||||
@@ -4014,7 +4003,7 @@ checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"dbus-secret-service",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework 3.7.0",
|
||||
"windows-sys 0.60.2",
|
||||
@@ -4075,7 +4064,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"gtk-sys",
|
||||
"libappindicator-sys",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4224,14 +4213,14 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.35"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rust2"
|
||||
@@ -4359,7 +4348,7 @@ version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"phf 0.11.3",
|
||||
"phf_codegen 0.11.3",
|
||||
"string_cache 0.8.9",
|
||||
@@ -4373,7 +4362,7 @@ version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"tendril 0.5.0",
|
||||
"web_atoms",
|
||||
]
|
||||
@@ -4403,9 +4392,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c"
|
||||
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -4486,7 +4475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -4498,7 +4487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@@ -4524,12 +4513,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multiversion_no_op"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
|
||||
|
||||
[[package]]
|
||||
name = "nanoid"
|
||||
version = "0.5.0"
|
||||
@@ -4546,7 +4529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
@@ -4564,7 +4547,7 @@ checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"jni-sys 0.3.0",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"ndk-sys",
|
||||
"num_enum",
|
||||
"raw-window-handle",
|
||||
@@ -4688,7 +4671,7 @@ dependencies = [
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"mio 1.2.4",
|
||||
"notify-types",
|
||||
"objc2-core-foundation",
|
||||
@@ -4819,7 +4802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.5",
|
||||
"objc2-cloud-kit",
|
||||
@@ -4968,7 +4951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.5",
|
||||
"objc2-core-foundation",
|
||||
@@ -5074,7 +5057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"objc2 0.6.5",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
@@ -5104,7 +5087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
@@ -5179,9 +5162,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.118"
|
||||
version = "0.9.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19c413d6ec9ebaf5e1c829d355cb3e9f783778d700f135be0921d52fce3c60ae"
|
||||
checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -5231,7 +5214,7 @@ version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fc863e2ca13dc2d5c34fb22ea4a588248ac14db929616ba65c45f21744b1e9"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -6613,7 +6596,7 @@ version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"parking_lot",
|
||||
"scheduled-thread-pool",
|
||||
]
|
||||
@@ -6935,7 +6918,7 @@ dependencies = [
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
@@ -6969,13 +6952,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"dispatch2",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
"js-sys",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
@@ -7014,9 +6997,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rmcp"
|
||||
version = "3.9.0"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24a7e92f4bcaa36b97fa93c4fa7ca8f3e669eced949345b3462dba333c7f909"
|
||||
checksum = "f8be15815186873ea063bf005011d16c356804973489e40c39116ae3a2878d1f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.23.1",
|
||||
@@ -7035,7 +7018,6 @@ dependencies = [
|
||||
"schemars 1.2.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.1",
|
||||
"sse-stream",
|
||||
"thiserror 2.0.21",
|
||||
"tokio",
|
||||
@@ -7048,9 +7030,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rmcp-macros"
|
||||
version = "3.9.0"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3140f24a766f80f9ec1607bde85680959a69b83d46fa9ce87dadab7e67ab029"
|
||||
checksum = "8b0748e054a5f5598b3213cf72099ae6b2745ba4d996c59ccf455150a7dbb839"
|
||||
dependencies = [
|
||||
"darling 0.24.1",
|
||||
"proc-macro2",
|
||||
@@ -7145,7 +7127,7 @@ dependencies = [
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"mio 1.2.4",
|
||||
"rolldown-notify-types",
|
||||
"walkdir",
|
||||
@@ -7158,7 +7140,7 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69128ae9ebf6d65638157e79dc9430e9341179aed9d85dde315ce6a702dba7e1"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"rolldown-file-id",
|
||||
"rolldown-notify",
|
||||
"rolldown-notify-types",
|
||||
@@ -7579,7 +7561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -7627,7 +7609,7 @@ dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni 0.22.4",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
@@ -7889,7 +7871,7 @@ dependencies = [
|
||||
"cssparser 0.29.6",
|
||||
"derive_more 0.99.20",
|
||||
"fxhash",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"phf 0.8.0",
|
||||
"phf_codegen 0.8.0",
|
||||
"precomputed-hash",
|
||||
@@ -7906,7 +7888,7 @@ dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"cssparser 0.36.0",
|
||||
"derive_more 2.1.1",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"new_debug_unreachable",
|
||||
"phf 0.13.1",
|
||||
"phf_codegen 0.13.1",
|
||||
@@ -8388,7 +8370,7 @@ dependencies = [
|
||||
"core-graphics 0.24.0",
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
@@ -8714,7 +8696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33f7f9e486ade65fcf1e45c440f9236c904f5c1002cdc7fc6ae582777345ce4"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics 0.25.0",
|
||||
"crossbeam-channel",
|
||||
@@ -8727,7 +8709,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"jni 0.21.1",
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"ndk",
|
||||
"ndk-sys",
|
||||
"objc2 0.6.5",
|
||||
@@ -8794,7 +8776,7 @@ dependencies = [
|
||||
"http-range",
|
||||
"jni 0.21.1",
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"mime",
|
||||
"muda",
|
||||
"objc2 0.6.5",
|
||||
@@ -8909,7 +8891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4136fb69d967753d000423d7e5f863f89bf949efbdfbecb43a580426a01a0194"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -8944,7 +8926,7 @@ version = "2.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61854a36651aa48381e5e209f69a01273b77f3f9f91f0c430b1b98d33bd47229"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"raw-window-handle",
|
||||
"rfd",
|
||||
"serde",
|
||||
@@ -8965,7 +8947,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2-foundation 0.3.2",
|
||||
"percent-encoding",
|
||||
"schemars 0.8.22",
|
||||
@@ -8988,7 +8970,7 @@ checksum = "86f2e3c40b77fd1eb7f8e1b6900fb036aee2da1ea2d3783c91255a82c636649d"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"fern",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2 0.6.5",
|
||||
"objc2-foundation 0.3.2",
|
||||
"serde",
|
||||
@@ -9030,7 +9012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997"
|
||||
dependencies = [
|
||||
"gethostname 1.0.2",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"os_info",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -9048,7 +9030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8548af174c5516e4f71f142acea4d02e00316296ea9aafa58798851481003e3c"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"open",
|
||||
"os_pipe",
|
||||
"regex 1.13.1",
|
||||
@@ -9091,7 +9073,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"infer",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
@@ -9147,7 +9129,7 @@ dependencies = [
|
||||
"html5ever 0.29.1",
|
||||
"http",
|
||||
"kuchikiki",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.2",
|
||||
@@ -9170,7 +9152,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"jni 0.21.1",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit",
|
||||
"objc2-web-kit",
|
||||
@@ -9205,7 +9187,7 @@ dependencies = [
|
||||
"infer",
|
||||
"json-patch",
|
||||
"kuchikiki",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"memchr",
|
||||
"phf 0.13.1",
|
||||
"plist",
|
||||
@@ -9242,7 +9224,7 @@ dependencies = [
|
||||
"http",
|
||||
"infer",
|
||||
"json-patch",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"memchr",
|
||||
"phf 0.13.1",
|
||||
"plist",
|
||||
@@ -9548,7 +9530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17a073bfed563fa236697a068031408a93cd9522e08abf9933ead3e73411bd71"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
@@ -9813,7 +9795,7 @@ version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
@@ -9857,7 +9839,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
@@ -9970,7 +9952,7 @@ dependencies = [
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"rand 0.10.3",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
@@ -10163,7 +10145,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"cookie_store",
|
||||
"flate2",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
@@ -10184,7 +10166,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"http",
|
||||
"httparse",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10249,9 +10231,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.29.1"
|
||||
version = "1.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a06f08a856b009bc7bbaf7d3e79808595d032dd19074c0c2ef26e49e03e6bdc"
|
||||
checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
|
||||
dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"js-sys",
|
||||
@@ -10519,7 +10501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
@@ -10557,12 +10539,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webbrowser"
|
||||
version = "1.2.6"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe8d20d0214027baf11b8b0cc307c1da7657d8f4db9aa956f7de27a6fb1acbd5"
|
||||
checksum = "d2c8ddfbbf66e501434657400268cefe51b95140a0266236415cda7627732df7"
|
||||
dependencies = [
|
||||
"jni 0.22.4",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"ndk-context",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit",
|
||||
@@ -11306,7 +11288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "959c7ac3b8afde864f7a7448ac16f29b2b83350b7eccbf77fb781c3999aa6848"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"os_pipe",
|
||||
"rustix 1.1.5",
|
||||
"thiserror 2.0.21",
|
||||
@@ -11330,7 +11312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dirs 6.0.0",
|
||||
@@ -11428,7 +11410,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.23.1",
|
||||
"chrono",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"md5",
|
||||
"rusqlite",
|
||||
"serde_json",
|
||||
@@ -11450,7 +11432,7 @@ dependencies = [
|
||||
name = "yaak-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"reqwest",
|
||||
"sysproxy",
|
||||
"thiserror 2.0.21",
|
||||
@@ -11467,7 +11449,7 @@ dependencies = [
|
||||
"cookie",
|
||||
"eventsource-client",
|
||||
"http",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"md5",
|
||||
"mime_guess",
|
||||
"notify",
|
||||
@@ -11534,7 +11516,7 @@ dependencies = [
|
||||
name = "yaak-app-proxy"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
@@ -11566,7 +11548,7 @@ dependencies = [
|
||||
"include_dir",
|
||||
"inquire",
|
||||
"keyring",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"oxc_resolver",
|
||||
"predicates",
|
||||
"rand 0.10.3",
|
||||
@@ -11638,7 +11620,7 @@ dependencies = [
|
||||
"chacha20poly1305",
|
||||
"dbus-secret-service",
|
||||
"keyring",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror 2.0.21",
|
||||
@@ -11652,7 +11634,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"getrandom 0.3.4",
|
||||
"include_dir",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"nanoid",
|
||||
"openssl-sys",
|
||||
"r2d2",
|
||||
@@ -11697,7 +11679,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"git2",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
@@ -11720,7 +11702,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"md5",
|
||||
"prost",
|
||||
"prost-reflect",
|
||||
@@ -11751,7 +11733,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http-body",
|
||||
"hyper-util",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"reqwest",
|
||||
@@ -11776,7 +11758,7 @@ name = "yaak-license"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -11793,7 +11775,7 @@ dependencies = [
|
||||
name = "yaak-lifecycle"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"yaak-models",
|
||||
]
|
||||
|
||||
@@ -11803,7 +11785,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"csscolorparser",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc",
|
||||
"rand 0.10.3",
|
||||
"tauri",
|
||||
@@ -11818,7 +11800,7 @@ dependencies = [
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"reqwest",
|
||||
"rmcp",
|
||||
"serde",
|
||||
@@ -11836,7 +11818,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"include_dir",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"nanoid",
|
||||
"r2d2",
|
||||
"r2d2_sqlite",
|
||||
@@ -11863,7 +11845,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -11882,7 +11864,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hex",
|
||||
"keyring",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"md5",
|
||||
"path-slash",
|
||||
"rand 0.10.3",
|
||||
@@ -11906,7 +11888,7 @@ name = "yaak-proxy"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"block2 0.6.2",
|
||||
"block2 0.6.1",
|
||||
"bytes",
|
||||
"dispatch2",
|
||||
"flate2",
|
||||
@@ -11916,7 +11898,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"pem",
|
||||
"rcgen",
|
||||
"reqwest",
|
||||
@@ -11959,7 +11941,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"include_dir",
|
||||
"libc",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.2",
|
||||
@@ -12012,7 +11994,7 @@ dependencies = [
|
||||
name = "yaak-rpc"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ts-rs",
|
||||
@@ -12048,7 +12030,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"notify",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -12068,7 +12050,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"dark-light",
|
||||
"dispatch2",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.2",
|
||||
"tauri",
|
||||
@@ -12088,7 +12070,7 @@ name = "yaak-templates"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
@@ -12102,7 +12084,7 @@ dependencies = [
|
||||
name = "yaak-tls"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"p12",
|
||||
"pem",
|
||||
"rustls",
|
||||
@@ -12119,7 +12101,7 @@ dependencies = [
|
||||
name = "yaak-updater"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"md5",
|
||||
"nanoid",
|
||||
"serde",
|
||||
@@ -12138,7 +12120,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
@@ -12163,7 +12145,7 @@ dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
@@ -12181,7 +12163,7 @@ dependencies = [
|
||||
name = "yaak-window"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"md5",
|
||||
"rand 0.10.3",
|
||||
"serde",
|
||||
@@ -12196,7 +12178,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"md5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -12480,7 +12462,7 @@ checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log 0.4.35",
|
||||
"log 0.4.34",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
|
||||
@@ -47,27 +47,14 @@ export function ClearFlowsButton() {
|
||||
<IconButton
|
||||
className="pointer-events-auto"
|
||||
icon="trash"
|
||||
title={
|
||||
locked != null
|
||||
? "Clear flows (traffic is locked)"
|
||||
: total === 0
|
||||
? "Clear flows (none yet)"
|
||||
: hotkey == null
|
||||
? "Clear flows"
|
||||
: `Clear flows ${hotkey}`
|
||||
}
|
||||
title={action === "disabled" || hotkey == null ? "Clear flows" : `Clear flows ${hotkey}`}
|
||||
tooltip
|
||||
size="sm"
|
||||
iconColor="secondary"
|
||||
disabled={action === "disabled"}
|
||||
onClick={start}
|
||||
/>
|
||||
<Dialog
|
||||
open={confirming}
|
||||
onClose={() => setConfirming(false)}
|
||||
size="sm"
|
||||
title="Clear traffic?"
|
||||
>
|
||||
<Dialog open={confirming} onClose={() => setConfirming(false)} size="sm" title="Clear Flows">
|
||||
{confirming && (
|
||||
<ConfirmClear
|
||||
total={total}
|
||||
@@ -103,8 +90,8 @@ function ConfirmClear({
|
||||
<form id={formId} className="flex flex-col gap-3" onSubmit={handleSubmit}>
|
||||
<p>
|
||||
{total === 1
|
||||
? "1 flow will be removed."
|
||||
: `${total.toLocaleString()} flows will be removed.`}
|
||||
? "Permanently clear 1 flow?"
|
||||
: `Permanently clear ${total.toLocaleString()} flows?`}
|
||||
</p>
|
||||
<Checkbox checked={dontAskAgain} title="Don't ask again" onChange={setDontAskAgain} />
|
||||
<DialogFooter
|
||||
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
everythingEdit,
|
||||
normalizeEntry,
|
||||
} from "../lib/decrypt";
|
||||
import { revealPath } from "../lib/opener";
|
||||
import { revealLabel, revealPath } from "../lib/opener";
|
||||
import { getOsType } from "../lib/tauri";
|
||||
import { useShowToast } from "../lib/toast";
|
||||
|
||||
/**
|
||||
@@ -75,21 +76,23 @@ export function DecryptionSettings() {
|
||||
<CertificateRow />
|
||||
<SettingRowBoolean
|
||||
title="Decrypt everything"
|
||||
description="Except built-in exclusions"
|
||||
description="Excluded hosts and apps stay encrypted."
|
||||
checked={everything}
|
||||
disabled={busy}
|
||||
onChange={(on) =>
|
||||
void change(
|
||||
everythingEdit(on),
|
||||
on ? "Decrypting everything" : "Stopped decrypting everything",
|
||||
on ? "Except Apple services and apps that pin" : undefined,
|
||||
on ? "Excluded hosts and apps stay encrypted." : undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div className="py-4 grid gap-2">
|
||||
<div className="flex items-center text-text">
|
||||
Hosts and apps
|
||||
<span className="ml-auto text-sm text-text-subtlest">New connections</span>
|
||||
<span className="ml-auto text-sm text-text-subtlest">
|
||||
Changes apply to new connections.
|
||||
</span>
|
||||
</div>
|
||||
<EntryInput
|
||||
seenRef={seenRef}
|
||||
@@ -106,7 +109,7 @@ export function DecryptionSettings() {
|
||||
/>
|
||||
<div className="rounded-md border border-border-subtle divide-y divide-border-subtle">
|
||||
{own.length === 0 && (
|
||||
<div className="px-3 py-2 text-sm text-text-subtle">Nothing listed</div>
|
||||
<div className="px-3 py-2 text-sm text-text-subtle">No hosts or apps</div>
|
||||
)}
|
||||
{own.map((e) => (
|
||||
<EntryRow key={e.entry} entry={e} disabled={busy} onRemove={() => remove(e.entry)} />
|
||||
@@ -156,7 +159,7 @@ function CertificateRow() {
|
||||
)
|
||||
}
|
||||
>
|
||||
Reveal
|
||||
{revealLabel(getOsType())}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -286,7 +289,7 @@ function EntryInput({
|
||||
if (disabledRef.current) return true;
|
||||
const entry = normalizeEntry(text);
|
||||
if (entry == null) {
|
||||
setError("Not a host or app");
|
||||
setError("Invalid host or app");
|
||||
return true;
|
||||
}
|
||||
setError(null);
|
||||
@@ -299,20 +302,18 @@ function EntryInput({
|
||||
}, [seenRef]);
|
||||
|
||||
return (
|
||||
<Tooltip wrap content="Applies to new connections" delay={CONTROL_TOOLTIP_DELAY}>
|
||||
<div className="grid gap-1">
|
||||
<Input
|
||||
label="Add entry"
|
||||
hideLabel
|
||||
size="sm"
|
||||
stateKey={null}
|
||||
language={null}
|
||||
placeholder="Add host or app"
|
||||
extensions={extensions}
|
||||
onChange={() => setError(null)}
|
||||
/>
|
||||
{error != null && <div className="text-xs text-danger">{error}</div>}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="grid gap-1">
|
||||
<Input
|
||||
label="Add entry"
|
||||
hideLabel
|
||||
size="sm"
|
||||
stateKey={null}
|
||||
language={null}
|
||||
placeholder="Add host or app"
|
||||
extensions={extensions}
|
||||
onChange={() => setError(null)}
|
||||
/>
|
||||
{error != null && <div className="text-xs text-danger">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -126,8 +126,7 @@ export function EncryptedCard({ flow }: { flow: Flow }) {
|
||||
<div className="mx-auto max-w-[27rem] py-10 grid gap-2">
|
||||
<div className="text-sm text-text mb-1">
|
||||
<LineText line={model.why} />
|
||||
{model.whySub != null && <span className="text-text-subtle"> · {model.whySub}</span>}
|
||||
<Tooltip wrap content={ABOUT} delay={CONTROL_TOOLTIP_DELAY}>
|
||||
<Tooltip wrap content={model.tooltip ?? ABOUT} delay={CONTROL_TOOLTIP_DELAY}>
|
||||
<IconButton
|
||||
size="2xs"
|
||||
icon="info"
|
||||
@@ -137,11 +136,6 @@ export function EncryptedCard({ flow }: { flow: Flow }) {
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{model.fact != null && (
|
||||
<div className="text-sm text-text-subtle -mt-1 mb-1">
|
||||
<LineText line={model.fact} />
|
||||
</div>
|
||||
)}
|
||||
{model.actions.map((action) => (
|
||||
<ActionButton
|
||||
key={action.label}
|
||||
@@ -162,7 +156,7 @@ export function EncryptedCard({ flow }: { flow: Flow }) {
|
||||
/>
|
||||
))}
|
||||
{model.foot.length > 0 && (
|
||||
<div className="text-xs text-text-subtlest mt-0.5">{model.foot.join(" · ")}</div>
|
||||
<div className="text-xs text-text-subtlest mt-0.5">{model.foot.join(" ")}</div>
|
||||
)}
|
||||
{flow.error != null && flow.tlsRejected == null && (
|
||||
<div className="text-xs text-danger mt-1">{flow.error}</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Icon, InlineCode, LoadingIcon } from "@yaakapp-internal/ui";
|
||||
import { Button, Icon, IconTooltip, InlineCode, LoadingIcon } from "@yaakapp-internal/ui";
|
||||
import classNames from "classnames";
|
||||
import { useAtomValue } from "jotai";
|
||||
import type { ReactNode } from "react";
|
||||
@@ -49,7 +49,7 @@ export function FirstRun() {
|
||||
<div className="text-xl font-semibold mb-1.5">Set up Yaak Proxy</div>
|
||||
<div className="text-text-subtle text-sm leading-relaxed mb-4">
|
||||
{systemSupported
|
||||
? "Two steps, once. Then every app on this Mac sends its traffic here."
|
||||
? "Traffic from every app on this Mac will be recorded."
|
||||
: "Traffic appears here as apps send it."}
|
||||
</div>
|
||||
<div className="rounded-lg border border-border-subtle bg-surface divide-y divide-border-subtle">
|
||||
@@ -57,12 +57,12 @@ export function FirstRun() {
|
||||
<Step
|
||||
n={1}
|
||||
state={systemState}
|
||||
title={systemOn ? "System proxy on" : "Turn on the system proxy"}
|
||||
title={systemOn ? "System proxy overridden" : "Override system proxy"}
|
||||
description={
|
||||
otherOwner && !systemOn
|
||||
? proxyState?.otherOwner?.owner === "cli"
|
||||
? "The CLI owns the proxy. Take over to turn this on from here."
|
||||
: "Another Yaak Proxy window owns the engine. Quit it to use this one."
|
||||
? "Take over from the CLI to change this."
|
||||
: "Quit the other Yaak Proxy to use this one."
|
||||
: systemSwitch.description
|
||||
}
|
||||
action={
|
||||
@@ -73,7 +73,7 @@ export function FirstRun() {
|
||||
color={systemState === "fail" ? "default" : "primary"}
|
||||
onClick={() => setSystemProxy.mutate({ enabled: true })}
|
||||
>
|
||||
{systemState === "fail" ? "Try Again" : "Turn On"}
|
||||
{systemState === "fail" ? "Retry" : "Enable"}
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
@@ -84,20 +84,20 @@ export function FirstRun() {
|
||||
state={trafficState}
|
||||
title={
|
||||
encrypting != null
|
||||
? `Encrypting stored traffic, ${encrypting}`
|
||||
? `Encrypting recorded traffic, ${encrypting}`
|
||||
: trafficState === "wait"
|
||||
? "Waiting for traffic"
|
||||
: "See traffic"
|
||||
}
|
||||
description={
|
||||
encrypting != null
|
||||
? "Traffic captured before this version is being encrypted on disk. Recording starts when it's done."
|
||||
? "Recording will start once encryption finishes."
|
||||
: trafficState === "fail"
|
||||
? (retry.error?.message ?? proxyState?.startError ?? "The proxy has not started.")
|
||||
? (retry.error?.message ?? proxyState?.startError ?? "The proxy failed to start.")
|
||||
: !recording
|
||||
? "Recording is paused, so traffic passes through without being stored."
|
||||
? "Recording is paused."
|
||||
: trafficState === "wait"
|
||||
? "Open a website or send a request, and its flow appears here."
|
||||
? "Open a website or send a request to begin."
|
||||
: "Flows appear here as apps send requests."
|
||||
}
|
||||
action={
|
||||
@@ -123,7 +123,7 @@ export function FirstRun() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-4 text-sm text-text-subtle">
|
||||
<span>Only need one terminal or one app? Copy the environment instead.</span>
|
||||
<span>To proxy a single terminal instead, copy the environment.</span>
|
||||
<Button
|
||||
size="2xs"
|
||||
variant="border"
|
||||
@@ -135,12 +135,14 @@ export function FirstRun() {
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-text-subtle">
|
||||
Working with an agent? <InlineCode>yaak mcp</InlineCode> gives it the proxy's flows and
|
||||
actions.
|
||||
Agents can connect through <InlineCode>yaak mcp</InlineCode>.
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-text-subtle">
|
||||
Captured traffic is encrypted on disk with a key kept in your keychain, so macOS may ask
|
||||
to let Yaak Proxy use it.
|
||||
<div className="flex items-center gap-1.5 mt-2 text-sm text-text-subtle">
|
||||
Recorded traffic is encrypted on disk.
|
||||
<IconTooltip
|
||||
iconSize="sm"
|
||||
content="The key is kept in the system keychain, so the system may ask to let Yaak Proxy use it."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
CsvViewer,
|
||||
HexdumpViewer,
|
||||
Icon,
|
||||
IconTooltip,
|
||||
ImageViewer,
|
||||
LARGE_BODY_BYTES,
|
||||
languageFromContentType,
|
||||
@@ -238,14 +239,20 @@ export function FlowBodyViewer({ flow, which, form, hideCutNote = false }: Props
|
||||
<div className="h-full grid grid-rows-[auto_auto_minmax(0,1fr)]">
|
||||
{decodeError != null && (
|
||||
<Banner color="danger" className="text-sm mb-2">
|
||||
{decodeError}. Showing the body as it was on the wire.
|
||||
{decodeError}. The body is shown as it was received.
|
||||
</Banner>
|
||||
)}
|
||||
{truncated && !hideCutNote && (
|
||||
<Banner color="notice" className="text-sm mb-2">
|
||||
{wireTruncated || decodeError != null
|
||||
? `Showing the first ${formatSize(storedSize)} of ${formatSize(wireSize)}, the capture limit. The app received all of it.`
|
||||
: `Showing the first ${formatSize(bytes.length)} of the decoded body, the capture limit. The app received all of it.`}
|
||||
<span className="flex items-center gap-1.5">
|
||||
{wireTruncated || decodeError != null
|
||||
? `The first ${formatSize(storedSize)} of ${formatSize(wireSize)} is shown.`
|
||||
: `The first ${formatSize(bytes.length)} of the decoded body is shown.`}
|
||||
<IconTooltip
|
||||
iconSize="sm"
|
||||
content="The body was truncated at the capture limit. The app received all of it."
|
||||
/>
|
||||
</span>
|
||||
</Banner>
|
||||
)}
|
||||
<div className="min-h-0 row-start-3">{viewer}</div>
|
||||
|
||||
@@ -323,7 +323,9 @@ function FlowResponseHalf({ flow, className }: { flow: Flow; className: string }
|
||||
)}
|
||||
>
|
||||
<Icon icon="scissors" size="xs" />
|
||||
{body.data == null ? "Body cut" : `First ${formatSize(body.data.storedSize)} kept`}
|
||||
{body.data == null
|
||||
? "Body truncated"
|
||||
: `First ${formatSize(body.data.storedSize)} recorded`}
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ export function FlowInfo({ flow }: { flow: Flow }) {
|
||||
"Started",
|
||||
format(utcTimestamp(flow.startedAt), "MMM d, yyyy, h:mm:ss.SSS a"),
|
||||
),
|
||||
row("headers", "Headers", ms(flow.elapsedHeadersMs)),
|
||||
row("total", "Total", ms(flow.elapsedMs)),
|
||||
row("headers", "Latency", ms(flow.elapsedHeadersMs)),
|
||||
row("total", "Time", ms(flow.elapsedMs)),
|
||||
]}
|
||||
</Section>
|
||||
<Section title="Size">
|
||||
@@ -75,11 +75,11 @@ export function FlowInfo({ flow }: { flow: Flow }) {
|
||||
row("response_size", "Response", formatSize(flow.responseSize)),
|
||||
]}
|
||||
</Section>
|
||||
<Section title="Record">
|
||||
<Section title="Flow">
|
||||
{[
|
||||
row("number", "Number", `#${flow.number}`, { copy: true }),
|
||||
row("id", "ID", flow.id, { copy: true }),
|
||||
row("saved", "Saved As", flow.savedRequestId, { copy: true }),
|
||||
row("saved", "Yaak Request ID", flow.savedRequestId, { copy: true }),
|
||||
]}
|
||||
</Section>
|
||||
<Timeline flow={flow} />
|
||||
|
||||
@@ -914,9 +914,9 @@ function encryptedReason(flow: Flow): string {
|
||||
? "Encrypted: excluded by default"
|
||||
: `Encrypted: excluded by ${flow.tunnelExcludedBy}`;
|
||||
case "rejected":
|
||||
return "Encrypted: passed through after a client rejected the certificate";
|
||||
return "Encrypted: an app rejected the certificate";
|
||||
default:
|
||||
return "Encrypted: not on the decrypt list";
|
||||
return "Encrypted: decryption is not enabled";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -929,12 +929,12 @@ function FlowMark({ flow }: { flow: Flow }) {
|
||||
const open = isOpenStream(flow);
|
||||
const rejected = flow.tlsRejected != null;
|
||||
const states: string[] = [];
|
||||
if (rejected) states.push("Rejected the proxy's certificate");
|
||||
if (rejected) states.push("Certificate rejected by the app");
|
||||
else if (flow.error != null) states.push(`Failed: ${flow.error}`);
|
||||
if (flow.cancelled) states.push("Cancelled by the app");
|
||||
if (flow.tunnelled) states.push(encryptedReason(flow));
|
||||
if (open) states.push("Streaming");
|
||||
if (flow.responseTruncated) states.push("Body cut at the capture limit");
|
||||
if (flow.responseTruncated) states.push("Body truncated at the capture limit");
|
||||
if (flow.savedRequestId != null) states.push("Saved to Yaak");
|
||||
const mark = (
|
||||
<span className="flex items-center justify-center">
|
||||
@@ -1369,9 +1369,9 @@ function useCellActions(): (flow: Flow, column: ColumnId) => DropdownItem[] {
|
||||
onSelect: async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
showToast({ message: "Copied value" });
|
||||
showToast({ message: "Copied to clipboard" });
|
||||
} catch (err) {
|
||||
showToast({ message: `Failed to copy value: ${String(err)}`, color: "danger" });
|
||||
showToast({ message: `Failed to copy: ${String(err)}`, color: "danger" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -31,18 +31,17 @@ export function FlowListEmpty() {
|
||||
<div className="text-text text-sm">{recording ? "No flows yet" : "Recording is paused"}</div>
|
||||
<div className="text-sm text-text-subtle max-w-[26rem]">
|
||||
{!running
|
||||
? "The proxy is not running. Retry from the menu by the state."
|
||||
? "Retry from the status menu to start the proxy."
|
||||
: !recording
|
||||
? "Traffic passes through without being stored."
|
||||
: `Recording on port ${port ?? "--"}. Flows appear as apps send requests.`}
|
||||
? "Traffic passes through without being recorded."
|
||||
: `Traffic sent to port ${port ?? "--"} will be recorded.`}
|
||||
</div>
|
||||
</EmptyStateText>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The list's last row while recording is paused, where the next flow would appear: since when,
|
||||
* and that nothing is stored.
|
||||
* The list's last row while recording is paused, where the next flow would appear: since when.
|
||||
*/
|
||||
export function PausedRow({ style }: { style?: React.CSSProperties }) {
|
||||
const pausedAt = useAtomValue(pausedAtAtom);
|
||||
@@ -56,8 +55,7 @@ export function PausedRow({ style }: { style?: React.CSSProperties }) {
|
||||
>
|
||||
<Icon icon="circle_pause" size="sm" className="text-text-subtlest" />
|
||||
<span className="truncate">
|
||||
{pausedAt == null ? "Paused." : `Paused at ${timeOf(pausedAt)}.`} Traffic passes through
|
||||
without being stored.
|
||||
{pausedAt == null ? "Recording is paused." : `Recording was paused at ${timeOf(pausedAt)}.`}
|
||||
</span>
|
||||
<Hotkey action="recording_toggle" variant="text" className="ml-auto" />
|
||||
</div>
|
||||
@@ -102,11 +100,10 @@ function NoResults({ text }: { text: string }) {
|
||||
className="not-italic !h-auto py-3 px-4 text-sm text-text-subtle"
|
||||
>
|
||||
<div className="text-center leading-relaxed">
|
||||
No flows match.
|
||||
{wider.length > 0 && " Found matches for "}
|
||||
{wider.length === 0 ? "No results" : "No results, but found matches for "}
|
||||
{wider.map((w, i) => (
|
||||
<span key={w.text}>
|
||||
{i > 0 && (i === wider.length - 1 ? ", or " : ", ")}
|
||||
{i > 0 && " or "}
|
||||
<button type="button" className="hover:underline" onClick={() => setText(w.text)}>
|
||||
<InlineCode>{w.text === "" ? "all traffic" : w.text}</InlineCode>
|
||||
</button>{" "}
|
||||
|
||||
@@ -159,7 +159,7 @@ export const FlowListHeader = memo(function FlowListHeader({
|
||||
: {}),
|
||||
};
|
||||
}),
|
||||
{ label: "Add Column…", icon: "plus", onSelect: () => openAddColumn(true) },
|
||||
{ label: "Add Column", icon: "plus", onSelect: () => openAddColumn(true) },
|
||||
{ type: "separator" },
|
||||
{ label: "Reset Sort", disabled: sort.length === 0, onSelect: onResetSort },
|
||||
{ label: "Reset Column Widths", onSelect: () => setPrefs((p) => resetWidths(p)) },
|
||||
@@ -179,7 +179,7 @@ export const FlowListHeader = memo(function FlowListHeader({
|
||||
{ label: "Sort Descending", onSelect: () => sortBy(true) },
|
||||
{ label: "Add to Sort Ascending", onSelect: () => addTo(false) },
|
||||
{ label: "Add to Sort Descending", onSelect: () => addTo(true) },
|
||||
{ label: "Clear Sorting", disabled: sort.length === 0, onSelect: onResetSort },
|
||||
{ label: "Reset Sort", disabled: sort.length === 0, onSelect: onResetSort },
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Move Left",
|
||||
|
||||
@@ -120,7 +120,9 @@ export function FlowMessages({ flow }: { flow: Flow }) {
|
||||
header={header}
|
||||
error={error ?? (open ? null : flow.error)}
|
||||
isLoading={loading && events.length === 0}
|
||||
emptyMessage={open ? `Waiting for the first ${noun.toLowerCase()}` : `No ${noun}s`}
|
||||
emptyMessage={
|
||||
open ? `Waiting for the first ${noun.toLowerCase()}` : `No ${noun.toLowerCase()}s`
|
||||
}
|
||||
splitLayoutStorageKey="proxy_flow_messages"
|
||||
columns={columns}
|
||||
loadPayload={loadPayload}
|
||||
|
||||
@@ -4,15 +4,14 @@ import { describe, expect, test, vi } from "vite-plus/test";
|
||||
import type { FlowBodyData } from "../lib/flowViews";
|
||||
import { FlowRequestBody } from "./FlowRequestBody";
|
||||
|
||||
// The package's index reaches for the window as it loads; these three are all this renders.
|
||||
// The package's index reaches for the window as it loads; these four are all this renders.
|
||||
vi.mock("@yaakapp-internal/ui", () => ({
|
||||
Banner: ({ children, color }: { children: ReactNode; color?: string }) => (
|
||||
<div data-banner={color}>{children}</div>
|
||||
),
|
||||
EmptyStateText: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
LoadingIcon: () => <span>LOADING</span>,
|
||||
CONTROL_TOOLTIP_DELAY: 0,
|
||||
Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
IconTooltip: ({ content }: { content: string }) => <span data-tooltip={content} />,
|
||||
}));
|
||||
|
||||
const data = (truncated = false): FlowBodyData => ({
|
||||
@@ -45,10 +44,13 @@ describe("FlowRequestBody", () => {
|
||||
expect(render({ data: data(), isLoading: false, error: null })).toContain("EDITOR");
|
||||
});
|
||||
|
||||
test("a cut body says how much was kept", () => {
|
||||
test("a truncated body says how much is shown, and why behind its info icon", () => {
|
||||
const html = render({ data: data(true), isLoading: false, error: null });
|
||||
expect(html).toContain("EDITOR");
|
||||
expect(html).toContain('data-banner="notice"');
|
||||
expect(html).toContain("kept");
|
||||
expect(html.replaceAll("<!-- -->", "")).toContain("The first 7 B of 2 KB is shown.");
|
||||
expect(html).toContain(
|
||||
'data-tooltip="The body was truncated at the capture limit. The app sent all of it."',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,5 @@
|
||||
import { formatSize } from "@yaakapp-internal/lib/formatSize";
|
||||
import {
|
||||
Banner,
|
||||
CONTROL_TOOLTIP_DELAY,
|
||||
EmptyStateText,
|
||||
LoadingIcon,
|
||||
Tooltip,
|
||||
} from "@yaakapp-internal/ui";
|
||||
import { Banner, EmptyStateText, IconTooltip, LoadingIcon } from "@yaakapp-internal/ui";
|
||||
import type { HttpBodyResult } from "@yaakapp-internal/ui/httpTypes";
|
||||
import type { ReactNode } from "react";
|
||||
import type { FlowBodyData } from "../lib/flowViews";
|
||||
@@ -42,11 +36,13 @@ export function FlowRequestBody({ body, viewer, requestSize }: Props) {
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)]">
|
||||
<Banner color="notice" className="text-sm mb-2">
|
||||
<Tooltip wrap content="The app sent all of it" delay={CONTROL_TOOLTIP_DELAY}>
|
||||
<span>
|
||||
First {formatSize(body.data.storedSize)} of {formatSize(requestSize)} kept
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="flex items-center gap-1.5">
|
||||
The first {formatSize(body.data.storedSize)} of {formatSize(requestSize)} is shown.
|
||||
<IconTooltip
|
||||
iconSize="sm"
|
||||
content="The body was truncated at the capture limit. The app sent all of it."
|
||||
/>
|
||||
</span>
|
||||
</Banner>
|
||||
<div className="min-h-0">{viewer}</div>
|
||||
</div>
|
||||
|
||||
@@ -48,8 +48,8 @@ export function HelperDialog() {
|
||||
open={open}
|
||||
onClose={close}
|
||||
size="dynamic"
|
||||
title="Turn on Yaak Proxy under Allow in the Background"
|
||||
description="In System Settings > General > Login Items. Newer macOS calls the list Background App Activity."
|
||||
title="Approve Helper"
|
||||
description="Turn on Yaak Proxy in System Settings > General > Login Items."
|
||||
className="w-[34rem]"
|
||||
>
|
||||
<div className="grid gap-3 pb-4">
|
||||
|
||||
@@ -24,7 +24,7 @@ const STRIP =
|
||||
|
||||
/**
|
||||
* Above the list while nothing is decrypted: says once that HTTPS is encrypted, and offers
|
||||
* Decrypt Everything, or Choose… for apps and sites one at a time. Goes once anything is on
|
||||
* Decrypt Everything, or Choose for apps and sites one at a time. Goes once anything is on
|
||||
* the list, or when dismissed; comes back, with Trust, while the list decrypts something the
|
||||
* certificate isn't trusted for.
|
||||
*/
|
||||
@@ -33,7 +33,7 @@ export function HttpsStrip() {
|
||||
const flows = useAtomValue(sidebarFlowsAtom);
|
||||
const caStatus = useRpcQuery("ca_status", {});
|
||||
const [dismissed, setDismissed] = useLocalStorage("https_strip_dismissed", false);
|
||||
// Kept while Choose… is open, so checking a row there leaves the menu where it is.
|
||||
// Kept while Choose is open, so checking a row there leaves the menu where it is.
|
||||
const [choosing, setChoosing] = useState(false);
|
||||
const { apply } = useDecrypt();
|
||||
const trusting = useTrusting();
|
||||
@@ -56,7 +56,7 @@ export function HttpsStrip() {
|
||||
<div className={STRIP}>
|
||||
<Icon icon="shield_off" size="sm" color="warning" className="shrink-0" />
|
||||
<span className="text-text shrink-0">Certificate not trusted</span>
|
||||
<span className="truncate">Required to decrypt</span>
|
||||
<span className="truncate">The certificate must be trusted to decrypt traffic.</span>
|
||||
<Button
|
||||
size="2xs"
|
||||
color="primary"
|
||||
@@ -93,7 +93,7 @@ export function HttpsStrip() {
|
||||
});
|
||||
},
|
||||
})),
|
||||
{ type: "separator", label: "Sites", hidden: options.sites.length === 0 },
|
||||
{ type: "separator", label: "Domains", hidden: options.sites.length === 0 },
|
||||
...options.sites.map((site): DropdownItem => ({
|
||||
label: <span className="font-mono text-xs">*.{site.site}</span>,
|
||||
keepOpenOnSelect: true,
|
||||
@@ -109,7 +109,7 @@ export function HttpsStrip() {
|
||||
})),
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Edit List in Settings…",
|
||||
label: "Decryption Settings",
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
onSelect: () => openSettings(SETTINGS_TAB_PROXY),
|
||||
},
|
||||
@@ -124,7 +124,7 @@ export function HttpsStrip() {
|
||||
<div className="ml-auto flex items-center gap-1.5 shrink-0">
|
||||
<Tooltip
|
||||
wrap
|
||||
content="New connections, except Apple services and apps that pin"
|
||||
content="New connections will be decrypted, except for excluded hosts and apps."
|
||||
delay={CONTROL_TOOLTIP_DELAY}
|
||||
>
|
||||
<Button
|
||||
@@ -136,7 +136,7 @@ export function HttpsStrip() {
|
||||
void apply({
|
||||
edit: everythingEdit(true),
|
||||
message: "Decrypting everything",
|
||||
sub: "Except Apple services and apps that pin",
|
||||
sub: "Excluded hosts and apps stay encrypted.",
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -145,14 +145,14 @@ export function HttpsStrip() {
|
||||
</Tooltip>
|
||||
<Dropdown items={items} onOpen={() => setChoosing(true)} onClose={() => setChoosing(false)}>
|
||||
<Button size="2xs" variant="border">
|
||||
Choose…
|
||||
Choose
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<IconButton
|
||||
size="xs"
|
||||
icon="x"
|
||||
iconColor="secondary"
|
||||
title="Not now"
|
||||
title="Dismiss"
|
||||
tooltip
|
||||
onClick={() => setDismissed(true)}
|
||||
/>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function LockedState({ lock }: { lock: ProxyLock }) {
|
||||
<div className="text-lg font-semibold whitespace-nowrap">
|
||||
{encrypting != null ? (
|
||||
<>
|
||||
Encrypting stored traffic,{" "}
|
||||
Encrypting recorded traffic,{" "}
|
||||
{/* Sized for "100%" and right-aligned, so the centred line never changes width as
|
||||
the number ticks: a changing width re-rasterizes every glyph at a new
|
||||
sub-pixel offset, and the line shakes */}
|
||||
|
||||
@@ -65,24 +65,20 @@ export function ProxyStatus() {
|
||||
status === "encrypting" ? (
|
||||
<MenuRow
|
||||
icon={<LoadingIcon size="sm" className="text-text-subtle" />}
|
||||
title={`Encrypting stored traffic, ${encrypting}`}
|
||||
description="Traffic captured before this version is being encrypted on disk. Recording starts when it's done."
|
||||
title={`Encrypting recorded traffic, ${encrypting}`}
|
||||
description="Recording will start once encryption finishes."
|
||||
/>
|
||||
) : status === "cli" && other != null ? (
|
||||
<MenuRow
|
||||
icon={<StateDot state="cli" />}
|
||||
title="Running from the CLI"
|
||||
description={`Started by yaak proxy start, pid ${other.pid}, since ${timeOf(other.startedAt)}.${other.proxyPort == null ? "" : ` Port ${other.proxyPort}.`}`}
|
||||
description={`The proxy was started by the CLI at ${timeOf(other.startedAt)}.`}
|
||||
/>
|
||||
) : status === "recording" || status === "paused" ? (
|
||||
<MenuRow
|
||||
icon={<StateDot state={status} />}
|
||||
title={recording ? `Recording on port ${port}` : `Paused on port ${port}`}
|
||||
description={
|
||||
recording
|
||||
? "Every flow is stored as it passes."
|
||||
: "Traffic passes through untouched, and nothing is stored."
|
||||
}
|
||||
description={recording ? null : "Traffic passes through without being recorded."}
|
||||
/>
|
||||
) : (
|
||||
<MenuRow
|
||||
@@ -90,7 +86,7 @@ export function ProxyStatus() {
|
||||
title="Not running"
|
||||
description={
|
||||
<span className="text-danger">
|
||||
{retry.error?.message ?? proxyState?.startError ?? "The proxy has not started."}
|
||||
{retry.error?.message ?? proxyState?.startError ?? "The proxy failed to start."}
|
||||
</span>
|
||||
}
|
||||
action={
|
||||
@@ -156,9 +152,7 @@ export function ProxyStatus() {
|
||||
{systemSwitch.description}
|
||||
</span>
|
||||
{helperNotice && (
|
||||
<span className="block text-notice">
|
||||
macOS asks for your password each time until the helper is approved in Settings.
|
||||
</span>
|
||||
<span className="block text-notice">macOS will ask for a password each time.</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
@@ -258,8 +252,8 @@ function DecryptEverythingRow({ broken }: { broken: boolean }) {
|
||||
trusting
|
||||
? "Waiting for macOS"
|
||||
: on
|
||||
? "Except Apple services and apps that pin"
|
||||
: (listSummary(list) ?? "Nothing listed")
|
||||
? "Excluded hosts and apps stay encrypted."
|
||||
: (listSummary(list) ?? "No hosts or apps")
|
||||
}
|
||||
action={
|
||||
<KeepMenuOpen>
|
||||
@@ -274,7 +268,7 @@ function DecryptEverythingRow({ broken }: { broken: boolean }) {
|
||||
? {
|
||||
edit: everythingEdit(true),
|
||||
message: "Decrypting everything",
|
||||
sub: "Except Apple services and apps that pin",
|
||||
sub: "Excluded hosts and apps stay encrypted.",
|
||||
}
|
||||
: { edit: everythingEdit(false), message: "Stopped decrypting everything" },
|
||||
)
|
||||
@@ -350,7 +344,7 @@ export function useCopyEnvironment() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(environmentExports(port, caPath));
|
||||
showToast({
|
||||
message: "Copied the environment. Paste it into a terminal to proxy what runs there.",
|
||||
message: "Copied the environment. Paste it into a terminal.",
|
||||
});
|
||||
} catch (err) {
|
||||
showToast({ message: `Failed to copy: ${String(err)}`, color: "danger" });
|
||||
@@ -429,6 +423,7 @@ function MenuRow({
|
||||
}: {
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
/** Under the title; null for a title that says it all */
|
||||
description: ReactNode;
|
||||
action?: ReactNode;
|
||||
}) {
|
||||
@@ -437,7 +432,9 @@ function MenuRow({
|
||||
<span className="h-5 flex items-center justify-center">{icon}</span>
|
||||
<div>
|
||||
<div className="text-sm text-text">{title}</div>
|
||||
<div className="text-xs text-text-subtle mt-0.5">{description}</div>
|
||||
{description != null && (
|
||||
<div className="text-xs text-text-subtle mt-0.5">{description}</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="self-center">{action}</span>
|
||||
</div>
|
||||
|
||||
@@ -40,8 +40,8 @@ export function SaveDialog() {
|
||||
open
|
||||
onClose={close}
|
||||
size="dynamic"
|
||||
title="Save as Yaak request"
|
||||
description="A new request in Yaak with this flow's method, URL, headers and body."
|
||||
title="Save as Yaak Request"
|
||||
description="A new request will be created in Yaak."
|
||||
className="w-[36rem]"
|
||||
>
|
||||
{workspaces.error != null ? (
|
||||
@@ -103,7 +103,7 @@ function SaveForm({
|
||||
message: (
|
||||
<>
|
||||
Saved <b className="font-semibold">{saved.name || unnamed}</b> to {workspace.name}
|
||||
{folderName == null ? "." : `, in ${folderName}.`} Yaak picks it up within a second.
|
||||
{folderName == null ? "" : `, in ${folderName}`}
|
||||
</>
|
||||
),
|
||||
});
|
||||
@@ -113,11 +113,7 @@ function SaveForm({
|
||||
};
|
||||
|
||||
if (workspaces.length === 0) {
|
||||
return (
|
||||
<Banner color="notice">
|
||||
Yaak has no workspaces yet. Create one in Yaak, then save here.
|
||||
</Banner>
|
||||
);
|
||||
return <Banner color="notice">Create a workspace in Yaak first.</Banner>;
|
||||
}
|
||||
|
||||
if (p == null) {
|
||||
@@ -209,13 +205,10 @@ function SaveForm({
|
||||
</div>
|
||||
{save.error != null && <Banner color="danger">{save.error.message}</Banner>}
|
||||
<DialogFooter
|
||||
leftSlot={
|
||||
<span className="text-xs text-text-subtlest">Yaak picks it up within a second</span>
|
||||
}
|
||||
actions={[
|
||||
{ label: "Cancel", onClick: onClose },
|
||||
{
|
||||
label: "Save request",
|
||||
label: "Save Request",
|
||||
form: SAVE_FORM_ID,
|
||||
color: "primary",
|
||||
isLoading: save.isPending,
|
||||
@@ -243,15 +236,19 @@ function NoYaakDialog({ flow, onClose }: { flow: Flow; onClose: () => void }) {
|
||||
return (
|
||||
<Dialog open onClose={onClose} size="dynamic" className="w-[32rem]">
|
||||
<DialogFooter
|
||||
leftSlot={<span className="text-xs text-text-subtlest">Or keep the flow as a file</span>}
|
||||
leftSlot={
|
||||
<span className="text-xs text-text-subtlest">
|
||||
The flow can be exported as a file instead.
|
||||
</span>
|
||||
}
|
||||
actions={[
|
||||
{
|
||||
label: "Export HAR",
|
||||
label: "Export as HAR",
|
||||
onClick: async () => {
|
||||
try {
|
||||
if ((await exportHar({ ids: [flow.id] }, "flow.har")) != null) {
|
||||
onClose();
|
||||
showToast({ message: "Exported the flow as HAR" });
|
||||
showToast({ message: "Exported 1 flow as HAR" });
|
||||
}
|
||||
} catch (err) {
|
||||
showToast({ message: `Failed to export HAR: ${String(err)}`, color: "danger" });
|
||||
@@ -262,11 +259,10 @@ function NoYaakDialog({ flow, onClose }: { flow: Flow; onClose: () => void }) {
|
||||
]}
|
||||
/>
|
||||
<div className="pt-6 pb-2">
|
||||
<div className="text-lg font-semibold mb-1.5">Saving needs Yaak</div>
|
||||
<div className="text-lg font-semibold mb-1.5">Yaak Required</div>
|
||||
<div className="text-sm text-text-subtle leading-relaxed">
|
||||
This flow becomes a request in one of Yaak's workspaces, and Yaak has not been opened on
|
||||
this Mac. Yaak is free for personal use and opens the request with the same viewers you
|
||||
see here.
|
||||
This flow will be saved as a request in one of Yaak's workspaces, but Yaak has not been
|
||||
opened on this computer.
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
@@ -308,18 +304,18 @@ function folderOptions(folders: FolderSummary[]): { id: string; name: string }[]
|
||||
function reasonLabel(reason: DropReason): string {
|
||||
switch (reason) {
|
||||
case "set_by_sending":
|
||||
return "Yaak sets it when it sends";
|
||||
return "Yaak sets it when it sends the request.";
|
||||
case "hop_by_hop":
|
||||
return "Only meant for the proxy";
|
||||
return "It was only meant for the proxy.";
|
||||
case "body_decoded":
|
||||
return "The body is saved decoded";
|
||||
return "The body is saved decoded.";
|
||||
}
|
||||
}
|
||||
|
||||
function droppedSummary(reasons: DropReason[]): string {
|
||||
const unique = [...new Set(reasons)];
|
||||
if (unique.length === 1 && unique[0] === "set_by_sending") {
|
||||
return "Yaak sets these itself when it sends";
|
||||
return "Yaak sets these itself when it sends the request.";
|
||||
}
|
||||
return "Yaak sets these itself, or they only meant something to the proxy";
|
||||
return "Yaak sets these, or they were meant for the proxy.";
|
||||
}
|
||||
@@ -47,7 +47,7 @@ function NameDialog({ dialog, onClose }: { dialog: SavedSearchDialog; onClose: (
|
||||
open
|
||||
onClose={onClose}
|
||||
size="dynamic"
|
||||
title={dialog.mode === "save" ? "Save search" : "Rename search"}
|
||||
title={dialog.mode === "save" ? "Save Search" : "Rename Search"}
|
||||
className="w-[26rem]"
|
||||
>
|
||||
<form
|
||||
|
||||
@@ -205,7 +205,7 @@ export const SearchBar = memo(function SearchBar() {
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
label: "Save search",
|
||||
label: "Save Search",
|
||||
leftSlot: <Icon icon="bookmark" />,
|
||||
onSelect: () =>
|
||||
setSavedDialog({
|
||||
@@ -275,7 +275,7 @@ function FilterHistory({
|
||||
|
||||
const items: DropdownItem[] =
|
||||
history.length === 0
|
||||
? [{ label: "No recent filters", disabled: true }]
|
||||
? [{ label: "No recent searches", disabled: true }]
|
||||
: [
|
||||
...history.map((filter): DropdownItem => ({
|
||||
label: (
|
||||
@@ -311,7 +311,7 @@ function FilterHistory({
|
||||
<IconButton
|
||||
size="xs"
|
||||
icon="search"
|
||||
title="Recent filters"
|
||||
title="Recent searches"
|
||||
tooltip
|
||||
iconColor="secondary"
|
||||
disabled={disabled}
|
||||
|
||||
@@ -235,7 +235,7 @@ function useAppInfo() {
|
||||
? "Reveal in Finder"
|
||||
: os === "windows"
|
||||
? "Show in Explorer"
|
||||
: "Show in Folder",
|
||||
: "Show in File Manager",
|
||||
}),
|
||||
[data, os],
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Dialog,
|
||||
DialogFooter,
|
||||
Icon,
|
||||
IconTooltip,
|
||||
RadioDropdown,
|
||||
SettingRow,
|
||||
SettingRowBoolean,
|
||||
@@ -58,27 +59,30 @@ export function SettingsProxy() {
|
||||
const elsewhere = proxyState?.otherOwner != null;
|
||||
|
||||
return (
|
||||
<SettingsPage title="Proxy" description="Listening, decryption, traffic and terminals">
|
||||
<SettingsPage
|
||||
title="Proxy"
|
||||
description="Configure the port, decryption, recorded traffic, and terminal environment."
|
||||
>
|
||||
<SettingsList className="space-y-8">
|
||||
<SettingsSection title="Proxy">
|
||||
<PortRow disabled={elsewhere} />
|
||||
<SettingRowSelect<HttpVersion>
|
||||
name="httpVersion"
|
||||
title="HTTP version"
|
||||
description="For new connections"
|
||||
description="The change applies to new connections."
|
||||
value={proxyState?.httpVersion ?? "auto"}
|
||||
disabled={elsewhere}
|
||||
onChange={(httpVersion) => setHttpVersion.mutate({ httpVersion })}
|
||||
options={[
|
||||
{ label: "Auto", value: "auto" },
|
||||
{ label: "Automatic", value: "auto" },
|
||||
{ label: "HTTP/1.1 only", value: "http1Only" },
|
||||
]}
|
||||
/>
|
||||
{systemProxy.data?.supported === true && (
|
||||
<>
|
||||
<SettingRowBoolean
|
||||
title="Override system proxy"
|
||||
description="At launch"
|
||||
title="Override system proxy at launch"
|
||||
description="Traffic from every app will be recorded."
|
||||
checked={proxyState?.systemProxyAtLaunch ?? true}
|
||||
disabled={setAtLaunch.isPending}
|
||||
onChange={(enabled) => setAtLaunch.mutate({ enabled })}
|
||||
@@ -147,7 +151,7 @@ function PortRow({ disabled }: { disabled: boolean }) {
|
||||
setPort.mutate({ port });
|
||||
};
|
||||
return (
|
||||
<SettingRow title="Port" description="Restarts the proxy">
|
||||
<SettingRow title="Port" description="Changing the port restarts the proxy.">
|
||||
<PlainInput
|
||||
hideLabel
|
||||
size="sm"
|
||||
@@ -184,7 +188,10 @@ function HelperRow({ status }: { status: SystemProxyStatus }) {
|
||||
unregister.error != null ? (
|
||||
<span className="text-danger">{unregister.error.message}</span>
|
||||
) : (
|
||||
`${capitalize(row.title.replace(/^Helper: /, ""))}. ${row.description}`
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
{row.description}
|
||||
<IconTooltip iconSize="sm" content={row.tooltip} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -200,7 +207,7 @@ function HelperRow({ status }: { status: SystemProxyStatus }) {
|
||||
? "Install"
|
||||
: row.action === "approve"
|
||||
? "Approve"
|
||||
: "Unregister"}
|
||||
: "Uninstall"}
|
||||
</Button>
|
||||
)}
|
||||
</SettingRow>
|
||||
@@ -217,7 +224,7 @@ function TrafficRow() {
|
||||
return (
|
||||
<SettingRowSelect<TrafficRetention>
|
||||
name="traffic"
|
||||
title="Captured traffic"
|
||||
title="Recorded traffic"
|
||||
selectClassName="w-56!"
|
||||
value={proxyState?.traffic ?? "clearOnLaunch"}
|
||||
disabled={proxyState?.otherOwner != null}
|
||||
@@ -240,7 +247,7 @@ function KeepTrafficRow() {
|
||||
});
|
||||
const keep = proxyState?.keepTrafficBytes ?? null;
|
||||
return (
|
||||
<SettingRow title="Size limit" description="Oldest flows dropped past it">
|
||||
<SettingRow title="Size limit" description="The oldest flows will be cleared first.">
|
||||
<div className="flex items-center gap-3">
|
||||
{size.data != null && (
|
||||
<span className="text-sm text-text-subtle tabular-nums whitespace-nowrap">
|
||||
@@ -280,21 +287,24 @@ function ResetRow() {
|
||||
onSuccess: () => {
|
||||
setConfirming(false);
|
||||
void queryClient.invalidateQueries();
|
||||
showToast({ message: "Captured data reset" });
|
||||
showToast({ message: "Recorded data reset" });
|
||||
},
|
||||
onSettled: () => void refreshProxyState(store),
|
||||
});
|
||||
return (
|
||||
<SettingRow title="Reset captured data" description="Deletes all flows and their keys">
|
||||
<SettingRow
|
||||
title="Reset recorded data"
|
||||
description="All traffic and its encryption keys will be deleted."
|
||||
>
|
||||
<Button size="xs" variant="border" color="danger" onClick={() => setConfirming(true)}>
|
||||
Reset…
|
||||
Reset
|
||||
</Button>
|
||||
<Dialog
|
||||
open={confirming}
|
||||
onClose={() => setConfirming(false)}
|
||||
size="sm"
|
||||
title="Reset captured data?"
|
||||
description="Every captured flow and the keys that encrypt them are deleted."
|
||||
title="Reset Recorded Data"
|
||||
description="Permanently delete all traffic and its encryption keys?"
|
||||
>
|
||||
{reset.error != null && <p className="text-danger text-sm">{reset.error.message}</p>}
|
||||
<DialogFooter
|
||||
@@ -351,8 +361,8 @@ function TerminalRow() {
|
||||
className="ml-auto"
|
||||
title={
|
||||
os === "macos"
|
||||
? "A new Terminal window with these set"
|
||||
: "Not available on this platform yet"
|
||||
? "A new Terminal window will open with these variables set."
|
||||
: "Opening a terminal is only supported on macOS."
|
||||
}
|
||||
disabled={caPath == null || os !== "macos"}
|
||||
isLoading={openTerminal.isPending}
|
||||
@@ -363,7 +373,7 @@ function TerminalRow() {
|
||||
Open Terminal
|
||||
</Button>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-text-subtle">Proxies one terminal instead of the system</p>
|
||||
<p className="mt-1 text-sm text-text-subtle">Traffic from one terminal will be recorded.</p>
|
||||
{caPath != null && (
|
||||
<SettingSnippet
|
||||
className="mt-3"
|
||||
@@ -374,7 +384,3 @@ function TerminalRow() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function capitalize(s: string): string {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
@@ -175,7 +175,7 @@ export function Sidebar() {
|
||||
onSelect: () => void revealPath(path, fail),
|
||||
},
|
||||
{
|
||||
label: "Copy path",
|
||||
label: "Copy Path",
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
hidden: item.kind !== "folder",
|
||||
onSelect: () => {
|
||||
@@ -203,9 +203,9 @@ export function Sidebar() {
|
||||
onSelect: async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(copyRowsText(items));
|
||||
showToast({ message: "Copied value" });
|
||||
showToast({ message: "Copied to clipboard" });
|
||||
} catch (err) {
|
||||
showToast({ message: `Failed to copy value: ${String(err)}`, color: "danger" });
|
||||
showToast({ message: `Failed to copy: ${String(err)}`, color: "danger" });
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -239,7 +239,7 @@ export function Sidebar() {
|
||||
void applyDecrypt({
|
||||
edit: everythingEdit(true),
|
||||
message: "Decrypting everything",
|
||||
sub: "Except Apple services and apps that pin",
|
||||
sub: "Excluded hosts and apps stay encrypted.",
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -29,11 +29,11 @@ export function TakeOverBanner() {
|
||||
<span className="truncate">
|
||||
{!cli ? (
|
||||
// Stopping another app halts its engine without releasing it, so it can't be taken over.
|
||||
"Another Yaak Proxy window owns the engine. Quit it to use this one."
|
||||
"Quit the other copy to use this one."
|
||||
) : takeOver.error != null ? (
|
||||
<span className="text-danger">{takeOver.error.message}</span>
|
||||
) : (
|
||||
`pid ${other.pid}, since ${since}. Flows still list here, read from the capture database.`
|
||||
`pid ${other.pid}, since ${since}`
|
||||
)}
|
||||
</span>
|
||||
{cli && (
|
||||
@@ -44,7 +44,7 @@ export function TakeOverBanner() {
|
||||
isLoading={takeOver.isPending}
|
||||
onClick={() => takeOver.mutate({})}
|
||||
>
|
||||
Take over
|
||||
Take Over
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,8 +9,8 @@ import { syncNavigation, syncSidebarSelection } from "../lib/navigation";
|
||||
import { syncFlows, syncProxyState } from "../lib/store";
|
||||
|
||||
const LOCKED =
|
||||
"Traffic is locked: Yaak Proxy stores traffic encrypted and couldn't read its key from the " +
|
||||
"keychain: Encryption error: User canceled the operation. Allow it access, then retry.";
|
||||
"Traffic is locked: Failed to read the encryption key from the keychain: Encryption error: " +
|
||||
"User canceled the operation";
|
||||
|
||||
const STATE: GetProxyStateResponse = {
|
||||
state: "stopped",
|
||||
|
||||
@@ -157,7 +157,7 @@ function mixedScene(): Flow[] {
|
||||
tunnelled: false,
|
||||
tunnelReason: null,
|
||||
tlsRejected: "bad_certificate",
|
||||
error: "Rejected the proxy's certificate",
|
||||
error: "Certificate rejected by the app",
|
||||
elapsedMs: 6,
|
||||
requestSize: 517,
|
||||
responseSize: 0,
|
||||
|
||||
@@ -46,7 +46,7 @@ export function useTrustCertificate() {
|
||||
showToast({
|
||||
id: "decrypt",
|
||||
color: "danger",
|
||||
message: `Certificate not trusted: ${err.message}`,
|
||||
message: `Failed to trust certificate: ${err.message}`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ describe("flowBodyQueryOptions", () => {
|
||||
|
||||
describe("a body that isn't there", () => {
|
||||
test("a WebSocket's note reaches the viewer, which points at its messages", () => {
|
||||
const note = "A WebSocket has no body after its 101 response. Its frames are its messages.";
|
||||
const note = "A WebSocket has no body";
|
||||
const body = viewerBody({ data: "", truncated: false, storedSize: 0, note });
|
||||
expect(body.note).toBe(note);
|
||||
expect(emptyBodyText("response", body.note)).toBe("No body. See Messages.");
|
||||
|
||||
@@ -194,7 +194,7 @@ describe("the sidebar menu", () => {
|
||||
expect(decryptMenuAction([], [site])?.label).toBe("Decrypt *.google.com");
|
||||
expect(decryptMenuAction([], [dia])?.label).toBe("Decrypt Dia");
|
||||
expect(decryptMenuAction([], [site, { kind: "site", site: "x.com" }])?.label).toBe(
|
||||
"Decrypt 2 Sites",
|
||||
"Decrypt 2 Domains",
|
||||
);
|
||||
expect(decryptMenuAction([], [dia, a, site])?.label).toBe("Decrypt 3 Selected");
|
||||
});
|
||||
|
||||
@@ -411,8 +411,8 @@ export function subjectName(subject: DecryptSubject): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Several subjects named together: one by its name, several of a kind counted, `3 Sites` in a
|
||||
* menu and `3 sites` in a toast, and mixed kinds as `3 Selected`
|
||||
* Several subjects named together: one by its name, several of a kind counted, `3 Domains` in a
|
||||
* menu and `3 domains` in a toast, and mixed kinds as `3 Selected`
|
||||
*/
|
||||
function subjectsName(subjects: DecryptSubject[]): { title: string; text: string } {
|
||||
const [first] = subjects;
|
||||
@@ -423,7 +423,7 @@ function subjectsName(subjects: DecryptSubject[]): { title: string; text: string
|
||||
const kinds = new Set(subjects.map((s) => s.kind));
|
||||
const n = subjects.length;
|
||||
if (kinds.size > 1) return { title: `${n} Selected`, text: `${n} selected` };
|
||||
const noun = first?.kind === "app" ? "Apps" : first?.kind === "site" ? "Sites" : "Hosts";
|
||||
const noun = first?.kind === "app" ? "Apps" : first?.kind === "site" ? "Domains" : "Hosts";
|
||||
return { title: `${n} ${noun}`, text: `${n} ${noun.toLowerCase()}` };
|
||||
}
|
||||
|
||||
@@ -473,8 +473,8 @@ export function decryptFilterOptions(seen: SeenTargets | null | undefined): Filt
|
||||
replaceValueOnly: true,
|
||||
negation: { prefix: "!", info: "Exclude" },
|
||||
fields: [
|
||||
{ name: "host", values: seen?.hosts ?? [], info: "Host or *.site" },
|
||||
{ name: "app", values: [...apps], info: "App name or bundle id" },
|
||||
{ name: "host", values: seen?.hosts ?? [], info: "Host or *.domain" },
|
||||
{ name: "app", values: [...apps], info: "App name or bundle ID" },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -106,10 +106,10 @@ describe("the toast after a choice", () => {
|
||||
test("offers Reconnect for the entry when open connections stay encrypted", () => {
|
||||
expect(choiceToast(choice, 3)).toEqual({
|
||||
message: "Decrypting www.google.com",
|
||||
sub: "3 open connections stay encrypted",
|
||||
sub: "3 open connections stay encrypted.",
|
||||
reconnect: ["host:www.google.com"],
|
||||
});
|
||||
expect(choiceToast(choice, 1).sub).toBe("1 open connection stays encrypted");
|
||||
expect(choiceToast(choice, 1).sub).toBe("1 open connection stays encrypted.");
|
||||
});
|
||||
|
||||
test("Reconnect for an exclusion taken off names the exclusion, as the engine reads it", () => {
|
||||
|
||||
@@ -81,7 +81,7 @@ export function choiceToast(choice: DecryptChoice, stillEncrypted: number): Choi
|
||||
const reconnect = reconnectEntries(choice.edit);
|
||||
return {
|
||||
message: choice.message,
|
||||
sub: `${n} open ${stillEncrypted === 1 ? "connection stays" : "connections stay"} encrypted`,
|
||||
sub: `${n} open ${stillEncrypted === 1 ? "connection stays" : "connections stay"} encrypted.`,
|
||||
reconnect,
|
||||
};
|
||||
}
|
||||
@@ -68,7 +68,9 @@ describe("connectionOf", () => {
|
||||
describe("the card of an encrypted connection", () => {
|
||||
test("nothing decrypted yet: the host, the app and everything, each with what it writes", () => {
|
||||
const c = card();
|
||||
expect(lineText(c.why)).toBe("Nothing is decrypted yet");
|
||||
expect(lineText(c.why)).toBe(
|
||||
"**Encrypted Content:** Enable decryption to see requests to `www.google.com`",
|
||||
);
|
||||
expect(summary(c)).toEqual([
|
||||
[
|
||||
"Decrypt www.google.com",
|
||||
@@ -80,11 +82,12 @@ describe("the card of an encrypted connection", () => {
|
||||
]);
|
||||
// Nothing under the buttons while the certificate is trusted
|
||||
expect(c.foot).toEqual([]);
|
||||
expect(c.fact).toBeNull();
|
||||
// The info button keeps its general line
|
||||
expect(c.tooltip).toBeNull();
|
||||
});
|
||||
|
||||
test("asks for the password once while the certificate isn't trusted, where it can be", () => {
|
||||
expect(card({ trusted: false }).foot).toEqual(["Asks for your password once"]);
|
||||
expect(card({ trusted: false }).foot).toEqual(["macOS will ask for a password once."]);
|
||||
expect(card({ trusted: false, canTrust: false }).foot).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -140,10 +143,7 @@ describe("the card of an encrypted connection", () => {
|
||||
tunnelExcludedBuiltIn: true,
|
||||
},
|
||||
});
|
||||
expect([lineText(pinned.why), pinned.whySub]).toEqual([
|
||||
"Excluded by default",
|
||||
"pins its certificate",
|
||||
]);
|
||||
expect(lineText(pinned.why)).toBe("Apps that pin certificates are excluded by default.");
|
||||
expect(summary(pinned)).toEqual([
|
||||
[
|
||||
"Decrypt my.1password.com",
|
||||
@@ -152,7 +152,7 @@ describe("the card of an encrypted connection", () => {
|
||||
],
|
||||
]);
|
||||
expect(pinned.actions[0]?.dashed).toBe(true);
|
||||
expect(pinned.foot).toEqual(["1Password may refuse it"]);
|
||||
expect(pinned.foot).toEqual(["1Password may reject the connection."]);
|
||||
|
||||
const apple = card({
|
||||
flow: {
|
||||
@@ -165,8 +165,8 @@ describe("the card of an encrypted connection", () => {
|
||||
tunnelExcludedBuiltIn: true,
|
||||
},
|
||||
});
|
||||
expect(apple.whySub).toBe("Apple service");
|
||||
expect(apple.foot).toEqual(["macOS may refuse it"]);
|
||||
expect(lineText(apple.why)).toBe("Apple services are excluded by default.");
|
||||
expect(apple.foot).toEqual(["macOS may reject the connection."]);
|
||||
});
|
||||
|
||||
test("excluded by the person: names it, and Stop Excluding when that alone decrypts it", () => {
|
||||
@@ -175,7 +175,7 @@ describe("the card of an encrypted connection", () => {
|
||||
list,
|
||||
flow: { ...tunnel, tunnelReason: "excluded", tunnelExcludedBy: "!host:*.google.com" },
|
||||
});
|
||||
expect(lineText(c.why)).toBe("Excluded by `!host:*.google.com`");
|
||||
expect(lineText(c.why)).toBe("This connection is excluded by `!host:*.google.com`.");
|
||||
// Nothing added wins over the person's own exclusion, so taking it off is the way in
|
||||
expect(summary(c)).toEqual([
|
||||
[
|
||||
@@ -208,9 +208,8 @@ describe("the card of an encrypted connection", () => {
|
||||
test("once the list decrypts it: opened before, the opposite, and Reconnect while open", () => {
|
||||
const list = [...user("host:www.google.com"), ...BUILT_INS];
|
||||
const closed = card({ list });
|
||||
expect(lineText(closed.why)).toBe("Not on the decrypt list");
|
||||
expect(closed.fact && lineText(closed.fact)).toBe(
|
||||
"Opened before `www.google.com` was decrypted",
|
||||
expect(lineText(closed.why)).toBe(
|
||||
"This connection was opened before `www.google.com` was decrypted.",
|
||||
);
|
||||
expect(summary(closed)).toEqual([
|
||||
[
|
||||
@@ -225,8 +224,8 @@ describe("the card of an encrypted connection", () => {
|
||||
expect(open.actions.map((a) => a.kind)).toEqual(["reconnect", "stop"]);
|
||||
|
||||
const everything = card({ list: [...user("*"), ...BUILT_INS] });
|
||||
expect(everything.fact && lineText(everything.fact)).toBe(
|
||||
"Opened before everything was decrypted",
|
||||
expect(lineText(everything.why)).toBe(
|
||||
"This connection was opened before everything was decrypted.",
|
||||
);
|
||||
expect(everything.actions[0]?.label).toBe("Stop Decrypting Everything");
|
||||
});
|
||||
@@ -245,22 +244,37 @@ describe("the card of a refused handshake", () => {
|
||||
};
|
||||
const listed = [...user("*"), ...BUILT_INS];
|
||||
|
||||
test("an app: likely pinned, Stop Decrypting first, Try Again while passed through", () => {
|
||||
test("an app: likely pinned, Stop Decrypting first, Retry while passed through", () => {
|
||||
const c = card({ flow: refused, list: listed, passthrough: ["sync.acme.dev:443"] });
|
||||
expect(c.rejected).toBe(true);
|
||||
expect([lineText(c.why), c.whySub]).toEqual(["Acme rejected the certificate", "likely pinned"]);
|
||||
expect([lineText(c.why), c.tooltip]).toEqual([
|
||||
"Acme rejected the certificate.",
|
||||
"Acme likely pins its certificate, so it cannot be decrypted.",
|
||||
]);
|
||||
expect(summary(c)).toEqual([
|
||||
["Stop Decrypting Acme", "!app:Acme", { add: ["!app:Acme"], remove: [] }],
|
||||
["Try Again", "next connection", null],
|
||||
["Retry", "next connection", null],
|
||||
]);
|
||||
expect(c.actions[0]?.primary).toBe(true);
|
||||
expect(c.foot).toEqual(["Passed through until relaunch"]);
|
||||
expect(c.foot).toEqual(["This host stays encrypted until Yaak Proxy is relaunched."]);
|
||||
});
|
||||
|
||||
test("once no longer passed through, Try Again goes and the foot says so", () => {
|
||||
test("once no longer passed through, Retry goes and the foot says so", () => {
|
||||
const c = card({ flow: refused, list: listed, passthrough: [] });
|
||||
expect(c.actions.map((a) => a.kind)).toEqual(["stop"]);
|
||||
expect(c.foot).toEqual(["No longer passed through"]);
|
||||
expect(c.foot).toEqual(["The next connection will be decrypted."]);
|
||||
});
|
||||
|
||||
test("no longer passed through but excluded since: no claim that it will be decrypted", () => {
|
||||
const excluded = [...user("*", "!app:Acme"), ...BUILT_INS];
|
||||
const c = card({ flow: refused, list: excluded, passthrough: [] });
|
||||
expect(c.actions).toEqual([]);
|
||||
expect(c.foot).toEqual([]);
|
||||
});
|
||||
|
||||
test("an untrusted certificate keeps its line once no longer passed through", () => {
|
||||
const c = card({ flow: refused, list: listed, trusted: false, passthrough: [] });
|
||||
expect(c.foot).toEqual(["This host stays encrypted until the certificate is trusted."]);
|
||||
});
|
||||
|
||||
test("a program: its own trust store, Copy Environment first", () => {
|
||||
@@ -272,16 +286,16 @@ describe("the card of a refused handshake", () => {
|
||||
clientPath: "/opt/homebrew/bin/python3",
|
||||
};
|
||||
const c = card({ flow: python, list: listed, passthrough: ["pypi.org:443"] });
|
||||
expect([lineText(c.why), c.whySub]).toEqual([
|
||||
"python3 rejected the certificate",
|
||||
"uses its own trust store",
|
||||
expect([lineText(c.why), c.tooltip]).toEqual([
|
||||
"python3 rejected the certificate.",
|
||||
"python3 uses its own trust store, so it does not trust the certificate.",
|
||||
]);
|
||||
expect(c.actions.map((a) => [a.kind, a.label])).toEqual([
|
||||
["copy_environment", "Copy Environment"],
|
||||
["stop", "Stop Decrypting python3"],
|
||||
["try_again", "Try Again"],
|
||||
["try_again", "Retry"],
|
||||
]);
|
||||
expect(c.foot).toEqual(["Then rerun python3"]);
|
||||
expect(c.foot).toEqual(["Paste it into a terminal, then rerun python3."]);
|
||||
});
|
||||
|
||||
test("an unknown client: the host", () => {
|
||||
@@ -289,7 +303,7 @@ describe("the card of a refused handshake", () => {
|
||||
flow: { ...refused, clientName: null, clientBundleId: null, clientPath: null },
|
||||
list: listed,
|
||||
});
|
||||
expect(lineText(c.why)).toBe("A client rejected the certificate");
|
||||
expect(lineText(c.why)).toBe("An app rejected the certificate.");
|
||||
expect(summary(c)[0]).toEqual([
|
||||
"Stop Decrypting sync.acme.dev",
|
||||
"!host:sync.acme.dev",
|
||||
@@ -304,9 +318,9 @@ describe("the card of a refused handshake", () => {
|
||||
trusted: false,
|
||||
passthrough: ["sync.acme.dev:443"],
|
||||
});
|
||||
expect(lineText(c.why)).toBe("Certificate not trusted");
|
||||
expect(lineText(c.why)).toBe("The certificate is not trusted.");
|
||||
expect(c.actions.map((a) => a.kind)).toEqual(["trust", "stop"]);
|
||||
expect(c.foot).toEqual(["Passed through until trusted"]);
|
||||
expect(c.foot).toEqual(["This host stays encrypted until the certificate is trusted."]);
|
||||
});
|
||||
|
||||
test("a tunnel after a refusal names the client that refused", () => {
|
||||
@@ -320,7 +334,7 @@ describe("the card of a refused handshake", () => {
|
||||
list: listed,
|
||||
rejectedBy: { clientName: "Acme", clientBundleId: "dev.acme", clientPath: null },
|
||||
});
|
||||
expect(lineText(c.why)).toBe("Acme rejected the certificate for this host");
|
||||
expect(lineText(c.why)).toBe("Acme rejected the certificate for this host.");
|
||||
// Stop Decrypting acts on this connection, so on Dia's own
|
||||
expect(c.actions[0]?.hint).toBe("!app:Dia");
|
||||
});
|
||||
|
||||
@@ -95,12 +95,10 @@ export function lineText(line: Line): string {
|
||||
|
||||
export interface EncryptedCardModel {
|
||||
rejected: boolean;
|
||||
/** Why the connection is encrypted, in one line */
|
||||
/** Why the connection is encrypted, in one sentence */
|
||||
why: Line;
|
||||
/** Quieter, after it */
|
||||
whySub: string | null;
|
||||
/** A fact about the connection since the list changed: when it opened */
|
||||
fact: Line | null;
|
||||
/** What the info button beside it says, in place of the general line; null for that line */
|
||||
tooltip: string | null;
|
||||
actions: CardAction[];
|
||||
/** Small lines under the buttons */
|
||||
foot: string[];
|
||||
@@ -155,7 +153,6 @@ function tunnelCard({ flow, list, trusted, canTrust }: EncryptedCardInput): Encr
|
||||
const c = connectionOf(flow);
|
||||
const q = queryOf(c);
|
||||
const decryptsNow = decrypts(list, q);
|
||||
const { why, whySub } = whyLine(flow, list, c, decryptsNow);
|
||||
const foot: string[] = [];
|
||||
|
||||
if (decryptsNow) {
|
||||
@@ -172,9 +169,8 @@ function tunnelCard({ flow, list, trusted, canTrust }: EncryptedCardInput): Encr
|
||||
first === EVERYTHING ? "everything" : { code: first == null ? c.label : entrySubject(first) };
|
||||
return {
|
||||
rejected: false,
|
||||
why,
|
||||
whySub,
|
||||
fact: ["Opened before ", what, " was decrypted"],
|
||||
why: ["This connection was opened before ", what, " was decrypted."],
|
||||
tooltip: null,
|
||||
actions,
|
||||
foot: [],
|
||||
};
|
||||
@@ -193,8 +189,8 @@ function tunnelCard({ flow, list, trusted, canTrust }: EncryptedCardInput): Encr
|
||||
foot.length = 0;
|
||||
foot.push(
|
||||
builtInBlock.kind === "apple" || c.app == null
|
||||
? "macOS may refuse it"
|
||||
: `${c.app.name} may refuse it`,
|
||||
? "macOS may reject the connection."
|
||||
: `${c.app.name} may reject the connection.`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -209,7 +205,7 @@ function tunnelCard({ flow, list, trusted, canTrust }: EncryptedCardInput): Encr
|
||||
label: "Decrypt Everything",
|
||||
hint: EVERYTHING,
|
||||
edit: { add: [EVERYTHING], remove: [] },
|
||||
toast: { message: "Decrypting everything", sub: "Except Apple services and apps that pin" },
|
||||
toast: { message: "Decrypting everything", sub: "Excluded hosts and apps stay encrypted." },
|
||||
icon: "everything",
|
||||
});
|
||||
}
|
||||
@@ -237,48 +233,41 @@ function tunnelCard({ flow, list, trusted, canTrust }: EncryptedCardInput): Encr
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actions.length > 0 && canTrust && trusted === false) foot.push("Asks for your password once");
|
||||
if (actions.length > 0 && canTrust && trusted === false) {
|
||||
foot.push("macOS will ask for a password once.");
|
||||
}
|
||||
return {
|
||||
rejected: false,
|
||||
why,
|
||||
whySub,
|
||||
fact: null,
|
||||
why: whyLine(flow, list, c),
|
||||
tooltip: null,
|
||||
actions,
|
||||
foot: actions.length === 0 ? [] : foot,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The why line, from the reason the engine recorded as the tunnel opened. One that nothing on
|
||||
* the list covers names its host; once the list covers it, it opened before that.
|
||||
* The why line of a connection the list does not decrypt, from the reason the engine recorded
|
||||
* as the tunnel opened: what excluded it, or, with nothing to exclude it, its host.
|
||||
*/
|
||||
function whyLine(
|
||||
flow: EncryptedCardInput["flow"],
|
||||
list: DecryptListEntry[],
|
||||
c: Connection,
|
||||
decryptsNow: boolean,
|
||||
): { why: Line; whySub: string | null } {
|
||||
function whyLine(flow: EncryptedCardInput["flow"], list: DecryptListEntry[], c: Connection): Line {
|
||||
if (flow.tunnelReason === "excluded" && flow.tunnelExcludedBy != null) {
|
||||
if (flow.tunnelExcludedBuiltIn) {
|
||||
const kind = list.find((e) => e.entry === flow.tunnelExcludedBy)?.kind;
|
||||
const whySub =
|
||||
kind === "apple" ? "Apple service" : kind === "pinned" ? "pins its certificate" : null;
|
||||
return { why: ["Excluded by default"], whySub };
|
||||
return [
|
||||
kind === "apple"
|
||||
? "Apple services are excluded by default."
|
||||
: kind === "pinned"
|
||||
? "Apps that pin certificates are excluded by default."
|
||||
: "This connection is excluded by default.",
|
||||
];
|
||||
}
|
||||
return { why: ["Excluded by ", { code: flow.tunnelExcludedBy }], whySub: null };
|
||||
return ["This connection is excluded by ", { code: flow.tunnelExcludedBy }, "."];
|
||||
}
|
||||
return {
|
||||
why: !decryptsAnything(list)
|
||||
? ["Nothing is decrypted yet"]
|
||||
: decryptsNow
|
||||
? ["Not on the decrypt list"]
|
||||
: [
|
||||
{ bold: "Encrypted Content:" },
|
||||
" Enable decryption to see requests to ",
|
||||
{ code: c.label },
|
||||
],
|
||||
whySub: null,
|
||||
};
|
||||
return [
|
||||
{ bold: "Encrypted Content:" },
|
||||
" Enable decryption to see requests to ",
|
||||
{ code: c.label },
|
||||
];
|
||||
}
|
||||
|
||||
function decryptSubject(
|
||||
@@ -370,20 +359,27 @@ function rejectedCard(input: EncryptedCardInput): EncryptedCardModel {
|
||||
const tryAgain: CardAction | null =
|
||||
passedThrough === false
|
||||
? null
|
||||
: { kind: "try_again", label: "Try Again", hint: "next connection", icon: "refresh" };
|
||||
const until = passedThrough === false ? "No longer passed through" : null;
|
||||
: { kind: "try_again", label: "Retry", hint: "next connection", icon: "refresh" };
|
||||
// No longer passed through: the next connection is decrypted while the list still decrypts
|
||||
// it. Excluded since, it stays encrypted, and there is nothing to add.
|
||||
const until: string[] | null =
|
||||
passedThrough === false
|
||||
? stop == null
|
||||
? []
|
||||
: ["The next connection will be decrypted."]
|
||||
: null;
|
||||
|
||||
if (canTrust && trusted === false && decryptsAnything(list)) {
|
||||
return {
|
||||
rejected: true,
|
||||
why: ["Certificate not trusted"],
|
||||
whySub: null,
|
||||
fact: null,
|
||||
why: ["The certificate is not trusted."],
|
||||
tooltip: null,
|
||||
actions: [
|
||||
{ kind: "trust", label: "Trust Certificate", hint: null, primary: true, icon: "shield" },
|
||||
...(stop == null ? [] : [stop]),
|
||||
],
|
||||
foot: [until ?? "Passed through until trusted"],
|
||||
// Whether or not it is still passed through: untrusted, the next one is rejected too.
|
||||
foot: ["This host stays encrypted until the certificate is trusted."],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -391,9 +387,8 @@ function rejectedCard(input: EncryptedCardInput): EncryptedCardModel {
|
||||
if (refuser.kind === "program") {
|
||||
return {
|
||||
rejected: true,
|
||||
why: [`${refuser.name} rejected the certificate${forHost}`],
|
||||
whySub: "uses its own trust store",
|
||||
fact: null,
|
||||
why: [`${refuser.name} rejected the certificate${forHost}.`],
|
||||
tooltip: `${refuser.name} uses its own trust store, so it does not trust the certificate.`,
|
||||
actions: [
|
||||
{
|
||||
kind: "copy_environment",
|
||||
@@ -405,22 +400,24 @@ function rejectedCard(input: EncryptedCardInput): EncryptedCardModel {
|
||||
...(stop == null ? [] : [stop]),
|
||||
...(tryAgain == null ? [] : [tryAgain]),
|
||||
],
|
||||
foot: until == null ? [`Then rerun ${refuser.name}`] : [until],
|
||||
foot: until ?? [`Paste it into a terminal, then rerun ${refuser.name}.`],
|
||||
};
|
||||
}
|
||||
return {
|
||||
rejected: true,
|
||||
why: [
|
||||
refuser.kind === "app"
|
||||
? `${refuser.name} rejected the certificate${forHost}`
|
||||
: "A client rejected the certificate",
|
||||
? `${refuser.name} rejected the certificate${forHost}.`
|
||||
: "An app rejected the certificate.",
|
||||
],
|
||||
whySub: refuser.kind === "app" ? "likely pinned" : null,
|
||||
fact: null,
|
||||
tooltip:
|
||||
refuser.kind === "app"
|
||||
? `${refuser.name} likely pins its certificate, so it cannot be decrypted.`
|
||||
: null,
|
||||
actions: [
|
||||
...(stop == null ? [] : [{ ...stop, primary: true }]),
|
||||
...(tryAgain == null ? [] : [tryAgain]),
|
||||
],
|
||||
foot: [until ?? "Passed through until relaunch"],
|
||||
foot: until ?? ["This host stays encrypted until Yaak Proxy is relaunched."],
|
||||
};
|
||||
}
|
||||
@@ -33,8 +33,8 @@ export function useSaveFlowBody(): (flow: Flow, which: FlowBody) => Promise<void
|
||||
if (path == null) return;
|
||||
const saved = await rpc("save_flow_body", { flowId: flow.id, which, path });
|
||||
const notes = [
|
||||
saved.raw ? "as it was on the wire, since it failed to decode" : null,
|
||||
saved.truncated ? "cut at the capture limit" : null,
|
||||
saved.raw ? "as received, since it failed to decode" : null,
|
||||
saved.truncated ? "truncated at the capture limit" : null,
|
||||
].filter((n) => n != null);
|
||||
showToast({
|
||||
message: `Saved ${formatSize(saved.size)}${notes.length > 0 ? `, ${notes.join(", ")}` : ""}`,
|
||||
@@ -135,9 +135,9 @@ export function useFlowActions(): (flow: Flow) => DropdownItem[] {
|
||||
return;
|
||||
}
|
||||
await navigator.clipboard.writeText(value);
|
||||
showToast({ message: `Copied ${label}` });
|
||||
showToast({ message: "Copied to clipboard" });
|
||||
} catch (err) {
|
||||
showToast({ message: `Failed to copy ${label}: ${String(err)}`, color: "danger" });
|
||||
showToast({ message: `Failed to copy: ${String(err)}`, color: "danger" });
|
||||
}
|
||||
};
|
||||
const noResponse = (flow.error != null || flow.cancelled) && flow.status == null;
|
||||
@@ -173,7 +173,7 @@ export function useFlowActions(): (flow: Flow) => DropdownItem[] {
|
||||
// A command that sends other bytes than the app did would mislead.
|
||||
if (body == null) {
|
||||
showToast({
|
||||
message: "Body is binary or truncated; export HAR instead",
|
||||
message: "The body is binary or truncated. Export as HAR instead.",
|
||||
color: "info",
|
||||
});
|
||||
return;
|
||||
@@ -209,12 +209,12 @@ export function useFlowActions(): (flow: Flow) => DropdownItem[] {
|
||||
onSelect: () => saveBody(flow, "response"),
|
||||
},
|
||||
{
|
||||
label: "Export HAR",
|
||||
label: "Export as HAR",
|
||||
icon: "download",
|
||||
onSelect: async () => {
|
||||
try {
|
||||
const written = await exportHar({ ids: [flow.id] }, "flow.har");
|
||||
if (written != null) showToast({ message: "Exported the flow as HAR" });
|
||||
if (written != null) showToast({ message: "Exported 1 flow as HAR" });
|
||||
} catch (err) {
|
||||
showToast({ message: `Failed to export HAR: ${String(err)}`, color: "danger" });
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ describe("flowResponseToView", () => {
|
||||
|
||||
test("says when the client gave up before a response", () => {
|
||||
const view = flowResponseToView({ ...flow, status: null, elapsedMs: null, cancelled: true });
|
||||
expect(view?.error).toBe("Cancelled by the client");
|
||||
expect(view?.error).toBe("Cancelled by the app");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ function responseView(flow: Flow, body: FlowBodyData | null, ended: boolean): Ht
|
||||
elapsedMs: flow.elapsedMs ?? 0,
|
||||
elapsedHeadersMs: flow.elapsedHeadersMs ?? 0,
|
||||
elapsedDnsMs: 0,
|
||||
error: flow.error ?? (flow.cancelled && flow.status == null ? "Cancelled by the client" : null),
|
||||
error: flow.error ?? (flow.cancelled && flow.status == null ? "Cancelled by the app" : null),
|
||||
remoteAddr: flow.remoteAddr,
|
||||
version: flow.upstreamHttpVersion ?? flow.clientHttpVersion,
|
||||
cookieHeaders: {
|
||||
|
||||
@@ -14,8 +14,8 @@ describe("lockedLine", () => {
|
||||
test("gives any other reason without the title's words", () => {
|
||||
const lock = {
|
||||
keychain: false,
|
||||
message: "Traffic is locked: the proxy is already running in the yaak CLI (pid 4).",
|
||||
message: "Traffic is locked: the proxy is already running in the CLI (pid 4).",
|
||||
};
|
||||
expect(lockedLine(lock, "macos")).toBe("The proxy is already running in the yaak CLI (pid 4).");
|
||||
expect(lockedLine(lock, "macos")).toBe("The proxy is already running in the CLI (pid 4).");
|
||||
});
|
||||
});
|
||||
@@ -271,7 +271,7 @@ describe("syncSidebarSelection", () => {
|
||||
|
||||
test("All traffic is exclusive either way round", () => {
|
||||
const { store, selected, idOf, choose } = sidebar();
|
||||
const [all, github] = [idOf("All traffic"), idOf("github.com")];
|
||||
const [all, github] = [idOf("All Traffic"), idOf("github.com")];
|
||||
choose([github]);
|
||||
choose([github, all]);
|
||||
expect(store.get(selected)).toEqual([all]);
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("opener", () => {
|
||||
await revealPath("/data/app.yaak.proxy", report);
|
||||
expect(opener.revealItemInDir).toHaveBeenCalledWith("/data/app.yaak.proxy");
|
||||
expect(report).toHaveBeenCalledWith(
|
||||
"Failed to show /data/app.yaak.proxy: not allowed: opener.reveal_item_in_dir",
|
||||
"Failed to reveal /data/app.yaak.proxy: not allowed: opener.reveal_item_in_dir",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -43,6 +43,6 @@ describe("opener", () => {
|
||||
test("names the file manager by platform", () => {
|
||||
expect(revealLabel("macos")).toBe("Reveal in Finder");
|
||||
expect(revealLabel("windows")).toBe("Show in Explorer");
|
||||
expect(revealLabel("linux")).toBe("Show in Folder");
|
||||
expect(revealLabel("linux")).toBe("Show in File Manager");
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ export async function revealPath(path: string, report: ReportFailure): Promise<v
|
||||
try {
|
||||
await revealItemInDir(path);
|
||||
} catch (err) {
|
||||
report(`Failed to show ${path}: ${String(err)}`);
|
||||
report(`Failed to reveal ${path}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,5 +25,5 @@ export async function openLink(url: string, report: ReportFailure): Promise<void
|
||||
export function revealLabel(os: string): string {
|
||||
if (os === "macos") return "Reveal in Finder";
|
||||
if (os === "windows") return "Show in Explorer";
|
||||
return "Show in Folder";
|
||||
return "Show in File Manager";
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export async function loadSeenValues(paths: string[]): Promise<Record<string, st
|
||||
const PROBLEM_LABELS: Record<QueryDiagnostic["kind"], string> = {
|
||||
unknownField: "Unknown field",
|
||||
invalidValue: "Invalid value",
|
||||
notYet: "Not yet",
|
||||
notYet: "Not supported",
|
||||
};
|
||||
|
||||
/** The terms of a search the backend cannot run, as the box underlines them */
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("buildSidebarTree", () => {
|
||||
|
||||
test("groups apps, programs by folder, sites and System", () => {
|
||||
expect((root.children ?? []).flatMap((c) => shape(c))).toEqual([
|
||||
"All traffic 40",
|
||||
"All Traffic 40",
|
||||
"Apps 3",
|
||||
" node (fieldwork) 2",
|
||||
" github.com 2",
|
||||
@@ -338,7 +338,7 @@ describe("saved searches", () => {
|
||||
];
|
||||
const root = buildSidebarTree(flows, 4, saved);
|
||||
expect((root.children ?? []).slice(0, 2).flatMap((c) => shape(c))).toEqual([
|
||||
"All traffic 4",
|
||||
"All Traffic 4",
|
||||
"Saved 0",
|
||||
" Errors 12",
|
||||
" Stripe 0",
|
||||
|
||||
@@ -347,7 +347,7 @@ export function buildSidebarTree(
|
||||
for (const site of domains.children.values()) domains.item.count += site.children.size;
|
||||
|
||||
root.children?.push({
|
||||
item: { id: ALL_ID, kind: "all", label: "All traffic", count: total, query: "" },
|
||||
item: { id: ALL_ID, kind: "all", label: "All Traffic", count: total, query: "" },
|
||||
parent: root,
|
||||
depth: 1,
|
||||
draggable: false,
|
||||
|
||||
@@ -14,12 +14,12 @@ describe("systemProxySwitch", () => {
|
||||
test("says whether this Mac's traffic is routed now", () => {
|
||||
expect(systemProxySwitch({ ...off, routing: true })).toEqual({
|
||||
checked: true,
|
||||
description: "Routing this Mac's web traffic through Yaak Proxy",
|
||||
description: "Traffic from every app will be recorded.",
|
||||
failed: false,
|
||||
});
|
||||
expect(systemProxySwitch(off)).toEqual({
|
||||
checked: false,
|
||||
description: "Not routing. Only apps pointed at port 9090 are captured",
|
||||
description: "Only traffic sent to port 9090 will be recorded.",
|
||||
failed: false,
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,7 @@ describe("systemProxySwitch", () => {
|
||||
test("shows the new value while the password prompt is up", () => {
|
||||
expect(systemProxySwitch({ ...off, pending: true })).toEqual({
|
||||
checked: true,
|
||||
description: "Waiting for your password",
|
||||
description: "Waiting for macOS",
|
||||
failed: false,
|
||||
});
|
||||
});
|
||||
@@ -41,9 +41,9 @@ describe("systemProxySwitch", () => {
|
||||
});
|
||||
|
||||
test("snaps back on a cancelled prompt and says so", () => {
|
||||
expect(systemProxySwitch({ ...off, error: "Cancelled in the password dialog" })).toEqual({
|
||||
expect(systemProxySwitch({ ...off, error: "The password prompt was cancelled." })).toEqual({
|
||||
checked: false,
|
||||
description: "Cancelled in the password prompt",
|
||||
description: "The password prompt was cancelled.",
|
||||
failed: true,
|
||||
});
|
||||
expect(systemProxySwitch({ ...off, error: "networksetup failed" }).description).toBe(
|
||||
@@ -53,27 +53,32 @@ describe("systemProxySwitch", () => {
|
||||
});
|
||||
|
||||
describe("helperRow", () => {
|
||||
test("offers each next step, and says which way changes go", () => {
|
||||
test("offers each next step, and says what the helper is for", () => {
|
||||
expect(helperRow("not_registered", "admin_prompt")).toMatchObject({
|
||||
title: "Helper: not installed",
|
||||
description: "The helper is not installed.",
|
||||
action: "install",
|
||||
});
|
||||
expect(helperRow("requires_approval", "admin_prompt")).toMatchObject({
|
||||
title: "Helper: awaiting approval",
|
||||
description: "The helper is awaiting approval.",
|
||||
action: "approve",
|
||||
});
|
||||
const enabled = helperRow("enabled", "helper");
|
||||
expect(enabled).toMatchObject({ title: "Helper: enabled", action: "unregister" });
|
||||
expect(enabled.description).toMatch(/No password/);
|
||||
expect(enabled).toMatchObject({
|
||||
description: "The helper is installed.",
|
||||
action: "unregister",
|
||||
});
|
||||
expect(enabled.tooltip).toMatch(/without a password prompt/);
|
||||
});
|
||||
|
||||
test("says when the password prompt still has settings to put back", () => {
|
||||
expect(helperRow("enabled", "admin_prompt").description).toMatch(/password prompt/);
|
||||
expect(helperRow("enabled", "admin_prompt").tooltip).toBe(
|
||||
"One more password prompt is needed to restore the system proxy settings.",
|
||||
);
|
||||
});
|
||||
|
||||
test("offers to install when macOS can't find it, rather than blaming a dev build", () => {
|
||||
expect(helperRow("not_found", "admin_prompt")).toMatchObject({
|
||||
title: "Helper: not found by macOS",
|
||||
description: "macOS could not find the helper.",
|
||||
action: "install",
|
||||
});
|
||||
});
|
||||
@@ -89,13 +94,13 @@ describe("helperRow", () => {
|
||||
for (const state of states) {
|
||||
for (const backend of ["helper", "admin_prompt"] as const) {
|
||||
const row = helperRow(state, backend);
|
||||
expect(row.title.length).toBeLessThanOrEqual(30);
|
||||
expect(row.description.length).toBeLessThanOrEqual(60);
|
||||
expect(row.tooltip.length).toBeLessThanOrEqual(80);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("has a titled row for every state the backend sends", () => {
|
||||
test("has a row for every state the backend sends", () => {
|
||||
// A missing key here fails the typecheck, so a new state can't be left out.
|
||||
const states: Record<HelperState, true> = {
|
||||
unavailable: true,
|
||||
@@ -106,7 +111,7 @@ describe("helperRow", () => {
|
||||
};
|
||||
for (const state of Object.keys(states) as HelperState[]) {
|
||||
for (const backend of ["helper", "admin_prompt", "sudo"] as const) {
|
||||
expect(helperRow(state, backend).title).toMatch(/^Helper: \S/);
|
||||
expect(helperRow(state, backend).description).toMatch(/helper.*\.$/);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -114,15 +119,15 @@ describe("helperRow", () => {
|
||||
test("still gives a row for a state this build doesn't know", () => {
|
||||
const row = helperRow("from_the_future" as HelperState, "admin_prompt");
|
||||
expect(row).toEqual({
|
||||
title: "Helper: from_the_future",
|
||||
description: "Changes use the password prompt.",
|
||||
description: "The helper is from_the_future.",
|
||||
tooltip: "The helper changes the system proxy without a password prompt.",
|
||||
action: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("says plainly that a dev build cannot use it", () => {
|
||||
expect(helperRow("unavailable", "admin_prompt")).toMatchObject({
|
||||
title: "Helper: not in dev builds",
|
||||
description: "The helper is not available in dev builds.",
|
||||
action: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface SystemProxySwitch {
|
||||
/** What the system proxy switch shows */
|
||||
export function systemProxySwitch(state: SystemProxyState): SystemProxySwitch {
|
||||
if (state.pending != null) {
|
||||
const description = state.helper ? "Changing the system proxy" : "Waiting for your password";
|
||||
const description = state.helper ? "Changing the system proxy" : "Waiting for macOS";
|
||||
return { checked: state.pending, description, failed: false };
|
||||
}
|
||||
// A failed change leaves the switch where the system proxy actually is.
|
||||
@@ -37,71 +37,75 @@ export function systemProxySwitch(state: SystemProxyState): SystemProxySwitch {
|
||||
const cancelled = /cancel/i.test(state.error);
|
||||
return {
|
||||
checked,
|
||||
description: cancelled ? "Cancelled in the password prompt" : state.error,
|
||||
description: cancelled ? "The password prompt was cancelled." : state.error,
|
||||
failed: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
checked,
|
||||
description: checked
|
||||
? "Routing this Mac's web traffic through Yaak Proxy"
|
||||
: `Not routing. Only apps pointed at port ${state.port ?? 9090} are captured`,
|
||||
? "Traffic from every app will be recorded."
|
||||
: `Only traffic sent to port ${state.port ?? 9090} will be recorded.`,
|
||||
failed: false,
|
||||
};
|
||||
}
|
||||
|
||||
export interface HelperRow {
|
||||
title: string;
|
||||
/** Which way changes go, and why */
|
||||
/** The helper's state, in one sentence */
|
||||
description: string;
|
||||
/** What the helper is for, or what this state still needs, behind the row's info icon */
|
||||
tooltip: string;
|
||||
/** What the row's button does, if it has one: the first two open the approval dialog */
|
||||
action: "install" | "approve" | "unregister" | null;
|
||||
}
|
||||
|
||||
/** The privileged helper's row in the state pill menu, kept short */
|
||||
/** What the helper is for, the same in every state but one */
|
||||
const HELPER_TOOLTIP = "The helper changes the system proxy without a password prompt.";
|
||||
|
||||
/** The privileged helper's row in Settings, kept short */
|
||||
export function helperRow(helper: HelperState, backend: SystemProxyBackend): HelperRow {
|
||||
switch (helper) {
|
||||
case "unavailable":
|
||||
return {
|
||||
title: "Helper: not in dev builds",
|
||||
description: "Changes use the password prompt.",
|
||||
description: "The helper is not available in dev builds.",
|
||||
tooltip: HELPER_TOOLTIP,
|
||||
action: null,
|
||||
};
|
||||
case "not_found":
|
||||
// What macOS reports until the helper is first installed.
|
||||
return {
|
||||
title: "Helper: not found by macOS",
|
||||
description: "Changes use the password prompt.",
|
||||
description: "macOS could not find the helper.",
|
||||
tooltip: HELPER_TOOLTIP,
|
||||
action: "install",
|
||||
};
|
||||
case "not_registered":
|
||||
return {
|
||||
title: "Helper: not installed",
|
||||
description: "Changes use the password prompt.",
|
||||
description: "The helper is not installed.",
|
||||
tooltip: HELPER_TOOLTIP,
|
||||
action: "install",
|
||||
};
|
||||
case "requires_approval":
|
||||
return {
|
||||
title: "Helper: awaiting approval",
|
||||
description: "Changes use the password prompt.",
|
||||
description: "The helper is awaiting approval.",
|
||||
tooltip: HELPER_TOOLTIP,
|
||||
action: "approve",
|
||||
};
|
||||
case "enabled":
|
||||
return {
|
||||
title: "Helper: enabled",
|
||||
description:
|
||||
description: "The helper is installed.",
|
||||
tooltip:
|
||||
backend === "helper"
|
||||
? "No password prompts."
|
||||
: "One more password prompt puts back your settings.",
|
||||
? HELPER_TOOLTIP
|
||||
: "One more password prompt is needed to restore the system proxy settings.",
|
||||
action: "unregister",
|
||||
};
|
||||
default: {
|
||||
// Every state is handled above; one this build doesn't know still gets a row, rather
|
||||
// than taking the whole menu down.
|
||||
// than taking the whole page down.
|
||||
const unknown: never = helper;
|
||||
return {
|
||||
title: `Helper: ${String(unknown)}`,
|
||||
description: "Changes use the password prompt.",
|
||||
description: `The helper is ${String(unknown)}.`,
|
||||
tooltip: HELPER_TOOLTIP,
|
||||
action: null,
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -672,7 +672,7 @@ droppedHeaders: Array<DroppedHeader>,
|
||||
*/
|
||||
keptHeaders: number,
|
||||
/**
|
||||
* The body as it is saved: "JSON", "form URL-encoded with 7 fields". None without a body.
|
||||
* The body as it is saved: "JSON", "a URL-encoded form with 7 fields". None without a body.
|
||||
*/
|
||||
body: string | null,
|
||||
/**
|
||||
|
||||
@@ -12,8 +12,7 @@ use yaak_models::blob_manager::BlobManager;
|
||||
const MESSAGE_PAGE: usize = 500;
|
||||
|
||||
/// What a WebSocket's response body reads as: it has none.
|
||||
pub const WEBSOCKET_BODY_NOTE: &str =
|
||||
"A WebSocket has no body after its 101 response. Its frames are its messages.";
|
||||
pub const WEBSOCKET_BODY_NOTE: &str = "A WebSocket has no body";
|
||||
|
||||
/// Whether a flow's body is its stream's messages rather than bytes stored on their own: an
|
||||
/// event stream's response, a gRPC call's request, and its response when the server answered
|
||||
|
||||
@@ -395,7 +395,7 @@ pub(crate) fn already_owned(owner: &ControlInfo) -> String {
|
||||
pub(crate) fn owner_name(owner: Owner) -> &'static str {
|
||||
match owner {
|
||||
Owner::App => "Yaak Proxy",
|
||||
Owner::Cli => "the yaak CLI",
|
||||
Owner::Cli => "the CLI",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,14 +65,11 @@ pub fn flow_to_request(flow: &Flow, body: &DecodedBody) -> Conversion {
|
||||
let (headers, dropped_headers) = split_headers(flow, body);
|
||||
let has_authorization = headers.iter().any(|h| h.name.eq_ignore_ascii_case("authorization"));
|
||||
if has_authorization {
|
||||
warnings.push("The Authorization header is saved as captured, in plain text.".into());
|
||||
warnings.push("The Authorization header is saved in plain text.".into());
|
||||
}
|
||||
let (body_type, body_fields) = request_body(flow, body, &mut warnings);
|
||||
if body.truncated {
|
||||
warnings.push(format!(
|
||||
"The body was cut at the capture limit, so only its first {} bytes are saved.",
|
||||
body.data.len()
|
||||
));
|
||||
warnings.push(format!("Only the first {} bytes of the body are saved.", body.data.len()));
|
||||
}
|
||||
|
||||
// No name, as in Yaak, which then shows the URL.
|
||||
@@ -169,10 +166,7 @@ fn request_body(
|
||||
let content_type = find_header(&flow.request_headers, "content-type").unwrap_or_default();
|
||||
let mime = content_type.split(';').next().unwrap_or_default().trim().to_ascii_lowercase();
|
||||
let Ok(text) = std::str::from_utf8(&body.data) else {
|
||||
warnings.push(
|
||||
"The body is binary and is saved as text, so bytes that are not UTF-8 are replaced."
|
||||
.into(),
|
||||
);
|
||||
warnings.push("The body is binary and may be altered when saved.".into());
|
||||
return text_body(BODY_TYPE_OTHER, &String::from_utf8_lossy(&body.data));
|
||||
};
|
||||
|
||||
@@ -195,9 +189,7 @@ fn request_body(
|
||||
);
|
||||
}
|
||||
if mime == "multipart/form-data" {
|
||||
warnings.push(
|
||||
"The multipart body is saved as text with its Content-Type, not as form fields.".into(),
|
||||
);
|
||||
warnings.push("The multipart body is saved as text, not form fields.".into());
|
||||
}
|
||||
text_body(BODY_TYPE_OTHER, text)
|
||||
}
|
||||
@@ -300,10 +292,7 @@ mod tests {
|
||||
("Proxy-Authorization", DropReason::HopByHop),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
c.warnings,
|
||||
vec!["The Authorization header is saved as captured, in plain text."]
|
||||
);
|
||||
assert_eq!(c.warnings, vec!["The Authorization header is saved in plain text."]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -463,9 +452,6 @@ mod tests {
|
||||
let c = flow_to_request(&f, &data);
|
||||
assert_eq!(names(&c.request), vec!["Content-Type"]);
|
||||
assert_eq!(c.dropped_headers[0].reason, DropReason::BodyDecoded);
|
||||
assert_eq!(
|
||||
c.warnings,
|
||||
vec!["The body was cut at the capture limit, so only its first 4 bytes are saved."]
|
||||
);
|
||||
assert_eq!(c.warnings, vec!["Only the first 4 bytes of the body are saved."]);
|
||||
}
|
||||
}
|
||||
@@ -154,10 +154,7 @@ fn any_encrypted(data_dir: &Path) -> bool {
|
||||
}
|
||||
|
||||
fn missing_data_key(data_dir: &Path) -> String {
|
||||
format!(
|
||||
"The proxy's captured traffic is encrypted, but its key is missing from {}",
|
||||
data_dir.display()
|
||||
)
|
||||
format!("The encryption key is missing from {}.", data_dir.display())
|
||||
}
|
||||
|
||||
/// Both databases, opened together.
|
||||
@@ -324,12 +321,11 @@ pub fn encrypt_existing(
|
||||
Ok(converted)
|
||||
}
|
||||
|
||||
const KEY_UNAVAILABLE: &str =
|
||||
"Yaak Proxy stores traffic encrypted and couldn't read its key from the keychain";
|
||||
const KEY_UNAVAILABLE: &str = "Failed to read the encryption key from the keychain";
|
||||
|
||||
/// Why the databases cannot be opened, or the CA made, without the data key.
|
||||
pub fn key_unavailable(reason: &str) -> String {
|
||||
format!("{KEY_UNAVAILABLE}: {reason}. Allow it access, then retry.")
|
||||
format!("{KEY_UNAVAILABLE}: {reason}")
|
||||
}
|
||||
|
||||
/// `reason` is [`key_unavailable`]'s: the keychain would not give up the key.
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct FilterHistoryEntry {
|
||||
}
|
||||
|
||||
fn failed(what: &str, e: impl std::fmt::Display) -> String {
|
||||
format!("Failed to {what} the filter history: {e}")
|
||||
format!("Failed to {what} the search history: {e}")
|
||||
}
|
||||
|
||||
/// Every filter kept, most recently applied first. Empty for a database from before the
|
||||
|
||||
@@ -719,7 +719,7 @@ pub fn delete_all(db: &ProxyQueryManager) -> Result<(), String> {
|
||||
}
|
||||
Ok::<_, yaak_database::Error>(())
|
||||
})
|
||||
.map_err(|e| format!("Failed to delete captured traffic: {e}"))
|
||||
.map_err(|e| format!("Failed to delete recorded traffic: {e}"))
|
||||
}
|
||||
|
||||
/// Flows deleted per transaction while deleting cleared ones, so a flow being written never
|
||||
|
||||
@@ -387,7 +387,7 @@ async fn find_schema(
|
||||
}
|
||||
Err(_) => Ok((None, Some(format!("No schema for {service} on {authority}")))),
|
||||
},
|
||||
Err(e) => Ok((None, Some(format!("No schema for {service}; reflection failed: {e}")))),
|
||||
Err(e) => Ok((None, Some(format!("No schema for {service}. Reflection failed: {e}")))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,11 +448,15 @@ fn decode_with(
|
||||
json: yaak_grpc::message_to_json(&decoded)?,
|
||||
message_type: Some(message_type),
|
||||
schema: Some(schema),
|
||||
note: message.truncated.then(|| "The message is cut at the capture limit".into()),
|
||||
note: message
|
||||
.truncated
|
||||
.then(|| "The message was truncated at the capture limit".into()),
|
||||
}),
|
||||
Err(e) => {
|
||||
Ok(schemaless(payload, message, Some(format!("Did not decode as {message_type}: {e}"))))
|
||||
}
|
||||
Err(e) => Ok(schemaless(
|
||||
payload,
|
||||
message,
|
||||
Some(format!("Failed to decode as {message_type}: {e}")),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,7 +464,7 @@ fn schemaless(payload: &[u8], message: &FlowMessage, reason: Option<String>) ->
|
||||
let mut notes: Vec<String> = reason.into_iter().collect();
|
||||
notes.push("Decoded without a schema: field names and types are guesses".into());
|
||||
if message.truncated {
|
||||
notes.push("The message is cut at the capture limit".into());
|
||||
notes.push("The message was truncated at the capture limit".into());
|
||||
}
|
||||
let fields = decode_fields(payload, 0).unwrap_or_else(|| {
|
||||
notes.push("Not a valid protobuf message: shown as bytes".into());
|
||||
|
||||
@@ -153,12 +153,12 @@ mod imp {
|
||||
}
|
||||
// SAFETY: unregisters the plist bundled with this app.
|
||||
unsafe { service.unregisterAndReturnError() }
|
||||
.map_err(|e| format!("Failed to remove the helper: {}", e.localizedDescription()))?;
|
||||
.map_err(|e| format!("Failed to uninstall the helper: {}", e.localizedDescription()))?;
|
||||
Ok(state_of(&service))
|
||||
}
|
||||
|
||||
fn unavailable() -> String {
|
||||
"The helper is only available in the installed app, not a dev build".to_string()
|
||||
"The helper is not available in dev builds".to_string()
|
||||
}
|
||||
|
||||
/// Sends one request and reads its response.
|
||||
@@ -181,13 +181,15 @@ mod imp {
|
||||
let _ = stream.set_write_timeout(Some(IO_TIMEOUT));
|
||||
let mut line = serde_json::to_string(request).map_err(|e| e.to_string())?;
|
||||
line.push('\n');
|
||||
(&stream).write_all(line.as_bytes()).map_err(|e| format!("The helper hung up: {e}"))?;
|
||||
(&stream)
|
||||
.write_all(line.as_bytes())
|
||||
.map_err(|e| format!("The helper disconnected: {e}"))?;
|
||||
let mut reply = String::new();
|
||||
BufReader::new(&stream)
|
||||
.read_line(&mut reply)
|
||||
.map_err(|e| format!("The helper did not answer: {e}"))?;
|
||||
.map_err(|e| format!("The helper did not respond: {e}"))?;
|
||||
let response: Response = serde_json::from_str(&reply)
|
||||
.map_err(|e| format!("The helper's answer was not understood: {e}"))?;
|
||||
.map_err(|e| format!("The helper sent an invalid response: {e}"))?;
|
||||
response.into_result()
|
||||
}
|
||||
|
||||
@@ -214,12 +216,12 @@ pub fn state() -> HelperState {
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn diagnose() -> String {
|
||||
"The helper is only available on macOS".to_string()
|
||||
"The helper is only supported on macOS".to_string()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn register() -> Result<HelperState, String> {
|
||||
Err("The helper is only available on macOS".to_string())
|
||||
Err("The helper is only supported on macOS".to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
||||
@@ -233,7 +233,7 @@ impl ProxyCtx {
|
||||
|
||||
fn lock_reason(&self) -> String {
|
||||
if self.encrypting().is_some() {
|
||||
return "traffic captured before this version is being encrypted.".to_string();
|
||||
return "traffic recorded before this version is being encrypted.".to_string();
|
||||
}
|
||||
let reason = self.lock_reason.lock().unwrap_or_else(PoisonError::into_inner).clone();
|
||||
reason.unwrap_or_else(|| "its key has not been read yet.".to_string())
|
||||
@@ -275,8 +275,7 @@ impl ProxyCtx {
|
||||
&& owner.pid != std::process::id()
|
||||
{
|
||||
return Err(format!(
|
||||
"{}, so the traffic it stored can't be encrypted yet. Take over or quit it, then \
|
||||
retry.",
|
||||
"{}. Take over or quit it, then retry.",
|
||||
control::already_owned(&owner)
|
||||
));
|
||||
}
|
||||
@@ -720,7 +719,7 @@ impl ProxyCtx {
|
||||
.map_err(|e| e.to_string())?;
|
||||
if !released {
|
||||
return Err(format!(
|
||||
"The proxy in {} (pid {}) stopped but did not let go of it",
|
||||
"Failed to take over from {} (pid {})",
|
||||
control::owner_name(owner.owner),
|
||||
owner.pid
|
||||
));
|
||||
@@ -808,7 +807,7 @@ impl ProxyCtx {
|
||||
pub fn set_system_proxy(&self, enabled: bool) -> Result<SystemProxyStatus, String> {
|
||||
let Some(port) = self.port() else {
|
||||
if enabled {
|
||||
return Err("The proxy is not running, so there is nothing to route to".into());
|
||||
return Err("The proxy is not running".into());
|
||||
}
|
||||
// Clears one left pointing at the configured port; asks for nothing otherwise.
|
||||
self.change_system_proxy(false, self.settings()?.port, false)?;
|
||||
@@ -2246,7 +2245,7 @@ pub struct SavePreview {
|
||||
/// Headers saved as captured, Authorization among them.
|
||||
#[ts(type = "number")]
|
||||
pub kept_headers: usize,
|
||||
/// The body as it is saved: "JSON", "form URL-encoded with 7 fields". None without a body.
|
||||
/// The body as it is saved: "JSON", "a URL-encoded form with 7 fields". None without a body.
|
||||
pub body: Option<String>,
|
||||
/// What does not carry over exactly, one sentence each.
|
||||
pub warnings: Vec<String>,
|
||||
@@ -2784,8 +2783,8 @@ async fn preview_save(ctx: ProxyCtx, req: PreviewSaveRequest) -> Result<SavePrev
|
||||
"application/x-www-form-urlencoded" => {
|
||||
let fields = request.body.get("form").and_then(|f| f.as_array()).map_or(0, Vec::len);
|
||||
match fields {
|
||||
1 => "form URL-encoded with 1 field".to_string(),
|
||||
n => format!("form URL-encoded with {n} fields"),
|
||||
1 => "a URL-encoded form with 1 field".to_string(),
|
||||
n => format!("a URL-encoded form with {n} fields"),
|
||||
}
|
||||
}
|
||||
_ => "text".to_string(),
|
||||
@@ -3184,8 +3183,7 @@ async fn count_saved_queries(
|
||||
// -- Event loop --
|
||||
|
||||
/// Why the engine cannot be taken over from another app.
|
||||
pub const ANOTHER_APP_OWNS: &str =
|
||||
"Another Yaak Proxy window owns the engine. Quit it to use this one.";
|
||||
pub const ANOTHER_APP_OWNS: &str = "Quit the other Yaak Proxy to use this one.";
|
||||
|
||||
/// How long taking over waits for the owner to let go of the engine.
|
||||
const TAKE_OVER_WAIT: Duration = Duration::from_secs(10);
|
||||
@@ -3229,11 +3227,10 @@ const STOPPED_MID_STREAM: &str = "Yaak Proxy stopped before the stream ended";
|
||||
const STOPPED_MID_TUNNEL: &str = "Yaak Proxy stopped before the connection closed";
|
||||
|
||||
/// Why a failed handshake's flow can't be saved as a request.
|
||||
pub(crate) const NOT_A_REQUEST: &str =
|
||||
"Not a request: the client's TLS handshake failed before it sent one";
|
||||
pub(crate) const NOT_A_REQUEST: &str = "The TLS handshake failed before any request was sent";
|
||||
|
||||
/// The error of a flow recording a client's refusal of the proxy's certificate.
|
||||
const REJECTED_CERTIFICATE: &str = "Rejected the proxy's certificate";
|
||||
const REJECTED_CERTIFICATE: &str = "Certificate rejected by the app";
|
||||
|
||||
/// A flow between its `RequestStart` and its end, with its bodies being written as they
|
||||
/// stream.
|
||||
|
||||
@@ -287,7 +287,7 @@ mod imp {
|
||||
.map_err(|e| format!("Failed to run osascript: {e}"))?;
|
||||
// -128 is the dialog's Cancel button.
|
||||
if String::from_utf8_lossy(&output.stderr).contains("(-128)") {
|
||||
return Err("Cancelled in the password dialog".to_string());
|
||||
return Err("The password prompt was cancelled.".to_string());
|
||||
}
|
||||
check(&output, "osascript")
|
||||
}
|
||||
@@ -647,7 +647,7 @@ mod tests {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join(SNAPSHOT_FILE);
|
||||
let ns = fake();
|
||||
let cancel = |_: &[Vec<String>]| Err("Cancelled in the password dialog".to_string());
|
||||
let cancel = |_: &[Vec<String>]| Err("The password prompt was cancelled.".to_string());
|
||||
assert!(set_scripted(&ns, cancel, true, 9090, &path).is_err());
|
||||
assert!(!path.exists());
|
||||
assert!(ns.changes.lock().unwrap().is_empty());
|
||||
@@ -663,7 +663,7 @@ mod tests {
|
||||
set_scripted(&ns, runner(&ns, &prompts), true, 9090, &path).unwrap();
|
||||
// A second enable, the engine on a new port, must not save ours as the user's.
|
||||
set_scripted(&ns, runner(&ns, &prompts), true, 9191, &path).unwrap();
|
||||
let cancel = |_: &[Vec<String>]| Err("Cancelled in the password dialog".to_string());
|
||||
let cancel = |_: &[Vec<String>]| Err("The password prompt was cancelled.".to_string());
|
||||
assert!(set_scripted(&ns, cancel, true, 9191, &path).is_err());
|
||||
assert!(path.exists(), "a cancelled re-enable keeps the original snapshot");
|
||||
set_scripted(&ns, runner(&ns, &prompts), false, 9191, &path).unwrap();
|
||||
|
||||
@@ -88,14 +88,14 @@ pub fn schema() -> &'static Schema {
|
||||
field("method", text(), "GET, POST, …", "@method:post"),
|
||||
field("status", number(), "Response status", "@status:4xx @status:>=400"),
|
||||
field("type", text(), "Content type", "@type:json"),
|
||||
field("app", text(), "App or program", "@app:Slack"),
|
||||
field("app", text(), "App", "@app:Slack"),
|
||||
field(
|
||||
"is",
|
||||
text(),
|
||||
"error, pending, system, encrypted, rejected, …",
|
||||
"@is:error -@is:system @is:encrypted",
|
||||
),
|
||||
map("header", "A header on either side", "@header.content-type:json"),
|
||||
map("header", "A request or response header", "@header.content-type:json"),
|
||||
map("req.header", "A request header", "@req.header.accept:*"),
|
||||
map("res.header", "A response header", "@res.header.x-cache:hit"),
|
||||
field("size", bytes(), "Response body size", "@size:>100kb"),
|
||||
@@ -109,8 +109,8 @@ pub fn schema() -> &'static Schema {
|
||||
field("port", number(), "Port", "@port:8080"),
|
||||
field("proto", text(), "h1, h2, ws, sse, grpc", "@proto:h2"),
|
||||
field("id", number(), "Flow number", "@id:>1200"),
|
||||
field("cwd", text(), "Working folder", "@cwd:myapp"),
|
||||
field("pid", number(), "Process id", "@pid:4121"),
|
||||
field("cwd", text(), "Working directory", "@cwd:myapp"),
|
||||
field("pid", number(), "Process ID", "@pid:4121"),
|
||||
field(
|
||||
"graphql.operation",
|
||||
text(),
|
||||
@@ -119,9 +119,9 @@ pub fn schema() -> &'static Schema {
|
||||
),
|
||||
field("grpc.service", text(), "gRPC service", "@grpc.service:pkg.Greeter"),
|
||||
field("grpc.method", text(), "gRPC method", "@grpc.method:SayHello"),
|
||||
field("body", text(), "Not yet", ""),
|
||||
field("req.body", text(), "Not yet", ""),
|
||||
field("res.body", text(), "Not yet", ""),
|
||||
field("body", text(), "Not supported", ""),
|
||||
field("req.body", text(), "Not supported", ""),
|
||||
field("res.body", text(), "Not supported", ""),
|
||||
],
|
||||
FreeText::Substring,
|
||||
)
|
||||
|
||||
@@ -391,7 +391,7 @@ fn encrypted_and_rejected() {
|
||||
url: "https://my.1password.com".into(),
|
||||
client_http_version: "HTTP/1.1".into(),
|
||||
tls_rejected: Some("unknown_ca".into()),
|
||||
error: Some("Rejected the proxy's certificate".into()),
|
||||
error: Some("Certificate rejected by the app".into()),
|
||||
elapsed_ms: Some(3),
|
||||
started_at: (now() - Duration::minutes(5)).naive_utc(),
|
||||
..Default::default()
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn open(vars: &[(String, String)]) -> Result<(), String> {
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn open(_vars: &[(String, String)]) -> Result<(), String> {
|
||||
Err("Opening a terminal with the proxy set is only supported on macOS so far".to_string())
|
||||
Err("Opening a terminal is only supported on macOS".to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -189,7 +189,7 @@ fn stores_nothing_in_plaintext_and_reads_back_with_the_key() {
|
||||
)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert!(err.contains("couldn't read its key from the keychain"), "{err}");
|
||||
assert!(err.contains("Failed to read the encryption key from the keychain"), "{err}");
|
||||
// Nor without any key at all.
|
||||
let raw = rusqlite::Connection::open(dir.path().join(PROXY_DB)).unwrap();
|
||||
let err = raw.query_row("SELECT count(*) FROM flows", [], |r| r.get::<_, i64>(0)).unwrap_err();
|
||||
@@ -366,8 +366,8 @@ fn a_refused_key_refuses_capture() {
|
||||
let err = ctx.start().unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
"Yaak Proxy stores traffic encrypted and couldn't read its key from the keychain: \
|
||||
Encryption error: User canceled the operation. Allow it access, then retry."
|
||||
"Failed to read the encryption key from the keychain: Encryption error: User canceled \
|
||||
the operation"
|
||||
);
|
||||
assert_eq!(ctx.start_error().as_deref(), Some(err.as_str()));
|
||||
assert_eq!(ctx.port(), None);
|
||||
@@ -426,8 +426,8 @@ fn waits_for_an_older_owner_before_converting() {
|
||||
std::fs::write(&control, serde_json::to_string(&owner).unwrap()).unwrap();
|
||||
|
||||
let ctx = open_ctx(dir.path(), MasterKeySource::memory());
|
||||
assert!(ctx.load_ca().err().unwrap().contains("can't be encrypted yet"));
|
||||
assert!(ctx.start().unwrap_err().starts_with("The proxy is already running in the yaak CLI"));
|
||||
assert!(ctx.load_ca().err().unwrap().contains("Take over or quit it, then retry."));
|
||||
assert!(ctx.start().unwrap_err().starts_with("The proxy is already running in the CLI"));
|
||||
for name in [PROXY_DB, BLOBS_DB] {
|
||||
assert_eq!(file_state(&dir.path().join(name)).unwrap(), FileState::Plain);
|
||||
}
|
||||
@@ -489,7 +489,7 @@ fn a_lost_data_key_is_not_replaced() {
|
||||
std::fs::remove_file(dir.path().join(DATA_KEY)).unwrap();
|
||||
let ctx = open_ctx(dir.path(), keys);
|
||||
let err = ctx.start().unwrap_err();
|
||||
assert!(err.starts_with("The proxy's captured traffic is encrypted, but its key is missing"));
|
||||
assert!(err.starts_with("The encryption key is missing from"));
|
||||
assert!(!dir.path().join(DATA_KEY).exists());
|
||||
assert_eq!(std::fs::read_to_string(dir.path().join("ca/ca.pem")).unwrap(), pem);
|
||||
assert!(ctx.db().is_err(), "locked");
|
||||
|
||||
@@ -346,10 +346,7 @@ async fn save_writes_the_request_into_yaak() {
|
||||
let saved = client.save(&flow.id, &target).await.unwrap();
|
||||
let dropped: Vec<&str> = saved.dropped_headers.iter().map(|h| h.name.as_str()).collect();
|
||||
assert!(dropped.contains(&"host") && dropped.contains(&"content-length"), "{dropped:?}");
|
||||
assert_eq!(
|
||||
saved.warnings,
|
||||
vec!["The Authorization header is saved as captured, in plain text."]
|
||||
);
|
||||
assert_eq!(saved.warnings, vec!["The Authorization header is saved in plain text."]);
|
||||
|
||||
// The row is in Yaak's database, written as a sync so a running Yaak picks it up.
|
||||
let request = query_manager.connect().get_http_request(&saved.request.id).unwrap();
|
||||
@@ -443,7 +440,7 @@ async fn start_is_refused_while_another_process_owns_the_engine() {
|
||||
std::fs::write(dir.path().join("control.json"), serde_json::to_vec(&owner).unwrap()).unwrap();
|
||||
|
||||
let err = ctx.start().unwrap_err();
|
||||
assert!(err.contains("already running in the yaak CLI"), "{err}");
|
||||
assert!(err.contains("already running in the CLI"), "{err}");
|
||||
assert_eq!(ctx.port(), None);
|
||||
|
||||
daemon.kill().unwrap();
|
||||
@@ -471,7 +468,7 @@ async fn the_app_takes_over_from_the_cli() {
|
||||
|
||||
// The app opens onto the CLI's engine and cannot start its own.
|
||||
let app = ctx(dir.path(), client_dir.path());
|
||||
assert!(app.serve_control_api().unwrap_err().contains("already running in the yaak CLI"));
|
||||
assert!(app.serve_control_api().unwrap_err().contains("already running in the CLI"));
|
||||
let state = rpc(&app, "get_proxy_state", json!({})).await.unwrap();
|
||||
assert_eq!(state["state"], "stopped");
|
||||
assert_eq!(state["otherOwner"]["owner"], "cli");
|
||||
|
||||
@@ -164,12 +164,12 @@ async fn a_refused_handshake_is_one_flow_per_address() {
|
||||
assert_eq!(rejected.url, url);
|
||||
assert_eq!(rejected.method, "");
|
||||
assert_eq!(rejected.tls_rejected.as_deref(), Some("unknown_ca"));
|
||||
assert_eq!(rejected.error.as_deref(), Some("Rejected the proxy's certificate"));
|
||||
assert_eq!(rejected.error.as_deref(), Some("Certificate rejected by the app"));
|
||||
assert!(!rejected.tunnelled);
|
||||
assert!(rejected.elapsed_ms.is_some(), "ended, not pending");
|
||||
// No request was sent, so there is none to save.
|
||||
let preview = rpc(&ctx, "preview_save", json!({"flowId": rejected.id})).await;
|
||||
assert!(preview.unwrap_err().starts_with("Not a request"));
|
||||
assert!(preview.unwrap_err().starts_with("The TLS handshake failed"));
|
||||
|
||||
// Passed through from now on, naming the refusal.
|
||||
let _tunnel = open_tunnel(&ctx, &target).await;
|
||||
|
||||
@@ -448,7 +448,7 @@ async fn grpc_web_calls_are_stored_and_decoded() {
|
||||
let decode = json!({ "flowId": flow.id, "seq": 1 });
|
||||
let schemaless = rpc(&ctx, "decode_grpc_message", decode.clone()).await.unwrap();
|
||||
assert_eq!(schemaless["messageType"], Value::Null);
|
||||
assert!(schemaless["note"].as_str().unwrap().contains("reflection failed"), "{schemaless}");
|
||||
assert!(schemaless["note"].as_str().unwrap().contains("Reflection failed"), "{schemaless}");
|
||||
let fields: Value = serde_json::from_str(schemaless["json"].as_str().unwrap()).unwrap();
|
||||
assert_eq!(fields[0]["string"], "Hello Ada");
|
||||
assert_eq!(fields[1], json!({ "field": 2, "wireType": "varint", "value": 3 }));
|
||||
|
||||
@@ -140,7 +140,7 @@ async fn cmd_open_settings(
|
||||
|
||||
let main = app_handle
|
||||
.get_webview_window(MAIN_WINDOW_LABEL)
|
||||
.ok_or("The main window is not open to open Settings over")?;
|
||||
.ok_or("Failed to open Settings: the main window is closed")?;
|
||||
let win = yaak_window::window::create_child_window(
|
||||
&main,
|
||||
"/",
|
||||
|
||||
@@ -184,7 +184,7 @@ impl GrpcParser {
|
||||
message.truncated = true;
|
||||
message.compressed = compressed;
|
||||
message.error = Some(format!(
|
||||
"The body ended partway through this message, {} of its {length} bytes in",
|
||||
"The body ended after {} of this message's {length} bytes.",
|
||||
length - remaining
|
||||
));
|
||||
Some(message)
|
||||
@@ -318,7 +318,7 @@ mod tests {
|
||||
assert_eq!(messages.len(), 1);
|
||||
let cut = parser.end().expect("the unfinished message");
|
||||
assert_eq!((&cut.data[..], cut.size, cut.truncated), (&b"hal"[..], 10, true));
|
||||
assert!(cut.error.as_deref().unwrap().contains("3 of its 10 bytes"));
|
||||
assert!(cut.error.as_deref().unwrap().contains("3 of this message's 10 bytes"));
|
||||
// Nothing unfinished: nothing more.
|
||||
assert!(parser.end().is_none());
|
||||
// A trailers frame cut short is still a trailers frame.
|
||||
|
||||
@@ -348,13 +348,13 @@ impl FrameParser {
|
||||
Some(inflater) if !inflater.failed => {
|
||||
if let Err(e) = inflater.inflate(chunk, &mut message.payload) {
|
||||
inflater.failed = true;
|
||||
message.error = Some(format!("Failed to inflate the message: {e}"));
|
||||
message.error = Some(format!("Failed to decompress the message: {e}"));
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
message.payload.push(chunk);
|
||||
message.error.get_or_insert_with(|| {
|
||||
"Not inflated: an earlier message failed to, which lost the context".into()
|
||||
"This message was not decompressed because an earlier one failed.".into()
|
||||
});
|
||||
}
|
||||
None => message.payload.push(chunk),
|
||||
@@ -396,7 +396,7 @@ impl FrameParser {
|
||||
&& let Err(e) = inflater.inflate(&DEFLATE_TAIL, &mut assembly.payload)
|
||||
{
|
||||
inflater.failed = true;
|
||||
assembly.error = Some(format!("Failed to inflate the message: {e}"));
|
||||
assembly.error = Some(format!("Failed to decompress the message: {e}"));
|
||||
}
|
||||
if inflater.no_context_takeover && !inflater.failed {
|
||||
inflater.decompress.reset(false);
|
||||
|
||||
@@ -481,7 +481,7 @@ function StreamEventDetail({
|
||||
/>
|
||||
{event.truncated || event.error ? (
|
||||
<Banner color={event.error ? "danger" : "notice"} className="mb-2 text-sm">
|
||||
{event.error ?? "The message is cut at the capture limit."}
|
||||
{event.error ?? "The message was truncated at the capture limit."}
|
||||
</Banner>
|
||||
) : (
|
||||
<span aria-hidden />
|
||||
|
||||
Reference in new issue
Block a user