From d1f0373916fa8accb2d36164e7c855231e89ccdc Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Thu, 2 Apr 2026 17:12:55 +0700 Subject: [PATCH] . --- crates/chat/src/lib.rs | 15 ++-- crates/coop/src/workspace.rs | 158 ++--------------------------------- crates/device/src/lib.rs | 26 +++--- crates/state/src/lib.rs | 127 ++-------------------------- crates/state/src/npubs.rs | 141 ------------------------------- 5 files changed, 34 insertions(+), 433 deletions(-) delete mode 100644 crates/state/src/npubs.rs diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index cd89cd0..8b565cc 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -128,22 +128,17 @@ impl ChatRegistry { subscriptions.push( // Subscribe to the signer event cx.subscribe(&nostr, |this, _state, event, cx| { - match event { - StateEvent::SignerSet => { - this.reset(cx); - this.get_rooms(cx); - } - StateEvent::RelayConnected => { - this.get_contact_list(cx); - this.get_messages(cx) - } - _ => {} + if event == &StateEvent::SignerSet { + this.reset(cx); + this.get_rooms(cx); }; }), ); // Run at the end of the current cycle cx.defer_in(window, |this, _window, cx| { + this.get_contact_list(cx); + this.get_messages(cx); this.get_rooms(cx); this.handle_notifications(cx); this.tracking(cx); diff --git a/crates/coop/src/workspace.rs b/crates/coop/src/workspace.rs index b88b981..f882be3 100644 --- a/crates/coop/src/workspace.rs +++ b/crates/coop/src/workspace.rs @@ -1,5 +1,3 @@ -use std::cell::Cell; -use std::rc::Rc; use std::sync::Arc; use ::settings::AppSettings; @@ -24,7 +22,7 @@ use ui::button::{Button, ButtonVariants}; use ui::dock::{ClosePanel, DockArea, DockItem, DockPlacement, PanelView}; use ui::menu::{DropdownMenu, PopupMenuItem}; use ui::notification::{Notification, NotificationKind}; -use ui::{Disableable, Icon, IconName, Root, Sizable, WindowExtension, h_flex, v_flex}; +use ui::{Icon, IconName, Root, Sizable, WindowExtension, h_flex, v_flex}; use crate::dialogs::restore::RestoreEncryption; use crate::dialogs::{accounts, settings}; @@ -51,7 +49,6 @@ enum Command { ToggleTheme, ToggleAccount, - RefreshRelayList, RefreshMessagingRelays, BackupEncryption, ImportEncryption, @@ -73,14 +70,8 @@ pub struct Workspace { /// App's Dock Area dock: Entity, - /// Whether a user's relay list is connected - relay_connected: bool, - - /// Whether the inbox is connected - inbox_connected: bool, - /// Event subscriptions - _subscriptions: SmallVec<[Subscription; 6]>, + _subscriptions: SmallVec<[Subscription; 5]>, } impl Workspace { @@ -88,7 +79,6 @@ impl Workspace { let chat = ChatRegistry::global(cx); let device = DeviceRegistry::global(cx); let nostr = NostrRegistry::global(cx); - let npubs = nostr.read(cx).npubs(); let titlebar = cx.new(|_| TitleBar::new()); let dock = cx.new(|cx| DockArea::new(window, cx)); @@ -102,15 +92,6 @@ impl Workspace { }), ); - subscriptions.push( - // Observe the npubs entity - cx.observe_in(&npubs, window, move |this, npubs, window, cx| { - if !npubs.read(cx).is_empty() { - this.account_selector(window, cx); - } - }), - ); - subscriptions.push( // Subscribe to the signer events cx.subscribe_in(&nostr, window, move |this, _state, event, window, cx| { @@ -141,25 +122,8 @@ impl Workspace { window.push_notification(note, cx); } - StateEvent::FetchingRelayList => { - let note = Notification::new() - .id::() - .message("Getting relay list...") - .with_kind(NotificationKind::Info); - - window.push_notification(note, cx); - } - StateEvent::RelayNotConfigured => { - this.relay_warning(window, cx); - } - StateEvent::RelayConnected => { - window.clear_notification::(cx); - this.set_relay_connected(true, cx); - } StateEvent::SignerSet => { this.set_center_layout(window, cx); - this.set_relay_connected(false, cx); - this.set_inbox_connected(false, cx); // Clear the signer notification window.clear_notification::(cx); } @@ -255,9 +219,6 @@ impl Workspace { }); }); } - ChatEvent::Subscribed => { - this.set_inbox_connected(true, cx); - } ChatEvent::Error(error) => { window.push_notification(Notification::error(error).autohide(false), cx); } @@ -285,8 +246,6 @@ impl Workspace { Self { titlebar, dock, - relay_connected: false, - inbox_connected: false, _subscriptions: subscriptions, } } @@ -318,18 +277,6 @@ impl Workspace { .collect() } - /// Set whether the relay list is connected - fn set_relay_connected(&mut self, connected: bool, cx: &mut Context) { - self.relay_connected = connected; - cx.notify(); - } - - /// Set whether the inbox is connected - fn set_inbox_connected(&mut self, connected: bool, cx: &mut Context) { - self.inbox_connected = connected; - cx.notify(); - } - /// Set the dock layout fn set_layout(&mut self, window: &mut Window, cx: &mut Context) { let left = DockItem::panel(Arc::new(sidebar::init(window, cx))); @@ -428,16 +375,6 @@ impl Workspace { ); }); } - Command::RefreshRelayList => { - let nostr = NostrRegistry::global(cx); - let signer = nostr.read(cx).signer(); - - if let Some(public_key) = signer.public_key() { - nostr.update(cx, |this, cx| { - this.ensure_relay_list(&public_key, cx); - }); - } - } Command::RefreshEncryption => { let device = DeviceRegistry::global(cx); device.update(cx, |this, cx| { @@ -630,55 +567,6 @@ impl Workspace { }); } - fn relay_warning(&mut self, window: &mut Window, cx: &mut Context) { - const BODY: &str = "Coop cannot found your gossip relay list. \ - Maybe you haven't set it yet or relay not responsed"; - - let nostr = NostrRegistry::global(cx); - let signer = nostr.read(cx).signer(); - - let Some(public_key) = signer.public_key() else { - return; - }; - - let entity = nostr.downgrade(); - let loading = Rc::new(Cell::new(false)); - - let note = Notification::new() - .autohide(false) - .id::() - .icon(IconName::Relay) - .title("Gossip Relays are required") - .message(BODY) - .action(move |_this, _window, _cx| { - let entity = entity.clone(); - let public_key = public_key.to_owned(); - - Button::new("retry") - .label("Retry") - .small() - .primary() - .loading(loading.get()) - .disabled(loading.get()) - .on_click({ - let loading = Rc::clone(&loading); - - move |_ev, _window, cx| { - // Set loading state to true - loading.set(true); - // Retry - entity - .update(cx, |this, cx| { - this.ensure_relay_list(&public_key, cx); - }) - .ok(); - } - }) - }); - - window.push_notification(note, cx); - } - fn titlebar_left(&mut self, cx: &mut Context) -> impl IntoElement { let nostr = NostrRegistry::global(cx); let signer = nostr.read(cx).signer(); @@ -759,9 +647,6 @@ impl Workspace { } fn titlebar_right(&mut self, cx: &mut Context) -> impl IntoElement { - let relay_connected = self.relay_connected; - let inbox_connected = self.inbox_connected; - let nostr = NostrRegistry::global(cx); let signer = nostr.read(cx).signer(); @@ -889,12 +774,6 @@ impl Workspace { .icon(IconName::Inbox) .small() .ghost() - .loading(!inbox_connected) - .disabled(!inbox_connected) - .when(!inbox_connected, |this| { - this.tooltip("Connecting to the user's messaging relays...") - }) - .when(inbox_connected, |this| this.indicator()) .dropdown_menu(move |this, _window, cx| { let chat = ChatRegistry::global(cx); let persons = PersonRegistry::global(cx); @@ -950,38 +829,17 @@ impl Workspace { Box::new(Command::RefreshMessagingRelays), ) .menu_with_icon( - "Update relays", + "Update gossip relays", + IconName::Relay, + Box::new(Command::ShowRelayList), + ) + .menu_with_icon( + "Update messaging relays", IconName::Settings, Box::new(Command::ShowMessaging), ) }), ) - .child( - Button::new("relay-list") - .icon(IconName::Relay) - .small() - .ghost() - .loading(!relay_connected) - .disabled(!relay_connected) - .when(!relay_connected, |this| { - this.tooltip("Connecting to the user's relay list...") - }) - .when(relay_connected, |this| this.indicator()) - .dropdown_menu(move |this, _window, _cx| { - this.label("User's Relay List") - .separator() - .menu_with_icon( - "Reload", - IconName::Refresh, - Box::new(Command::RefreshRelayList), - ) - .menu_with_icon( - "Update", - IconName::Settings, - Box::new(Command::ShowRelayList), - ) - }), - ) } } diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 61b4f69..f0fdbc3 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -111,18 +111,10 @@ impl DeviceRegistry { /// Create a new device registry instance fn new(window: &mut Window, cx: &mut Context) -> Self { let nostr = NostrRegistry::global(cx); - - // Get announcement when signer is set let subscription = cx.subscribe_in(&nostr, window, |this, _e, event, _window, cx| { - match event { - StateEvent::SignerSet => { - this.set_subscribing(false, cx); - this.set_requesting(false, cx); - } - StateEvent::RelayConnected => { - this.get_announcement(cx); - } - _ => {} + if event == &StateEvent::SignerSet { + this.set_subscribing(false, cx); + this.set_requesting(false, cx); }; }); @@ -147,6 +139,7 @@ impl DeviceRegistry { self.tasks.push(cx.background_spawn(async move { let mut notifications = client.notifications(); let mut processed_events = HashSet::new(); + let mut found_relay_list = false; while let Some(notification) = notifications.next().await { if let ClientNotification::Message { message, .. } = notification @@ -158,6 +151,17 @@ impl DeviceRegistry { } match event.kind { + Kind::RelayList => { + // Skip if the relay list has already been found + if found_relay_list { + continue; + } + // Verify the relay list event is signed by the user's signer + if verify_author(&client, event.as_ref()).await { + tx.send_async(event.into_owned()).await?; + found_relay_list = true; + } + } Kind::Custom(4454) => { if verify_author(&client, event.as_ref()).await { tx.send_async(event.into_owned()).await?; diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index d966015..491f998 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -44,18 +44,12 @@ impl Global for GlobalNostrRegistry {} /// Signer event. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum StateEvent { - /// Creating the signer - Creating, /// Connecting to the bootstrapping relay Connecting, /// Connected to the bootstrapping relay Connected, - /// Fetching the relay list - FetchingRelayList, - /// User has not set up NIP-65 relays - RelayNotConfigured, - /// Connected to NIP-65 relays - RelayConnected, + /// Creating the signer + Creating, /// A new signer has been set SignerSet, /// An error occurred @@ -154,6 +148,10 @@ impl NostrRegistry { // Run at the end of current cycle cx.defer_in(window, |this, _window, cx| { this.connect(cx); + // Create an identity if none exists + if this.npubs.read(cx).is_empty() { + this.create_identity(cx); + } }); Self { @@ -465,7 +463,6 @@ impl NostrRegistry { { let client = self.client(); let signer = self.signer(); - let key_dir = self.key_dir.clone(); // Create a task to update the signer and verify the public key let task: Task> = cx.background_spawn(async move { @@ -578,118 +575,6 @@ impl NostrRegistry { })); } - /// Ensure the relay list is fetched for the given public key - pub fn ensure_relay_list(&mut self, public_key: &PublicKey, cx: &mut Context) { - let task = self.get_event(public_key, Kind::RelayList, cx); - - // Emit a fetching event before starting the task - cx.emit(StateEvent::FetchingRelayList); - - self.tasks.push(cx.spawn(async move |this, cx| { - match task.await { - Ok(event) => { - this.update(cx, |this, cx| { - this.ensure_connection(&event, cx); - })?; - } - Err(e) => { - this.update(cx, |_this, cx| { - cx.emit(StateEvent::RelayNotConfigured); - cx.emit(StateEvent::error(e.to_string())); - })?; - } - }; - - Ok(()) - })); - } - - /// Ensure that the user is connected to the relay specified in the NIP-65 event. - pub fn ensure_connection(&mut self, event: &Event, cx: &mut Context) { - let client = self.client(); - // Extract the relay list from the event - let relays: Vec<(RelayUrl, Option)> = nip65::extract_relay_list(event) - .map(|(url, metadata)| (url.to_owned(), metadata.to_owned())) - .collect(); - - let task: Task> = cx.background_spawn(async move { - for (url, metadata) in relays.into_iter() { - match metadata { - Some(RelayMetadata::Read) => { - client - .add_relay(url) - .capabilities(RelayCapabilities::READ) - .connect_timeout(Duration::from_secs(TIMEOUT)) - .and_connect() - .await?; - } - Some(RelayMetadata::Write) => { - client - .add_relay(url) - .capabilities(RelayCapabilities::WRITE) - .connect_timeout(Duration::from_secs(TIMEOUT)) - .and_connect() - .await?; - } - None => { - client - .add_relay(url) - .capabilities(RelayCapabilities::NONE) - .connect_timeout(Duration::from_secs(TIMEOUT)) - .and_connect() - .await?; - } - } - } - Ok(()) - }); - - self.tasks.push(cx.spawn(async move |this, cx| { - match task.await { - Ok(_) => { - this.update(cx, |_this, cx| { - cx.emit(StateEvent::RelayConnected); - })?; - } - Err(e) => { - this.update(cx, |_this, cx| { - cx.emit(StateEvent::RelayNotConfigured); - cx.emit(StateEvent::error(e.to_string())); - })?; - } - }; - - Ok(()) - })); - } - - /// Get an event with the given author and kind. - pub fn get_event( - &self, - author: &PublicKey, - kind: Kind, - cx: &App, - ) -> Task> { - let client = self.client(); - let public_key = *author; - - cx.background_spawn(async move { - let filter = Filter::new().kind(kind).author(public_key).limit(1); - let mut stream = client - .stream_events(filter) - .timeout(Duration::from_millis(800)) - .await?; - - while let Some((_url, res)) = stream.next().await { - if let Ok(event) = res { - return Ok(event); - } - } - - Err(anyhow!("No event found")) - }) - } - /// Get the public key of a NIP-05 address pub fn get_address(&self, addr: Nip05Address, cx: &App) -> Task> { let client = self.client(); diff --git a/crates/state/src/npubs.rs b/crates/state/src/npubs.rs deleted file mode 100644 index 13dd899..0000000 --- a/crates/state/src/npubs.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; - -use anyhow::{Error, anyhow}; -use common::config_dir; -use gpui::{App, Context}; -use nostr_connect::prelude::*; - -use crate::{CLIENT_NAME, NOSTR_CONNECT_TIMEOUT}; - -#[derive(Debug)] -pub struct NostrRing { - /// Keys directory - dir: PathBuf, - - /// Master app keys used for various operations. - /// - /// Example: Nostr Connect and NIP-4e operations - app_keys: Keys, - - /// All local stored identities - npubs: Vec, -} - -impl NostrRing { - pub fn new(cx: &mut Context) -> Self { - let dir = config_dir().join("keys"); - let app_keys = get_or_init_app_keys(cx).unwrap_or(Keys::generate()); - - // Get all local stored npubs - let npubs = match Self::discover(&dir) { - Ok(npubs) => npubs, - Err(e) => { - log::error!("Failed to discover npubs: {e}"); - vec![] - } - }; - - Self { - dir, - app_keys, - npubs, - } - } - - /// Get the secret for a given npub, if it exists - fn get_secret(&self, public_key: &PublicKey, cx: &App) -> Result, Error> { - let npub = public_key.to_bech32()?; - let key_path = self.dir.join(format!("{}.npub", npub)); - - if let Ok(secret) = std::fs::read_to_string(key_path) { - let secret = SecretKey::parse(&secret)?; - let keys = Keys::new(secret); - - Ok(keys.into_nostr_signer()) - } else { - self.get_secret_keyring(&npub, cx) - } - } - - /// Get the secret for a given npub in the os credentials store - #[deprecated = "Use get_secret instead"] - fn get_secret_keyring(&self, user: &str, cx: &App) -> Result, Error> { - let read = cx.read_credentials(user); - let app_keys = self.app_keys.clone(); - - cx.foreground_executor().block_on(async move { - let (_, secret) = read - .await - .map_err(|_| anyhow!("Failed to get signer. Please re-import the secret key"))? - .ok_or_else(|| anyhow!("Failed to get signer. Please re-import the secret key"))?; - - // Try to parse as a direct secret key first - if let Ok(secret_key) = SecretKey::from_slice(&secret) { - return Ok(Keys::new(secret_key).into_nostr_signer()); - } - - // Convert the secret into string - let sec = String::from_utf8(secret) - .map_err(|_| anyhow!("Failed to parse secret as UTF-8"))?; - - // Try to parse as a NIP-46 URI - let uri = - NostrConnectUri::parse(&sec).map_err(|_| anyhow!("Failed to parse NIP-46 URI"))?; - - let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT); - let mut nip46 = NostrConnect::new(uri, app_keys, timeout, None)?; - - // Set the auth URL handler - nip46.auth_url_handler(CoopAuthUrlHandler); - - Ok(nip46.into_nostr_signer()) - }) - } - - /// Add a new npub to the keys directory - fn add(&mut self, public_key: PublicKey, secret: &str) -> Result<(), Error> { - let npub = public_key.to_bech32()?; - let key_path = self.dir.join(format!("{}.npub", npub)); - std::fs::write(key_path, secret)?; - - Ok(()) - } - - /// Remove a npub from the keys directory - fn remove(&self, public_key: &PublicKey) -> Result<(), Error> { - let npub = public_key.to_bech32()?; - let key_path = self.dir.join(format!("{}.npub", npub)); - std::fs::remove_file(key_path)?; - - Ok(()) - } - - /// Discover all npubs in the keys directory - fn discover(dir: &PathBuf) -> Result, Error> { - // Ensure keys directory exists - std::fs::create_dir_all(dir)?; - - let files = std::fs::read_dir(dir)?; - let mut entries = Vec::new(); - let mut npubs: Vec = Vec::new(); - - for file in files.flatten() { - let metadata = file.metadata()?; - let modified_time = metadata.modified()?; - let name = file.file_name().into_string().unwrap().replace(".npub", ""); - entries.push((modified_time, name)); - } - - // Sort by modification time (most recent first) - entries.sort_by(|a, b| b.0.cmp(&a.0)); - - for (_, name) in entries { - let public_key = PublicKey::parse(&name)?; - npubs.push(public_key); - } - - Ok(npubs) - } -}