diff --git a/crates/coop/src/command_bar.rs b/crates/coop/src/command_bar.rs index 6cea72f..96ab1cc 100644 --- a/crates/coop/src/command_bar.rs +++ b/crates/coop/src/command_bar.rs @@ -190,11 +190,7 @@ impl CommandBar { // Block the input until the search completes self.set_finding(true, window, cx); - let find_users = if nostr.read(cx).owned_signer() { - nostr.read(cx).wot_search(&query, cx) - } else { - nostr.read(cx).search(&query, cx) - }; + let find_users = nostr.read(cx).search(&query, cx); // Run task in the main thread self.find_task = Some(cx.spawn_in(window, async move |this, cx| { diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 4a02aeb..3aefe87 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -250,6 +250,8 @@ impl DeviceRegistry { *this = Some(Arc::new(signer)); cx.notify(); }); + + log::info!("Device Signer set"); } /// Set the device state @@ -308,7 +310,7 @@ impl DeviceRegistry { Ok(()) }); - task.detach(); + task.detach_and_log_err(cx); } /// Get device announcement for current user diff --git a/crates/relay_auth/src/lib.rs b/crates/relay_auth/src/lib.rs index 1f182ea..6314811 100644 --- a/crates/relay_auth/src/lib.rs +++ b/crates/relay_auth/src/lib.rs @@ -3,6 +3,7 @@ use std::cell::Cell; use std::collections::HashSet; use std::hash::{Hash, Hasher}; use std::rc::Rc; +use std::sync::Arc; use anyhow::{anyhow, Context as AnyhowContext, Error}; use gpui::{ @@ -28,8 +29,8 @@ pub fn init(window: &mut Window, cx: &mut App) { /// Authentication request #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct AuthRequest { - pub url: RelayUrl, - pub challenge: String, + url: RelayUrl, + challenge: String, } impl Hash for AuthRequest { @@ -45,6 +46,14 @@ impl AuthRequest { url, } } + + pub fn url(&self) -> &RelayUrl { + &self.url + } + + pub fn challenge(&self) -> &str { + &self.challenge + } } struct GlobalRelayAuth(Entity); @@ -55,7 +64,7 @@ impl Global for GlobalRelayAuth {} #[derive(Debug)] pub struct RelayAuth { /// Entity for managing auth requests - requests: HashSet, + requests: HashSet>, /// Event subscriptions _subscriptions: SmallVec<[Subscription; 1]>, @@ -91,14 +100,14 @@ impl RelayAuth { subscriptions.push( // Observe the current state - cx.observe_in(&entity, window, |this, _, window, cx| { + cx.observe_in(&entity, window, |this, _state, window, cx| { let settings = AppSettings::global(cx); let mode = AppSettings::get_auth_mode(cx); - for req in this.requests.clone().into_iter() { - let is_trusted_relay = settings.read(cx).is_trusted_relay(&req.url, cx); + for req in this.requests.iter() { + let trusted_relay = settings.read(cx).trusted_relay(req.url(), cx); - if is_trusted_relay && mode == AuthMode::Auto { + if trusted_relay && mode == AuthMode::Auto { // Automatically authenticate if the relay is authenticated before this.response(req, window, cx); } else { @@ -111,7 +120,9 @@ impl RelayAuth { tasks.push( // Handle nostr notifications - cx.background_spawn(async move { Self::handle_notifications(&client, &tx).await }), + cx.background_spawn(async move { + Self::handle_notifications(&client, &tx).await; + }), ); tasks.push( @@ -136,16 +147,16 @@ impl RelayAuth { // Handle nostr notifications async fn handle_notifications(client: &Client, tx: &flume::Sender) { let mut notifications = client.notifications(); + let mut challenges: HashSet> = HashSet::default(); while let Some(notification) = notifications.next().await { match notification { ClientNotification::Message { relay_url, message } => { match message { RelayMessage::Auth { challenge } => { - let request = AuthRequest::new(challenge, relay_url); - - if let Err(e) = tx.send_async(request).await { - log::error!("Failed to send auth request: {}", e); + if challenges.insert(challenge.clone()) { + let request = AuthRequest::new(challenge, relay_url); + tx.send_async(request).await.ok(); } } RelayMessage::Ok { @@ -174,7 +185,7 @@ impl RelayAuth { /// Add a new authentication request. fn add_request(&mut self, request: AuthRequest, cx: &mut Context) { - self.requests.insert(request); + self.requests.insert(Arc::new(request)); cx.notify(); } @@ -185,35 +196,34 @@ impl RelayAuth { /// Reask for approval for all pending requests. pub fn re_ask(&mut self, window: &mut Window, cx: &mut Context) { - for request in self.requests.clone().into_iter() { + for request in self.requests.iter() { self.ask_for_approval(request, window, cx); } } /// Respond to an authentication request. - fn response(&mut self, req: AuthRequest, window: &mut Window, cx: &mut Context) { + fn response(&self, req: &Arc, window: &Window, cx: &Context) { let settings = AppSettings::global(cx); - let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let challenge = req.challenge.to_owned(); - let url = req.url.to_owned(); - - let challenge_clone = challenge.clone(); - let url_clone = url.clone(); + let req = req.clone(); + let challenge = req.challenge().to_string(); + let async_req = req.clone(); let task: Task> = cx.background_spawn(async move { // Construct event - let builder = EventBuilder::auth(challenge_clone, url_clone.clone()); + let builder = EventBuilder::auth(async_req.challenge(), async_req.url().clone()); let event = client.sign_event_builder(builder).await?; // Get the event ID let id = event.id; // Get the relay - let relay = client.relay(url_clone).await?.context("Relay not found")?; - let relay_url = relay.url(); + let relay = client + .relay(async_req.url()) + .await? + .context("Relay not found")?; // Subscribe to notifications let mut notifications = relay.notifications(); @@ -234,7 +244,7 @@ impl RelayAuth { // Get all pending events that need to be resent let mut tracker = tracker().write().await; - let ids: Vec = tracker.pending_resend(relay_url); + let ids: Vec = tracker.pending_resend(relay.url()); for id in ids.into_iter() { if let Some(event) = client.database().event_by_id(&id).await? { @@ -254,47 +264,56 @@ impl RelayAuth { Err(anyhow!("Authentication failed")) }); - self._tasks.push( - // Handle response in the background - cx.spawn_in(window, async move |this, cx| { - match task.await { + cx.spawn_in(window, async move |this, cx| { + let result = task.await; + let url = req.url(); + + this.update_in(cx, |this, window, cx| { + match result { Ok(_) => { - this.update_in(cx, |this, window, cx| { - // Clear the current notification - window.clear_notification(challenge, cx); + window.clear_notification(challenge, cx); + window.push_notification(format!("{} has been authenticated", url), cx); - // Push a new notification - window.push_notification(format!("{url} has been authenticated"), cx); + // Save the authenticated relay to automatically authenticate future requests + settings.update(cx, |this, cx| { + this.add_trusted_relay(url, cx); + }); - // Save the authenticated relay to automatically authenticate future requests - settings.update(cx, |this, cx| { - this.add_trusted_relay(url, cx); - }); - - // Remove the challenge from the list of pending authentications - this.requests.remove(&req); - cx.notify(); - }) - .expect("Entity has been released"); + // Remove the challenge from the list of pending authentications + this.requests.remove(&req); + cx.notify(); } Err(e) => { - this.update_in(cx, |_, window, cx| { - window.push_notification(Notification::error(e.to_string()), cx); - }) - .expect("Entity has been released"); + window.push_notification(Notification::error(e.to_string()), cx); } - }; - }), - ); + } + }) + .ok(); + }) + .detach(); } /// Push a popup to approve the authentication request. - fn ask_for_approval(&mut self, req: AuthRequest, window: &mut Window, cx: &mut Context) { - let url = SharedString::from(req.url.clone().to_string()); + fn ask_for_approval(&self, req: &Arc, window: &Window, cx: &Context) { + let notification = self.notification(req, cx); + + cx.spawn_in(window, async move |_this, cx| { + cx.update(|window, cx| { + window.push_notification(notification, cx); + }) + .ok(); + }) + .detach(); + } + + /// Build a notification for the authentication request. + fn notification(&self, req: &Arc, cx: &Context) -> Notification { + let req = req.clone(); + let url = SharedString::from(req.url().to_string()); let entity = cx.entity().downgrade(); let loading = Rc::new(Cell::new(false)); - let note = Notification::new() + Notification::new() .custom_id(SharedString::from(&req.challenge)) .autohide(false) .icon(IconName::Info) @@ -317,7 +336,7 @@ impl RelayAuth { .into_any_element() }) .action(move |_window, _cx| { - let entity = entity.clone(); + let view = entity.clone(); let req = req.clone(); Button::new("approve") @@ -328,24 +347,18 @@ impl RelayAuth { .disabled(loading.get()) .on_click({ let loading = Rc::clone(&loading); + move |_ev, window, cx| { // Set loading state to true loading.set(true); // Process to approve the request - entity - .update(cx, |this, cx| { - this.response(req.clone(), window, cx); - }) - .ok(); + view.update(cx, |this, cx| { + this.response(&req, window, cx); + }) + .ok(); } }) - }); - - // Push the notification to the current window - window.push_notification(note, cx); - - // Bring the window to the front - cx.activate(true); + }) } } diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 42645c5..adfdf84 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -47,8 +47,8 @@ setting_accessors! { #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub enum AuthMode { #[default] - Manual, Auto, + Manual, } /// Signer kind @@ -121,7 +121,7 @@ pub struct AppSettings { _subscriptions: SmallVec<[Subscription; 1]>, /// Background tasks - tasks: SmallVec<[Task<()>; 1]>, + tasks: SmallVec<[Task>; 1]>, } impl AppSettings { @@ -151,13 +151,15 @@ impl AppSettings { tasks.push( // Load the initial settings cx.spawn(async move |this, cx| { - if let Ok(settings) = load_settings.await { - this.update(cx, |this, cx| { - this.values = settings; - cx.notify(); - }) - .ok(); - } + let settings = load_settings.await.unwrap_or(Settings::default()); + log::info!("Settings: {settings:?}"); + + // Update the settings state + this.update(cx, |this, cx| { + this.set_settings(settings, cx); + })?; + + Ok(()) }), ); @@ -168,6 +170,12 @@ impl AppSettings { } } + /// Update settings + fn set_settings(&mut self, settings: Settings, cx: &mut Context) { + self.values = settings; + cx.notify(); + } + /// Get settings from the database fn get_from_database(cx: &App) -> Task> { let nostr = NostrRegistry::global(cx); @@ -189,7 +197,7 @@ impl AppSettings { } if let Some(event) = client.database().query(filter).await?.first_owned() { - Ok(serde_json::from_str(&event.content).unwrap_or(Settings::default())) + Ok(serde_json::from_str(&event.content)?) } else { Err(anyhow!("Not found")) } @@ -203,13 +211,13 @@ impl AppSettings { self.tasks.push( // Run task in the background cx.spawn(async move |this, cx| { - if let Ok(settings) = task.await { - this.update(cx, |this, cx| { - this.values = settings; - cx.notify(); - }) - .ok(); - } + let settings = task.await?; + // Update settings + this.update(cx, |this, cx| { + this.set_settings(settings, cx); + })?; + + Ok(()) }), ); } @@ -218,36 +226,37 @@ impl AppSettings { pub fn save(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); + let settings = self.values.clone(); - if let Ok(content) = serde_json::to_string(&self.values) { - let task: Task> = cx.background_spawn(async move { - let signer = client.signer().context("Signer not found")?; - let public_key = signer.get_public_key().await?; + self.tasks.push(cx.background_spawn(async move { + let signer = client.signer().context("Signer not found")?; + let public_key = signer.get_public_key().await?; + let content = serde_json::to_string(&settings)?; - let event = EventBuilder::new(Kind::ApplicationSpecificData, content) - .tag(Tag::identifier(SETTINGS_IDENTIFIER)) - .build(public_key) - .sign(&Keys::generate()) - .await?; + let event = EventBuilder::new(Kind::ApplicationSpecificData, content) + .tag(Tag::identifier(SETTINGS_IDENTIFIER)) + .build(public_key) + .sign(&Keys::generate()) + .await?; - // Save event to the local database without sending to relays - client.database().save_event(&event).await?; + // Save event to the local database only + client.database().save_event(&event).await?; + log::info!("Settings saved successfully"); - Ok(()) - }); - - task.detach(); - } + Ok(()) + })); } - /// Check if the given relay is trusted - pub fn is_trusted_relay(&self, url: &RelayUrl, _cx: &App) -> bool { - self.values.trusted_relays.contains(url) + /// Check if the given relay is already authenticated + pub fn trusted_relay(&self, url: &RelayUrl, _cx: &App) -> bool { + self.values.trusted_relays.iter().any(|relay| { + relay.as_str_without_trailing_slash() == url.as_str_without_trailing_slash() + }) } /// Add a relay to the trusted list - pub fn add_trusted_relay(&mut self, url: RelayUrl, cx: &mut Context) { - self.values.trusted_relays.insert(url); + pub fn add_trusted_relay(&mut self, url: &RelayUrl, cx: &mut Context) { + self.values.trusted_relays.insert(url.clone()); cx.notify(); } diff --git a/crates/state/src/constants.rs b/crates/state/src/constants.rs index 5e54cfb..90a1993 100644 --- a/crates/state/src/constants.rs +++ b/crates/state/src/constants.rs @@ -10,7 +10,7 @@ pub const COOP_PUBKEY: &str = "npub126kl5fruqan90py77gf6pvfvygefl2mu2ukew6xdx5pc pub const APP_ID: &str = "su.reya.coop"; /// Keyring name -pub const KEYRING: &str = "Coop Secret Storage"; +pub const KEYRING: &str = "Coop Safe Storage"; /// Default timeout for subscription pub const TIMEOUT: u64 = 3; @@ -37,7 +37,7 @@ 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; 2] = ["wss://antiprimal.net", "wss://search.nos.today"]; +pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://relay.noswhere.com"]; /// Default bootstrap relays pub const BOOTSTRAP_RELAYS: [&str; 4] = [ diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 66615f4..06e1147 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -164,11 +164,6 @@ impl NostrRegistry { client.add_relay(url).await?; } - // Add wot relay to the relay pool - for url in WOT_RELAYS.into_iter() { - client.add_relay(url).await?; - } - // Connect to all added relays client.connect().await; @@ -261,14 +256,21 @@ impl NostrRegistry { .author(public_key) .limit(1); - client + let relays: Vec = client .database() .query(filter) .await .ok() .and_then(|events| events.first_owned()) .map(|event| nip17::extract_owned_relay_list(event).collect()) - .unwrap_or_default() + .unwrap_or_default(); + + for relay in relays.iter() { + client.add_relay(relay).await.ok(); + client.connect_relay(relay).await.ok(); + } + + relays }) }