diff --git a/Cargo.lock b/Cargo.lock index 72d1ada..bdb3ede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,10 +212,12 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +checksum = "6448dfb3960f0b038e88c781ead1e7eb7929dfc3a71a1336ec9086c00f6d1e75" dependencies = [ + "compression-codecs", + "compression-core", "deflate64", "flate2", "futures-core", @@ -530,7 +532,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cexpr", "clang-sys", "itertools 0.12.1", @@ -553,7 +555,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cexpr", "clang-sys", "itertools 0.13.0", @@ -640,9 +642,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" dependencies = [ "serde", ] @@ -660,7 +662,7 @@ source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605 dependencies = [ "ash", "ash-window", - "bitflags 2.9.2", + "bitflags 2.9.3", "bytemuck", "codespan-reporting 0.11.1", "glow", @@ -819,7 +821,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "log", "polling", "rustix 0.38.44", @@ -904,9 +906,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.33" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", @@ -1062,7 +1064,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "block", "cocoa-foundation 0.2.1", "core-foundation 0.10.1", @@ -1092,7 +1094,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "block", "core-foundation 0.10.1", "core-graphics-types 0.2.0", @@ -1123,7 +1125,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1170,13 +1172,33 @@ dependencies = [ "nostr", "nostr-connect", "nostr-sdk", - "qrcode-generator", + "qrcode", "reqwest 0.12.23", "smallvec", "smol", "webbrowser", ] +[[package]] +name = "compression-codecs" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46cc6539bf1c592cff488b9f253b30bc0ec50d15407c2cf45e27bd8f308d5905" +dependencies = [ + "compression-core", + "deflate64", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "compression-core" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2957e823c15bde7ecf1e8b64e537aa03a6be5fda0e2334e99887669e75b12e01" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1293,7 +1315,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -1306,7 +1328,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.9.4", "core-graphics-types 0.1.3", "foreign-types 0.5.0", @@ -1330,7 +1352,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.10.1", "libc", ] @@ -1341,7 +1363,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e4583956b9806b69f73fcb23aee05eb3620efc282972f08f6a6db7504f8334d" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "block", "cfg-if", "core-foundation 0.10.1", @@ -1389,7 +1411,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "fontdb 0.16.2", "log", "rangemap", @@ -1509,9 +1531,9 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-url" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "deflate64" @@ -1544,7 +1566,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "proc-macro2", "quote", @@ -1616,7 +1638,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "objc2", ] @@ -1687,9 +1709,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dwrote" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be" +checksum = "20c93d234bac0cdd0e2ac08bc8a5133f8df2169e95b262dfcea5e5cb7855672f" dependencies = [ "lazy_static", "libc", @@ -1915,14 +1937,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1931,7 +1953,7 @@ version = "25.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "rustc_version", ] @@ -1992,7 +2014,7 @@ name = "font-kit" version = "0.14.1" source = "git+https://github.com/zed-industries/font-kit?rev=5474cfad4b719a72ec8ed2cb7327b2b01fd10568#5474cfad4b719a72ec8ed2cb7327b2b01fd10568" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "byteorder", "core-foundation 0.10.1", "core-graphics 0.24.0", @@ -2037,7 +2059,7 @@ checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.9.7", + "memmap2 0.9.8", "slotmap", "tinyvec", "ttf-parser 0.20.0", @@ -2051,7 +2073,7 @@ checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.9.7", + "memmap2 0.9.8", "slotmap", "tinyvec", "ttf-parser 0.25.1", @@ -2101,9 +2123,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -2409,7 +2431,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "gpu-alloc-types", ] @@ -2430,13 +2452,13 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] name = "gpui" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2530,7 +2552,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2542,8 +2564,9 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ + "anyhow", "gpui", "tokio", "util", @@ -2628,7 +2651,7 @@ version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d4f449bab7320c56003d37732a917e18798e2f1709d80263face2b4f9436ddb" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "byteorder", "heed-traits", "heed-types", @@ -2727,15 +2750,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "html-escape" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" -dependencies = [ - "utf8-width", -] - [[package]] name = "http" version = "1.3.1" @@ -2773,7 +2787,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "anyhow", "bytes", @@ -2793,7 +2807,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "rustls", "rustls-platform-verifier", @@ -2806,6 +2820,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.7.0" @@ -2820,6 +2840,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -3016,17 +3037,19 @@ dependencies = [ "log", "nostr-connect", "nostr-sdk", - "oneshot", "settings", + "signer_proxy", "smallvec", + "smol", "ui", + "webbrowser", ] [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -3106,9 +3129,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -3150,9 +3173,9 @@ dependencies = [ [[package]] name = "inventory" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" dependencies = [ "rustversion", ] @@ -3171,11 +3194,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -3281,9 +3304,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -3400,7 +3423,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "libc", "redox_syscall", ] @@ -3597,7 +3620,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "anyhow", "bindgen 0.71.1", @@ -3627,9 +3650,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -3649,7 +3672,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "block", "core-graphics-types 0.1.3", "foreign-types 0.5.0", @@ -3721,7 +3744,7 @@ checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg_aliases", "codespan-reporting 0.12.0", "half", @@ -3788,7 +3811,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "cfg_aliases", "libc", @@ -3800,7 +3823,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "cfg_aliases", "libc", @@ -3835,7 +3858,7 @@ dependencies = [ [[package]] name = "nostr" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#36cc4bbf921044527b03b7e63bf7113d60ac935b" +source = "git+https://github.com/rust-nostr/nostr#81c3b2fcc87bac915aaae3fb6c3668508d970087" dependencies = [ "aes", "base64", @@ -3858,7 +3881,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#36cc4bbf921044527b03b7e63bf7113d60ac935b" +source = "git+https://github.com/rust-nostr/nostr#81c3b2fcc87bac915aaae3fb6c3668508d970087" dependencies = [ "async-utility", "nostr", @@ -3870,7 +3893,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#36cc4bbf921044527b03b7e63bf7113d60ac935b" +source = "git+https://github.com/rust-nostr/nostr#81c3b2fcc87bac915aaae3fb6c3668508d970087" dependencies = [ "flatbuffers", "lru", @@ -3881,7 +3904,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#36cc4bbf921044527b03b7e63bf7113d60ac935b" +source = "git+https://github.com/rust-nostr/nostr#81c3b2fcc87bac915aaae3fb6c3668508d970087" dependencies = [ "async-utility", "flume", @@ -3895,7 +3918,7 @@ dependencies = [ [[package]] name = "nostr-relay-pool" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#36cc4bbf921044527b03b7e63bf7113d60ac935b" +source = "git+https://github.com/rust-nostr/nostr#81c3b2fcc87bac915aaae3fb6c3668508d970087" dependencies = [ "async-utility", "async-wsocket", @@ -3911,7 +3934,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#36cc4bbf921044527b03b7e63bf7113d60ac935b" +source = "git+https://github.com/rust-nostr/nostr#81c3b2fcc87bac915aaae3fb6c3668508d970087" dependencies = [ "async-utility", "nostr", @@ -4095,7 +4118,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -4108,7 +4131,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "dispatch2", "objc2", ] @@ -4125,7 +4148,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "objc2", "objc2-core-foundation", ] @@ -4136,7 +4159,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "block2", "objc2", "objc2-foundation", @@ -4148,7 +4171,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -4161,7 +4184,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -4265,7 +4288,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "foreign-types 0.3.2", "libc", @@ -4428,9 +4451,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" @@ -4702,22 +4725,14 @@ dependencies = [ ] [[package]] -name = "qrcode-generator" -version = "5.0.0" +name = "qrcode" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0051849b5465059b75f59d388c7318aad6554701b74ecf02afc2573b0306c" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" dependencies = [ - "html-escape", "image", - "qrcodegen", ] -[[package]] -name = "qrcodegen" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" - [[package]] name = "quick-error" version = "2.0.1" @@ -4981,7 +4996,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] @@ -5018,7 +5033,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "derive_refineable", "workspace-hack", @@ -5026,9 +5041,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -5038,9 +5053,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -5049,9 +5064,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "registry" @@ -5067,7 +5082,6 @@ dependencies = [ "log", "nostr", "nostr-sdk", - "oneshot", "settings", "smallvec", "smol", @@ -5174,7 +5188,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "anyhow", "bytes", @@ -5365,7 +5379,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys 0.4.15", @@ -5378,7 +5392,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys 0.9.4", @@ -5483,7 +5497,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "bytemuck", "libm", "smallvec", @@ -5500,7 +5514,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "bytemuck", "core_maths", "log", @@ -5671,7 +5685,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -5684,7 +5698,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -5710,7 +5724,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "anyhow", "serde", @@ -5910,6 +5924,28 @@ dependencies = [ "libc", ] +[[package]] +name = "signer_proxy" +version = "0.2.2" +dependencies = [ + "anyhow", + "atomic-destructor", + "bytes", + "futures", + "global", + "http-body-util", + "hyper", + "hyper-util", + "log", + "nostr", + "oneshot", + "serde", + "serde_json", + "smallvec", + "smol", + "uuid", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -6029,7 +6065,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] @@ -6139,7 +6175,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "arrayvec", "log", @@ -6332,7 +6368,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -6810,7 +6846,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "bytes", "futures-util", "http", @@ -7115,9 +7151,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -7158,12 +7194,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -7173,7 +7203,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fbba6addfd1f1539408af582e65b356a308ba2f7" +source = "git+https://github.com/zed-industries/zed#11545c669e100392a8ca60063476037ab52c7cb5" dependencies = [ "anyhow", "async-fs", @@ -7459,7 +7489,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "rustix 1.0.8", "wayland-backend", "wayland-scanner", @@ -7482,7 +7512,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "wayland-backend", "wayland-client", "wayland-scanner", @@ -7494,7 +7524,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8154,9 +8184,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -8186,7 +8216,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] @@ -8294,7 +8324,7 @@ name = "xim-parser" version = "0.2.1" source = "git+https://github.com/zed-industries/xim-rs?rev=c0a70c1bd2ce197364216e5e818a2cb3adb99a8d#c0a70c1bd2ce197364216e5e818a2cb3adb99a8d" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] @@ -8305,7 +8335,7 @@ checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" dependencies = [ "as-raw-xcb-connection", "libc", - "memmap2 0.9.7", + "memmap2 0.9.8", "xkeysym", ] @@ -8364,9 +8394,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.9.0" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" +checksum = "67a073be99ace1adc48af593701c8015cd9817df372e14a1a6b0ee8f8bf043be" dependencies = [ "async-broadcast", "async-executor", @@ -8388,7 +8418,7 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "windows-sys 0.59.0", + "windows-sys 0.60.2", "winnow", "zbus_macros", "zbus_names", @@ -8397,9 +8427,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.9.0" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" +checksum = "0e80cd713a45a49859dcb648053f63265f4f2851b6420d47a958e5697c68b131" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8548,9 +8578,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" dependencies = [ "endi", "enumflags2", @@ -8563,9 +8593,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8576,14 +8606,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" dependencies = [ "proc-macro2", "quote", "serde", - "static_assertions", "syn 2.0.106", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index b3e8e62..f952e78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ serde_json = "1.0" smallvec = "1.14.0" smol = "2" tracing = "0.1.40" +webbrowser = "1.0.4" [profile.release] strip = true diff --git a/crates/client_keys/src/lib.rs b/crates/client_keys/src/lib.rs index f27798d..3ae7905 100644 --- a/crates/client_keys/src/lib.rs +++ b/crates/client_keys/src/lib.rs @@ -1,4 +1,5 @@ -use global::{constants::KEYRING_URL, first_run}; +use global::constants::KEYRING_URL; +use global::first_run; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Window}; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; @@ -24,7 +25,7 @@ impl ClientKeys { } /// Retrieve the Client Keys instance - pub fn get_global(cx: &App) -> &Self { + pub fn read_global(cx: &App) -> &Self { cx.global::().0.read(cx) } @@ -49,11 +50,20 @@ impl ClientKeys { } pub fn load(&mut self, window: &mut Window, cx: &mut Context) { + // Prevent macOS from asking for password every time + // Only for debug builds + if cfg!(debug_assertions) && cfg!(target_os = "macos") { + log::warn!("Running debug build on macOS"); + log::warn!("Skipping keychain access, generating new client keys"); + self.new_keys(cx); + return; + } + let read_client_keys = cx.read_credentials(KEYRING_URL); cx.spawn_in(window, async move |this, cx| { if let Ok(Some((_, secret))) = read_client_keys.await { - // Update keys + // Update the client keys with the stored secret key from the keychain this.update(cx, |this, cx| { let Ok(secret_key) = SecretKey::from_slice(&secret) else { this.set_keys(None, false, true, cx); @@ -64,7 +74,7 @@ impl ClientKeys { }) .ok(); } else if *first_run() { - // Generate a new keys and update + // If this is the first run, generate new keys and use them for the client keys this.update(cx, |this, cx| { this.new_keys(cx); }) @@ -102,6 +112,7 @@ impl ClientKeys { } self.keys = keys; + // Notify GPUI to reload UI if notify { cx.notify(); @@ -118,8 +129,7 @@ impl ClientKeys { pub fn keys(&self) -> Keys { self.keys - .as_ref() - .cloned() + .clone() .expect("Keys should always be initialized") } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index a78a196..ffbb804 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -19,6 +19,6 @@ smol.workspace = true futures.workspace = true reqwest.workspace = true log.workspace = true +webbrowser.workspace = true -webbrowser = "1.0.4" -qrcode-generator = "5.0.0" +qrcode = "0.14.1" diff --git a/crates/common/src/display.rs b/crates/common/src/display.rs index 5c80a44..0e86bcb 100644 --- a/crates/common/src/display.rs +++ b/crates/common/src/display.rs @@ -4,7 +4,8 @@ use anyhow::{anyhow, Error}; use global::constants::IMAGE_RESIZE_SERVICE; use gpui::{Image, ImageFormat, SharedString}; use nostr_sdk::prelude::*; -use qrcode_generator::QrCodeEcc; +use qrcode::render::svg; +use qrcode::QrCode; const FALLBACK_IMG: &str = "https://image.nostr.build/c30703b48f511c293a9003be8100cdad37b8798b77a1dc3ec6eb8a20443d5dea.png"; @@ -54,45 +55,32 @@ pub trait TextUtils { fn to_qr(&self) -> Option>; } -impl TextUtils for String { +impl> TextUtils for T { fn to_public_key(&self) -> Result { - if self.starts_with("nprofile1") { - Ok(Nip19Profile::from_bech32(self)?.public_key) - } else if self.starts_with("npub1") { - Ok(PublicKey::parse(self)?) + let s = self.as_ref(); + if s.starts_with("nprofile1") { + Ok(Nip19Profile::from_bech32(s)?.public_key) + } else if s.starts_with("npub1") { + Ok(PublicKey::parse(s)?) } else { Err(anyhow!("Invalid public key")) } } fn to_qr(&self) -> Option> { - let Ok(bytes) = qrcode_generator::to_png_to_vec_from_str(self, QrCodeEcc::Medium, 256) - else { - return None; - }; + let s = self.as_ref(); + let code = QrCode::new(s).unwrap(); + let svg = code + .render() + .min_dimensions(256, 256) + .dark_color(svg::Color("#000000")) + .light_color(svg::Color("#FFFFFF")) + .build(); - Some(Arc::new(Image::from_bytes(ImageFormat::Png, bytes))) - } -} - -impl TextUtils for &str { - fn to_public_key(&self) -> Result { - if self.starts_with("nprofile1") { - Ok(Nip19Profile::from_bech32(self)?.public_key) - } else if self.starts_with("npub1") { - Ok(PublicKey::parse(self)?) - } else { - Err(anyhow!("Invalid public key")) - } - } - - fn to_qr(&self) -> Option> { - let Ok(bytes) = qrcode_generator::to_png_to_vec_from_str(self, QrCodeEcc::Medium, 256) - else { - return None; - }; - - Some(Arc::new(Image::from_bytes(ImageFormat::Png, bytes))) + Some(Arc::new(Image::from_bytes( + ImageFormat::Svg, + svg.into_bytes(), + ))) } } diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index b973f7c..407fd38 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -1,14 +1,16 @@ use std::sync::Arc; +use anyhow::anyhow; use auto_update::AutoUpdater; use client_keys::ClientKeys; use common::display::DisplayProfile; -use global::constants::DEFAULT_SIDEBAR_WIDTH; +use global::constants::{ACCOUNT_IDENTIFIER, DEFAULT_SIDEBAR_WIDTH}; +use global::{global_channel, nostr_client, NostrSignal}; use gpui::prelude::FluentBuilder; use gpui::{ - actions, div, px, rems, Action, App, AppContext, Axis, Context, Entity, InteractiveElement, - IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, - Subscription, Window, + actions, div, px, rems, Action, App, AppContext, AsyncWindowContext, Axis, Context, Entity, + InteractiveElement, IntoElement, ParentElement, Render, SharedString, + StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, Window, }; use i18n::{shared_t, t}; use identity::Identity; @@ -31,18 +33,18 @@ use ui::indicator::Indicator; use ui::modal::ModalButtonProps; use ui::popup_menu::PopupMenuExt; use ui::tooltip::Tooltip; -use ui::{h_flex, ContextModal, IconName, Root, Sizable, StyledExt}; +use ui::{h_flex, v_flex, ContextModal, IconName, Root, Sizable, StyledExt}; use crate::views::compose::compose_button; use crate::views::screening::Screening; use crate::views::user_profile::UserProfile; use crate::views::{ - backup_keys, chat, login, messaging_relays, new_account, onboarding, preferences, sidebar, - startup, user_profile, welcome, + account, chat, login, messaging_relays, new_account, onboarding, preferences, sidebar, + user_profile, welcome, }; pub fn init(window: &mut Window, cx: &mut App) -> Entity { - ChatSpace::new(window, cx) + cx.new(|cx| ChatSpace::new(window, cx)) } pub fn login(window: &mut Window, cx: &mut App) { @@ -85,118 +87,189 @@ pub struct ToggleModal { pub struct ChatSpace { title_bar: Entity, dock: Entity, - #[allow(unused)] - subscriptions: SmallVec<[Subscription; 5]>, + _subscriptions: SmallVec<[Subscription; 4]>, + _tasks: SmallVec<[Task<()>; 1]>, } impl ChatSpace { - pub fn new(window: &mut Window, cx: &mut App) -> Entity { + pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let registry = Registry::global(cx); + let client_keys = ClientKeys::global(cx); + let title_bar = cx.new(|_| TitleBar::new()); - let dock = cx.new(|cx| { - let panel = Arc::new(startup::init(window, cx)); - let center = DockItem::panel(panel); - let mut dock = DockArea::new(window, cx); - // Initialize the dock area with the center panel - dock.set_center(center, window, cx); - dock - }); + let dock = cx.new(|cx| DockArea::new(window, cx)); - cx.new(|cx| { - let registry = Registry::global(cx); - let client_keys = ClientKeys::global(cx); - let identity = Identity::global(cx); - let mut subscriptions = smallvec![]; + let mut subscriptions = smallvec![]; + let mut tasks = smallvec![]; + subscriptions.push( // Observe the client keys and show an alert modal if they fail to initialize - subscriptions.push(cx.observe_in( - &client_keys, - window, - |this: &mut Self, state, window, cx| { - if !state.read(cx).has_keys() { - this.render_client_keys_modal(window, cx); - } - }, - )); - - // Observe the identity and show onboarding if it fails to initialize - subscriptions.push(cx.observe_in( - &identity, - window, - |this: &mut Self, state, window, cx| { - if !state.read(cx).has_signer() { - this.set_onboarding_panels(window, cx); - } else { - this.set_chat_panels(window, cx); - } - }, - )); + cx.observe_in(&client_keys, window, |this, state, window, cx| { + if !state.read(cx).has_keys() { + this.render_client_keys_modal(window, cx); + } else { + this.load_local_account(window, cx); + } + }), + ); + subscriptions.push( // Automatically run load function when UserProfile is created - subscriptions.push(cx.observe_new::(|this, window, cx| { + cx.observe_new::(|this, window, cx| { if let Some(window) = window { this.load(window, cx); } - })); + }), + ); + subscriptions.push( // Automatically run load function when Screening is created - subscriptions.push(cx.observe_new::(|this, window, cx| { + cx.observe_new::(|this, window, cx| { if let Some(window) = window { this.load(window, cx); } - })); + }), + ); + subscriptions.push( // Subscribe to open chat room requests - subscriptions.push(cx.subscribe_in( - ®istry, - window, - |this: &mut Self, _state, event, window, cx| { - match event { - RegistrySignal::Open(room) => { - if let Some(room) = room.upgrade() { - this.dock.update(cx, |this, cx| { - let panel = chat::init(room, window, cx); - - // Load messages when the panel is created - panel.update(cx, |this, cx| { - this.load_messages(window, cx); - }); - - // Add the panel to the center dock (tabs) - this.add_panel( - Arc::new(panel), - DockPlacement::Center, - window, - cx, - ); - }); - } else { - window.push_notification(t!("common.room_error"), cx); - } - } - RegistrySignal::Close(..) => { + cx.subscribe_in(®istry, window, |this, _e, event, window, cx| { + match event { + RegistrySignal::Open(room) => { + if let Some(room) = room.upgrade() { this.dock.update(cx, |this, cx| { - this.focus_tab_panel(window, cx); + let panel = chat::init(room, window, cx); - cx.defer_in(window, |_, window, cx| { - window.dispatch_action(Box::new(ClosePanel), cx); - window.close_all_modals(cx); + // Load messages when the panel is created + panel.update(cx, |this, cx| { + this.load_messages(window, cx); }); - }); - } - _ => {} - } - }, - )); - Self { - dock, - title_bar, - subscriptions, - } - }) + // Add the panel to the center dock (tabs) + this.add_panel(Arc::new(panel), DockPlacement::Center, window, cx); + }); + } else { + window.push_notification(t!("common.room_error"), cx); + } + } + RegistrySignal::Close(..) => { + this.dock.update(cx, |this, cx| { + this.focus_tab_panel(window, cx); + + cx.defer_in(window, |_, window, cx| { + window.dispatch_action(Box::new(ClosePanel), cx); + window.close_all_modals(cx); + }); + }); + } + _ => {} + } + }), + ); + + tasks.push( + // Continuously handle signals from the Nostr channel + cx.spawn_in(window, async move |this, cx| { + ChatSpace::handle_signal(this, cx).await + }), + ); + + Self { + dock, + title_bar, + _subscriptions: subscriptions, + _tasks: tasks, + } } - pub fn set_onboarding_panels(&mut self, window: &mut Window, cx: &mut Context) { + async fn handle_signal(e: WeakEntity, cx: &mut AsyncWindowContext) { + let channel = global_channel(); + let mut is_open_proxy_modal = false; + + while let Ok(signal) = channel.1.recv().await { + cx.update(|window, cx| { + let registry = Registry::global(cx); + + match signal { + NostrSignal::SignerSet(public_key) => { + window.close_modal(cx); + + // Setup the default layout for current workspace + e.update(cx, |this, cx| { + this.set_default_layout(window, cx); + }) + .ok(); + + // Initialize identity + identity::init(public_key, window, cx); + + // Load all chat rooms + registry.update(cx, |this, cx| { + this.load_rooms(window, cx); + }); + } + NostrSignal::SignerUnset => { + e.update(cx, |this, cx| { + this.set_onboarding_layout(window, cx); + }) + .ok(); + } + NostrSignal::ProxyDown => { + if !is_open_proxy_modal { + e.update(cx, |this, cx| { + this.render_proxy_modal(window, cx); + }) + .ok(); + is_open_proxy_modal = true; + } + } + // Load chat rooms and stop the loading status + NostrSignal::Finish => { + registry.update(cx, |this, cx| { + this.load_rooms(window, cx); + this.set_loading(false, cx); + // Send a signal to refresh all opened rooms' messages + if let Some(ids) = ChatSpace::all_panels(window, cx) { + this.refresh_rooms(ids, cx); + } + }); + } + // Load chat rooms without setting as finished + NostrSignal::PartialFinish => { + registry.update(cx, |this, cx| { + this.load_rooms(window, cx); + // Send a signal to refresh all opened rooms' messages + if let Some(ids) = ChatSpace::all_panels(window, cx) { + this.refresh_rooms(ids, cx); + } + }); + } + // Add the new metadata to the registry or update the existing one + NostrSignal::Metadata(event) => { + registry.update(cx, |this, cx| { + this.insert_or_update_person(event, cx); + }); + } + // Convert the gift wrapped message to a message + NostrSignal::GiftWrap(event) => { + let identity = Identity::read_global(cx).public_key(); + registry.update(cx, |this, cx| { + this.event_to_message(identity, event, window, cx); + }); + } + NostrSignal::DmRelaysFound => { + // + } + NostrSignal::Notice(_msg) => { + // window.push_notification(msg, cx); + } + }; + }) + .ok(); + } + } + + pub fn set_onboarding_layout(&mut self, window: &mut Window, cx: &mut Context) { let panel = Arc::new(onboarding::init(window, cx)); let center = DockItem::panel(panel); @@ -206,48 +279,93 @@ impl ChatSpace { }); } - pub fn set_chat_panels(&mut self, window: &mut Window, cx: &mut Context) { - let registry = Registry::global(cx); + pub fn set_account_layout( + &mut self, + secret: String, + profile: Profile, + window: &mut Window, + cx: &mut Context, + ) { + let panel = Arc::new(account::init(secret, profile, window, cx)); + let center = DockItem::panel(panel); + + self.dock.update(cx, |this, cx| { + this.reset(window, cx); + this.set_center(center, window, cx); + }); + } + + fn set_default_layout(&mut self, window: &mut Window, cx: &mut Context) { let weak_dock = self.dock.downgrade(); - // The left panel will render sidebar - let left = DockItem::panel(Arc::new(sidebar::init(window, cx))); + let sidebar = Arc::new(sidebar::init(window, cx)); + let center = Arc::new(welcome::init(window, cx)); - // The center panel will render chat rooms (as tabs) + let left = DockItem::panel(sidebar); let center = DockItem::split_with_sizes( Axis::Vertical, - vec![DockItem::tabs( - vec![Arc::new(welcome::init(window, cx))], - None, - &weak_dock, - window, - cx, - )], + vec![DockItem::tabs(vec![center], None, &weak_dock, window, cx)], vec![None], &weak_dock, window, cx, ); - // Update dock self.dock.update(cx, |this, cx| { this.set_left_dock(left, Some(px(DEFAULT_SIDEBAR_WIDTH)), true, window, cx); this.set_center(center, window, cx); }); + } - // Load all chat rooms from the database - registry.update(cx, |this, cx| { - this.load_rooms(window, cx); + fn load_local_account(&mut self, window: &mut Window, cx: &mut Context) { + let task = cx.background_spawn(async move { + let client = nostr_client(); + let filter = Filter::new() + .kind(Kind::ApplicationSpecificData) + .identifier(ACCOUNT_IDENTIFIER) + .limit(1); + + if let Some(event) = client.database().query(filter).await?.first_owned() { + let metadata = client + .database() + .metadata(event.pubkey) + .await? + .unwrap_or_default(); + + Ok((event.content, Profile::new(event.pubkey, metadata))) + } else { + Err(anyhow!("Empty")) + } }); + + cx.spawn_in(window, async move |this, cx| { + if let Ok((secret, profile)) = task.await { + cx.update(|window, cx| { + this.update(cx, |this, cx| { + this.set_account_layout(secret, profile, window, cx); + }) + .ok(); + }) + .ok(); + } else { + cx.update(|window, cx| { + this.update(cx, |this, cx| { + this.set_onboarding_layout(window, cx); + }) + .ok(); + }) + .ok(); + } + }) + .detach(); } fn on_settings(&mut self, _ev: &Settings, window: &mut Window, cx: &mut Context) { let view = preferences::init(window, cx); - let title = SharedString::new(t!("common.preferences")); - window.open_modal(cx, move |modal, _, _| { + window.open_modal(cx, move |modal, _window, _cx| { modal - .title(title.clone()) + .title(shared_t!("common.preferences")) .width(px(480.)) .child(view.clone()) }); @@ -261,17 +379,30 @@ impl ChatSpace { } } - fn on_sign_out(&mut self, _ev: &Logout, window: &mut Window, cx: &mut Context) { - let registry = Registry::global(cx); - let identity = Identity::global(cx); - - registry.update(cx, |this, cx| { + fn on_sign_out(&mut self, _e: &Logout, _window: &mut Window, cx: &mut Context) { + Identity::remove_global(cx); + Registry::global(cx).update(cx, |this, cx| { this.reset(cx); }); - identity.update(cx, |this, cx| { - this.unload(window, cx); - }); + cx.background_spawn(async move { + let client = nostr_client(); + let channel = global_channel(); + + let filter = Filter::new() + .kind(Kind::ApplicationSpecificData) + .identifier(ACCOUNT_IDENTIFIER); + + // Delete account + client.database().delete(filter).await.ok(); + + // Reset the nostr client + client.reset().await; + + // Notify the channel about the signer being unset + channel.0.send(NostrSignal::SignerUnset).await.ok(); + }) + .detach(); } fn on_open_profile(&mut self, ev: &OpenProfile, window: &mut Window, cx: &mut Context) { @@ -292,10 +423,33 @@ impl ChatSpace { }); } - fn render_client_keys_modal(&mut self, window: &mut Window, cx: &mut Context) { - let title = SharedString::new(t!("startup.client_keys_warning")); - let desc = SharedString::new(t!("startup.client_keys_desc")); + fn render_proxy_modal(&mut self, window: &mut Window, cx: &mut App) { + window.open_modal(cx, |this, _window, _cx| { + this.overlay_closable(false) + .show_close(false) + .keyboard(false) + .alert() + .button_props(ModalButtonProps::default().ok_text(t!("common.open_browser"))) + .title(shared_t!("proxy.label")) + .child( + v_flex() + .p_3() + .gap_1() + .w_full() + .items_center() + .justify_center() + .text_center() + .text_sm() + .child(shared_t!("proxy.description")), + ) + .on_ok(move |_e, _window, cx| { + cx.open_url("http://localhost:7400"); + false + }) + }); + } + fn render_client_keys_modal(&mut self, window: &mut Window, cx: &mut Context) { window.open_modal(cx, move |this, _window, cx| { this.overlay_closable(false) .show_close(false) @@ -321,9 +475,9 @@ impl ChatSpace { div() .font_semibold() .text_color(cx.theme().text_muted) - .child(title.clone()), + .child(shared_t!("startup.client_keys_warning")), ) - .child(desc.clone()), + .child(shared_t!("startup.client_keys_desc")), ) .on_cancel(|_, _window, cx| { ClientKeys::global(cx).update(cx, |this, cx| { @@ -379,8 +533,7 @@ impl ChatSpace { cx: &Context, ) -> impl IntoElement { let proxy = AppSettings::get_proxy_user_avatars(cx); - let need_backup = Identity::read_global(cx).need_backup(); - let has_dm_relays = Identity::read_global(cx).has_dm_relays(); + let nip17_relays = Identity::read_global(cx).nip17_relays(); let updating = AutoUpdater::read_global(cx).status.is_updating(); let updated = AutoUpdater::read_global(cx).status.is_updated(); @@ -415,12 +568,9 @@ impl ChatSpace { }), ) }) - .when_some(has_dm_relays, |this, status| { + .when_some(nip17_relays, |this, status| { this.when(!status, |this| this.child(messaging_relays::relay_button())) }) - .when_some(need_backup, |this, keys| { - this.child(backup_keys::backup_button(keys.to_owned())) - }) .child( Button::new("user") .small() @@ -437,24 +587,6 @@ impl ChatSpace { ) } - pub(crate) fn set_center_panel

(panel: P, window: &mut Window, cx: &mut App) - where - P: PanelView, - { - if let Some(Some(root)) = window.root::() { - if let Ok(chatspace) = root.read(cx).view().clone().downcast::() { - let panel = Arc::new(panel); - let center = DockItem::panel(panel); - - chatspace.update(cx, |this, cx| { - this.dock.update(cx, |this, cx| { - this.set_center(center, window, cx); - }); - }); - } - } - } - pub(crate) fn all_panels(window: &mut Window, cx: &mut App) -> Option> { let Some(Some(root)) = window.root::() else { return None; @@ -476,15 +608,35 @@ impl ChatSpace { Some(ids) } + + pub(crate) fn set_center_panel

(panel: P, window: &mut Window, cx: &mut App) + where + P: PanelView, + { + if let Some(Some(root)) = window.root::() { + if let Ok(chatspace) = root.read(cx).view().clone().downcast::() { + let panel = Arc::new(panel); + let center = DockItem::panel(panel); + + chatspace.update(cx, |this, cx| { + this.dock.update(cx, |this, cx| { + this.set_center(center, window, cx); + }); + }); + } + } + } } impl Render for ChatSpace { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let modal_layer = Root::render_modal_layer(window, cx); let notification_layer = Root::render_notification_layer(window, cx); + let logged_in = Identity::has_global(cx); - // Only render titlebar element if user is logged in - if let Some(identity) = Identity::read_global(cx).public_key() { + // Only render titlebar child elements if user is logged in + if logged_in { + let identity = Identity::read_global(cx).public_key(); let profile = Registry::read_global(cx).get_person(&identity, cx); let left_side = self diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index e041683..215ae11 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -9,22 +9,18 @@ use global::constants::{ APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, SEARCH_RELAYS, WAIT_FOR_FINISH, }; -use global::{nostr_client, processed_events, starting_time, NostrSignal}; +use global::{global_channel, nostr_client, processed_events, starting_time, NostrSignal}; use gpui::{ actions, point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, }; -use identity::Identity; use itertools::Itertools; use nostr_sdk::prelude::*; -use registry::Registry; use smol::channel::{self, Sender}; use theme::Theme; use ui::Root; -use crate::chatspace::ChatSpace; - pub(crate) mod chatspace; pub(crate) mod views; @@ -40,17 +36,15 @@ fn main() { let client = nostr_client(); // Initialize the starting time - let _ = starting_time(); + let _starting_time = starting_time(); // Initialize the Application let app = Application::new() .with_assets(Assets) .with_http_client(Arc::new(reqwest_client::ReqwestClient::new())); - let (signal_tx, signal_rx) = channel::bounded::(2048); - let (mta_tx, mta_rx) = channel::bounded::(1024); + let (pubkey_tx, pubkey_rx) = channel::bounded::(1024); let (event_tx, event_rx) = channel::bounded::(2048); - let signal_tx_clone = signal_tx.clone(); app.background_executor() .spawn(async move { @@ -62,12 +56,33 @@ fn main() { // Handle Nostr notifications. // // Send the redefined signal back to GPUI via channel. - if let Err(e) = handle_nostr_notifications(&signal_tx_clone, &event_tx).await { + if let Err(e) = handle_nostr_notifications(&event_tx).await { log::error!("Failed to handle Nostr notifications: {e}"); } }) .detach(); + app.background_executor() + .spawn(async move { + let channel = global_channel(); + + loop { + if let Ok(signer) = client.signer().await { + if let Ok(public_key) = signer.get_public_key().await { + // Notify the app that the signer has been set. + _ = channel.0.send(NostrSignal::SignerSet(public_key)).await; + + // Get the NIP-65 relays for the public key. + get_nip65_relays(public_key).await.ok(); + + break; + } + } + smol::Timer::after(Duration::from_secs(1)).await; + } + }) + .detach(); + app.background_executor() .spawn(async move { let duration = Duration::from_millis(METADATA_BATCH_TIMEOUT); @@ -85,7 +100,7 @@ fn main() { let duration = smol::Timer::after(duration); let recv = || async { - if let Ok(public_key) = mta_rx.recv().await { + if let Ok(public_key) = pubkey_rx.recv().await { BatchEvent::NewKeys(public_key) } else { BatchEvent::Closed @@ -126,6 +141,7 @@ fn main() { app.background_executor() .spawn(async move { + let channel = global_channel(); let mut counter = 0; loop { @@ -149,7 +165,7 @@ fn main() { match smol::future::or(recv(), timeout()).await { Some(event) => { - let cached = unwrap_gift(&event, &signal_tx, &mta_tx).await; + let cached = unwrap_gift(&event, &pubkey_tx).await; // Increment the total messages counter if message is not from cache if !cached { @@ -158,14 +174,14 @@ fn main() { // Send partial finish signal to GPUI if counter >= 20 { - signal_tx.send(NostrSignal::PartialFinish).await.ok(); + channel.0.send(NostrSignal::PartialFinish).await.ok(); // Reset counter counter = 0; } } None => { // Notify the UI that the processing is finished - signal_tx.send(NostrSignal::Finish).await.ok(); + channel.0.send(NostrSignal::Finish).await.ok(); } } } @@ -226,77 +242,22 @@ fn main() { cx.activate(true); // Initialize the tokio runtime gpui_tokio::init(cx); + // Initialize components ui::init(cx); - // Initialize app registry - registry::init(cx); - // Initialize settings - settings::init(cx); + // Initialize client keys client_keys::init(cx); - // Initialize identity - identity::init(window, cx); + + // Initialize app registry + registry::init(cx); + + // Initialize settings + settings::init(cx); + // Initialize auto update auto_update::init(cx); - // Spawn a task to handle events from nostr channel - cx.spawn_in(window, async move |_, cx| { - while let Ok(signal) = signal_rx.recv().await { - cx.update(|window, cx| { - let registry = Registry::global(cx); - let identity = Identity::global(cx); - - match signal { - // Load chat rooms and stop the loading status - NostrSignal::Finish => { - registry.update(cx, |this, cx| { - this.load_rooms(window, cx); - this.set_loading(false, cx); - // Send a signal to refresh all opened rooms' messages - if let Some(ids) = ChatSpace::all_panels(window, cx) { - this.refresh_rooms(ids, cx); - } - }); - } - // Load chat rooms without setting as finished - NostrSignal::PartialFinish => { - registry.update(cx, |this, cx| { - this.load_rooms(window, cx); - // Send a signal to refresh all opened rooms' messages - if let Some(ids) = ChatSpace::all_panels(window, cx) { - this.refresh_rooms(ids, cx); - } - }); - } - // Add the new metadata to the registry or update the existing one - NostrSignal::Metadata(event) => { - registry.update(cx, |this, cx| { - this.insert_or_update_person(event, cx); - }); - } - // Convert the gift wrapped message to a message - NostrSignal::GiftWrap(event) => { - if let Some(public_key) = identity.read(cx).public_key() { - registry.update(cx, |this, cx| { - this.event_to_message(public_key, event, window, cx); - }); - } - } - NostrSignal::DmRelaysFound => { - identity.update(cx, |this, cx| { - this.set_has_dm_relays(cx); - }); - } - NostrSignal::Notice(_msg) => { - // window.push_notification(msg, cx); - } - }; - }) - .ok(); - } - }) - .detach(); - Root::new(chatspace::init(window, cx).into(), window, cx) }) }) @@ -352,11 +313,9 @@ async fn connect(client: &Client) -> Result<(), Error> { Ok(()) } -async fn handle_nostr_notifications( - signal_tx: &Sender, - event_tx: &Sender, -) -> Result<(), Error> { +async fn handle_nostr_notifications(event_tx: &Sender) -> Result<(), Error> { let client = nostr_client(); + let channel = global_channel(); let auto_close = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let mut notifications = client.notifications(); @@ -379,10 +338,8 @@ async fn handle_nostr_notifications( // Get metadata for event's pubkey that matches the current user's pubkey if let Ok(true) = is_from_current_user(&event).await { let sub_id = SubscriptionId::new("metadata"); - let filter = Filter::new() - .kinds(vec![Kind::Metadata, Kind::ContactList, Kind::InboxRelays]) - .author(event.pubkey) - .limit(10); + let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::InboxRelays]; + let filter = Filter::new().kinds(kinds).author(event.pubkey).limit(10); client .subscribe_with_id(sub_id, filter, Some(auto_close)) @@ -416,7 +373,7 @@ async fn handle_nostr_notifications( let sub_id = SubscriptionId::new("gift-wrap"); // Notify the UI that the current user has set up the DM relays - signal_tx.send(NostrSignal::DmRelaysFound).await.ok(); + channel.0.send(NostrSignal::DmRelaysFound).await.ok(); if client .subscribe_with_id_to(relays.clone(), sub_id, filter, None) @@ -442,7 +399,8 @@ async fn handle_nostr_notifications( } } Kind::Metadata => { - signal_tx + channel + .0 .send(NostrSignal::Metadata(event.into_owned())) .await .ok(); @@ -457,6 +415,21 @@ async fn handle_nostr_notifications( Ok(()) } +async fn get_nip65_relays(public_key: PublicKey) -> Result<(), Error> { + let client = nostr_client(); + let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); + let sub_id = SubscriptionId::new("nip65-relays"); + + let filter = Filter::new() + .kind(Kind::RelayList) + .author(public_key) + .limit(1); + + client.subscribe_with_id(sub_id, filter, Some(opts)).await?; + + Ok(()) +} + async fn is_from_current_user(event: &Event) -> Result { let client = nostr_client(); let signer = client.signer().await?; @@ -522,12 +495,9 @@ async fn get_unwrapped(root: EventId) -> Result { } /// Unwraps a gift-wrapped event and processes its contents. -async fn unwrap_gift( - gift: &Event, - signal_tx: &Sender, - mta_tx: &Sender, -) -> bool { +async fn unwrap_gift(gift: &Event, pubkey_tx: &Sender) -> bool { let client = nostr_client(); + let channel = global_channel(); let mut is_cached = false; let event = match get_unwrapped(gift.id).await { @@ -561,12 +531,12 @@ async fn unwrap_gift( // Send all pubkeys to the metadata batch to sync data for public_key in event.all_pubkeys() { - mta_tx.send(public_key).await.ok(); + pubkey_tx.send(public_key).await.ok(); } // Send a notify to GPUI if this is a new message if starting_time() <= &event.created_at { - signal_tx.send(NostrSignal::GiftWrap(event)).await.ok(); + channel.0.send(NostrSignal::GiftWrap(event)).await.ok(); } is_cached diff --git a/crates/coop/src/views/account.rs b/crates/coop/src/views/account.rs new file mode 100644 index 0000000..973ee13 --- /dev/null +++ b/crates/coop/src/views/account.rs @@ -0,0 +1,398 @@ +use std::time::Duration; + +use anyhow::Error; +use client_keys::ClientKeys; +use common::display::DisplayProfile; +use common::handle_auth::CoopAuthUrlHandler; +use global::constants::ACCOUNT_IDENTIFIER; +use global::nostr_client; +use gpui::prelude::FluentBuilder; +use gpui::{ + div, relative, rems, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter, + FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, + StatefulInteractiveElement, Styled, Task, WeakEntity, Window, +}; +use i18n::{shared_t, t}; +use identity::Identity; +use nostr_connect::prelude::*; +use nostr_sdk::prelude::*; +use theme::ActiveTheme; +use ui::avatar::Avatar; +use ui::button::{Button, ButtonVariants}; +use ui::dock_area::panel::{Panel, PanelEvent}; +use ui::indicator::Indicator; +use ui::input::{InputState, TextInput}; +use ui::popup_menu::PopupMenu; +use ui::{h_flex, v_flex, ContextModal, Disableable, Sizable, StyledExt}; + +pub fn init( + secret: String, + profile: Profile, + window: &mut Window, + cx: &mut App, +) -> Entity { + Account::new(secret, profile, window, cx) +} + +pub struct Account { + profile: Profile, + stored_secret: String, + is_bunker: bool, + is_extension: bool, + loading: bool, + // Panel + name: SharedString, + focus_handle: FocusHandle, +} + +impl Account { + fn new(secret: String, profile: Profile, _window: &mut Window, cx: &mut App) -> Entity { + let is_bunker = secret.starts_with("bunker://"); + let is_extension = secret.starts_with("extension"); + + cx.new(|cx| Self { + profile, + is_bunker, + is_extension, + stored_secret: secret, + loading: false, + name: "Account".into(), + focus_handle: cx.focus_handle(), + }) + } + + fn login(&mut self, window: &mut Window, cx: &mut Context) { + self.set_loading(true, cx); + + if self.is_bunker { + if let Ok(uri) = NostrConnectURI::parse(&self.stored_secret) { + self.nostr_connect(uri, window, cx); + } + } else if self.is_extension { + self.proxy(window, cx); + } else if let Ok(enc) = EncryptedSecretKey::from_bech32(&self.stored_secret) { + self.keys(enc, window, cx); + } else { + window.push_notification("Cannot continue with current account", cx); + self.set_loading(false, cx); + } + } + + fn nostr_connect(&mut self, uri: NostrConnectURI, window: &mut Window, cx: &mut Context) { + let client_keys = ClientKeys::global(cx); + let app_keys = client_keys.read(cx).keys(); + + let secs = 30; + let timeout = Duration::from_secs(secs); + let mut signer = NostrConnect::new(uri, app_keys, timeout, None).unwrap(); + + // Handle auth url with the default browser + signer.auth_url_handler(CoopAuthUrlHandler); + + // Handle connection + cx.spawn_in(window, async move |_this, cx| { + let client = nostr_client(); + + match signer.bunker_uri().await { + Ok(_) => { + // Set the client's signer with the current nostr connect instance + client.set_signer(signer).await; + } + Err(e) => { + cx.update(|window, cx| { + window.push_notification(e.to_string(), cx); + }) + .ok(); + } + } + }) + .detach(); + } + + fn proxy(&mut self, _window: &mut Window, cx: &mut Context) { + Identity::start_browser_proxy(cx); + } + + fn keys(&mut self, enc: EncryptedSecretKey, window: &mut Window, cx: &mut Context) { + let pwd_input: Entity = cx.new(|cx| InputState::new(window, cx).masked(true)); + let weak_input = pwd_input.downgrade(); + + let error: Entity> = cx.new(|_| None); + let weak_error = error.downgrade(); + + let entity = cx.weak_entity(); + + window.open_modal(cx, move |this, _window, cx| { + let entity = entity.clone(); + let entity_clone = entity.clone(); + let weak_input = weak_input.clone(); + let weak_error = weak_error.clone(); + + this.overlay_closable(false) + .show_close(false) + .keyboard(false) + .confirm() + .on_cancel(move |_, _window, cx| { + entity + .update(cx, |this, cx| { + this.set_loading(false, cx); + }) + .ok(); + + // true to close the modal + true + }) + .on_ok(move |_, window, cx| { + let weak_error = weak_error.clone(); + let password = weak_input + .read_with(cx, |state, _cx| state.value().to_owned()) + .ok(); + + entity_clone + .update(cx, |this, cx| { + this.verify_keys(enc, password, weak_error, window, cx); + }) + .ok(); + + // false to keep the modal open + false + }) + .child( + div() + .w_full() + .flex() + .flex_col() + .gap_1() + .text_sm() + .child(shared_t!("login.password_to_decrypt")) + .child(TextInput::new(&pwd_input).small()) + .when_some(error.read(cx).as_ref(), |this, error| { + this.child( + div() + .text_xs() + .italic() + .text_color(cx.theme().danger_foreground) + .child(error.clone()), + ) + }), + ) + }); + } + + fn verify_keys( + &mut self, + enc: EncryptedSecretKey, + password: Option, + error: WeakEntity>, + window: &mut Window, + cx: &mut Context, + ) { + let Some(password) = password else { + error + .update(cx, |this, cx| { + *this = Some("Password is required".into()); + cx.notify(); + }) + .ok(); + return; + }; + + if password.is_empty() { + error + .update(cx, |this, cx| { + *this = Some("Password cannot be empty".into()); + cx.notify(); + }) + .ok(); + return; + } + + let task: Task> = cx.background_spawn(async move { + let secret = enc.decrypt(&password)?; + Ok(secret) + }); + + cx.spawn_in(window, async move |_this, cx| { + match task.await { + Ok(secret) => { + cx.update(|window, cx| { + window.close_all_modals(cx); + }) + .ok(); + + let client = nostr_client(); + let keys = Keys::new(secret); + + // Set the client's signer with the current keys + client.set_signer(keys).await + } + Err(e) => { + error + .update(cx, |this, cx| { + *this = Some(e.to_string().into()); + cx.notify(); + }) + .ok(); + } + }; + }) + .detach(); + } + + fn logout(&mut self, window: &mut Window, cx: &mut Context) { + let task: Task> = cx.background_spawn(async move { + let client = nostr_client(); + let filter = Filter::new() + .kind(Kind::ApplicationSpecificData) + .identifier(ACCOUNT_IDENTIFIER); + + // Delete account + client.database().delete(filter).await?; + + Ok(()) + }); + + cx.spawn_in(window, async move |_this, cx| { + if task.await.is_ok() { + cx.update(|_window, cx| { + cx.restart(); + }) + .ok(); + } + }) + .detach(); + } + + fn set_loading(&mut self, status: bool, cx: &mut Context) { + self.loading = status; + cx.notify(); + } +} + +impl Panel for Account { + fn panel_id(&self) -> SharedString { + self.name.clone() + } + + fn title(&self, _cx: &App) -> AnyElement { + self.name.clone().into_any_element() + } + + fn popup_menu(&self, menu: PopupMenu, _cx: &App) -> PopupMenu { + menu.track_focus(&self.focus_handle) + } + + fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec