diff --git a/Cargo.lock b/Cargo.lock index 40eb67c..78c7da7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1157,7 +1157,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1597,7 +1597,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "proc-macro2", "quote", @@ -2494,7 +2494,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2589,7 +2589,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2600,7 +2600,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "anyhow", "gpui", @@ -2829,7 +2829,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "anyhow", "async-compression", @@ -2854,7 +2854,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "rustls", "rustls-platform-verifier", @@ -3672,7 +3672,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "anyhow", "bindgen 0.71.1", @@ -4514,7 +4514,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "collections", "serde", @@ -5130,7 +5130,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "derive_refineable", ] @@ -5237,7 +5237,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "anyhow", "bytes", @@ -5291,13 +5291,12 @@ dependencies = [ [[package]] name = "rope" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "arrayvec", "log", "rayon", "regex", - "smallvec", "sum_tree", "unicode-segmentation", "util", @@ -5494,9 +5493,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "web-time", "zeroize", @@ -5759,7 +5758,7 @@ checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "anyhow", "serde", @@ -6132,6 +6131,8 @@ dependencies = [ "nostr-lmdb", "nostr-sdk", "rustls", + "serde", + "serde_json", "smol", "whoami", ] @@ -6203,7 +6204,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "arrayvec", "log", @@ -7239,7 +7240,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "anyhow", "async-fs", @@ -7274,7 +7275,7 @@ dependencies = [ [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" +source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8" dependencies = [ "perf", "quote", diff --git a/crates/auto_update/src/lib.rs b/crates/auto_update/src/lib.rs index 0dbe551..d084c19 100644 --- a/crates/auto_update/src/lib.rs +++ b/crates/auto_update/src/lib.rs @@ -4,7 +4,7 @@ use cargo_packager_updater::{check_update, Config, Update}; use gpui::http_client::Url; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window}; use smallvec::{smallvec, SmallVec}; -use states::constants::{APP_PUBKEY, APP_UPDATER_ENDPOINT}; +use states::{APP_PUBKEY, APP_UPDATER_ENDPOINT}; pub fn init(cx: &mut App) { AutoUpdater::set_global(cx.new(AutoUpdater::new), cx); diff --git a/crates/common/src/display.rs b/crates/common/src/display.rs index e008540..c555209 100644 --- a/crates/common/src/display.rs +++ b/crates/common/src/display.rs @@ -6,7 +6,7 @@ use gpui::{Image, ImageFormat, SharedString, SharedUri}; use nostr_sdk::prelude::*; use qrcode::render::svg; use qrcode::QrCode; -use states::constants::IMAGE_RESIZE_SERVICE; +use states::IMAGE_RESIZE_SERVICE; const NOW: &str = "now"; const SECONDS_IN_MINUTE: i64 = 60; diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index 8126b5b..399d8b7 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -21,9 +21,10 @@ use nostr_sdk::prelude::*; use registry::{Registry, RegistryEvent}; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; -use states::constants::{BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH}; -use states::state::{Announcement, AuthRequest, Response, SignalKind, UnwrappingStatus}; -use states::{app_state, default_nip17_relays, default_nip65_relays}; +use states::{ + app_state, default_nip17_relays, default_nip65_relays, Announcement, AuthRequest, Response, + SignalKind, UnwrappingStatus, BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH, +}; use theme::{ActiveTheme, Theme, ThemeMode}; use title_bar::TitleBar; use ui::actions::{CopyPublicKey, OpenPublicKey}; @@ -324,7 +325,7 @@ impl ChatSpace { } fn load_encryption(&self, ann: Announcement, window: &Window, cx: &Context) { - log::info!("Loading encryption keys: {ann:?}"); + log::info!("Found encryption announcement: {ann:?}"); cx.spawn_in(window, async move |this, cx| { let state = app_state(); diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index e3cb516..30ffeca 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -6,8 +6,7 @@ use gpui::{ TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, }; -use states::app_state; -use states::constants::{APP_ID, CLIENT_NAME}; +use states::{app_state, APP_ID, CLIENT_NAME}; use ui::Root; use crate::actions::{load_embedded_fonts, quit, Quit}; diff --git a/crates/coop/src/views/account.rs b/crates/coop/src/views/account.rs index 74b67db..2e4bc8e 100644 --- a/crates/coop/src/views/account.rs +++ b/crates/coop/src/views/account.rs @@ -14,8 +14,7 @@ use key_store::KeyStore; use nostr_connect::prelude::*; use registry::Registry; use smallvec::{smallvec, SmallVec}; -use states::app_state; -use states::constants::BUNKER_TIMEOUT; +use states::{app_state, BUNKER_TIMEOUT}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; diff --git a/crates/coop/src/views/chat/mod.rs b/crates/coop/src/views/chat/mod.rs index 4584e09..ab968ba 100644 --- a/crates/coop/src/views/chat/mod.rs +++ b/crates/coop/src/views/chat/mod.rs @@ -17,13 +17,13 @@ use indexset::{BTreeMap, BTreeSet}; use itertools::Itertools; use nostr_sdk::prelude::*; use registry::message::{Message, RenderedMessage}; -use registry::room::{Room, RoomKind, RoomSignal, SendOptions, SendReport, SignerKind}; +use registry::room::{Room, RoomKind, RoomSignal, SendOptions, SendReport}; use registry::Registry; use serde::Deserialize; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use smol::fs; -use states::app_state; +use states::{app_state, SignerKind}; use theme::ActiveTheme; use ui::actions::{CopyPublicKey, OpenPublicKey}; use ui::avatar::Avatar; @@ -107,6 +107,15 @@ impl Chat { let mut subscriptions = smallvec![]; let mut tasks = smallvec![]; + tasks.push( + // Get messaging relays and encryption keys announcement for all members + cx.background_spawn(async move { + if let Err(e) = connect.await { + log::error!("Failed to initialize room: {e}"); + } + }), + ); + tasks.push( // Load all messages belonging to this room cx.spawn_in(window, async move |this, cx| { @@ -126,15 +135,6 @@ impl Chat { }), ); - tasks.push( - // Get messaging relays and encryption keys announcement for all members - cx.background_spawn(async move { - if let Err(e) = connect.await { - log::error!("Failed to initialize room: {e}"); - } - }), - ); - subscriptions.push( // Subscribe to input events cx.subscribe_in( @@ -346,6 +346,7 @@ impl Chat { } /// Resend a failed message + #[allow(dead_code)] fn resend_message(&mut self, id: &EventId, window: &mut Window, cx: &mut Context) { if let Some(reports) = self.reports_by_id.get(id).cloned() { let id_clone = id.to_owned(); @@ -705,23 +706,7 @@ impl Chat { }) .child(text) .when(is_sent_failed, |this| { - this.child( - h_flex() - .gap_1() - .child(self.render_message_reports(&id, cx)) - .child( - Button::new(SharedString::from(id.to_hex())) - .label(t!("common.resend")) - .danger() - .xsmall() - .rounded() - .on_click(cx.listener( - move |this, _, window, cx| { - this.resend_message(&id, window, cx); - }, - )), - ), - ) + this.child(self.render_message_reports(&id, cx)) }), ), ) diff --git a/crates/coop/src/views/compose.rs b/crates/coop/src/views/compose.rs index 511fd81..d12b950 100644 --- a/crates/coop/src/views/compose.rs +++ b/crates/coop/src/views/compose.rs @@ -17,8 +17,7 @@ use registry::room::Room; use registry::Registry; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; -use states::app_state; -use states::constants::BOOTSTRAP_RELAYS; +use states::{app_state, BOOTSTRAP_RELAYS}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; diff --git a/crates/coop/src/views/login.rs b/crates/coop/src/views/login.rs index a14915c..0bcdf96 100644 --- a/crates/coop/src/views/login.rs +++ b/crates/coop/src/views/login.rs @@ -11,8 +11,7 @@ use key_store::backend::KeyItem; use key_store::KeyStore; use nostr_connect::prelude::*; use smallvec::{smallvec, SmallVec}; -use states::app_state; -use states::constants::BUNKER_TIMEOUT; +use states::{app_state, BUNKER_TIMEOUT}; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; diff --git a/crates/coop/src/views/new_account.rs b/crates/coop/src/views/new_account.rs index 99dc112..edd4992 100644 --- a/crates/coop/src/views/new_account.rs +++ b/crates/coop/src/views/new_account.rs @@ -12,8 +12,7 @@ use key_store::KeyStore; use nostr_sdk::prelude::*; use settings::AppSettings; use smol::fs; -use states::constants::BOOTSTRAP_RELAYS; -use states::{app_state, default_nip17_relays, default_nip65_relays}; +use states::{app_state, default_nip17_relays, default_nip65_relays, BOOTSTRAP_RELAYS}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; diff --git a/crates/coop/src/views/onboarding.rs b/crates/coop/src/views/onboarding.rs index 1508db8..73cf698 100644 --- a/crates/coop/src/views/onboarding.rs +++ b/crates/coop/src/views/onboarding.rs @@ -13,8 +13,7 @@ use key_store::backend::KeyItem; use key_store::KeyStore; use nostr_connect::prelude::*; use smallvec::{smallvec, SmallVec}; -use states::app_state; -use states::constants::{CLIENT_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT}; +use states::{app_state, CLIENT_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT}; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; diff --git a/crates/coop/src/views/screening.rs b/crates/coop/src/views/screening.rs index e834d82..1bd1239 100644 --- a/crates/coop/src/views/screening.rs +++ b/crates/coop/src/views/screening.rs @@ -13,8 +13,7 @@ use nostr_sdk::prelude::*; use registry::Registry; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; -use states::app_state; -use states::constants::BOOTSTRAP_RELAYS; +use states::{app_state, BOOTSTRAP_RELAYS}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; diff --git a/crates/coop/src/views/sidebar/mod.rs b/crates/coop/src/views/sidebar/mod.rs index 5aef821..13adff2 100644 --- a/crates/coop/src/views/sidebar/mod.rs +++ b/crates/coop/src/views/sidebar/mod.rs @@ -20,8 +20,7 @@ use registry::room::{Room, RoomKind}; use registry::{Registry, RegistryEvent}; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; -use states::app_state; -use states::constants::{BOOTSTRAP_RELAYS, SEARCH_RELAYS}; +use states::{app_state, BOOTSTRAP_RELAYS, SEARCH_RELAYS}; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; @@ -626,7 +625,7 @@ impl Sidebar { .name(this.display_name(cx)) .avatar(this.display_image(proxy, cx)) .created_at(this.created_at.to_ago()) - .public_key(this.members.iter().nth(0).unwrap().0) + .public_key(&this.members[0]) .kind(this.kind) .on_click(handler), ) diff --git a/crates/key_store/src/backend.rs b/crates/key_store/src/backend.rs index 75dd466..336d9c9 100644 --- a/crates/key_store/src/backend.rs +++ b/crates/key_store/src/backend.rs @@ -8,7 +8,7 @@ use std::pin::Pin; use anyhow::Result; use futures::FutureExt as _; use gpui::AsyncApp; -use states::paths::config_dir; +use states::config_dir; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum KeyItem { diff --git a/crates/registry/src/lib.rs b/crates/registry/src/lib.rs index 11badf9..fdfbf38 100644 --- a/crates/registry/src/lib.rs +++ b/crates/registry/src/lib.rs @@ -245,7 +245,7 @@ impl Registry { pub fn search_by_public_key(&self, public_key: PublicKey, cx: &App) -> Vec> { self.rooms .iter() - .filter(|room| room.read(cx).members.contains_key(&public_key)) + .filter(|room| room.read(cx).members.contains(&public_key)) .cloned() .collect() } diff --git a/crates/registry/src/room.rs b/crates/registry/src/room.rs index cd13bd5..2aad0cd 100644 --- a/crates/registry/src/room.rs +++ b/crates/registry/src/room.rs @@ -8,20 +8,10 @@ use common::display::RenderedProfile; use common::event::EventUtils; use gpui::{App, AppContext, Context, EventEmitter, SharedString, SharedUri, Task}; use nostr_sdk::prelude::*; -use serde::{Deserialize, Serialize}; -use states::app_state; -use states::constants::SEND_RETRY; +use states::{app_state, SignerKind, SEND_RETRY}; use crate::Registry; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize, Serialize)] -pub enum SignerKind { - Encryption, - User, - #[default] - Auto, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct SendOptions { pub backup: bool, @@ -127,8 +117,6 @@ pub enum RoomKind { Request, } -type DevicePublicKey = PublicKey; - #[derive(Debug)] pub struct Room { pub id: u64, @@ -136,7 +124,7 @@ pub struct Room { /// Subject of the room pub subject: Option, /// All members of the room - pub members: HashMap>, + pub members: Vec, /// Kind pub kind: RoomKind, } @@ -175,11 +163,7 @@ 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: HashMap> = val - .all_pubkeys() - .into_iter() - .map(|public_key| (public_key, None)) - .collect(); + let members = val.all_pubkeys(); // Get subject from tags let subject = val @@ -203,11 +187,7 @@ 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: HashMap> = val - .all_pubkeys() - .into_iter() - .map(|public_key| (public_key, None)) - .collect(); + let members = val.all_pubkeys(); // Get subject from tags let subject = val @@ -289,7 +269,7 @@ impl Room { /// Returns the members of the room pub fn members(&self) -> Vec { - self.members.keys().cloned().collect() + self.members.clone() } /// Checks if the room has more than two members (group) @@ -324,9 +304,9 @@ impl Room { let target_member = self .members - .keys() + .iter() .find(|&member| Some(member) != signer_pubkey.as_ref()) - .or_else(|| self.members.keys().next()) + .or_else(|| self.members.first()) .expect("Room should have at least one member"); registry.read(cx).get_person(target_member, cx) @@ -339,7 +319,7 @@ impl Room { if self.is_group() { let profiles: Vec = self .members - .keys() + .iter() .map(|public_key| registry.get_person(public_key, cx)) .collect(); @@ -447,7 +427,7 @@ impl Room { // Add receivers // // NOTE: current user will be removed from the list of receivers - for (member, _) in self.members.iter() { + for member in self.members.iter() { // Get relay hint if available let relay_url = relay_cache .get(member) @@ -505,54 +485,75 @@ impl Room { opts: &SendOptions, cx: &App, ) -> Task, Error>> { - let mut members = self.members.clone(); let rumor = rumor.to_owned(); let opts = opts.to_owned(); + // Get all members + let mut members = self.members(); + cx.background_spawn(async move { let state = app_state(); - let client = state.client(); + let signer_kind = opts.signer_kind; + let relay_cache = state.relay_cache.read().await; - let device = state.device.read().await.encryption.clone(); + let announcement_cache = state.announcement_cache.read().await; + + let encryption = state.device.read().await.encryption.clone(); + // Get the encryption public key + let encryption_pubkey = if let Some(signer) = encryption.as_ref() { + signer.get_public_key().await.ok() + } else { + None + }; let user_signer = client.signer().await?; let user_pubkey = user_signer.get_public_key().await?; // Remove the current user's public key from the list of receivers - // Current user will be handled separately - let (public_key, device_pubkey) = members.remove_entry(&user_pubkey).unwrap(); + // the current user will be handled separately + members.retain(|&pk| pk != user_pubkey); // Determine the signer will be used based on the provided options - let (signer, signer_kind) = - Self::select_signer(&opts.signer_kind, device, user_signer)?; + let signer = Self::select_signer(&opts.signer_kind, user_signer, encryption)?; // Collect the send reports let mut reports: Vec = vec![]; - for (user, device_pubkey) in members.into_iter() { - let urls = relay_cache.get(&user).cloned().unwrap_or_default(); + for member in members.into_iter() { + // Get user's messaging relays + let urls = relay_cache.get(&member).cloned().unwrap_or_default(); // Check if there are any relays to send the message to if urls.is_empty() { - reports.push(SendReport::new(user).relays_not_found()); + reports.push(SendReport::new(member).relays_not_found()); continue; } - // Skip sending if using encryption keys but device not found - if device_pubkey.is_none() && matches!(opts.signer_kind, SignerKind::Encryption) { - reports.push(SendReport::new(user).device_not_found()); + // Ensure connection to the relays + for url in urls.iter() { + client.add_relay(url).await.ok(); + client.connect_relay(url).await.ok(); + } + + // Get user's encryption public key if available + let encryption = announcement_cache + .get(&member) + .and_then(|a| a.to_owned().map(|a| a.public_key())); + + // Skip sending if using encryption signer but receiver's encryption keys not found + if encryption.is_none() && matches!(signer_kind, SignerKind::Encryption) { + reports.push(SendReport::new(member).device_not_found()); continue; } - // Determine the receiver based on the signer kind - let receiver = Self::select_receiver(&opts.signer_kind, user, device_pubkey)?; + let receiver = Self::select_receiver(&signer_kind, member, encryption)?; + let rumor = rumor.clone(); // Construct the gift wrap event - let rumor = rumor.clone(); let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, []).await?; - // Send the event to the messaging relays + // Send the gift wrap event to the messaging relays match client.send_event_to(urls, &event).await { Ok(output) => { let id = output.id().to_owned(); @@ -590,33 +591,32 @@ impl Room { } } - // Select a signer based on previous usage, not from the options - let receiver = Self::select_receiver(&signer_kind, public_key, device_pubkey)?; - - // Construct a gift wrap to back up to current user's owned messaging relays + let receiver = Self::select_receiver(&signer_kind, user_pubkey, encryption_pubkey)?; let rumor = rumor.clone(); + + // Construct the gift-wrapped event let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, []).await?; // Only send a backup message to current user if sent successfully to others if opts.backup() && reports.iter().all(|r| r.is_sent_success()) { - let urls = relay_cache.get(&public_key).cloned().unwrap_or_default(); + let urls = relay_cache.get(&user_pubkey).cloned().unwrap_or_default(); // Check if there are any relays to send the event to if urls.is_empty() { - reports.push(SendReport::new(public_key).relays_not_found()); + reports.push(SendReport::new(user_pubkey).relays_not_found()); } else { // Send the event to the messaging relays match client.send_event_to(urls, &event).await { Ok(output) => { - reports.push(SendReport::new(public_key).status(output)); + reports.push(SendReport::new(user_pubkey).status(output)); } Err(e) => { - reports.push(SendReport::new(public_key).error(e.to_string())); + reports.push(SendReport::new(user_pubkey).error(e.to_string())); } } } } else { - reports.push(SendReport::new(public_key).on_hold(event)); + reports.push(SendReport::new(user_pubkey).on_hold(event)); } Ok(reports) @@ -683,37 +683,30 @@ impl Room { }) } - fn select_signer( - kind: &SignerKind, - device: Option, - user: T, - ) -> Result<(T, SignerKind), Error> + fn select_signer(kind: &SignerKind, user: T, encryption: Option) -> Result where T: NostrSigner, { match kind { - SignerKind::Auto => device - .map(|d| (d, SignerKind::Encryption)) - .or(Some((user, SignerKind::User))) - .ok_or_else(|| anyhow!("No signer available")), - SignerKind::Encryption => device - .map(|d| (d, SignerKind::Encryption)) - .ok_or_else(|| anyhow!("No encryption key found")), - SignerKind::User => Ok((user, SignerKind::User)), + SignerKind::Encryption => { + Ok(encryption.ok_or_else(|| anyhow!("No encryption key found"))?) + } + SignerKind::User => Ok(user), + SignerKind::Auto => Ok(encryption.unwrap_or(user)), } } fn select_receiver( kind: &SignerKind, user: PublicKey, - device: Option, + encryption: Option, ) -> Result { match kind { SignerKind::Encryption => { - Ok(device.ok_or_else(|| anyhow!("Receiver's encryption key not found"))?) + Ok(encryption.ok_or_else(|| anyhow!("Receiver's encryption key not found"))?) } SignerKind::User => Ok(user), - SignerKind::Auto => Ok(device.unwrap_or(user)), + SignerKind::Auto => Ok(encryption.unwrap_or(user)), } } } diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index b9d5405..1a1dc47 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -3,8 +3,7 @@ use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task}; use nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use smallvec::{smallvec, SmallVec}; -use states::app_state; -use states::constants::SETTINGS_IDENTIFIER; +use states::{app_state, SETTINGS_IDENTIFIER}; pub fn init(cx: &mut App) { let state = cx.new(AppSettings::new); diff --git a/crates/states/Cargo.toml b/crates/states/Cargo.toml index ebd0b6b..b8a298c 100644 --- a/crates/states/Cargo.toml +++ b/crates/states/Cargo.toml @@ -13,6 +13,8 @@ smol.workspace = true flume.workspace = true log.workspace = true anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true whoami = "1.6.1" rustls = "0.23.23" diff --git a/crates/states/src/lib.rs b/crates/states/src/lib.rs index 11d90f5..c6199d5 100644 --- a/crates/states/src/lib.rs +++ b/crates/states/src/lib.rs @@ -3,12 +3,13 @@ use std::sync::OnceLock; use nostr_sdk::prelude::*; use whoami::{devicename, platform}; -use crate::constants::CLIENT_NAME; -use crate::state::AppState; +mod constants; +mod paths; +mod state; -pub mod constants; -pub mod paths; -pub mod state; +pub use constants::*; +pub use paths::*; +pub use state::*; static APP_STATE: OnceLock = OnceLock::new(); static APP_NAME: OnceLock = OnceLock::new(); diff --git a/crates/states/src/state/device.rs b/crates/states/src/state/device.rs index 8870e16..171dd1d 100644 --- a/crates/states/src/state/device.rs +++ b/crates/states/src/state/device.rs @@ -1,6 +1,15 @@ use std::sync::Arc; use nostr_sdk::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize, Serialize)] +pub enum SignerKind { + Encryption, + #[default] + User, + Auto, +} #[derive(Debug, Clone, Default)] pub struct Device { diff --git a/crates/states/src/state/mod.rs b/crates/states/src/state/mod.rs index 8c5770f..d865677 100644 --- a/crates/states/src/state/mod.rs +++ b/crates/states/src/state/mod.rs @@ -15,7 +15,6 @@ use crate::constants::{ BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, QUERY_TIMEOUT, SEARCH_RELAYS, }; use crate::paths::config_dir; -use crate::state::device::Device; use crate::state::ingester::Ingester; use crate::state::tracker::EventTracker; @@ -24,6 +23,7 @@ mod ingester; mod signal; mod tracker; +pub use device::*; pub use signal::*; #[derive(Debug)] @@ -43,6 +43,9 @@ pub struct AppState { /// Cache of messaging relays for each public key pub relay_cache: RwLock>>, + /// Cache of device announcement for each public key + pub announcement_cache: RwLock>>, + /// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md pub device: RwLock, @@ -83,6 +86,7 @@ impl AppState { let device = RwLock::new(Device::default()); let event_tracker = RwLock::new(EventTracker::default()); let relay_cache = RwLock::new(HashMap::default()); + let announcement_cache = RwLock::new(HashMap::default()); let signal = Signal::default(); let ingester = Ingester::default(); @@ -92,6 +96,7 @@ impl AppState { device, event_tracker, relay_cache, + announcement_cache, signal, ingester, initialized_at: Timestamp::now(), @@ -138,6 +143,9 @@ impl AppState { // Get user's gossip relays self.get_nip65(pk).await.ok(); + // Initialize the relay and announcement caches + self.init_cache().await.ok(); + // Initialize client keys self.init_client_keys().await.ok(); @@ -236,12 +244,16 @@ impl AppState { match event.kind { // Encryption Keys announcement event Kind::Custom(10044) => { - if let Ok(true) = self.is_self_authored(&event).await { - if let Ok(announcement) = self.extract_announcement(&event) { + if let Ok(announcement) = self.extract_announcement(&event) { + if let Ok(true) = self.is_self_authored(&event).await { self.signal - .send(SignalKind::EncryptionSet(announcement)) + .send(SignalKind::EncryptionSet(announcement.clone())) .await; } + + // Cache the announcement for further queries + let mut announcement_cache = self.announcement_cache.write().await; + announcement_cache.insert(event.pubkey, Some(announcement)); } } // Encryption Keys request event @@ -572,6 +584,33 @@ impl AppState { Ok(()) } + /// Initialize the relay and announcement caches with events from the local database + pub async fn init_cache(&self) -> Result<(), Error> { + let filter = Filter::new().kind(Kind::InboxRelays); + let events = self.client.database().query(filter).await?; + let mut relay_cache = self.relay_cache.write().await; + + for event in events.into_iter() { + let relays: Vec = + nip17::extract_relay_list(&event).take(3).cloned().collect(); + + // Push all relays to the relay cache + relay_cache.entry(event.pubkey).or_default().extend(relays); + } + + let filter = Filter::new().kind(Kind::Custom(10044)); + let events = self.client.database().query(filter).await?; + let mut announcement_cache = self.announcement_cache.write().await; + + for event in events.into_iter() { + if let Ok(announcement) = self.extract_announcement(&event) { + announcement_cache.insert(event.pubkey, Some(announcement)); + } + } + + Ok(()) + } + /// Initialize the client keys to communicate between clients /// /// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md @@ -961,7 +1000,7 @@ impl AppState { // Subscribe to gift wrap events self.client - .subscribe_with_id_to(urls, id, filter, None) + .subscribe_with_id_to(&urls, id, filter, None) .await?; Ok(()) @@ -1037,22 +1076,22 @@ impl AppState { } // Try to unwrap with the available signer - if let Ok(unwrapped) = self.try_unwrap_gift_wrap(gift_wrap).await { - let sender = unwrapped.sender; - let mut rumor_unsigned = unwrapped.rumor; + let unwrapped = self.try_unwrap_gift_wrap(gift_wrap).await?; + let sender = unwrapped.sender; + let mut rumor_unsigned = unwrapped.rumor; - if !self.verify_sender(sender, &rumor_unsigned).await { - return Err(anyhow!("Invalid rumor")); - }; + if !self.verify_sender(sender, &rumor_unsigned).await { + return Err(anyhow!("Invalid rumor")); + }; - // Generate event id for the rumor if it doesn't have one - rumor_unsigned.ensure_id(); + // Generate event id for the rumor if it doesn't have one + rumor_unsigned.ensure_id(); - self.set_rumor(gift_wrap.id, &rumor_unsigned).await?; - self.process_rumor(gift_wrap.id, rumor_unsigned).await?; + // Cache the rumor + self.set_rumor(gift_wrap.id, &rumor_unsigned).await?; - return Ok(()); - } + // Process the rumor + self.process_rumor(gift_wrap.id, rumor_unsigned).await?; Ok(()) } @@ -1062,20 +1101,21 @@ impl AppState { // Try to unwrap with the device's encryption keys first // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md if let Some(signer) = self.device.read().await.encryption.as_ref() { - if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(signer, gift_wrap).await { - return Ok(unwrapped); + match UnwrappedGift::from_gift_wrap(signer, gift_wrap).await { + Ok(unwrapped) => { + return Ok(unwrapped); + } + Err(e) => { + log::error!("Failed to unwrap with the encryption key: {e}") + } } } - // Get user's signer - let signer = self.client.signer().await?; - // Try to unwrap with the user's signer - if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await { - return Ok(unwrapped); - } + let signer = self.client.signer().await?; + let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?; - Err(anyhow!("No signer available")) + Ok(unwrapped) } /// Process a rumor event.