diff --git a/Cargo.lock b/Cargo.lock index b541f88e..4355fa6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,7 +190,7 @@ dependencies = [ [[package]] name = "assets" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "gpui", @@ -428,7 +428,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "auto_update" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "cargo-packager-updater", @@ -482,9 +482,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba2e2516bdf37af57fc6ff047855f54abad0066e5c4fdaaeb76dabb2e05bcf5" +checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" dependencies = [ "bindgen 0.72.1", "cc", @@ -661,14 +661,15 @@ checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "blade-graphics" -version = "0.6.0" -source = "git+https://github.com/kvark/blade?rev=bfa594ea697d4b6326ea29f747525c85ecf933b9#bfa594ea697d4b6326ea29f747525c85ecf933b9" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4deb8f595ce7f00dee3543ebf6fd9a20ea86fc421ab79600dac30876250bdae" dependencies = [ "ash", "ash-window", "bitflags 2.9.4", "bytemuck", - "codespan-reporting 0.11.1", + "codespan-reporting", "glow", "gpu-alloc", "gpu-alloc-ash", @@ -686,6 +687,7 @@ dependencies = [ "objc2-metal", "objc2-quartz-core", "objc2-ui-kit", + "once_cell", "raw-window-handle", "slab", "wasm-bindgen", @@ -695,7 +697,8 @@ dependencies = [ [[package]] name = "blade-macros" version = "0.3.0" -source = "git+https://github.com/kvark/blade?rev=bfa594ea697d4b6326ea29f747525c85ecf933b9#bfa594ea697d4b6326ea29f747525c85ecf933b9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27142319e2f4c264581067eaccb9f80acccdde60d8b4bf57cc50cd3152f109ca" dependencies = [ "proc-macro2", "quote", @@ -704,8 +707,9 @@ dependencies = [ [[package]] name = "blade-util" -version = "0.2.0" -source = "git+https://github.com/kvark/blade?rev=bfa594ea697d4b6326ea29f747525c85ecf933b9#bfa594ea697d4b6326ea29f747525c85ecf933b9" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6be3a82c001ba7a17b6f8e413ede5d1004e6047213f8efaf0ffc15b5c4904c" dependencies = [ "blade-graphics", "bytemuck", @@ -1027,7 +1031,7 @@ dependencies = [ [[package]] name = "client_keys" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "global", @@ -1105,16 +1109,6 @@ dependencies = [ "objc", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width 0.1.14", -] - [[package]] name = "codespan-reporting" version = "0.12.0" @@ -1123,13 +1117,13 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ "serde", "termcolor", - "unicode-width 0.2.1", + "unicode-width", ] [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1159,12 +1153,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f849b92c694fe237ecd8fafd1ba0df7ae0d45c1df6daeb7f68ed4220d51640bd" dependencies = [ "nix 0.30.1", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "common" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "chrono", @@ -1237,7 +1231,7 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "coop" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "assets", @@ -1570,7 +1564,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "proc-macro2", "quote", @@ -2413,7 +2407,7 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "global" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "dirs 5.0.1", @@ -2463,9 +2457,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.14.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" dependencies = [ "js-sys", "slotmap", @@ -2506,7 +2500,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2575,7 +2569,7 @@ dependencies = [ "strum 0.27.2", "sum_tree", "taffy", - "thiserror 2.0.16", + "thiserror 2.0.17", "usvg", "util", "util_macros", @@ -2600,7 +2594,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2612,7 +2606,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "anyhow", "gpui", @@ -2832,7 +2826,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "anyhow", "bytes", @@ -2852,7 +2846,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "rustls", "rustls-platform-verifier", @@ -2955,7 +2949,7 @@ dependencies = [ [[package]] name = "i18n" -version = "0.2.10" +version = "0.2.11" dependencies = [ "rust-i18n", ] @@ -3657,7 +3651,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "anyhow", "bindgen 0.71.1", @@ -3793,7 +3787,7 @@ dependencies = [ "bit-set", "bitflags 2.9.4", "cfg_aliases", - "codespan-reporting 0.12.0", + "codespan-reporting", "half", "hashbrown 0.15.5", "hexf-parse", @@ -3804,7 +3798,7 @@ dependencies = [ "rustc-hash 1.1.0", "spirv", "strum 0.26.3", - "thiserror 2.0.16", + "thiserror 2.0.17", "unicode-ident", ] @@ -3905,7 +3899,7 @@ dependencies = [ [[package]] name = "nostr" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac" +source = "git+https://github.com/rust-nostr/nostr#a5b37e2bc510b609c20e3ea28b1f3892a186241c" dependencies = [ "aes", "base64", @@ -3929,7 +3923,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac" +source = "git+https://github.com/rust-nostr/nostr#a5b37e2bc510b609c20e3ea28b1f3892a186241c" dependencies = [ "async-utility", "nostr", @@ -3941,7 +3935,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac" +source = "git+https://github.com/rust-nostr/nostr#a5b37e2bc510b609c20e3ea28b1f3892a186241c" dependencies = [ "flatbuffers", "lru", @@ -3952,7 +3946,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac" +source = "git+https://github.com/rust-nostr/nostr#a5b37e2bc510b609c20e3ea28b1f3892a186241c" dependencies = [ "async-utility", "flume", @@ -3966,7 +3960,7 @@ dependencies = [ [[package]] name = "nostr-relay-pool" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac" +source = "git+https://github.com/rust-nostr/nostr#a5b37e2bc510b609c20e3ea28b1f3892a186241c" dependencies = [ "async-utility", "async-wsocket", @@ -3983,7 +3977,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#75345c65c645d33d15f6a5ec10fa6ffd0d8f4cac" +source = "git+https://github.com/rust-nostr/nostr#a5b37e2bc510b609c20e3ea28b1f3892a186241c" dependencies = [ "async-utility", "nostr", @@ -4500,7 +4494,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "collections", "serde", @@ -4846,7 +4840,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -4867,7 +4861,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.16", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -4889,9 +4883,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -5087,18 +5081,18 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -5108,7 +5102,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "derive_refineable", "workspace-hack", @@ -5145,7 +5139,7 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "registry" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "common", @@ -5262,7 +5256,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "anyhow", "bytes", @@ -5317,7 +5311,7 @@ dependencies = [ [[package]] name = "rope" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "arrayvec", "log", @@ -5512,7 +5506,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.0", + "security-framework 3.5.1", ] [[package]] @@ -5549,7 +5543,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.5.0", + "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs 0.26.11", "windows-sys 0.59.0", @@ -5782,9 +5776,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.5.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags 2.9.4", "core-foundation 0.10.1", @@ -5812,7 +5806,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "anyhow", "serde", @@ -5958,7 +5952,7 @@ dependencies = [ [[package]] name = "settings" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "global", @@ -6025,7 +6019,7 @@ dependencies = [ [[package]] name = "signer_proxy" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "atomic-destructor", @@ -6264,7 +6258,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "arrayvec", "log", @@ -6567,7 +6561,7 @@ dependencies = [ [[package]] name = "theme" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "gpui", @@ -6585,11 +6579,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -6605,9 +6599,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -6730,7 +6724,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "title_bar" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "common", @@ -7084,7 +7078,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.16", + "thiserror 2.0.17", "utf-8", ] @@ -7113,7 +7107,7 @@ dependencies = [ [[package]] name = "ui" -version = "0.2.10" +version = "0.2.11" dependencies = [ "anyhow", "common", @@ -7222,12 +7216,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.1" @@ -7310,7 +7298,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "anyhow", "async-fs", @@ -7345,7 +7333,7 @@ dependencies = [ [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#78098f6809346ba8b45db7ffa768fba75578a263" +source = "git+https://github.com/zed-industries/zed#1659fb81e7ead8b050a9fe10bcea6e46c5ded6b0" dependencies = [ "perf", "quote", @@ -7833,7 +7821,7 @@ checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" dependencies = [ "parking_lot", "rayon", - "thiserror 2.0.16", + "thiserror 2.0.17", "windows 0.61.3", "windows-future", ] @@ -8661,9 +8649,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index b9283ed2..061964c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = ["crates/*"] default-members = ["crates/coop"] [workspace.package] -version = "0.2.10" +version = "0.2.11" edition = "2021" publish = false diff --git a/crates/common/src/event.rs b/crates/common/src/event.rs index b7e6428f..cf5bafb1 100644 --- a/crates/common/src/event.rs +++ b/crates/common/src/event.rs @@ -1,4 +1,3 @@ -use std::collections::HashSet; use std::hash::{DefaultHasher, Hash, Hasher}; use itertools::Itertools; @@ -7,26 +6,14 @@ use nostr_sdk::prelude::*; pub trait EventUtils { fn uniq_id(&self) -> u64; fn all_pubkeys(&self) -> Vec; - fn compare_pubkeys(&self, other: &[PublicKey]) -> bool; } impl EventUtils for Event { fn uniq_id(&self) -> u64 { let mut hasher = DefaultHasher::new(); - let mut pubkeys: Vec = vec![]; - - // Add all public keys from event - pubkeys.push(self.pubkey); - pubkeys.extend(self.tags.public_keys().collect::>()); - - // Generate unique hash - pubkeys - .into_iter() - .unique() - .sorted() - .collect::>() - .hash(&mut hasher); - + let mut pubkeys: Vec = self.all_pubkeys(); + pubkeys.sort(); + pubkeys.hash(&mut hasher); hasher.finish() } @@ -34,15 +21,7 @@ impl EventUtils for Event { let mut public_keys: Vec = self.tags.public_keys().copied().collect(); public_keys.push(self.pubkey); - public_keys - } - - fn compare_pubkeys(&self, other: &[PublicKey]) -> bool { - let pubkeys = self.all_pubkeys(); - let a: HashSet<_> = pubkeys.iter().collect(); - let b: HashSet<_> = other.iter().collect(); - - a == b + public_keys.into_iter().unique().collect() } } @@ -72,12 +51,4 @@ impl EventUtils for UnsignedEvent { public_keys } - - fn compare_pubkeys(&self, other: &[PublicKey]) -> bool { - let pubkeys = self.all_pubkeys(); - let a: HashSet<_> = pubkeys.iter().collect(); - let b: HashSet<_> = other.iter().collect(); - - a == b - } } diff --git a/crates/coop/Cargo.toml b/crates/coop/Cargo.toml index d217522d..32d2d3b9 100644 --- a/crates/coop/Cargo.toml +++ b/crates/coop/Cargo.toml @@ -14,7 +14,7 @@ product-name = "Coop" description = "Chat Freely, Stay Private on Nostr" identifier = "su.reya.coop" category = "SocialNetworking" -version = "0.2.10" +version = "0.2.11" out-dir = "../../dist" before-packaging-command = "cargo build --release" resources = ["Cargo.toml", "src"] diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index dd908d4a..183c03b1 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -545,7 +545,7 @@ impl ChatSpace { // Load all chat rooms registry.update(cx, |this, cx| { - this.set_identity(public_key, cx); + this.set_signer_pubkey(public_key, cx); this.load_rooms(window, cx); }); } @@ -1481,8 +1481,8 @@ impl Render for ChatSpace { let registry = Registry::read_global(cx); // Only render titlebar child elements if user is logged in - if registry.identity.is_some() { - let profile = registry.identity(cx); + if let Some(public_key) = registry.signer_pubkey() { + let profile = registry.get_person(&public_key, cx); let left_side = self .render_titlebar_left_side(window, cx) diff --git a/crates/coop/src/views/chat/mod.rs b/crates/coop/src/views/chat/mod.rs index ed7846f4..a8cc88a9 100644 --- a/crates/coop/src/views/chat/mod.rs +++ b/crates/coop/src/views/chat/mod.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::time::Duration; use common::display::{RenderedProfile, RenderedTimestamp}; use common::nip96::nip96_upload; @@ -99,7 +100,7 @@ impl Chat { let messages = BTreeSet::from([Message::system()]); let list_state = ListState::new(messages.len(), ListAlignment::Bottom, px(1024.)); - let connect_relays = room.read(cx).connect_relays(cx); + let connect = room.read(cx).connect(cx); let load_messages = room.read(cx).load_messages(cx); let mut subscriptions = smallvec![]; @@ -108,43 +109,41 @@ impl Chat { tasks.push( // Load all messages belonging to this room cx.spawn_in(window, async move |this, cx| { - match connect_relays.await { - Ok(relays) => { - this.update(cx, |this, cx| { - this.relays.update(cx, |this, cx| { - *this = relays; - cx.notify(); - }); - }) - .ok(); - } - Err(e) => { - cx.update(|window, cx| { + let result = load_messages.await; + + this.update_in(cx, |this, window, cx| { + match result { + Ok(events) => { + this.insert_messages(events, cx); + } + Err(e) => { window.push_notification(e.to_string(), cx); - }) - .ok(); - } - }; + } + }; + }) + .ok(); }), ); tasks.push( - // Load all messages belonging to this room + // Get messaging relays for all members cx.spawn_in(window, async move |this, cx| { - match load_messages.await { - Ok(events) => { - this.update(cx, |this, cx| { - this.insert_messages(events, cx); - }) - .ok(); - } - Err(e) => { - cx.update(|window, cx| { - window.push_notification(e.to_string(), cx); - }) - .ok(); - } - }; + let result = connect.await; + + this.update_in(cx, |this, _window, cx| { + match result { + Ok(relays) => { + this.relays.update(cx, |this, cx| { + this.extend(relays); + cx.notify(); + }); + } + Err(e) => { + this.insert_warning(e.to_string(), cx); + } + }; + }) + .ok(); }), ); @@ -192,9 +191,12 @@ impl Chat { subscriptions.push( // Observe the messaging relays of the room's members cx.observe_in(&relays, window, |this, entity, _window, cx| { - for (public_key, urls) in entity.read(cx).clone().into_iter() { + let registry = Registry::global(cx); + let relays = entity.read(cx).clone(); + + for (public_key, urls) in relays.iter() { if urls.is_empty() { - let profile = Registry::read_global(cx).get_person(&public_key, cx); + let profile = registry.read(cx).get_person(public_key, cx); let content = t!("chat.nip17_not_found", u = profile.name()); this.insert_warning(content, cx); @@ -206,7 +208,6 @@ impl Chat { subscriptions.push( // Observe when user close chat panel cx.on_release_in(window, move |this, window, cx| { - this.disconnect_relays(cx); this.messages.clear(); this.rendered_texts_by_id.clear(); this.reports_by_id.clear(); @@ -235,20 +236,6 @@ impl Chat { } } - /// Disconnect all relays when the user closes the chat panel - fn disconnect_relays(&mut self, cx: &mut App) { - let relays = self.relays.read(cx).clone(); - - cx.background_spawn(async move { - let client = nostr_client(); - - for relay in relays.values().flatten() { - client.disconnect_relay(relay).await.ok(); - } - }) - .detach(); - } - /// Load all messages belonging to this room fn load_messages(&mut self, window: &mut Window, cx: &mut Context) { let load_messages = self.room.read(cx).load_messages(cx); @@ -273,11 +260,6 @@ impl Chat { ); } - #[allow(dead_code)] - fn mention_popup(&mut self, _text: &str, _input: &Entity, _cx: &mut Context) { - // TODO: open mention popup at current cursor position - } - /// Get user input content and merged all attachments fn input_content(&self, cx: &Context) -> String { let mut content = self.input.read(cx).value().trim().to_string(); @@ -313,36 +295,48 @@ impl Chat { return; } + // Temporary disable the message input + self.input.update(cx, |this, cx| { + this.set_loading(false, cx); + this.set_disabled(false, cx); + this.set_value("", window, cx); + }); + // Get the backup setting let backup = AppSettings::get_backup_messages(cx); // Get replies_to if it's present - let replies = self.replies_to.read(cx).iter().copied().collect_vec(); + let replies: Vec = self.replies_to.read(cx).iter().copied().collect(); // Get the current room entity let room = self.room.read(cx); - let identity = Registry::read_global(cx).identity(cx).public_key(); // Create a temporary message for optimistic update - let temp_message = room.create_temp_message(identity, &content, replies.as_ref()); - let temp_id = temp_message.id.unwrap(); + let rumor = room.create_message(&content, replies.as_ref(), cx); + let rumor_id = rumor.id.unwrap(); // Create a task for sending the message in the background - let send_message = room.send_in_background(&content, replies, backup, cx); + let send_message = room.send_message(rumor.clone(), backup, cx); // Optimistically update message list - self.insert_message(Message::user(temp_message), true, cx); - - // Remove all replies - self.remove_all_replies(cx); - - // remove all attachments - self.remove_all_attachments(cx); + cx.spawn_in(window, async move |this, cx| { + cx.background_executor() + .timer(Duration::from_millis(100)) + .await; - // Reset the input state - self.input.update(cx, |this, cx| { - this.set_value("", window, cx); - }); + this.update_in(cx, |this, window, cx| { + this.insert_message(Message::user(rumor), true, cx); + this.remove_all_replies(cx); + this.remove_all_attachments(cx); + this.input.update(cx, |this, cx| { + this.set_loading(false, cx); + this.set_disabled(false, cx); + this.set_value("", window, cx); + }); + }) + .ok(); + }) + .detach(); // Continue sending the message in the background cx.spawn_in(window, async move |this, cx| { @@ -363,7 +357,7 @@ impl Chat { }); // Insert the sent reports - this.reports_by_id.insert(temp_id, reports); + this.reports_by_id.insert(rumor_id, reports); cx.notify(); } @@ -377,37 +371,31 @@ impl Chat { .detach(); } + /// Resend a failed message fn resend_message(&mut self, id: &EventId, window: &mut Window, cx: &mut Context) { if let Some(reports) = self.reports_by_id.get(id).cloned() { - if let Some(message) = self.message(id) { - let backup = AppSettings::get_backup_messages(cx); - let id_clone = id.to_owned(); - let message = message.content.to_owned(); - let task = self.room.read(cx).resend(reports, message, backup, cx); - - cx.spawn_in(window, async move |this, cx| { - match task.await { + let id_clone = id.to_owned(); + let resend = self.room.read(cx).resend_message(reports, cx); + + cx.spawn_in(window, async move |this, cx| { + let result = resend.await; + + this.update_in(cx, |this, window, cx| { + match result { Ok(reports) => { - if !reports.is_empty() { - this.update(cx, |this, cx| { - this.reports_by_id.entry(id_clone).and_modify(|this| { - *this = reports; - }); - cx.notify(); - }) - .ok(); - } + this.reports_by_id.entry(id_clone).and_modify(|this| { + *this = reports; + }); + cx.notify(); } Err(e) => { - cx.update(|window, cx| { - window.push_notification(e.to_string(), cx); - }) - .ok(); + window.push_notification(Notification::error(e.to_string()), cx); } }; }) - .detach(); - } + .ok(); + }) + .detach(); } } @@ -612,7 +600,7 @@ impl Chat { }); } - fn render_announcement(&mut self, ix: usize, cx: &mut Context) -> AnyElement { + fn render_announcement(&self, ix: usize, cx: &Context) -> AnyElement { v_flex() .id(ix) .group("") @@ -638,7 +626,7 @@ impl Chat { .into_any_element() } - fn render_warning(&mut self, ix: usize, content: String, cx: &mut Context) -> AnyElement { + fn render_warning(&self, ix: usize, content: SharedString, cx: &Context) -> AnyElement { div() .id(ix) .relative() @@ -652,7 +640,7 @@ impl Chat { .text_sm() .text_color(cx.theme().warning_foreground) .child(Avatar::new("brand/system.png").size(rems(2.))) - .child(SharedString::from(content)), + .child(content), ) .child( div() @@ -666,23 +654,6 @@ impl Chat { .into_any_element() } - fn render_message_not_found(&self, ix: usize, cx: &Context) -> AnyElement { - div() - .id(ix) - .w_full() - .py_1() - .px_3() - .child( - h_flex() - .gap_1() - .text_xs() - .text_color(cx.theme().danger_foreground) - .child(SharedString::from(ix.to_string())) - .child(shared_t!("chat.not_found")), - ) - .into_any_element() - } - fn render_message( &self, ix: usize, @@ -1237,8 +1208,7 @@ impl Chat { weak_view.read_with(cx, |this, cx| this.new_subject(cx)) { room.update(cx, |this, cx| { - this.subject = Some(subject); - cx.notify(); + this.set_subject(subject, cx); }) .ok(); } @@ -1381,13 +1351,13 @@ impl Render for Chat { this.render_message(ix, rendered, text, cx) } - Message::Warning(content, _) => { - this.render_warning(ix, content.to_owned(), cx) + Message::Warning(content, _timestamp) => { + this.render_warning(ix, SharedString::from(content), cx) } - Message::System(_) => this.render_announcement(ix, cx), + Message::System(_timestamp) => this.render_announcement(ix, cx), } } else { - this.render_message_not_found(ix, cx) + this.render_warning(ix, shared_t!("chat.not_found"), cx) } }), ) diff --git a/crates/coop/src/views/compose.rs b/crates/coop/src/views/compose.rs index 3074af08..bfb590c0 100644 --- a/crates/coop/src/views/compose.rs +++ b/crates/coop/src/views/compose.rs @@ -19,7 +19,6 @@ use registry::room::Room; use registry::Registry; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; -use smol::Timer; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -237,7 +236,7 @@ impl Compose { }); }); } else { - self.set_error(Some(t!("compose.contact_existed").into()), cx); + self.set_error(t!("compose.contact_existed"), cx); } } @@ -283,7 +282,7 @@ impl Compose { } Ok(Err(e)) => { this.update(cx, |this, cx| { - this.set_error(Some(e.to_string().into()), cx); + this.set_error(e.to_string(), cx); }) .ok(); } @@ -312,47 +311,38 @@ impl Compose { fn submit(&mut self, window: &mut Window, cx: &mut Context) { let registry = Registry::global(cx); - let public_keys: Vec = self.selected(cx); + let receivers: Vec = self.selected(cx); + let subject_input = self.title_input.read(cx).value(); + let subject = (!subject_input.is_empty()).then(|| subject_input.to_string()); if !self.user_input.read(cx).value().is_empty() { self.add_and_select_contact(window, cx); return; }; - if public_keys.is_empty() { - self.set_error(Some(t!("compose.receiver_required").into()), cx); - return; - }; - - // Convert selected pubkeys into Nostr tags - let mut tags: Tags = Tags::from_list( - public_keys - .iter() - .map(|pubkey| Tag::public_key(pubkey.to_owned())) - .collect(), - ); - - // Add subject if it is present - if !self.title_input.read(cx).value().is_empty() { - tags.push(Tag::custom( - TagKind::Subject, - vec![self.title_input.read(cx).value().to_string()], - )); - } - - // Create a new room - let room = Room::new(public_keys[0], tags, cx); + cx.spawn_in(window, async move |this, cx| { + let result = Room::new(subject, receivers).await; - // Insert the new room into the registry - registry.update(cx, |this, cx| { - this.push_room(cx.new(|_| room), cx); - }); + this.update_in(cx, |this, window, cx| { + match result { + Ok(room) => { + registry.update(cx, |this, cx| { + this.push_room(cx.new(|_| room), cx); + }); - // Close the current modal - window.close_modal(cx); + window.close_modal(cx); + } + Err(e) => { + this.set_error(e.to_string(), cx); + } + }; + }) + .ok(); + }) + .detach(); } - fn set_error(&mut self, error: impl Into>, cx: &mut Context) { + fn set_error(&mut self, error: impl Into, cx: &mut Context) { // Unlock the user input self.user_input.update(cx, |this, cx| { this.set_loading(false, cx); @@ -360,15 +350,19 @@ impl Compose { // Update error message self.error_message.update(cx, |this, cx| { - *this = error.into(); + *this = Some(error.into()); cx.notify(); }); // Dismiss error after 2 seconds cx.spawn(async move |this, cx| { - Timer::after(Duration::from_secs(2)).await; + cx.background_executor().timer(Duration::from_secs(2)).await; + this.update(cx, |this, cx| { - this.set_error(None, cx); + this.error_message.update(cx, |this, cx| { + *this = None; + cx.notify(); + }); }) .ok(); }) diff --git a/crates/coop/src/views/preferences.rs b/crates/coop/src/views/preferences.rs index 62f36763..5993d717 100644 --- a/crates/coop/src/views/preferences.rs +++ b/crates/coop/src/views/preferences.rs @@ -1,5 +1,6 @@ use common::display::RenderedProfile; use gpui::http_client::Url; +use gpui::prelude::FluentBuilder; use gpui::{ div, px, relative, rems, App, AppContext, Context, Entity, InteractiveElement, IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window, @@ -111,9 +112,6 @@ impl Preferences { impl Render for Preferences { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let input_state = self.media_input.downgrade(); - let profile = Registry::read_global(cx).identity(cx); - let auto_auth = AppSettings::get_auto_auth(cx); let backup = AppSettings::get_backup_messages(cx); let screening = AppSettings::get_screening(cx); @@ -121,6 +119,9 @@ impl Render for Preferences { let proxy = AppSettings::get_proxy_user_avatars(cx); let hide = AppSettings::get_hide_user_avatars(cx); + let registry = Registry::read_global(cx); + let input_state = self.media_input.downgrade(); + v_flex() .child( v_flex() @@ -133,48 +134,54 @@ impl Render for Preferences { .font_semibold() .child(shared_t!("preferences.account_header")), ) - .child( - h_flex() - .w_full() - .justify_between() - .child( - h_flex() - .id("user") - .gap_2() - .child(Avatar::new(profile.avatar(proxy)).size(rems(2.4))) - .child( - div() - .flex_1() - .text_sm() - .child( - div() - .font_semibold() - .line_height(relative(1.3)) - .child(profile.display_name()), - ) - .child( - div() - .text_xs() - .text_color(cx.theme().text_muted) - .line_height(relative(1.3)) - .child(shared_t!("preferences.account_btn")), - ), - ) - .on_click(cx.listener(move |this, _e, window, cx| { - this.open_edit_profile(window, cx); - })), - ) - .child( - Button::new("relays") - .label("Messaging Relays") - .xsmall() - .ghost_alt() - .rounded() - .on_click(cx.listener(move |this, _e, window, cx| { - this.open_relays(window, cx); - })), - ), - ), + .when_some(registry.signer_pubkey(), |this, public_key| { + let profile = registry.get_person(&public_key, cx); + + this.child( + h_flex() + .w_full() + .justify_between() + .child( + h_flex() + .id("user") + .gap_2() + .child(Avatar::new(profile.avatar(proxy)).size(rems(2.4))) + .child( + div() + .flex_1() + .text_sm() + .child( + div() + .font_semibold() + .line_height(relative(1.3)) + .child(profile.display_name()), + ) + .child( + div() + .text_xs() + .text_color(cx.theme().text_muted) + .line_height(relative(1.3)) + .child(shared_t!( + "preferences.account_btn" + )), + ), + ) + .on_click(cx.listener(move |this, _e, window, cx| { + this.open_edit_profile(window, cx); + })), + ) + .child( + Button::new("relays") + .label("Messaging Relays") + .xsmall() + .ghost_alt() + .rounded() + .on_click(cx.listener(move |this, _e, window, cx| { + this.open_relays(window, cx); + })), + ), + ) + }), ) .child( v_flex() diff --git a/crates/coop/src/views/screening.rs b/crates/coop/src/views/screening.rs index ad1197a7..d4606d47 100644 --- a/crates/coop/src/views/screening.rs +++ b/crates/coop/src/views/screening.rs @@ -37,33 +37,35 @@ pub struct Screening { impl Screening { pub fn new(public_key: PublicKey, window: &mut Window, cx: &mut Context) -> Self { let registry = Registry::read_global(cx); - let identity = registry.identity(cx).public_key(); let profile = registry.get_person(&public_key, cx); let mut tasks = smallvec![]; - let contact_check: Task<(bool, Vec)> = cx.background_spawn(async move { - let client = nostr_client(); - - // Check if user is in contact list - let contacts = client.database().contacts_public_keys(identity).await; - let followed = contacts.unwrap_or_default().contains(&public_key); - - // Check mutual contacts - let contact_list = Filter::new().kind(Kind::ContactList).pubkey(public_key); - let mut mutual_contacts = vec![]; - - if let Ok(events) = client.database().query(contact_list).await { - for event in events.into_iter().filter(|ev| ev.pubkey != identity) { - if let Ok(metadata) = client.database().metadata(event.pubkey).await { - let profile = Profile::new(event.pubkey, metadata.unwrap_or_default()); - mutual_contacts.push(profile); + let contact_check: Task), Error>> = + cx.background_spawn(async move { + let client = nostr_client(); + let signer = client.signer().await?; + let signer_pubkey = signer.get_public_key().await?; + + // Check if user is in contact list + let contacts = client.database().contacts_public_keys(signer_pubkey).await; + let followed = contacts.unwrap_or_default().contains(&public_key); + + // Check mutual contacts + let contact_list = Filter::new().kind(Kind::ContactList).pubkey(public_key); + let mut mutual_contacts = vec![]; + + if let Ok(events) = client.database().query(contact_list).await { + for event in events.into_iter().filter(|ev| ev.pubkey != signer_pubkey) { + if let Ok(metadata) = client.database().metadata(event.pubkey).await { + let profile = Profile::new(event.pubkey, metadata.unwrap_or_default()); + mutual_contacts.push(profile); + } } } - } - (followed, mutual_contacts) - }); + Ok((followed, mutual_contacts)) + }); let activity_check = cx.background_spawn(async move { let client = nostr_client(); @@ -93,14 +95,14 @@ impl Screening { tasks.push( // Run the contact check in the background cx.spawn_in(window, async move |this, cx| { - let (followed, mutual_contacts) = contact_check.await; - - this.update(cx, |this, cx| { - this.followed = followed; - this.mutual_contacts = mutual_contacts; - cx.notify(); - }) - .ok(); + if let Ok((followed, mutual_contacts)) = contact_check.await { + this.update(cx, |this, cx| { + this.followed = followed; + this.mutual_contacts = mutual_contacts; + cx.notify(); + }) + .ok(); + } }), ); diff --git a/crates/coop/src/views/setup_relay.rs b/crates/coop/src/views/setup_relay.rs index bbd8bf8b..2e112f6c 100644 --- a/crates/coop/src/views/setup_relay.rs +++ b/crates/coop/src/views/setup_relay.rs @@ -11,7 +11,6 @@ use gpui::{ }; use i18n::{shared_t, t}; use nostr_sdk::prelude::*; -use registry::Registry; use smallvec::{smallvec, SmallVec}; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; @@ -70,7 +69,6 @@ pub struct SetupRelay { impl SetupRelay { pub fn new(kind: Kind, window: &mut Window, cx: &mut Context) -> Self { - let identity = Registry::read_global(cx).identity(cx).public_key(); let input = cx.new(|cx| InputState::new(window, cx).placeholder("wss://example.com")); let mut subscriptions = smallvec![]; @@ -78,7 +76,10 @@ impl SetupRelay { let load_relay = cx.background_spawn(async move { let client = nostr_client(); - let filter = Filter::new().kind(kind).author(identity).limit(1); + let signer = client.signer().await?; + let public_key = signer.get_public_key().await?; + + let filter = Filter::new().kind(kind).author(public_key).limit(1); if let Some(event) = client.database().query(filter).await?.first() { let relays: Vec = event diff --git a/crates/coop/src/views/sidebar/mod.rs b/crates/coop/src/views/sidebar/mod.rs index f9a7f3a7..43c76580 100644 --- a/crates/coop/src/views/sidebar/mod.rs +++ b/crates/coop/src/views/sidebar/mod.rs @@ -138,7 +138,8 @@ impl Sidebar { } } - async fn request_metadata(client: &Client, public_key: PublicKey) -> Result<(), Error> { + async fn request_metadata(public_key: PublicKey) -> Result<(), Error> { + let client = nostr_client(); let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList]; let filter = Filter::new().author(public_key).kinds(kinds).limit(10); @@ -152,23 +153,21 @@ impl Sidebar { Ok(()) } - async fn create_temp_room(identity: PublicKey, public_key: PublicKey) -> Result { - let client = nostr_client(); - let keys = Keys::generate(); - let builder = EventBuilder::private_msg_rumor(public_key, ""); - let event = builder.build(identity).sign(&keys).await?; - + async fn create_temp_room(receiver: PublicKey) -> Result { // Request to get user's metadata - Self::request_metadata(client, public_key).await?; + Self::request_metadata(receiver).await?; // Create a temporary room - let room = Room::from(&event).current_user(identity); + let room = Room::new(None, vec![receiver]).await?; Ok(room) } - async fn nip50(identity: PublicKey, query: &str) -> BTreeSet { + async fn nip50(query: &str) -> Result, Error> { let client = nostr_client(); + let signer = client.signer().await?; + let public_key = signer.get_public_key().await?; + let timeout = Duration::from_secs(2); let mut rooms: BTreeSet = BTreeSet::new(); @@ -184,18 +183,18 @@ impl Sidebar { // Process to verify the search results for event in events.into_iter().unique_by(|event| event.pubkey) { // Skip if author is match current user - if event.pubkey == identity { + if event.pubkey == public_key { continue; } // Return a temporary room - if let Ok(room) = Self::create_temp_room(identity, event.pubkey).await { + if let Ok(room) = Self::create_temp_room(event.pubkey).await { rooms.insert(room); } } } - rooms + Ok(rooms) } fn debounced_search(&self, window: &mut Window, cx: &mut Context) -> Task<()> { @@ -214,15 +213,11 @@ impl Sidebar { window: &mut Window, cx: &mut Context, ) { - let identity = Registry::read_global(cx).identity(cx).public_key(); let query = query.to_owned(); let query_cloned = query.clone(); let task = smol::future::or( - Tokio::spawn(cx, async move { - let rooms = Self::nip50(identity, &query).await; - Some(rooms) - }), + Tokio::spawn(cx, async move { Self::nip50(&query).await.ok() }), Tokio::spawn(cx, async move { let _ = rx.recv().await.is_ok(); None @@ -269,12 +264,11 @@ impl Sidebar { } fn search_by_nip05(&mut self, query: &str, window: &mut Window, cx: &mut Context) { - let identity = Registry::read_global(cx).identity(cx).public_key(); let address = query.to_owned(); let task = Tokio::spawn(cx, async move { if let Ok(profile) = common::nip05::nip05_profile(&address).await { - Self::create_temp_room(identity, profile.public_key).await + Self::create_temp_room(profile.public_key).await } else { Err(anyhow!(t!("sidebar.addr_error"))) } @@ -323,10 +317,9 @@ impl Sidebar { return; }; - let identity = Registry::read_global(cx).identity(cx).public_key(); let task: Task> = cx.background_spawn(async move { // Create a gift wrap event to represent as room - Self::create_temp_room(identity, public_key).await + Self::create_temp_room(public_key).await }); cx.spawn_in(window, async move |this, cx| { diff --git a/crates/global/src/lib.rs b/crates/global/src/lib.rs index a1f2251d..a74b50ea 100644 --- a/crates/global/src/lib.rs +++ b/crates/global/src/lib.rs @@ -147,28 +147,40 @@ impl Ingester { /// A simple storage to store all states that using across the application. #[derive(Debug)] pub struct AppState { + /// The timestamp when the application was initialized. pub init_at: Timestamp, + /// The timestamp when the application was last used. pub last_used_at: Option, + /// Whether this is the first run of the application. pub is_first_run: AtomicBool, + /// Subscription ID for listening to gift wrap events from relays. pub gift_wrap_sub_id: SubscriptionId, - pub gift_wrap_processing: AtomicBool, - + /// Auto-close options for relay subscriptions pub auto_close_opts: Option, + /// Whether gift wrap processing is in progress. + pub gift_wrap_processing: AtomicBool, + + /// Tracking events sent by Coop in the current session pub sent_ids: RwLock>, + /// Tracking events seen on which relays in the current session pub seen_on_relays: RwLock>>, + /// Tracking events that have been resent by Coop in the current session pub resent_ids: RwLock>>, + /// Temporarily store events that need to be resent later pub resend_queue: RwLock>, + /// Signal channel for communication between Nostr and GPUI pub signal: Signal, + /// Ingester channel for processing public keys pub ingester: Ingester, } @@ -223,7 +235,7 @@ pub fn nostr_client() -> &'static Client { .automatic_authentication(false) .verify_subscriptions(false) .sleep_when_idle(SleepWhenIdle::Enabled { - timeout: Duration::from_secs(300), + timeout: Duration::from_secs(600), }); ClientBuilder::default().database(lmdb).opts(opts).build() diff --git a/crates/registry/src/lib.rs b/crates/registry/src/lib.rs index 831f4a08..9231535a 100644 --- a/crates/registry/src/lib.rs +++ b/crates/registry/src/lib.rs @@ -44,8 +44,8 @@ pub struct Registry { /// Status of the unwrapping process pub unwrapping_status: Entity, - /// Public Key of the current user - pub identity: Option, + /// Public key of the currently activated signer + signer_pubkey: Option, /// Tasks for asynchronous operations _tasks: SmallVec<[Task<()>; 1]>, @@ -106,21 +106,19 @@ impl Registry { unwrapping_status, rooms: vec![], persons: HashMap::new(), - identity: None, + signer_pubkey: None, _tasks: tasks, } } - /// Returns the identity of the user. - /// - /// WARNING: This method will panic if user is not logged in. - pub fn identity(&self, cx: &App) -> Profile { - self.get_person(&self.identity.unwrap(), cx) + /// Returns the public key of the currently activated signer. + pub fn signer_pubkey(&self) -> Option { + self.signer_pubkey } - /// Sets the identity of the user. - pub fn set_identity(&mut self, identity: PublicKey, cx: &mut Context) { - self.identity = Some(identity); + /// Update the public key of the currently activated signer. + pub fn set_signer_pubkey(&mut self, public_key: PublicKey, cx: &mut Context) { + self.signer_pubkey = Some(public_key); cx.notify(); } @@ -254,7 +252,7 @@ impl Registry { self.set_unwrapping_status(UnwrappingStatus::default(), cx); // Clear the current identity - self.identity = None; + self.signer_pubkey = None; // Clear all current rooms self.rooms.clear(); @@ -276,7 +274,7 @@ impl Registry { let contacts = client.database().contacts_public_keys(public_key).await?; // Get messages sent by the user - let send = Filter::new() + let sent = Filter::new() .kind(Kind::PrivateDirectMessage) .author(public_key); @@ -285,9 +283,9 @@ impl Registry { .kind(Kind::PrivateDirectMessage) .pubkey(public_key); - let send_events = client.database().query(send).await?; + let sent_events = client.database().query(sent).await?; let recv_events = client.database().query(recv).await?; - let events = send_events.merge(recv_events); + let events = sent_events.merge(recv_events); let mut rooms: HashSet = HashSet::new(); @@ -297,12 +295,16 @@ impl Registry { .sorted_by_key(|event| Reverse(event.created_at)) .filter(|ev| ev.tags.public_keys().peekable().peek().is_some()) { - if rooms.iter().any(|room| room.id == event.uniq_id()) { + // Parse the room from the nostr event + let room = Room::from(&event); + + // Skip if the room is already in the set + if rooms.iter().any(|r| r.id == room.id) { continue; } // Get all public keys from the event's tags - let mut public_keys = event.all_pubkeys(); + let mut public_keys: Vec = room.members().to_vec(); public_keys.retain(|pk| pk != &public_key); // Bypass screening flag @@ -323,9 +325,6 @@ impl Registry { // If current user has sent a message at least once, mark as ongoing let is_ongoing = client.database().count(filter).await.unwrap_or(1) >= 1; - // Create a new room - let room = Room::from(&event).current_user(public_key); - if is_ongoing || bypassed { rooms.insert(room.kind(RoomKind::Ongoing)); } else { @@ -419,7 +418,7 @@ impl Registry { /// Updates room ordering based on the most recent messages. pub fn event_to_message( &mut self, - gift_wrap_id: EventId, + gift_wrap: EventId, event: Event, window: &mut Window, cx: &mut Context, @@ -427,7 +426,7 @@ impl Registry { let id = event.uniq_id(); let author = event.pubkey; - let Some(identity) = self.identity else { + let Some(public_key) = self.signer_pubkey else { return; }; @@ -437,17 +436,17 @@ impl Registry { // Update room room.update(cx, |this, cx| { if is_new_event { - this.created_at(event.created_at, cx); + this.set_created_at(event.created_at, cx); } // Set this room is ongoing if the new message is from current user - if author == identity { + if author == public_key { this.set_ongoing(cx); } // Emit the new message to the room cx.defer_in(window, move |this, _window, cx| { - this.emit_message(gift_wrap_id, event, cx); + this.emit_message(gift_wrap, event, cx); }); }); @@ -458,10 +457,8 @@ impl Registry { }); } } else { - let room = Room::from(&event).current_user(identity); - // Push the new room to the front of the list - self.add_room(cx.new(|_| room), cx); + self.add_room(cx.new(|_| Room::from(&event)), cx); // Notify the UI about the new room cx.defer_in(window, move |_this, _window, cx| { diff --git a/crates/registry/src/message.rs b/crates/registry/src/message.rs index 0f75e263..495f036a 100644 --- a/crates/registry/src/message.rs +++ b/crates/registry/src/message.rs @@ -14,8 +14,8 @@ impl Message { Self::User(user.into()) } - pub fn warning(content: String) -> Self { - Self::Warning(content, Timestamp::now()) + pub fn warning(content: impl Into) -> Self { + Self::Warning(content.into(), Timestamp::now()) } pub fn system() -> Self { diff --git a/crates/registry/src/room.rs b/crates/registry/src/room.rs index e413982c..45dd11a8 100644 --- a/crates/registry/src/room.rs +++ b/crates/registry/src/room.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; use std::time::Duration; -use anyhow::Error; +use anyhow::{anyhow, Error}; use common::display::RenderedProfile; use common::event::EventUtils; use global::constants::SEND_RETRY; @@ -17,9 +17,9 @@ use crate::Registry; #[derive(Debug, Clone)] pub struct SendReport { pub receiver: PublicKey, - pub tags: Option>, pub status: Option>, pub error: Option, + pub on_hold: Option, pub relays_not_found: bool, } @@ -29,13 +29,14 @@ impl SendReport { receiver, status: None, error: None, - tags: None, + on_hold: None, relays_not_found: false, } } - pub fn not_found(mut self) -> Self { - self.relays_not_found = true; + pub fn status(mut self, output: Output) -> Self { + self.status = Some(output); + self.relays_not_found = false; self } @@ -45,14 +46,13 @@ impl SendReport { self } - pub fn status(mut self, output: Output) -> Self { - self.status = Some(output); - self.relays_not_found = false; + pub fn on_hold(mut self, event: Event) -> Self { + self.on_hold = Some(event); self } - pub fn tags(mut self, tags: &Vec) -> Self { - self.tags = Some(tags.to_owned()); + pub fn not_found(mut self) -> Self { + self.relays_not_found = true; self } @@ -88,8 +88,6 @@ pub struct Room { pub created_at: Timestamp, /// Subject of the room pub subject: Option, - /// Picture of the room - pub picture: Option, /// All members of the room pub members: Vec, /// Kind @@ -130,32 +128,18 @@ impl From<&Event> for Room { let created_at = val.created_at; // Get the members from the event's tags and event's pubkey - let members = val - .all_pubkeys() - .into_iter() - .unique() - .sorted() - .collect_vec(); - - // Get the subject from the event's tags - let subject = if let Some(tag) = val.tags.find(TagKind::Subject) { - tag.content().map(|s| s.to_owned()) - } else { - None - }; + let members = val.all_pubkeys(); - // Get the picture from the event's tags - let picture = if let Some(tag) = val.tags.find(TagKind::custom("picture")) { - tag.content().map(|s| s.to_owned()) - } else { - None - }; + // Get subject from tags + let subject = val + .tags + .find(TagKind::Subject) + .and_then(|tag| tag.content().map(|s| s.to_owned())); Room { id, created_at, subject, - picture, members, kind: RoomKind::default(), } @@ -168,32 +152,18 @@ impl From<&UnsignedEvent> for Room { let created_at = val.created_at; // Get the members from the event's tags and event's pubkey - let members = val - .all_pubkeys() - .into_iter() - .unique() - .sorted() - .collect_vec(); - - // Get the subject from the event's tags - let subject = if let Some(tag) = val.tags.find(TagKind::Subject) { - tag.content().map(|s| s.to_owned()) - } else { - None - }; + let members = val.all_pubkeys(); - // Get the picture from the event's tags - let picture = if let Some(tag) = val.tags.find(TagKind::custom("picture")) { - tag.content().map(|s| s.to_owned()) - } else { - None - }; + // Get subject from tags + let subject = val + .tags + .find(TagKind::Subject) + .and_then(|tag| tag.content().map(|s| s.to_owned())); Room { id, created_at, subject, - picture, members, kind: RoomKind::default(), } @@ -201,32 +171,39 @@ impl From<&UnsignedEvent> for Room { } impl Room { - /// Constructs a new room instance with a given receiver. - pub fn new(receiver: PublicKey, tags: Tags, cx: &App) -> Self { - let identity = Registry::read_global(cx).identity(cx); + /// Constructs a new room instance for a private message with the given receiver and tags. + pub async fn new(subject: Option, receivers: Vec) -> Result { + let client = nostr_client(); + let signer = client.signer().await?; + let public_key = signer.get_public_key().await?; + + if receivers.is_empty() { + return Err(anyhow!("You need to add at least one receiver")); + }; + + // Convert receiver's public keys into tags + let mut tags: Tags = Tags::from_list( + receivers + .iter() + .map(|pubkey| Tag::public_key(pubkey.to_owned())) + .collect(), + ); + + // Add subject if it is present + if let Some(subject) = subject { + tags.push(Tag::from_standardized_without_cell(TagStandard::Subject( + subject, + ))); + } - let mut event = EventBuilder::private_msg_rumor(receiver, "") + let mut event = EventBuilder::new(Kind::PrivateDirectMessage, "") .tags(tags) - .build(identity.public_key()); + .build(public_key); - // Ensure event ID is generated + // Generate event ID event.ensure_id(); - Room::from(&event).current_user(identity.public_key()) - } - - /// Constructs a new room instance from an nostr event. - pub fn from(event: impl Into) -> Self { - event.into() - } - - /// Call this function to ensure the current user is always at the bottom of the members list - pub fn current_user(mut self, public_key: PublicKey) -> Self { - let (not_match, matches): (Vec, Vec) = - self.members.iter().partition(|&key| key != &public_key); - self.members = not_match; - self.members.extend(matches); - self + Ok(Room::from(&event)) } /// Sets the kind of the room and returns the modified room @@ -235,7 +212,7 @@ impl Room { self } - /// Set the room kind to ongoing + /// Sets this room is ongoing conversation pub fn set_ongoing(&mut self, cx: &mut Context) { if self.kind != RoomKind::Ongoing { self.kind = RoomKind::Ongoing; @@ -243,27 +220,26 @@ impl Room { } } - /// Checks if the room is a group chat - pub fn is_group(&self) -> bool { - self.members.len() > 2 - } - /// Updates the creation timestamp of the room - pub fn created_at(&mut self, created_at: impl Into, cx: &mut Context) { + pub fn set_created_at(&mut self, created_at: impl Into, cx: &mut Context) { self.created_at = created_at.into(); cx.notify(); } /// Updates the subject of the room - pub fn subject(&mut self, subject: String, cx: &mut Context) { + pub fn set_subject(&mut self, subject: String, cx: &mut Context) { self.subject = Some(subject); cx.notify(); } - /// Updates the picture of the room - pub fn picture(&mut self, picture: String, cx: &mut Context) { - self.picture = Some(picture); - cx.notify(); + /// Returns the members of the room + pub fn members(&self) -> &Vec { + &self.members + } + + /// Checks if the room has more than two members (group) + pub fn is_group(&self) -> bool { + self.members.len() > 2 } /// Gets the display name for the room @@ -277,33 +253,40 @@ impl Room { /// Gets the display image for the room pub fn display_image(&self, proxy: bool, cx: &App) -> SharedUri { - if let Some(picture) = self.picture.as_ref() { - SharedUri::from(picture) - } else if !self.is_group() { - self.first_member(cx).avatar(proxy) + if !self.is_group() { + self.display_member(cx).avatar(proxy) } else { SharedUri::from("brand/group.png") } } - /// Get the first member of the room. + /// Get a single member to represent the room /// - /// First member is always different from the current user. - pub(crate) fn first_member(&self, cx: &App) -> Profile { + /// This member is always different from the current user. + fn display_member(&self, cx: &App) -> Profile { let registry = Registry::read_global(cx); + + if let Some(public_key) = registry.signer_pubkey() { + for member in self.members() { + if member != &public_key { + return registry.get_person(member, cx); + } + } + } + registry.get_person(&self.members[0], cx) } /// Merge the names of the first two members of the room. - pub(crate) fn merged_name(&self, cx: &App) -> SharedString { + fn merged_name(&self, cx: &App) -> SharedString { let registry = Registry::read_global(cx); if self.is_group() { - let profiles = self + let profiles: Vec = self .members .iter() - .map(|pk| registry.get_person(pk, cx)) - .collect::>(); + .map(|public_key| registry.get_person(public_key, cx)) + .collect(); let mut name = profiles .iter() @@ -318,47 +301,75 @@ impl Room { SharedString::from(name) } else { - self.first_member(cx).display_name() + self.display_member(cx).display_name() } } - /// Connects to all members' messaging relays - pub fn connect_relays( - &self, - cx: &App, - ) -> Task>, Error>> { + /// Connects to all members's messaging relays + pub fn connect(&self, cx: &App) -> Task>, Error>> { let members = self.members.clone(); cx.background_spawn(async move { let client = nostr_client(); - let timeout = Duration::from_secs(3); + let signer = client.signer().await?; + let public_key = signer.get_public_key().await?; + + let mut relays = HashMap::new(); let mut processed = HashSet::new(); - let mut relays: HashMap> = HashMap::new(); - - if let Some((_, members)) = members.split_last() { - for member in members.iter() { - relays.insert(member.to_owned(), vec![]); - - let filter = Filter::new() - .kind(Kind::InboxRelays) - .author(member.to_owned()) - .limit(1); - - if let Ok(mut stream) = client.stream_events(filter, timeout).await { - if let Some(event) = stream.next().await { - if processed.insert(event.id) { - let urls = nip17::extract_owned_relay_list(event).collect_vec(); - relays.entry(member.to_owned()).or_default().extend(urls); - } + + for member in members.into_iter() { + if member == public_key { + continue; + }; + + relays.insert(member, vec![]); + + let filter = Filter::new() + .kind(Kind::InboxRelays) + .author(member) + .limit(1); + + let mut stream = client + .stream_events(filter, Duration::from_secs(10)) + .await?; + + if let Some(event) = stream.next().await { + if processed.insert(event.id) { + let public_key = event.pubkey; + let urls: Vec = nip17::extract_owned_relay_list(event).collect(); + + // Check if at least one URL exists + if urls.is_empty() { + continue; } + + // Connect to relays + for url in urls.iter() { + client.add_relay(url).await?; + client.connect_relay(url).await?; + } + + relays.entry(public_key).and_modify(|v| v.extend(urls)); } } - }; + } Ok(relays) }) } + pub fn disconnect(&self, relays: Vec, cx: &App) -> Task> { + cx.background_spawn(async move { + let client = nostr_client(); + + for relay in relays.into_iter() { + client.disconnect_relay(relay).await?; + } + + Ok(()) + }) + } + /// Loads all messages for this room from the database pub fn load_messages(&self, cx: &App) -> Task, Error>> { let members = self.members.clone(); @@ -415,24 +426,43 @@ impl Room { }) } - /// Creates a temporary message for optimistic updates - /// - /// The event must not been published to relays. - pub fn create_temp_message( - &self, - receiver: PublicKey, - content: &str, - replies: &[EventId], - ) -> UnsignedEvent { - let builder = EventBuilder::private_msg_rumor(receiver, content); + /// Emits a new message signal to the current room + pub fn emit_message(&self, gift_wrap_id: EventId, event: Event, cx: &mut Context) { + cx.emit(RoomSignal::NewMessage((gift_wrap_id, Box::new(event)))); + } + + /// Emits a signal to refresh the current room's messages. + pub fn emit_refresh(&mut self, cx: &mut Context) { + cx.emit(RoomSignal::Refresh); + } + + /// Create a new message event (unsigned) + pub fn create_message(&self, content: &str, replies: &[EventId], cx: &App) -> UnsignedEvent { + let public_key = Registry::read_global(cx).signer_pubkey().unwrap(); + let subject = self.subject.clone(); + let mut tags = vec![]; - // Add event reference if it's present (replying to another event) + // Add receivers + // + // NOTE: current user will be removed from the list of receivers + for member in self.members.iter() { + tags.push(Tag::public_key(member.to_owned())); + } + + // Add subject tag if it's present + if let Some(subject) = subject { + tags.push(Tag::from_standardized_without_cell(TagStandard::Subject( + subject, + ))); + } + + // Add reply/quote tag if replies.len() == 1 { tags.push(Tag::event(replies[0])) } else { - for id in replies.iter() { - tags.push(Tag::from_standardized(TagStandard::Quote { + for id in replies { + tags.push(Tag::from_standardized_without_cell(TagStandard::Quote { event_id: id.to_owned(), relay_url: None, public_key: None, @@ -440,26 +470,27 @@ impl Room { } } - let mut event = builder.tags(tags).build(receiver); + // Construct a direct message event + // + // WARNING: never send this event to relays + let mut event = EventBuilder::new(Kind::PrivateDirectMessage, content) + .tags(tags) + .build(public_key); - // Ensure event ID is set + // Generate event ID event.ensure_id(); event } - /// Create a task to sends a message to all members in the background - pub fn send_in_background( + /// Create a task to send a message to all room members + pub fn send_message( &self, - content: &str, - replies: Vec, + rumor: UnsignedEvent, backup: bool, cx: &App, ) -> Task, Error>> { - let content = content.to_owned(); - let subject = self.subject.clone(); - let picture = self.picture.clone(); - let mut public_keys = self.members.clone(); + let mut members = self.members.clone(); cx.background_spawn(async move { let app_state = app_state(); @@ -467,57 +498,26 @@ impl Room { let signer = client.signer().await?; let public_key = signer.get_public_key().await?; - let mut tags: Vec = public_keys - .iter() - .filter_map(|&this| { - if this != public_key { - Some(Tag::public_key(this)) - } else { - None - } - }) - .collect(); + // Remove the current user's public key from the list of receivers + // Current user will be handled separately + members.retain(|&pk| pk != public_key); - // Add event reference if it's present (replying to another event) - if replies.len() == 1 { - tags.push(Tag::event(replies[0])) - } else { - for id in replies.iter() { - tags.push(Tag::from_standardized(TagStandard::Quote { - event_id: id.to_owned(), - relay_url: None, - public_key: None, - })) - } - } + let mut reports: Vec = vec![]; - // Add subject tag if it's present - if let Some(subject) = subject { - tags.push(Tag::from_standardized(TagStandard::Subject( - subject.to_string(), - ))); - } - - // Add picture tag if it's present - if let Some(picture) = picture { - tags.push(Tag::custom(TagKind::custom("picture"), vec![picture])); - } - - // Remove the current public key from the list of receivers - public_keys.retain(|&pk| pk != public_key); + for receiver in members.into_iter() { + let rumor = rumor.clone(); + let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, vec![]).await?; - // Stored all send errors - let mut reports = vec![]; + let Ok(relay_urls) = Self::messaging_relays(receiver).await else { + reports.push(SendReport::new(receiver).not_found()); + continue; + }; - for pubkey in public_keys.into_iter() { - match client - .send_private_msg(pubkey, &content, tags.clone()) - .await - { + match client.send_event_to(relay_urls, &event).await { Ok(output) => { let id = output.id().to_owned(); let auth_required = output.failed.iter().any(|m| m.1.starts_with("auth-")); - let report = SendReport::new(pubkey).status(output).tags(&tags); + let report = SendReport::new(receiver).status(output); if auth_required { // Wait for authenticated and resent event successfully @@ -526,7 +526,7 @@ impl Room { // Check if event was successfully resent if let Some(output) = ids.iter().find(|e| e.id() == &id).cloned() { - let output = SendReport::new(pubkey).status(output).tags(&tags); + let output = SendReport::new(receiver).status(output); reports.push(output); break; } @@ -544,33 +544,31 @@ impl Room { } } Err(e) => { - if let nostr_sdk::client::Error::PrivateMsgRelaysNotFound = e { - reports.push(SendReport::new(pubkey).not_found().tags(&tags)); - } else { - reports.push(SendReport::new(pubkey).error(e.to_string()).tags(&tags)); - } + reports.push(SendReport::new(receiver).error(e.to_string())); } } } + // Construct a gift wrap to back up to current user's owned messaging relays + let rumor = rumor.clone(); + let event = EventBuilder::gift_wrap(&signer, &public_key, rumor, vec![]).await?; + // Only send a backup message to current user if sent successfully to others if reports.iter().all(|r| r.is_sent_success()) && backup { - match client - .send_private_msg(public_key, &content, tags.clone()) - .await - { - Ok(output) => { - reports.push(SendReport::new(public_key).status(output).tags(&tags)); - } - Err(e) => { - if let nostr_sdk::client::Error::PrivateMsgRelaysNotFound = e { - reports.push(SendReport::new(public_key).not_found()); - } else { - reports - .push(SendReport::new(public_key).error(e.to_string()).tags(&tags)); + if let Ok(relay_urls) = Self::messaging_relays(public_key).await { + match client.send_event_to(relay_urls, &event).await { + Ok(output) => { + reports.push(SendReport::new(public_key).status(output)); + } + Err(e) => { + reports.push(SendReport::new(public_key).error(e.to_string())); } } + } else { + reports.push(SendReport::new(public_key).not_found()); } + } else { + reports.push(SendReport::new(public_key).on_hold(event)); } Ok(reports) @@ -578,19 +576,19 @@ impl Room { } /// Create a task to resend a failed message - pub fn resend( + pub fn resend_message( &self, reports: Vec, - message: String, - backup: bool, cx: &App, ) -> Task, Error>> { cx.background_spawn(async move { let client = nostr_client(); let mut resend_reports = vec![]; - let mut resend_tag = vec![]; for report in reports.into_iter() { + let receiver = report.receiver; + + // Process failed events if let Some(output) = report.status { let id = output.id(); let urls: Vec<&RelayUrl> = output.failed.keys().collect(); @@ -599,44 +597,68 @@ impl Room { for url in urls.into_iter() { let relay = client.pool().relay(url).await?; let id = relay.send_event(&event).await?; + let resent: Output = Output { val: id, success: HashSet::from([url.to_owned()]), failed: HashMap::new(), }; - resend_reports.push(SendReport::new(report.receiver).status(resent)); + resend_reports.push(SendReport::new(receiver).status(resent)); } + } + } - if let Some(tags) = report.tags { - resend_tag.extend(tags); + // Process the on hold event if it exists + if let Some(event) = report.on_hold { + if let Ok(relay_urls) = Self::messaging_relays(receiver).await { + match client.send_event_to(relay_urls, &event).await { + Ok(output) => { + resend_reports.push(SendReport::new(receiver).status(output)); + } + Err(e) => { + resend_reports.push(SendReport::new(receiver).error(e.to_string())); + } } + } else { + resend_reports.push(SendReport::new(receiver).not_found()); } } } - // Only send a backup message to current user if sent successfully to others - if backup && !resend_reports.is_empty() { - let signer = client.signer().await?; - let public_key = signer.get_public_key().await?; - let output = client - .send_private_msg(public_key, message, resend_tag) - .await?; - - resend_reports.push(SendReport::new(public_key).status(output)); - } - Ok(resend_reports) }) } - /// Emits a new message signal to the current room - pub fn emit_message(&self, gift_wrap_id: EventId, event: Event, cx: &mut Context) { - cx.emit(RoomSignal::NewMessage((gift_wrap_id, Box::new(event)))); - } + /// Gets messaging relays for public key + async fn messaging_relays(public_key: PublicKey) -> Result, Error> { + let client = nostr_client(); + let mut relay_urls = vec![]; - /// Emits a signal to refresh the current room's messages. - pub fn emit_refresh(&mut self, cx: &mut Context) { - cx.emit(RoomSignal::Refresh); + let filter = Filter::new() + .kind(Kind::InboxRelays) + .author(public_key) + .limit(1); + + if let Some(event) = client.database().query(filter).await?.first_owned() { + let urls: Vec = nip17::extract_owned_relay_list(event).collect(); + + // Check if at least one URL exists + if urls.is_empty() { + return Err(anyhow!("Not found")); + } + + // Connect to relays + for url in urls.iter() { + client.add_relay(url).await?; + client.connect_relay(url).await?; + } + + relay_urls.extend(urls.into_iter().take(3).unique()); + } else { + return Err(anyhow!("Not found")); + } + + Ok(relay_urls) } } diff --git a/locales/app.yml b/locales/app.yml index f21b0db1..243e4adf 100644 --- a/locales/app.yml +++ b/locales/app.yml @@ -331,8 +331,6 @@ compose: en: "Your recently contacts will appear here." contact_existed: en: "Contact already added" - receiver_required: - en: "You need to add at least 1 receiver" description: en: "Start a conversation with someone using their npub or NIP-05 (like foo@bar.com)." subject_label: diff --git a/script/release b/script/release index b1b187b4..9c383771 100755 --- a/script/release +++ b/script/release @@ -57,9 +57,65 @@ echo "Updating versions..." update_version "$WORKSPACE_CARGO" update_version "$CRATE_CARGO" +# Check git status before committing +echo "Checking git status..." +if git status --porcelain | grep -q .; then + echo "Current uncommitted changes:" + git status --short + + # Ask user if they want to commit all changes or just version files + echo "" + echo "Do you want to:" + echo "1) Commit all current changes (including the version updates)" + echo "2) Commit only the version file changes" + echo "3) Abort the release" + read -p "Enter choice (1/2/3): " choice + + case $choice in + 1) + echo "Committing all changes..." + git add . + ;; + 2) + echo "Committing only version file changes..." + git add "$WORKSPACE_CARGO" "$CRATE_CARGO" + ;; + 3) + echo "Release aborted by user" + exit 0 + ;; + *) + echo "Invalid choice. Release aborted." + exit 1 + ;; + esac +else + # Only version files were modified, add them specifically + echo "Only version files were modified, adding them for commit..." + git add "$WORKSPACE_CARGO" "$CRATE_CARGO" +fi + +# Commit the changes +COMMIT_MSG="chore: release version $NEW_VERSION" + +if git commit -m "$COMMIT_MSG"; then + echo "✓ Committed version changes" +else + echo "Error: Failed to commit version changes" + exit 1 +fi + +# Push version changes to origin +echo "Pushing version changes to origin..." +if git push origin master; then + echo "✓ Successfully pushed version changes to origin" +else + echo "Error: Failed to push version changes to origin" + exit 1 +fi + # Create git tag TAG_NAME="v$NEW_VERSION" -COMMIT_MSG="Release version $NEW_VERSION" if git tag -a "$TAG_NAME" -m "$COMMIT_MSG"; then echo "✓ Created git tag: $TAG_NAME" @@ -68,12 +124,12 @@ else exit 1 fi -# Push to origin (both commits and tags) -echo "Pushing to origin..." -if git push origin master && git push origin "$TAG_NAME"; then - echo "✓ Successfully pushed to origin" +# Push tag to origin +echo "Pushing tag to origin..." +if git push origin "$TAG_NAME"; then + echo "✓ Successfully pushed tag to origin" echo "✓ Release $NEW_VERSION completed successfully!" else - echo "Error: Failed to push to origin" + echo "Error: Failed to push tag to origin" exit 1 fi