diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index 2736d30..c036d39 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -7,6 +7,7 @@ use std::time::Duration; use anyhow::{Context as AnyhowContext, Error, anyhow}; use common::EventUtils; +use device::{DeviceEvent, DeviceRegistry}; use fuzzy_matcher::FuzzyMatcher; use fuzzy_matcher::skim::SkimMatcherV2; use gpui::{ @@ -104,7 +105,7 @@ pub struct ChatRegistry { tasks: SmallVec<[Task>; 2]>, /// Subscriptions - _subscriptions: SmallVec<[Subscription; 1]>, + _subscriptions: SmallVec<[Subscription; 2]>, } impl EventEmitter for ChatRegistry {} @@ -123,17 +124,48 @@ impl ChatRegistry { /// Create a new chat registry instance fn new(window: &mut Window, cx: &mut Context) -> Self { let nostr = NostrRegistry::global(cx); + let device = DeviceRegistry::global(cx); + let (tx, rx) = flume::unbounded::(); let mut subscriptions = smallvec![]; subscriptions.push( // Subscribe to the signer event - cx.subscribe(&nostr, |this, _state, event, cx| { + cx.subscribe_in(&nostr, window, |this, state, event, window, cx| { if event == &StateEvent::SignerSet { this.reset(cx); this.get_contact_list(cx); - this.get_messages(cx); this.get_rooms(cx); + + let signer = state.read(cx).signer(); + cx.spawn_in(window, async move |this, cx| { + let user_signer = signer.get().await; + this.update(cx, |this, cx| { + this.get_messages(user_signer, cx); + }) + .ok(); + }) + .detach(); + }; + }), + ); + + subscriptions.push( + // Subscribe to the device event + cx.subscribe_in(&device, window, |_this, _s, event, window, cx| { + if event == &DeviceEvent::Set { + let nostr = NostrRegistry::global(cx); + let signer = nostr.read(cx).signer(); + + cx.spawn_in(window, async move |this, cx| { + if let Some(device_signer) = signer.get_encryption_signer().await { + this.update(cx, |this, cx| { + this.get_messages(device_signer, cx); + }) + .ok(); + } + }) + .detach(); }; }), ); @@ -297,7 +329,7 @@ impl ChatRegistry { } /// Get contact list from relays - pub fn get_contact_list(&mut self, cx: &mut Context) { + fn get_contact_list(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); let signer = nostr.read(cx).signer(); @@ -327,9 +359,12 @@ impl ChatRegistry { self.tasks.push(task); } - /// Get all messages for current user - pub fn get_messages(&mut self, cx: &mut Context) { - let task = self.subscribe_gift_wrap_events(cx); + /// Get all messages for the provided signer + fn get_messages(&mut self, signer: T, cx: &mut Context) + where + T: NostrSigner + 'static, + { + let task = self.subscribe_gift_wrap_events(signer, cx); self.tasks.push(cx.spawn(async move |this, cx| { match task.await { @@ -382,18 +417,20 @@ impl ChatRegistry { }) } - /// Continuously get gift wrap events for the current user in their messaging relays - fn subscribe_gift_wrap_events(&self, cx: &App) -> Task> { + /// Continuously get gift wrap events for the signer + fn subscribe_gift_wrap_events(&self, signer: T, cx: &App) -> Task> + where + T: NostrSigner + 'static, + { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let signer = nostr.read(cx).signer(); let urls = self.get_messaging_relays(cx); cx.background_spawn(async move { let urls = urls.await?; let public_key = signer.get_public_key().await?; let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); - let id = SubscriptionId::new(USER_GIFTWRAP); + let id = SubscriptionId::new(format!("{}-msg", public_key.to_hex())); // Ensure relay connections for url in urls.iter() { @@ -417,6 +454,31 @@ impl ChatRegistry { }) } + /// Refresh the chat registry, fetching messages and contact list from relays. + pub fn refresh(&mut self, window: &mut Window, cx: &mut Context) { + self.reset(cx); + self.get_contact_list(cx); + self.get_rooms(cx); + + let nostr = NostrRegistry::global(cx); + let signer = nostr.read(cx).signer(); + + cx.spawn_in(window, async move |this, cx| { + let user_signer = signer.get().await; + let device_signer = signer.get_encryption_signer().await; + + this.update(cx, |this, cx| { + this.get_messages(user_signer, cx); + + if let Some(device_signer) = device_signer { + this.get_messages(device_signer, cx); + } + }) + .ok(); + }) + .detach(); + } + /// Set the initializing status of the chat registry fn set_initializing(&mut self, initializing: bool, cx: &mut Context) { self.initializing = initializing; diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index 2903aad..b7d1edd 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -92,14 +92,14 @@ fn main() { // Initialize relay auth registry relay_auth::init(window, cx); - // Initialize app registry - chat::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 app registry + chat::init(window, cx); + // Initialize auto update auto_update::init(window, cx); diff --git a/crates/coop/src/workspace.rs b/crates/coop/src/workspace.rs index 0867b49..5571f28 100644 --- a/crates/coop/src/workspace.rs +++ b/crates/coop/src/workspace.rs @@ -354,8 +354,9 @@ impl Workspace { } Command::RefreshMessagingRelays => { let chat = ChatRegistry::global(cx); + // Trigger a refresh of the chat registry chat.update(cx, |this, cx| { - this.get_messages(cx); + this.refresh(window, cx); }); } Command::ShowRelayList => { diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index cc9386d..8f7fee2 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -1,5 +1,5 @@ use std::cell::Cell; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::path::PathBuf; use std::rc::Rc; use std::time::Duration; @@ -11,7 +11,7 @@ use gpui::{ }; use nostr_sdk::prelude::*; use person::PersonRegistry; -use state::{Announcement, DEVICE_GIFTWRAP, NostrRegistry, StateEvent, TIMEOUT, app_name}; +use state::{Announcement, NostrRegistry, StateEvent, TIMEOUT, app_name}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -213,76 +213,14 @@ impl DeviceRegistry { // Update state this.update(cx, |this, cx| { + this.set_initializing(false, cx); cx.emit(DeviceEvent::Set); - this.get_messages(cx); })?; Ok(()) })); } - /// Get all messages for encryption keys - fn get_messages(&mut self, cx: &mut Context) { - let task = self.subscribe_gift_wrap_events(cx); - - self.tasks.push(cx.spawn(async move |this, cx| { - if let Err(e) = task.await { - this.update(cx, |_this, cx| { - cx.emit(DeviceEvent::error(e.to_string())); - })?; - } else { - this.update(cx, |this, cx| { - this.set_initializing(false, cx); - })?; - } - Ok(()) - })); - } - - /// Continuously get gift wrap events for the current user in their messaging relays - fn subscribe_gift_wrap_events(&self, cx: &App) -> Task> { - let persons = PersonRegistry::global(cx); - let nostr = NostrRegistry::global(cx); - let client = nostr.read(cx).client(); - let signer = nostr.read(cx).signer(); - - let Some(user) = signer.public_key() else { - return Task::ready(Err(anyhow!("User not found"))); - }; - - let profile = persons.read(cx).get(&user, cx); - let relays = profile.messaging_relays().clone(); - - cx.background_spawn(async move { - let encryption = signer.get_encryption_signer().await.context("not found")?; - let public_key = encryption.get_public_key().await?; - - let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); - let id = SubscriptionId::new(DEVICE_GIFTWRAP); - - // Ensure user has relays configured - if relays.is_empty() { - return Err(anyhow!("No messaging relays found")); - } - - // Ensure relays are connected - for url in relays.iter() { - client.add_relay(url).and_connect().await?; - } - - // Construct target for subscription - let target: HashMap = relays - .into_iter() - .map(|relay| (relay, filter.clone())) - .collect(); - - // Subscribe - client.subscribe(target).with_id(id).await?; - - Ok(()) - }) - } - /// Backup the encryption's secret key to a file pub fn backup(&self, path: PathBuf, cx: &App) -> Task> { let nostr = NostrRegistry::global(cx);