diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index dd91c94..6cce226 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -701,7 +701,7 @@ async fn try_unwrap( // Try with the user's signer let user_signer = client.signer().context("Signer not found")?; - let unwrapped = UnwrappedGift::from_gift_wrap(user_signer, gift_wrap).await?; + let unwrapped = try_unwrap_with(gift_wrap, user_signer).await?; Ok(unwrapped) } diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index a576eea..97e3a55 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -3,14 +3,14 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::time::Duration; -use anyhow::{anyhow, Error}; +use anyhow::Error; use common::EventUtils; use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task}; use itertools::Itertools; use nostr_sdk::prelude::*; use person::{Person, PersonRegistry}; use settings::{RoomConfig, SignerKind}; -use state::{NostrRegistry, TIMEOUT}; +use state::{NostrRegistry, BOOTSTRAP_RELAYS, TIMEOUT}; use crate::NewMessage; @@ -153,7 +153,7 @@ impl From<&UnsignedEvent> for Room { subject, members, kind: RoomKind::default(), - config: RoomConfig::default(), + config: RoomConfig::new(), } } } @@ -232,6 +232,12 @@ impl Room { cx.notify(); } + /// Updates the backup config for the room + pub fn set_backup(&mut self, cx: &mut Context) { + self.config.toggle_backup(); + cx.notify(); + } + /// Returns the config of the room pub fn config(&self) -> &RoomConfig { &self.config @@ -319,85 +325,53 @@ impl Room { cx.emit(RoomEvent::Reload); } - #[allow(clippy::type_complexity)] /// Get gossip relays for each member - pub fn connect(&self, cx: &App) -> HashMap>> { + pub fn connect(&self, cx: &App) -> Task> { let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); + let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + let sender = signer.public_key().unwrap(); - let members = self.members(); - let mut tasks = HashMap::new(); + // Get room's id + let id = self.id; - for member in members.into_iter() { - // Skip if member is the current user - if member == public_key { - continue; - } + // Get all members, excluding the sender + let members: Vec = self + .members + .iter() + .filter(|public_key| public_key != &&sender) + .copied() + .collect(); - let client = nostr.read(cx).client(); - let ensure_write_relays = nostr.read(cx).ensure_write_relays(&member, cx); + cx.background_spawn(async move { + let id = SubscriptionId::new(format!("room-{id}")); + let opts = SubscribeAutoCloseOptions::default() + .exit_policy(ReqExitPolicy::ExitOnEOSE) + .timeout(Some(Duration::from_secs(TIMEOUT))); - let task = cx.background_spawn(async move { - let mut has_inbox = false; - let mut has_announcement = false; + // Construct filters for each member + let filters: Vec = members + .into_iter() + .map(|public_key| { + Filter::new() + .author(public_key) + .kind(Kind::RelayList) + .limit(1) + }) + .collect(); - // Get user's write relays - let urls = ensure_write_relays.await; + // Construct target for subscription + let target: HashMap<&str, Vec> = BOOTSTRAP_RELAYS + .into_iter() + .map(|relay| (relay, filters.clone())) + .collect(); - // Return if no relays are available - if urls.is_empty() { - return Err(anyhow!( - "User has not set up any relays. You cannot send messages to them." - )); - } + // Subscribe to the target + client.subscribe(target).close_on(opts).with_id(id).await?; - // Construct filters for inbox relays - let inbox = Filter::new() - .kind(Kind::InboxRelays) - .author(member) - .limit(1); - - // Construct filters for announcement - let announcement = Filter::new() - .kind(Kind::Custom(10044)) - .author(member) - .limit(1); - - // Create subscription targets - let target: HashMap> = urls - .into_iter() - .map(|relay| (relay, vec![inbox.clone(), announcement.clone()])) - .collect(); - - // Stream events from user's write relays - let mut stream = client - .stream_events(target) - .timeout(Duration::from_secs(TIMEOUT)) - .await?; - - while let Some((_url, res)) = stream.next().await { - let event = res?; - - match event.kind { - Kind::InboxRelays => has_inbox = true, - Kind::Custom(10044) => has_announcement = true, - _ => {} - } - - // Early exit if both flags are found - if has_inbox && has_announcement { - break; - } - } - - Ok((has_inbox, has_announcement)) - }); - - tasks.insert(member, task); - } - - tasks + Ok(()) + }) } /// Get all messages belonging to the room @@ -440,7 +414,7 @@ impl Room { // Get current user's public key let sender = nostr.read(cx).signer().public_key()?; - // Get all members + // Get all members, excluding the sender let members: Vec = self .members .iter() @@ -567,7 +541,9 @@ impl Room { reports.push(report); sents += 1; } - Err(report) => reports.push(report), + Err(report) => { + reports.push(report); + } } } diff --git a/crates/chat_ui/src/actions.rs b/crates/chat_ui/src/actions.rs index 5e2c267..915f212 100644 --- a/crates/chat_ui/src/actions.rs +++ b/crates/chat_ui/src/actions.rs @@ -9,6 +9,7 @@ pub enum Command { Insert(&'static str), ChangeSubject(&'static str), ChangeSigner(SignerKind), + ToggleBackup, } #[derive(Action, Clone, PartialEq, Eq, Deserialize)] diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index c1ca330..d998a3d 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -1,5 +1,6 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::sync::Arc; +use std::time::Duration; pub use actions::*; use anyhow::{Context as AnyhowContext, Error}; @@ -134,7 +135,6 @@ impl ChatPanel { cx.defer_in(window, |this, window, cx| { this.connect(window, cx); this.handle_notifications(cx); - this.subscribe_room_events(window, cx); this.get_messages(window, cx); }); @@ -157,6 +157,49 @@ impl ChatPanel { } } + /// Get all necessary data for each member + fn connect(&mut self, window: &mut Window, cx: &mut Context) { + let Ok((members, connect)) = self + .room + .read_with(cx, |this, cx| (this.members(), this.connect(cx))) + else { + return; + }; + + // Run the connect task in background + self.tasks.push(connect); + + // Spawn another task to verify after 2 seconds + self.tasks.push(cx.spawn_in(window, async move |this, cx| { + cx.background_executor().timer(Duration::from_secs(2)).await; + + // Verify the connection + this.update_in(cx, |this, _window, cx| { + let persons = PersonRegistry::global(cx); + + for member in members.into_iter() { + let profile = persons.read(cx).get(&member, cx); + + if profile.announcement().is_none() { + let content = format!("{} {}", profile.name(), NO_ANNOUNCEMENT); + let message = Message::warning(content); + + this.insert_message(message, true, cx); + } + + if profile.messaging_relays().is_empty() { + let content = format!("{} {}", profile.name(), NO_INBOX); + let message = Message::warning(content); + + this.insert_message(message, true, cx); + } + } + })?; + + Ok(()) + })); + } + /// Handle nostr notifications fn handle_notifications(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); @@ -227,46 +270,6 @@ impl ChatPanel { ); } - /// Get all necessary data for each member - fn connect(&mut self, window: &mut Window, cx: &mut Context) { - let Ok(tasks) = self.room.read_with(cx, |this, cx| this.connect(cx)) else { - return; - }; - - self.tasks.push(cx.spawn_in(window, async move |this, cx| { - for (member, task) in tasks.into_iter() { - match task.await { - Ok((has_inbox, has_announcement)) => { - this.update(cx, |this, cx| { - let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get(&member, cx); - - if !has_inbox { - let content = format!("{} {}", profile.name(), NO_INBOX); - let message = Message::warning(content); - - this.insert_message(message, true, cx); - } - - if !has_announcement { - let content = format!("{} {}", profile.name(), NO_ANNOUNCEMENT); - let message = Message::warning(content); - - this.insert_message(message, true, cx); - } - })?; - } - Err(e) => { - this.update(cx, |this, cx| { - this.insert_message(Message::warning(e.to_string()), true, cx); - })?; - } - }; - } - Ok(()) - })); - } - /// Load all messages belonging to this room fn get_messages(&mut self, _window: &mut Window, cx: &mut Context) { let Ok(get_messages) = self.room.read_with(cx, |this, cx| this.get_messages(cx)) else { @@ -363,10 +366,12 @@ impl ChatPanel { // This can't fail, because we already ensured that the ID is set let id = rumor.id.unwrap(); + // Upgrade room reference let Some(room) = self.room.upgrade() else { return; }; + // Get the send message task let Some(task) = room.read(cx).send(rumor, cx) else { window.push_notification("Failed to send message", cx); return; @@ -612,6 +617,17 @@ impl ChatPanel { window.push_notification(Notification::error("Failed to change signer"), cx); } } + Command::ToggleBackup => { + if self + .room + .update(cx, |this, cx| { + this.set_backup(cx); + }) + .is_err() + { + window.push_notification(Notification::error("Failed to toggle backup"), cx); + } + } } } @@ -662,7 +678,14 @@ impl ChatPanel { .text_color(cx.theme().warning_foreground) .child(Icon::new(IconName::Warning).small()), ) - .child(content), + .child( + h_flex() + .flex_1() + .w_full() + .flex_initial() + .overflow_hidden() + .child(content), + ), ) .child( div() @@ -880,7 +903,7 @@ impl ChatPanel { h_flex() .id(SharedString::from(id.to_hex())) .gap_0p5() - .text_color(cx.theme().danger_foreground) + .text_color(cx.theme().danger_active) .text_xs() .italic() .child(Icon::new(IconName::Info).xsmall()) @@ -935,13 +958,13 @@ impl ChatPanel { h_flex() .flex_wrap() .justify_center() - .p_2() - .h_20() + .p_1() + .h_16() .w_full() .text_sm() .rounded(cx.theme().radius) - .bg(cx.theme().danger_background) - .text_color(cx.theme().danger_foreground) + .bg(cx.theme().warning_background) + .text_color(cx.theme().warning_foreground) .child(div().flex_1().w_full().text_center().child(error)), ) }) @@ -957,11 +980,10 @@ impl ChatPanel { items.push( v_flex() .gap_0p5() - .py_1() - .px_2() + .p_1() .w_full() .rounded(cx.theme().radius) - .bg(cx.theme().elevated_surface_background) + .bg(cx.theme().danger_background) .child( div() .text_xs() @@ -971,7 +993,7 @@ impl ChatPanel { ) .child( div() - .text_sm() + .text_xs() .text_color(cx.theme().danger_foreground) .line_height(relative(1.25)) .child(SharedString::from(msg.to_string())), @@ -988,8 +1010,7 @@ impl ChatPanel { items.push( v_flex() .gap_0p5() - .py_1() - .px_2() + .p_1() .w_full() .rounded(cx.theme().radius) .bg(cx.theme().elevated_surface_background) @@ -1002,8 +1023,7 @@ impl ChatPanel { ) .child( div() - .text_sm() - .text_color(cx.theme().secondary_foreground) + .text_xs() .line_height(relative(1.25)) .child(SharedString::from("Successfully")), ), @@ -1196,15 +1216,17 @@ impl ChatPanel { items } - fn render_encryption_menu(&self, _window: &mut Window, cx: &Context) -> impl IntoElement { - let signer_kind = self + fn render_config_menu(&self, _window: &mut Window, cx: &Context) -> impl IntoElement { + let (backup, signer_kind) = self .room - .read_with(cx, |this, _cx| this.config().signer_kind().clone()) + .read_with(cx, |this, _cx| { + (this.config().backup(), this.config().signer_kind().clone()) + }) .ok() .unwrap_or_default(); Button::new("encryption") - .icon(IconName::UserKey) + .icon(IconName::Settings) .ghost() .large() .dropdown_menu(move |this, _window, _cx| { @@ -1212,24 +1234,28 @@ impl ChatPanel { let encryption = matches!(signer_kind, SignerKind::Encryption); let user = matches!(signer_kind, SignerKind::User); - this.menu_with_check_and_disabled( - "Auto", - auto, - Box::new(Command::ChangeSigner(SignerKind::Auto)), - auto, - ) - .menu_with_check_and_disabled( - "Decoupled Encryption Key", - encryption, - Box::new(Command::ChangeSigner(SignerKind::Encryption)), - encryption, - ) - .menu_with_check_and_disabled( - "User Identity", - user, - Box::new(Command::ChangeSigner(SignerKind::User)), - user, - ) + this.label("Signer") + .menu_with_check_and_disabled( + "Auto", + auto, + Box::new(Command::ChangeSigner(SignerKind::Auto)), + auto, + ) + .menu_with_check_and_disabled( + "Decoupled Encryption Key", + encryption, + Box::new(Command::ChangeSigner(SignerKind::Encryption)), + encryption, + ) + .menu_with_check_and_disabled( + "User Identity", + user, + Box::new(Command::ChangeSigner(SignerKind::User)), + user, + ) + .separator() + .label("Backup") + .menu_with_check("Backup messages", backup, Box::new(Command::ToggleBackup)) }) } @@ -1327,15 +1353,15 @@ impl Render for ChatPanel { .child( TextInput::new(&self.input) .appearance(false) - .flex_1() - .text_sm(), + .text_sm() + .flex_1(), ) .child( h_flex() .pl_1() .gap_1() .child(self.render_emoji_menu(window, cx)) - .child(self.render_encryption_menu(window, cx)) + .child(self.render_config_menu(window, cx)) .child( Button::new("send") .icon(IconName::PaperPlaneFill) diff --git a/crates/coop/src/dialogs/screening.rs b/crates/coop/src/dialogs/screening.rs index d7ff40e..cff37c7 100644 --- a/crates/coop/src/dialogs/screening.rs +++ b/crates/coop/src/dialogs/screening.rs @@ -6,7 +6,8 @@ use common::RenderedTimestamp; use gpui::prelude::FluentBuilder; use gpui::{ div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity, - InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, + InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, + Task, Window, }; use nostr_sdk::prelude::*; use person::{shorten_pubkey, Person, PersonRegistry}; @@ -41,10 +42,20 @@ pub struct Screening { /// Async tasks tasks: SmallVec<[Task<()>; 3]>, + + /// Subscriptions + _subscriptions: SmallVec<[Subscription; 1]>, } impl Screening { pub fn new(public_key: PublicKey, window: &mut Window, cx: &mut Context) -> Self { + let mut subscriptions = smallvec![]; + + subscriptions.push(cx.on_release_in(window, move |this, window, cx| { + this.tasks.clear(); + window.close_all_modals(cx); + })); + cx.defer_in(window, move |this, _window, cx| { this.check_contact(cx); this.check_wot(cx); @@ -59,6 +70,7 @@ impl Screening { last_active: None, mutual_contacts: vec![], tasks: smallvec![], + _subscriptions: subscriptions, } } @@ -137,10 +149,10 @@ impl Screening { let mut activity: Option = None; // Construct target for subscription - let target = BOOTSTRAP_RELAYS + let target: HashMap<&str, Vec> = BOOTSTRAP_RELAYS .into_iter() .map(|relay| (relay, vec![filter.clone()])) - .collect::>(); + .collect(); if let Ok(mut stream) = client .stream_events(target) @@ -279,11 +291,21 @@ impl Screening { impl Render for Screening { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + const CONTACT: &str = "This person is one of your contacts."; + const NOT_CONTACT: &str = "This person is not one of your contacts."; + const NO_ACTIVITY: &str = "This person hasn't had any activity."; + const RELAY_INFO: &str = "Only checked on public relays; may be inaccurate."; + const NO_MUTUAL: &str = "You don't have any mutual contacts."; + const NIP05_MATCH: &str = "The address matches the user's public key."; + const NIP05_NOT_MATCH: &str = "The address does not match the user's public key."; + const NO_NIP05: &str = "This person has not set up their friendly address"; + let profile = self.profile(cx); let shorten_pubkey = shorten_pubkey(self.public_key, 8); - let total_mutuals = self.mutual_contacts.len(); let last_active = self.last_active.map(|_| true); + let mutuals = self.mutual_contacts.len(); + let mutuals_str = format!("You have {} mutual contacts with this person.", mutuals); v_flex() .gap_4() @@ -336,6 +358,7 @@ impl Render for Screening { Button::new("report") .tooltip("Report as a scam or impostor") .icon(IconName::Boom) + .small() .danger() .rounded() .on_click(cx.listener(move |this, _e, window, cx| { @@ -363,9 +386,9 @@ impl Render for Screening { .text_color(cx.theme().text_muted) .child({ if self.followed { - SharedString::from("This person is one of your contacts.") + SharedString::from(CONTACT) } else { - SharedString::from("This person is not one of your contacts.") + SharedString::from(NOT_CONTACT) } }), ), @@ -390,7 +413,7 @@ impl Render for Screening { .xsmall() .ghost() .rounded() - .tooltip("This may be inaccurate if the user only publishes to their private relays."), + .tooltip(RELAY_INFO), ), ) .child( @@ -399,13 +422,13 @@ impl Render for Screening { .line_clamp(1) .text_color(cx.theme().text_muted) .map(|this| { - if let Some(date) = self.last_active { + if let Some(t) = self.last_active { this.child(SharedString::from(format!( "Last active: {}.", - date.to_human_time() + t.to_human_time() ))) } else { - this.child(SharedString::from("This person hasn't had any activity.")) + this.child(SharedString::from(NO_ACTIVITY)) } }), ), @@ -423,7 +446,9 @@ impl Render for Screening { if let Some(addr) = self.address(cx) { SharedString::from(format!("{} validation", addr)) } else { - SharedString::from("Friendly Address (NIP-05) validation") + SharedString::from( + "Friendly Address (NIP-05) validation", + ) } }) .child( @@ -433,12 +458,12 @@ impl Render for Screening { .child({ if self.address(cx).is_some() { if self.verified { - SharedString::from("The address matches the user's public key.") + SharedString::from(NIP05_MATCH) } else { - SharedString::from("The address does not match the user's public key.") + SharedString::from(NIP05_NOT_MATCH) } } else { - SharedString::from("This person has not set up their friendly address") + SharedString::from(NO_NIP05) } }), ), @@ -448,7 +473,7 @@ impl Render for Screening { h_flex() .items_start() .gap_2() - .child(status_badge(Some(total_mutuals > 0), cx)) + .child(status_badge(Some(mutuals > 0), cx)) .child( v_flex() .text_sm() @@ -474,13 +499,10 @@ impl Render for Screening { .line_clamp(1) .text_color(cx.theme().text_muted) .child({ - if total_mutuals > 0 { - SharedString::from(format!( - "You have {} mutual contacts with this person.", - total_mutuals - )) + if mutuals > 0 { + SharedString::from(mutuals_str) } else { - SharedString::from("You don't have any mutual contacts with this person.") + SharedString::from(NO_MUTUAL) } }), ), diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 6ea6d2f..982d28f 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -57,14 +57,8 @@ impl DeviceRegistry { subscriptions.push( // Observe the NIP-65 state cx.observe(&nostr, |this, state, cx| { - match state.read(cx).relay_list_state() { - RelayState::Idle => { - this.reset(cx); - } - RelayState::Configured => { - this.get_announcement(cx); - } - _ => {} + if state.read(cx).relay_list_state() == RelayState::Configured { + this.get_announcement(cx); }; }), ); @@ -255,6 +249,10 @@ impl DeviceRegistry { let signer = nostr.read(cx).signer(); let public_key = signer.public_key().unwrap(); + // Reset state before fetching announcement + self.reset(cx); + + // Get user's write relays let write_relays = nostr.read(cx).write_relays(&public_key, cx); let task: Task> = cx.background_spawn(async move { @@ -664,7 +662,8 @@ async fn get_keys(client: &Client) -> Result { let filter = Filter::new() .kind(Kind::ApplicationSpecificData) - .identifier(IDENTIFIER); + .identifier(IDENTIFIER) + .author(public_key); if let Some(event) = client.database().query(filter).await?.first() { let content = signer.nip44_decrypt(&public_key, &event.content).await?; diff --git a/crates/person/src/lib.rs b/crates/person/src/lib.rs index 054c221..8721372 100644 --- a/crates/person/src/lib.rs +++ b/crates/person/src/lib.rs @@ -253,7 +253,7 @@ impl PersonRegistry { match self.persons.get(&public_key) { Some(this) => { this.update(cx, |this, cx| { - *this = person; + this.set_metadata(person.metadata()); cx.notify(); }); } @@ -313,10 +313,10 @@ where .limit(limit); // Construct target for subscription - let target = BOOTSTRAP_RELAYS + let target: HashMap<&str, Vec> = BOOTSTRAP_RELAYS .into_iter() .map(|relay| (relay, vec![filter.clone()])) - .collect::>(); + .collect(); client.subscribe(target).close_on(opts).await?; diff --git a/crates/person/src/person.rs b/crates/person/src/person.rs index 9c9c582..0aea82f 100644 --- a/crates/person/src/person.rs +++ b/crates/person/src/person.rs @@ -75,6 +75,11 @@ impl Person { self.metadata.clone() } + /// Set profile metadata + pub fn set_metadata(&mut self, metadata: Metadata) { + self.metadata = metadata; + } + /// Get profile encryption keys announcement pub fn announcement(&self) -> Option { self.announcement.clone() @@ -83,7 +88,6 @@ impl Person { /// Set profile encryption keys announcement pub fn set_announcement(&mut self, announcement: Announcement) { self.announcement = Some(announcement); - log::info!("Updated announcement for: {}", self.public_key()); } /// Get profile messaging relays @@ -102,7 +106,6 @@ impl Person { I: IntoIterator, { self.messaging_relays = relays.into_iter().collect(); - log::info!("Updated messaging relays for: {}", self.public_key()); } /// Get profile avatar diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 4b52f52..2fe1a84 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -94,21 +94,28 @@ pub struct RoomConfig { } impl RoomConfig { + pub fn new() -> Self { + Self { + backup: true, + signer_kind: SignerKind::Auto, + } + } + /// Get backup config pub fn backup(&self) -> bool { self.backup } + /// Set backup config + pub fn toggle_backup(&mut self) { + self.backup = !self.backup; + } + /// Get signer kind config pub fn signer_kind(&self) -> &SignerKind { &self.signer_kind } - /// Set backup config - pub fn set_backup(&mut self, backup: bool) { - self.backup = backup; - } - /// Set signer kind config pub fn set_signer_kind(&mut self, kind: &SignerKind) { self.signer_kind = kind.to_owned(); diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index f6cabe7..9c2f968 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -168,14 +168,18 @@ impl NostrRegistry { // Channel for communication between nostr and gpui let (tx, rx) = flume::bounded::(2048); - let task: Task> = cx.background_spawn(async move { + self.tasks.push(cx.background_spawn(async move { // Handle nostr notifications let mut notifications = client.notifications(); let mut processed_events = HashSet::new(); while let Some(notification) = notifications.next().await { if let ClientNotification::Message { - message: RelayMessage::Event { event, .. }, + message: + RelayMessage::Event { + event, + subscription_id, + }, .. } = notification { @@ -183,6 +187,9 @@ impl NostrRegistry { if processed_events.insert(event.id) { match event.kind { Kind::RelayList => { + if subscription_id.as_str().contains("room-") { + get_events_for_room(&client, &event).await.ok(); + } tx.send_async(event.into_owned()).await?; } Kind::InboxRelays => { @@ -195,10 +202,7 @@ impl NostrRegistry { } Ok(()) - }); - - // Run task in the background - task.detach(); + })); self.tasks.push(cx.spawn(async move |_this, cx| { while let Ok(event) = rx.recv_async().await { @@ -763,10 +767,10 @@ impl NostrRegistry { .event(output.id().to_owned()); // Construct target for subscription - let target = WOT_RELAYS + let target: HashMap<&str, Vec> = WOT_RELAYS .into_iter() .map(|relay| (relay, vec![filter.clone()])) - .collect::>(); + .collect(); // Stream events from the wot relays let mut stream = client @@ -833,6 +837,52 @@ fn get_or_init_app_keys() -> Result { Ok(keys) } +async fn get_events_for_room(client: &Client, nip65: &Event) -> Result<(), Error> { + // Subscription options + let opts = SubscribeAutoCloseOptions::default() + .timeout(Some(Duration::from_secs(TIMEOUT))) + .exit_policy(ReqExitPolicy::ExitOnEOSE); + + // Extract write relays from event + let write_relays: Vec<&RelayUrl> = nip65::extract_relay_list(nip65) + .filter_map(|(url, metadata)| { + if metadata.is_none() || metadata == &Some(RelayMetadata::Write) { + Some(url) + } else { + None + } + }) + .collect(); + + // Ensure relay connections + for url in write_relays.iter() { + client.add_relay(*url).and_connect().await.ok(); + } + + // Construct filter for inbox relays + let inbox = Filter::new() + .kind(Kind::InboxRelays) + .author(nip65.pubkey) + .limit(1); + + // Construct filter for encryption announcement + let announcement = Filter::new() + .kind(Kind::Custom(10044)) + .author(nip65.pubkey) + .limit(1); + + // Construct target for subscription + let target: HashMap<&RelayUrl, Vec> = write_relays + .into_iter() + .map(|relay| (relay, vec![inbox.clone(), announcement.clone()])) + .collect(); + + // Subscribe to inbox relays and encryption announcements + client.subscribe(target).close_on(opts).await?; + + Ok(()) +} + fn default_relay_list() -> Vec<(RelayUrl, Option)> { vec![ ( diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index 81506d1..efe7368 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -1026,7 +1026,7 @@ impl PopupMenu { } else if checked { Icon::new(IconName::Check) } else { - return None; + Icon::empty() }; Some(icon.small())