diff --git a/Cargo.lock b/Cargo.lock index 8270456..bdd5d5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1912,7 +1912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1976,18 +1976,6 @@ dependencies = [ "zune-inflate", ] -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fastrand" version = "1.9.0" @@ -3601,17 +3589,6 @@ dependencies = [ "redox_syscall 0.7.2", ] -[[package]] -name = "libsqlite3-sys" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linebender_resource_handle" version = "0.1.1" @@ -4118,18 +4095,6 @@ dependencies = [ "nostr", ] -[[package]] -name = "nostr-gossip-sqlite" -version = "0.44.0" -source = "git+https://github.com/rust-nostr/nostr#b1ac65997d05424ad7b888dbdb5214b8999924ff" -dependencies = [ - "async-utility", - "nostr", - "nostr-gossip", - "rusqlite", - "tokio", -] - [[package]] name = "nostr-lmdb" version = "0.44.0" @@ -4179,7 +4144,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5012,7 +4977,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5431,30 +5396,6 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" -[[package]] -name = "rsqlite-vfs" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" -dependencies = [ - "hashbrown 0.16.1", - "thiserror 2.0.18", -] - -[[package]] -name = "rusqlite" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" -dependencies = [ - "bitflags 2.11.0", - "fallible-iterator", - "fallible-streaming-iterator", - "libsqlite3-sys", - "smallvec", - "sqlite-wasm-rs", -] - [[package]] name = "rust-embed" version = "8.11.0" @@ -5550,7 +5491,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6176,18 +6117,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "sqlite-wasm-rs" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b" -dependencies = [ - "cc", - "js-sys", - "rsqlite-vfs", - "wasm-bindgen", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -6242,7 +6171,6 @@ dependencies = [ "nostr", "nostr-blossom", "nostr-connect", - "nostr-gossip-sqlite", "nostr-lmdb", "nostr-sdk", "petname", @@ -6543,7 +6471,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7359,12 +7287,6 @@ dependencies = [ "sval_serde", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -7923,7 +7845,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 06664d2..337a929 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ reqwest_client = { git = "https://github.com/zed-industries/zed" } nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" } nostr-connect = { git = "https://github.com/rust-nostr/nostr" } nostr-blossom = { git = "https://github.com/rust-nostr/nostr" } -nostr-gossip-sqlite = { git = "https://github.com/rust-nostr/nostr" } nostr-sdk = { git = "https://github.com/rust-nostr/nostr" } nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip96", "nip59", "nip49", "nip44" ] } diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index e44e36f..65c2cbb 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -285,17 +285,24 @@ impl ChatRegistry { let signer = nostr.read(cx).signer(); let public_key = signer.public_key().unwrap(); + let write_relays = nostr.read(cx).write_relays(&public_key, cx); cx.background_spawn(async move { + let urls = write_relays.await; + // Construct filter for inbox relays let filter = Filter::new() .kind(Kind::InboxRelays) .author(public_key) .limit(1); + // Construct target for subscription + let target: HashMap<&RelayUrl, Filter> = + urls.iter().map(|relay| (relay, filter.clone())).collect(); + // Stream events from user's write relays let mut stream = client - .stream_events(filter) + .stream_events(target) .timeout(Duration::from_secs(TIMEOUT)) .await?; diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index ca34ad7..55a1915 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::time::Duration; -use anyhow::Error; +use anyhow::{anyhow, Error}; use common::EventUtils; use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task}; use itertools::Itertools; @@ -323,7 +323,6 @@ impl Room { /// Get gossip relays for each member pub fn connect(&self, cx: &App) -> HashMap>> { let nostr = NostrRegistry::global(cx); - let signer = nostr.read(cx).signer(); let public_key = signer.public_key().unwrap(); @@ -331,18 +330,25 @@ impl Room { let mut tasks = HashMap::new(); for member in members.into_iter() { - let client = nostr.read(cx).client(); - // Skip if member is the current user if member == public_key { continue; } + let client = nostr.read(cx).client(); + let write_relays = nostr.read(cx).write_relays(&member, cx); + tasks.insert( member, cx.background_spawn(async move { - let mut has_inbox = false; - let mut has_announcement = false; + let urls = 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 let inbox = Filter::new() @@ -356,12 +362,21 @@ impl Room { .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(vec![inbox.clone(), announcement.clone()]) + .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?; diff --git a/crates/coop/src/panels/messaging_relays.rs b/crates/coop/src/panels/messaging_relays.rs index caad3df..ad88825 100644 --- a/crates/coop/src/panels/messaging_relays.rs +++ b/crates/coop/src/panels/messaging_relays.rs @@ -10,7 +10,7 @@ use gpui::{ }; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; -use state::{NostrRegistry, TIMEOUT}; +use state::NostrRegistry; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; @@ -170,6 +170,15 @@ impl MessagingRelayPanel { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); + let signer = nostr.read(cx).signer(); + + let Some(public_key) = signer.public_key() else { + window.push_notification("Public Key not found", cx); + return; + }; + + // Get user's write relays + let write_relays = nostr.read(cx).write_relays(&public_key, cx); // Construct event tags let tags: Vec = self @@ -182,16 +191,14 @@ impl MessagingRelayPanel { self.set_updating(true, cx); let task: Task> = cx.background_spawn(async move { + let urls = write_relays.await; + // Construct nip17 event builder let builder = EventBuilder::new(Kind::InboxRelays, "").tags(tags); let event = client.sign_event_builder(builder).await?; // Set messaging relays - client - .send_event(&event) - .to_nip65() - .ok_timeout(Duration::from_secs(TIMEOUT)) - .await?; + client.send_event(&event).to(urls).await?; Ok(()) }); diff --git a/crates/coop/src/workspace.rs b/crates/coop/src/workspace.rs index e52bdee..fb887a1 100644 --- a/crates/coop/src/workspace.rs +++ b/crates/coop/src/workspace.rs @@ -582,6 +582,47 @@ impl Workspace { .dropdown_menu(move |this, _window, _cx| { this.min_w(px(260.)) .label("Relays") + .menu_element_with_disabled( + Box::new(Command::ShowRelayList), + true, + move |_window, cx| { + let nostr = NostrRegistry::global(cx); + let urls = nostr.read(cx).read_only_relays(&pkey, cx); + + v_flex() + .gap_1() + .w_full() + .items_start() + .justify_start() + .children({ + let mut items = vec![]; + + for url in urls.into_iter() { + items.push( + h_flex() + .h_6() + .w_full() + .gap_2() + .px_2() + .text_xs() + .bg(cx + .theme() + .elevated_surface_background) + .rounded(cx.theme().radius) + .child( + div() + .size_1() + .rounded_full() + .bg(gpui::green()), + ) + .child(url), + ); + } + + items + }) + }, + ) .separator() .menu_with_icon( "Reload", diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index c0eb0b9..876e1a8 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -255,16 +255,24 @@ impl DeviceRegistry { let signer = nostr.read(cx).signer(); let public_key = signer.public_key().unwrap(); + let write_relays = nostr.read(cx).write_relays(&public_key, cx); + let task: Task> = cx.background_spawn(async move { + let urls = write_relays.await; + // Construct the filter for the device announcement event let filter = Filter::new() .kind(Kind::Custom(10044)) .author(public_key) .limit(1); + // Construct target for subscription + let target: HashMap<&RelayUrl, Filter> = + urls.iter().map(|relay| (relay, filter.clone())).collect(); + // Stream events from user's write relays let mut stream = client - .stream_events(filter) + .stream_events(target) .timeout(Duration::from_secs(TIMEOUT)) .await?; @@ -305,12 +313,20 @@ impl DeviceRegistry { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - // Generate encryption keys + // Get current user + let signer = nostr.read(cx).signer(); + let public_key = signer.public_key().unwrap(); + + // Get user's write relays + let write_relays = nostr.read(cx).write_relays(&public_key, cx); + let keys = Keys::generate(); let secret = keys.secret_key().to_secret_hex(); let n = keys.public_key(); cx.background_spawn(async move { + let urls = write_relays.await; + // Construct an announcement event let event = client .sign_event_builder(EventBuilder::new(Kind::Custom(10044), "").tags(vec![ @@ -320,11 +336,7 @@ impl DeviceRegistry { .await?; // Publish announcement - client - .send_event(&event) - .to_nip65() - .ok_timeout(Duration::from_secs(TIMEOUT)) - .await?; + client.send_event(&event).to(urls).await?; // Save device keys to the database set_keys(&client, &secret).await?; @@ -404,15 +416,23 @@ impl DeviceRegistry { let signer = nostr.read(cx).signer(); let public_key = signer.public_key().unwrap(); + let write_relays = nostr.read(cx).write_relays(&public_key, cx); + let task: Task> = cx.background_spawn(async move { + let urls = write_relays.await; + // Construct a filter for device key requests let filter = Filter::new() .kind(Kind::Custom(4454)) .author(public_key) .since(Timestamp::now()); + // Construct target for subscription + let target: HashMap<&RelayUrl, Filter> = + urls.iter().map(|relay| (relay, filter.clone())).collect(); + // Subscribe to the device key requests on user's write relays - client.subscribe(filter).await?; + client.subscribe(target).await?; Ok(()) }); @@ -428,15 +448,23 @@ impl DeviceRegistry { let signer = nostr.read(cx).signer(); let public_key = signer.public_key().unwrap(); + let write_relays = nostr.read(cx).write_relays(&public_key, cx); + self.tasks.push(cx.background_spawn(async move { + let urls = write_relays.await; + // Construct a filter for device key requests let filter = Filter::new() .kind(Kind::Custom(4455)) .author(public_key) .since(Timestamp::now()); + // Construct target for subscription + let target: HashMap<&RelayUrl, Filter> = + urls.iter().map(|relay| (relay, filter.clone())).collect(); + // Subscribe to the device key requests on user's write relays - client.subscribe(filter).await?; + client.subscribe(target).await?; Ok(()) })); @@ -446,7 +474,11 @@ impl DeviceRegistry { fn request(&mut self, cx: &mut Context) { 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 write_relays = nostr.read(cx).write_relays(&public_key, cx); let app_keys = nostr.read(cx).app_keys().clone(); let app_pubkey = app_keys.public_key(); @@ -478,20 +510,18 @@ impl DeviceRegistry { Ok(Some(keys)) } None => { + let urls = write_relays.await; + // Construct an event for device key request let event = client .sign_event_builder(EventBuilder::new(Kind::Custom(4454), "").tags(vec![ - Tag::custom(TagKind::custom("P"), vec![app_pubkey]), Tag::client(app_name()), + Tag::custom(TagKind::custom("P"), vec![app_pubkey]), ])) .await?; // Send the event to write relays - client - .send_event(&event) - .to_nip65() - .ok_timeout(Duration::from_secs(TIMEOUT)) - .await?; + client.send_event(&event).to(urls).await?; Ok(None) } @@ -558,12 +588,18 @@ impl DeviceRegistry { pub fn approve(&self, event: &Event, cx: &App) -> Task> { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); + + // Get current user let signer = nostr.read(cx).signer(); + let public_key = signer.public_key().unwrap(); // Get user's write relays + let write_relays = nostr.read(cx).write_relays(&public_key, cx); let event = event.clone(); cx.background_spawn(async move { + let urls = write_relays.await; + // Get device keys let keys = get_keys(&client).await?; let secret = keys.secret_key().to_secret_hex(); @@ -591,11 +627,7 @@ impl DeviceRegistry { .await?; // Send the response event to the user's relay list - client - .send_event(&event) - .to_nip65() - .ok_timeout(Duration::from_secs(TIMEOUT)) - .await?; + client.send_event(&event).to(urls).await?; Ok(()) }) diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index 06bb079..fcedd0d 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -11,7 +11,6 @@ nostr.workspace = true nostr-sdk.workspace = true nostr-lmdb.workspace = true nostr-connect.workspace = true -nostr-gossip-sqlite.workspace = true nostr-blossom.workspace = true gpui.workspace = true diff --git a/crates/state/src/constants.rs b/crates/state/src/constants.rs index d4db28d..3357b2d 100644 --- a/crates/state/src/constants.rs +++ b/crates/state/src/constants.rs @@ -12,7 +12,7 @@ pub const APP_ID: &str = "su.reya.coop"; /// Keyring name pub const KEYRING: &str = "Coop Safe Storage"; -/// Default timeout in second for subscription +/// Default timeout for subscription pub const TIMEOUT: u64 = 2; /// Default delay for searching diff --git a/crates/state/src/gossip.rs b/crates/state/src/gossip.rs new file mode 100644 index 0000000..805a1ac --- /dev/null +++ b/crates/state/src/gossip.rs @@ -0,0 +1,83 @@ +use std::collections::{HashMap, HashSet}; + +use gpui::SharedString; +use nostr_sdk::prelude::*; + +/// Gossip +#[derive(Debug, Clone, Default)] +pub struct Gossip { + relays: HashMap)>>, +} + +impl Gossip { + pub fn read_only_relays(&self, public_key: &PublicKey) -> Vec { + self.relays + .get(public_key) + .map(|relays| { + relays + .iter() + .map(|(url, _)| url.to_string().into()) + .collect() + }) + .unwrap_or_default() + } + + /// Get read relays for a given public key + pub fn read_relays(&self, public_key: &PublicKey) -> Vec { + self.relays + .get(public_key) + .map(|relays| { + relays + .iter() + .filter_map(|(url, metadata)| { + if metadata.is_none() || metadata == &Some(RelayMetadata::Read) { + Some(url.to_owned()) + } else { + None + } + }) + .collect() + }) + .unwrap_or_default() + } + + /// Get write relays for a given public key + pub fn write_relays(&self, public_key: &PublicKey) -> Vec { + self.relays + .get(public_key) + .map(|relays| { + relays + .iter() + .filter_map(|(url, metadata)| { + if metadata.is_none() || metadata == &Some(RelayMetadata::Write) { + Some(url.to_owned()) + } else { + None + } + }) + .collect() + }) + .unwrap_or_default() + } + + /// Insert gossip relays for a public key + pub fn insert_relays(&mut self, event: &Event) { + self.relays.entry(event.pubkey).or_default().extend( + event + .tags + .iter() + .filter_map(|tag| { + if let Some(TagStandard::RelayMetadata { + relay_url, + metadata, + }) = tag.clone().to_standardized() + { + Some((relay_url, metadata)) + } else { + None + } + }) + .take(3), + ); + } +} diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 1c97311..fb5466c 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -1,25 +1,26 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::os::unix::fs::PermissionsExt; use std::sync::Arc; use std::time::Duration; use anyhow::{anyhow, Context as AnyhowContext, Error}; use common::config_dir; -use gpui::{App, AppContext, Context, Entity, Global, Task, Window}; +use gpui::{App, AppContext, Context, Entity, Global, SharedString, Task, Window}; use nostr_connect::prelude::*; -use nostr_gossip_sqlite::prelude::*; use nostr_lmdb::prelude::*; use nostr_sdk::prelude::*; mod blossom; mod constants; mod device; +mod gossip; mod nip05; mod signer; pub use blossom::*; pub use constants::*; pub use device::*; +pub use gossip::*; pub use nip05::*; pub use signer::*; @@ -55,6 +56,9 @@ pub struct NostrRegistry { /// Used for Nostr Connect and NIP-4e operations app_keys: Keys, + /// Custom gossip implementation + gossip: Entity, + /// Relay list state relay_list_state: RelayState, @@ -85,6 +89,9 @@ impl NostrRegistry { let app_keys = get_or_init_app_keys().unwrap_or(Keys::generate()); let signer = Arc::new(CoopSigner::new(app_keys.clone())); + // Construct the gossip entity + let gossip = cx.new(|_| Gossip::default()); + // Construct the nostr lmdb instance let lmdb = cx.foreground_executor().block_on(async move { NostrLmdb::open(config_dir().join("nostr")) @@ -92,21 +99,13 @@ impl NostrRegistry { .expect("Failed to initialize database") }); - // Construct the nostr gossip instance - let gossip = cx.foreground_executor().block_on(async move { - NostrGossipSqlite::open(config_dir().join("gossip")) - .await - .expect("Failed to initialize gossip database") - }); - // Construct the nostr client let client = ClientBuilder::default() .signer(signer.clone()) .database(lmdb) - .gossip(gossip) .automatic_authentication(false) .verify_subscriptions(false) - .connect_timeout(Duration::from_secs(5)) + .connect_timeout(Duration::from_secs(TIMEOUT)) .sleep_when_idle(SleepWhenIdle::Enabled { timeout: Duration::from_secs(600), }) @@ -115,12 +114,14 @@ impl NostrRegistry { // Run at the end of current cycle cx.defer_in(window, |this, _window, cx| { this.connect(cx); + this.handle_notifications(cx); }); Self { client, signer, app_keys, + gossip, relay_list_state: RelayState::Idle, connected: false, creating: false, @@ -145,7 +146,10 @@ impl NostrRegistry { } // Connect to all added relays - client.connect().and_wait(Duration::from_secs(5)).await; + client + .connect() + .and_wait(Duration::from_secs(TIMEOUT)) + .await; }) .await; @@ -159,6 +163,60 @@ impl NostrRegistry { })); } + /// Handle nostr notifications + fn handle_notifications(&mut self, cx: &mut Context) { + let client = self.client(); + let gossip = self.gossip.downgrade(); + + // Channel for communication between nostr and gpui + let (tx, rx) = flume::bounded::(2048); + + let task: Task> = 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, .. }, + .. + } = notification + { + // Skip if the event has already been processed + if processed_events.insert(event.id) { + match event.kind { + Kind::RelayList => { + tx.send_async(event.into_owned()).await?; + } + Kind::InboxRelays => { + tx.send_async(event.into_owned()).await?; + } + _ => {} + } + } + } + } + + 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 { + if let Kind::RelayList = event.kind { + gossip.update(cx, |this, cx| { + this.insert_relays(&event); + cx.notify(); + })?; + } + } + + Ok(()) + })); + } + /// Get the nostr client pub fn client(&self) -> Client { self.client.clone() @@ -189,6 +247,41 @@ impl NostrRegistry { self.relay_list_state.clone() } + /// Get all relays for a given public key without ensuring connections + pub fn read_only_relays(&self, public_key: &PublicKey, cx: &App) -> Vec { + self.gossip.read(cx).read_only_relays(public_key) + } + + /// 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(); + let relays = self.gossip.read(cx).write_relays(public_key); + + cx.background_spawn(async move { + // Ensure relay connections + for url in relays.iter() { + client.add_relay(url).and_connect().await.ok(); + } + + relays + }) + } + + /// Get a list of read relays for a given public key + pub fn read_relays(&self, public_key: &PublicKey, cx: &App) -> Task> { + let client = self.client(); + let relays = self.gossip.read(cx).read_relays(public_key); + + cx.background_spawn(async move { + // Ensure relay connections + for url in relays.iter() { + client.add_relay(url).and_connect().await.ok(); + } + + relays + }) + } + /// Set the connected status of the client fn set_connected(&mut self, cx: &mut Context) { self.connected = true; @@ -395,24 +488,16 @@ impl NostrRegistry { cx.notify(); } - /// Set relay list state - fn set_relay_list_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); - // Reset state - self.set_relay_list_state(RelayState::default(), cx); - self.tasks.push(cx.spawn(async move |this, cx| { let result = task.await?; // Update state this.update(cx, |this, cx| { - this.set_relay_list_state(result, cx); + this.relay_list_state = result; + cx.notify(); })?; Ok(())