From 2dcf825105c4e1c1344a7a7e7e65d26ce8e3efad Mon Sep 17 00:00:00 2001 From: reya Date: Fri, 27 Feb 2026 08:11:40 +0700 Subject: [PATCH] . --- crates/chat/src/lib.rs | 10 ++-- crates/chat/src/room.rs | 109 +++++++++++++++++----------------- crates/coop/src/main.rs | 18 +++--- crates/relay_auth/src/lib.rs | 51 +++++++++------- crates/state/src/constants.rs | 9 +-- crates/state/src/lib.rs | 94 +++++++++++++++++++++++++---- 6 files changed, 184 insertions(+), 107 deletions(-) diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index 65c2cbb..dd91c94 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -122,6 +122,9 @@ impl ChatRegistry { } _ => {} } + + // Load rooms on every state change + this.get_rooms(cx); }), ); @@ -137,13 +140,8 @@ impl ChatRegistry { // Run at the end of the current cycle cx.defer_in(window, |this, _window, cx| { - // Load chat rooms this.get_rooms(cx); - - // Handle nostr notifications this.handle_notifications(cx); - - // Track unwrap gift wrap progress this.tracking(cx); }); @@ -248,7 +246,7 @@ impl ChatRegistry { let status = self.tracking_flag.clone(); self.tasks.push(cx.background_spawn(async move { - let loop_duration = Duration::from_secs(10); + let loop_duration = Duration::from_secs(15); loop { if status.load(Ordering::Acquire) { diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index c54d5d8..a576eea 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -336,68 +336,65 @@ impl Room { } let client = nostr.read(cx).client(); - let write_relays = nostr.read(cx).write_relays(&member, cx); + let ensure_write_relays = nostr.read(cx).ensure_write_relays(&member, cx); - tasks.insert( - member, - cx.background_spawn(async move { - let urls = write_relays.await; + let task = cx.background_spawn(async move { + let mut has_inbox = false; + let mut has_announcement = false; - // 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." - )); + // Get user's write relays + let urls = ensure_write_relays.await; + + // 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." + )); + } + + // 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, + _ => {} } - // Construct filters for inbox and announcement - let inbox_filter = Filter::new() - .kind(Kind::InboxRelays) - .author(member) - .limit(1); - let announcement_filter = Filter::new() - .kind(Kind::Custom(10044)) - .author(member) - .limit(1); - - // Create subscription targets - let target = urls - .into_iter() - .map(|relay| { - ( - relay, - vec![inbox_filter.clone(), announcement_filter.clone()], - ) - }) - .collect::>(); - - // Stream events from user's write relays - let mut stream = client - .stream_events(target) - .timeout(Duration::from_secs(TIMEOUT)) - .await?; - - let mut has_inbox = false; - let mut has_announcement = false; - - 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; - } + // Early exit if both flags are found + if has_inbox && has_announcement { + break; } + } - Ok((has_inbox, has_announcement)) - }), - ); + Ok((has_inbox, has_announcement)) + }); + + tasks.insert(member, task); } tasks diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index 902f80f..eb8ca3d 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -79,16 +79,14 @@ fn main() { // Initialize theme registry theme::init(cx); + // Initialize settings + settings::init(window, cx); + // Initialize the nostr client state::init(window, cx); - // Initialize device signer - // - // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md - device::init(window, cx); - - // Initialize settings - settings::init(window, cx); + // Initialize person registry + person::init(cx); // Initialize relay auth registry relay_auth::init(window, cx); @@ -96,8 +94,10 @@ fn main() { // Initialize app registry chat::init(window, cx); - // Initialize person registry - person::init(cx); + // Initialize device signer + // + // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md + device::init(window, cx); // Initialize auto update auto_update::init(window, cx); diff --git a/crates/relay_auth/src/lib.rs b/crates/relay_auth/src/lib.rs index c19c847..96770c1 100644 --- a/crates/relay_auth/src/lib.rs +++ b/crates/relay_auth/src/lib.rs @@ -67,7 +67,7 @@ pub struct RelayAuth { pending_events: HashSet<(EventId, RelayUrl)>, /// Tasks for asynchronous operations - tasks: SmallVec<[Task<()>; 2]>, + _tasks: SmallVec<[Task<()>; 2]>, } impl RelayAuth { @@ -83,26 +83,15 @@ impl RelayAuth { /// Create a new relay auth instance fn new(window: &mut Window, cx: &mut Context) -> Self { - cx.defer_in(window, |this, window, cx| { - this.handle_notifications(window, cx); - }); - - Self { - pending_events: HashSet::default(), - tasks: smallvec![], - } - } - - /// Handle nostr notifications - fn handle_notifications(&mut self, window: &mut Window, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); + let mut tasks = smallvec![]; + // Channel for communication between nostr and gpui let (tx, rx) = flume::bounded::(256); - self.tasks.push(cx.background_spawn(async move { - log::info!("Started handling nostr notifications"); + tasks.push(cx.background_spawn(async move { let mut notifications = client.notifications(); let mut challenges: HashSet> = HashSet::default(); @@ -117,6 +106,22 @@ impl RelayAuth { tx.send_async(signal).await.ok(); } } + RelayMessage::Closed { + subscription_id, + message, + } => { + let msg = MachineReadablePrefix::parse(&message); + + if let Some(MachineReadablePrefix::AuthRequired) = msg { + if let Ok(Some(relay)) = client.relay(&relay_url).await { + // Send close message to relay + relay + .send_msg(ClientMessage::Close(subscription_id)) + .await + .ok(); + } + } + } RelayMessage::Ok { event_id, message, .. } => { @@ -134,7 +139,7 @@ impl RelayAuth { } })); - self.tasks.push(cx.spawn_in(window, async move |this, cx| { + tasks.push(cx.spawn_in(window, async move |this, cx| { while let Ok(signal) = rx.recv_async().await { match signal { Signal::Auth(req) => { @@ -152,6 +157,11 @@ impl RelayAuth { } } })); + + Self { + pending_events: HashSet::default(), + _tasks: tasks, + } } /// Insert a pending event waiting for resend after authentication @@ -162,15 +172,12 @@ impl RelayAuth { /// Get all pending events for a specific relay, fn get_pending_events(&self, relay: &RelayUrl, _cx: &App) -> Vec { - let pending_events: Vec = self - .pending_events + self.pending_events .iter() .filter(|(_, pending_relay)| pending_relay == relay) .map(|(id, _relay)| id) .cloned() - .collect(); - - pending_events + .collect() } /// Clear all pending events for a specific relay, @@ -282,10 +289,12 @@ impl RelayAuth { Ok(_) => { // Clear pending events for the authenticated relay this.clear_pending_events(url, cx); + // Save the authenticated relay to automatically authenticate future requests settings.update(cx, |this, cx| { this.add_trusted_relay(url, cx); }); + window.push_notification(format!("{} has been authenticated", url), cx); } Err(e) => { diff --git a/crates/state/src/constants.rs b/crates/state/src/constants.rs index cb34e2b..98555bd 100644 --- a/crates/state/src/constants.rs +++ b/crates/state/src/constants.rs @@ -13,7 +13,7 @@ pub const APP_ID: &str = "su.reya.coop"; pub const KEYRING: &str = "Coop Safe Storage"; /// Default timeout for subscription -pub const TIMEOUT: u64 = 3; +pub const TIMEOUT: u64 = 2; /// Default delay for searching pub const FIND_DELAY: u64 = 600; @@ -37,12 +37,13 @@ pub const USER_GIFTWRAP: &str = "user-gift-wraps"; pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"]; /// Default search relays -pub const SEARCH_RELAYS: [&str; 1] = ["wss://antiprimal.net"]; +pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://search.nos.today"]; /// Default bootstrap relays -pub const BOOTSTRAP_RELAYS: [&str; 3] = [ - "wss://relay.damus.io", +pub const BOOTSTRAP_RELAYS: [&str; 4] = [ "wss://nos.lol", + "wss://relay.damus.io", + "wss://relay.primal.net", "wss://user.kindpag.es", ]; diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index fb5466c..f6cabe7 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -146,10 +146,7 @@ impl NostrRegistry { } // Connect to all added relays - client - .connect() - .and_wait(Duration::from_secs(TIMEOUT)) - .await; + client.connect().and_wait(Duration::from_secs(5)).await; }) .await; @@ -252,6 +249,63 @@ impl NostrRegistry { self.gossip.read(cx).read_only_relays(public_key) } + /// Ensure write relays for a given public key + pub fn ensure_write_relays(&self, public_key: &PublicKey, cx: &App) -> Task> { + let client = self.client(); + let public_key = *public_key; + + cx.background_spawn(async move { + let mut relays = vec![]; + + let filter = Filter::new() + .kind(Kind::RelayList) + .author(public_key) + .limit(1); + + // Construct target for subscription + let target: HashMap<&str, Vec> = BOOTSTRAP_RELAYS + .into_iter() + .map(|relay| (relay, vec![filter.clone()])) + .collect(); + + if let Ok(mut stream) = client + .stream_events(target) + .timeout(Duration::from_secs(TIMEOUT)) + .await + { + while let Some((_url, res)) = stream.next().await { + match res { + Ok(event) => { + // Extract relay urls + relays.extend(nip65::extract_owned_relay_list(event).filter_map( + |(url, metadata)| { + if metadata.is_none() || metadata == Some(RelayMetadata::Write) + { + Some(url) + } else { + None + } + }, + )); + + // Ensure connections + for url in relays.iter() { + client.add_relay(url).and_connect().await.ok(); + } + + return relays; + } + Err(e) => { + log::error!("Failed to receive relay list event: {e}"); + } + } + } + } + + relays + }) + } + /// Get a list of write relays for a given public key pub fn write_relays(&self, public_key: &PublicKey, cx: &App) -> Task> { let client = self.client(); @@ -423,7 +477,6 @@ impl NostrRegistry { let event = EventBuilder::relay_list(relay_list).sign(&signer).await?; client .send_event(&event) - .broadcast() .ok_timeout(Duration::from_secs(TIMEOUT)) .await?; @@ -447,7 +500,6 @@ impl NostrRegistry { let event = EventBuilder::contact_list(contacts).sign(&signer).await?; client .send_event(&event) - .broadcast() .ok_timeout(Duration::from_secs(TIMEOUT)) .ack_policy(AckPolicy::none()) .await?; @@ -488,9 +540,18 @@ impl NostrRegistry { cx.notify(); } + /// Set the state of the relay list + fn set_relay_state(&mut self, state: RelayState, cx: &mut Context) { + self.relay_list_state = state; + cx.notify(); + } + pub fn ensure_relay_list(&mut self, cx: &mut Context) { let task = self.verify_relay_list(cx); + // Set the state to idle before starting the task + self.set_relay_state(RelayState::default(), cx); + self.tasks.push(cx.spawn(async move |this, cx| { let result = task.await?; @@ -517,9 +578,15 @@ impl NostrRegistry { .author(public_key) .limit(1); + // Construct target for subscription + let target: HashMap<&str, Vec> = BOOTSTRAP_RELAYS + .into_iter() + .map(|relay| (relay, vec![filter.clone()])) + .collect(); + // Stream events from the bootstrap relays let mut stream = client - .stream_events(filter) + .stream_events(target) .timeout(Duration::from_secs(TIMEOUT)) .await?; @@ -601,10 +668,10 @@ impl NostrRegistry { .limit(1); // 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?; @@ -648,10 +715,10 @@ impl NostrRegistry { .limit(FIND_LIMIT); // Construct target for subscription - let target = SEARCH_RELAYS + let target: HashMap<&str, Vec> = SEARCH_RELAYS .into_iter() .map(|relay| (relay, vec![filter.clone()])) - .collect::>(); + .collect(); // Stream events from the search relays let mut stream = client @@ -784,6 +851,10 @@ fn default_relay_list() -> Vec<(RelayUrl, Option)> { RelayUrl::parse("wss://nos.lol").unwrap(), Some(RelayMetadata::Read), ), + ( + RelayUrl::parse("wss://nostr.superfriends.online").unwrap(), + None, + ), ] } @@ -791,6 +862,7 @@ fn default_messaging_relays() -> Vec { vec![ RelayUrl::parse("wss://nos.lol").unwrap(), RelayUrl::parse("wss://nip17.com").unwrap(), + RelayUrl::parse("wss://relay.0xchat.com").unwrap(), ] }