From 34a32a1bd86f1e3e9c305d6ab755b8f17a45b2a0 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Sat, 7 Mar 2026 16:01:41 +0700 Subject: [PATCH] use nostr sdk gossip --- Cargo.lock | 92 +++++- Cargo.toml | 1 + crates/chat/src/lib.rs | 25 +- crates/coop/src/panels/contact_list.rs | 21 +- crates/coop/src/panels/messaging_relays.rs | 23 +- crates/coop/src/workspace.rs | 4 +- crates/device/src/lib.rs | 74 +---- crates/state/Cargo.toml | 1 + crates/state/src/lib.rs | 325 ++------------------- 9 files changed, 135 insertions(+), 431 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fdac7ab..6fd80a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1949,7 +1949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2013,6 +2013,18 @@ 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" @@ -3702,6 +3714,17 @@ dependencies = [ "redox_syscall 0.7.3", ] +[[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" @@ -4250,6 +4273,18 @@ dependencies = [ "nostr", ] +[[package]] +name = "nostr-gossip-sqlite" +version = "0.44.0" +source = "git+https://github.com/rust-nostr/nostr#9bcc6cd779a7c6eb41509b37aee4575fa5ae47b9" +dependencies = [ + "async-utility", + "nostr", + "nostr-gossip", + "rusqlite", + "tokio", +] + [[package]] name = "nostr-lmdb" version = "0.44.0" @@ -4299,7 +4334,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5160,7 +5195,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -5585,6 +5620,30 @@ 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" @@ -5680,7 +5739,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6307,6 +6366,18 @@ 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" @@ -6361,6 +6432,7 @@ dependencies = [ "nostr", "nostr-blossom", "nostr-connect", + "nostr-gossip-sqlite", "nostr-lmdb", "nostr-sdk", "petname", @@ -6661,7 +6733,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7189,7 +7261,7 @@ checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7491,6 +7563,12 @@ 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" @@ -8054,7 +8132,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8319337..b35859f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ 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 8ab42f2..eea3538 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -268,29 +268,20 @@ impl ChatRegistry { return; }; - let write_relays = nostr.read(cx).write_relays(&public_key, cx); - let task: Task> = cx.background_spawn(async move { let id = SubscriptionId::new("contact-list"); let opts = SubscribeAutoCloseOptions::default() .exit_policy(ReqExitPolicy::ExitOnEOSE) .timeout(Some(Duration::from_secs(TIMEOUT))); - // Get user's write relays - let urls = write_relays.await; - // Construct filter for inbox relays let filter = Filter::new() .kind(Kind::ContactList) .author(public_key) .limit(1); - // Construct target for subscription - let target: HashMap<&RelayUrl, Filter> = - urls.iter().map(|relay| (relay, filter.clone())).collect(); - // Subscribe - client.subscribe(target).close_on(opts).with_id(id).await?; + client.subscribe(filter).close_on(opts).with_id(id).await?; Ok(()) }); @@ -323,14 +314,8 @@ impl ChatRegistry { let client = nostr.read(cx).client(); let signer = nostr.read(cx).signer(); - let Some(public_key) = signer.public_key() else { - return Task::ready(Err(anyhow!("User not found"))); - }; - - let write_relays = nostr.read(cx).write_relays(&public_key, cx); - cx.background_spawn(async move { - let urls = write_relays.await; + let public_key = signer.get_public_key().await?; // Construct filter for inbox relays let filter = Filter::new() @@ -338,13 +323,9 @@ impl ChatRegistry { .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(target) + .stream_events(filter) .timeout(Duration::from_secs(TIMEOUT)) .await?; diff --git a/crates/coop/src/panels/contact_list.rs b/crates/coop/src/panels/contact_list.rs index addbb82..c6899ff 100644 --- a/crates/coop/src/panels/contact_list.rs +++ b/crates/coop/src/panels/contact_list.rs @@ -4,20 +4,20 @@ use std::time::Duration; use anyhow::{Context as AnyhowContext, Error}; use gpui::prelude::FluentBuilder; use gpui::{ - div, rems, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, + AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, - Task, TextAlign, Window, + Task, TextAlign, Window, div, rems, }; use nostr_sdk::prelude::*; use person::PersonRegistry; -use smallvec::{smallvec, SmallVec}; +use smallvec::{SmallVec, smallvec}; use state::NostrRegistry; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; use ui::input::{InputEvent, InputState, TextInput}; -use ui::{h_flex, v_flex, Disableable, IconName, Sizable, StyledExt, WindowExtension}; +use ui::{Disableable, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex}; pub fn init(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| ContactListPanel::new(window, cx)) @@ -156,15 +156,6 @@ impl ContactListPanel { 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); // Get contacts let contacts: Vec = self @@ -177,14 +168,12 @@ impl ContactListPanel { self.set_updating(true, cx); let task: Task> = cx.background_spawn(async move { - let urls = write_relays.await; - // Construct contact list event builder let builder = EventBuilder::contact_list(contacts); let event = client.sign_event_builder(builder).await?; // Set contact list - client.send_event(&event).to(urls).await?; + client.send_event(&event).to_nip65().await?; Ok(()) }); diff --git a/crates/coop/src/panels/messaging_relays.rs b/crates/coop/src/panels/messaging_relays.rs index 47d46b2..5c5c36b 100644 --- a/crates/coop/src/panels/messaging_relays.rs +++ b/crates/coop/src/panels/messaging_relays.rs @@ -1,21 +1,21 @@ use std::collections::HashSet; use std::time::Duration; -use anyhow::{anyhow, Context as AnyhowContext, Error}; +use anyhow::{Context as AnyhowContext, Error, anyhow}; use gpui::prelude::FluentBuilder; use gpui::{ - div, rems, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, + AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, - Task, TextAlign, Window, + Task, TextAlign, Window, div, rems, }; use nostr_sdk::prelude::*; -use smallvec::{smallvec, SmallVec}; +use smallvec::{SmallVec, smallvec}; use state::NostrRegistry; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; use ui::input::{InputEvent, InputState, TextInput}; -use ui::{divider, h_flex, v_flex, Disableable, IconName, Sizable, StyledExt, WindowExtension}; +use ui::{Disableable, IconName, Sizable, StyledExt, WindowExtension, divider, h_flex, v_flex}; const MSG: &str = "Messaging Relays are relays that hosted all your messages. \ Other users will find your relays and send messages to it."; @@ -170,15 +170,6 @@ 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 @@ -191,14 +182,12 @@ 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(urls).await?; + client.send_event(&event).to_nip65().await?; Ok(()) }); diff --git a/crates/coop/src/workspace.rs b/crates/coop/src/workspace.rs index d75691e..f2a58a9 100644 --- a/crates/coop/src/workspace.rs +++ b/crates/coop/src/workspace.rs @@ -286,7 +286,7 @@ impl Workspace { Command::RefreshRelayList => { let nostr = NostrRegistry::global(cx); nostr.update(cx, |this, cx| { - this.ensure_relay_list(cx); + //this.ensure_relay_list(cx); }); } Command::ResetEncryption => { @@ -685,7 +685,7 @@ impl Workspace { }) .dropdown_menu(move |this, _window, cx| { let nostr = NostrRegistry::global(cx); - let urls = nostr.read(cx).read_only_relays(&pkey, cx); + let urls: Vec = vec![]; // Header let menu = this.min_w(px(260.)).label("Relays"); diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 6a5aa5c..62c8b6f 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -3,22 +3,22 @@ use std::collections::{HashMap, HashSet}; use std::rc::Rc; use std::time::Duration; -use anyhow::{anyhow, Context as AnyhowContext, Error}; +use anyhow::{Context as AnyhowContext, Error, anyhow}; use gpui::{ - div, App, AppContext, Context, Entity, Global, IntoElement, ParentElement, SharedString, - Styled, Subscription, Task, Window, + App, AppContext, Context, Entity, Global, IntoElement, ParentElement, SharedString, Styled, + Subscription, Task, Window, div, }; use nostr_sdk::prelude::*; use person::PersonRegistry; -use smallvec::{smallvec, SmallVec}; +use smallvec::{SmallVec, smallvec}; use state::{ - app_name, Announcement, DeviceState, NostrRegistry, RelayState, DEVICE_GIFTWRAP, TIMEOUT, + Announcement, DEVICE_GIFTWRAP, DeviceState, NostrRegistry, RelayState, TIMEOUT, app_name, }; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; use ui::notification::Notification; -use ui::{h_flex, v_flex, Disableable, IconName, Sizable, WindowExtension}; +use ui::{Disableable, IconName, Sizable, WindowExtension, h_flex, v_flex}; const IDENTIFIER: &str = "coop:device"; const MSG: &str = "You've requested an encryption key from another device. \ @@ -248,25 +248,16 @@ impl DeviceRegistry { // Reset state before fetching announcement self.reset(cx); - // Get user's write relays - 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(target) + .stream_events(filter) .timeout(Duration::from_secs(TIMEOUT)) .await?; @@ -307,22 +298,12 @@ impl DeviceRegistry { pub fn create_encryption(&self, cx: &App) -> Task> { 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 { - return Task::ready(Err(anyhow!("User not found"))); - }; - - // 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![ @@ -332,7 +313,7 @@ impl DeviceRegistry { .await?; // Publish announcement - client.send_event(&event).to(urls).await?; + client.send_event(&event).to_nip65().await?; // Save device keys to the database set_keys(&client, &secret).await?; @@ -409,23 +390,15 @@ impl DeviceRegistry { return; }; - 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(target).await?; + client.subscribe(filter).await?; Ok(()) }); @@ -443,23 +416,15 @@ impl DeviceRegistry { return; }; - 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(target).await?; + client.subscribe(filter).await?; Ok(()) })); @@ -471,12 +436,6 @@ impl DeviceRegistry { let client = nostr.read(cx).client(); let signer = nostr.read(cx).signer(); - let Some(public_key) = signer.public_key() else { - return; - }; - - 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(); @@ -507,8 +466,6 @@ 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![ @@ -518,7 +475,7 @@ impl DeviceRegistry { .await?; // Send the event to write relays - client.send_event(&event).to(urls).await?; + client.send_event(&event).to_nip65().await?; Ok(None) } @@ -586,18 +543,11 @@ impl DeviceRegistry { let client = nostr.read(cx).client(); let signer = nostr.read(cx).signer(); - let Some(public_key) = signer.public_key() else { - return; - }; - // Get user's write relays - let write_relays = nostr.read(cx).write_relays(&public_key, cx); let event = event.clone(); let id: SharedString = event.id.to_hex().into(); let task: Task> = 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(); @@ -626,7 +576,7 @@ impl DeviceRegistry { let event = client.sign_event_builder(builder).await?; // Send the response event to the user's relay list - client.send_event(&event).to(urls).await?; + client.send_event(&event).to_nip65().await?; Ok(()) }); diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index fcedd0d..81736b5 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -10,6 +10,7 @@ common = { path = "../common" } nostr.workspace = true nostr-sdk.workspace = true nostr-lmdb.workspace = true +nostr-gossip-sqlite.workspace = true nostr-connect.workspace = true nostr-blossom.workspace = true diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index e7fb8b6..251a38c 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -1,11 +1,12 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use anyhow::{Context as AnyhowContext, Error, anyhow}; use common::config_dir; -use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, SharedString, Task, Window}; +use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task, Window}; use nostr_connect::prelude::*; +use nostr_gossip_sqlite::prelude::*; use nostr_lmdb::prelude::*; use nostr_sdk::prelude::*; @@ -53,9 +54,6 @@ pub struct NostrRegistry { /// Local public keys npubs: Entity>, - /// Custom gossip implementation - gossip: Entity, - /// App keys /// /// Used for Nostr Connect and NIP-4e operations @@ -88,8 +86,12 @@ impl NostrRegistry { // Construct the nostr npubs entity let npubs = cx.new(|_| vec![]); - // Construct the gossip entity - let gossip = cx.new(|_| Gossip::default()); + // 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 instance") + }); // Construct the nostr lmdb instance let lmdb = cx.foreground_executor().block_on(async move { @@ -101,6 +103,7 @@ impl NostrRegistry { // Construct the nostr client let client = ClientBuilder::default() .signer(signer.clone()) + .gossip(gossip) .database(lmdb) .automatic_authentication(false) .verify_subscriptions(false) @@ -113,7 +116,6 @@ impl NostrRegistry { // Run at the end of current cycle cx.defer_in(window, |this, _window, cx| { this.connect(cx); - this.handle_notifications(cx); }); Self { @@ -121,7 +123,6 @@ impl NostrRegistry { signer, npubs, app_keys, - gossip, relay_list_state: RelayState::Idle, tasks: vec![], } @@ -173,60 +174,6 @@ 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); - - self.tasks.push(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, - subscription_id, - }, - .. - } = notification - { - if !processed_events.insert(event.id) { - // Skip if the event has already been processed - continue; - } - - if let Kind::RelayList = event.kind { - if subscription_id.as_str().contains("room-") { - get_events_for_room(&client, &event).await.ok(); - } - tx.send_async(event.into_owned()).await?; - } - } - } - - Ok(()) - })); - - 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 all used npubs fn get_npubs(&mut self, cx: &mut Context) { let npubs = self.npubs.downgrade(); @@ -307,74 +254,49 @@ impl NostrRegistry { let task: Task> = cx.background_spawn(async move { let signer = async_keys.into_nostr_signer(); - // Get default relay list + // Construct relay list event let relay_list = default_relay_list(); - - // Extract write relays - let write_urls: Vec = relay_list - .iter() - .filter_map(|(url, metadata)| { - if metadata.is_none() || metadata == &Some(RelayMetadata::Write) { - Some(url) - } else { - None - } - }) - .cloned() - .collect(); - - // Ensure connected to all relays - for (url, _metadata) in relay_list.iter() { - client.add_relay(url).and_connect().await?; - } - - // Publish relay list event let event = EventBuilder::relay_list(relay_list).sign(&signer).await?; - let output = client + + // Publish relay list + client .send_event(&event) .to(BOOTSTRAP_RELAYS) .ok_timeout(Duration::from_secs(TIMEOUT)) .await?; - log::info!("Sent gossip relay list: {output:?}"); - // Construct the default metadata let name = petname::petname(2, "-").unwrap_or("Cooper".to_string()); let avatar = Url::parse(&format!("https://avatar.vercel.sh/{name}")).unwrap(); let metadata = Metadata::new().display_name(&name).picture(avatar); + let event = EventBuilder::metadata(&metadata).sign(&signer).await?; // Publish metadata event - let event = EventBuilder::metadata(&metadata).sign(&signer).await?; client .send_event(&event) - .to(&write_urls) + .to_nip65() .ack_policy(AckPolicy::none()) .await?; // Construct the default contact list let contacts = vec![Contact::new(PublicKey::parse(COOP_PUBKEY).unwrap())]; + let event = EventBuilder::contact_list(contacts).sign(&signer).await?; // Publish contact list event - let event = EventBuilder::contact_list(contacts).sign(&signer).await?; client .send_event(&event) - .to(&write_urls) + .to_nip65() .ack_policy(AckPolicy::none()) .await?; // Construct the default messaging relay list let relays = default_messaging_relays(); - - // Ensure connected to all relays - for url in relays.iter() { - client.add_relay(url).and_connect().await?; - } + let event = EventBuilder::nip17_relay_list(relays).sign(&signer).await?; // Publish messaging relay list event - let event = EventBuilder::nip17_relay_list(relays).sign(&signer).await?; client .send_event(&event) - .to(&write_urls) + .to_nip65() .ack_policy(AckPolicy::none()) .await?; @@ -480,9 +402,6 @@ impl NostrRegistry { } }); - // Ensure relay list for the user - this.ensure_relay_list(cx); - // Emit signer changed event cx.emit(SignerEvent::Set); })?; @@ -592,164 +511,6 @@ impl NostrRegistry { })); } - /// 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?; - - // Update state - this.update(cx, |this, cx| { - this.relay_list_state = result; - cx.notify(); - })?; - - Ok(()) - })); - } - - // Verify relay list for current user - fn verify_relay_list(&mut self, cx: &mut Context) -> Task> { - let client = self.client(); - - cx.background_spawn(async move { - let signer = client.signer().context("Signer not found")?; - let public_key = signer.get_public_key().await?; - - 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(); - - // Stream events from the bootstrap relays - let mut stream = client - .stream_events(target) - .timeout(Duration::from_secs(TIMEOUT)) - .await?; - - while let Some((_url, res)) = stream.next().await { - match res { - Ok(event) => { - log::info!("Received relay list event: {event:?}"); - return Ok(RelayState::Configured); - } - Err(e) => { - log::error!("Failed to receive relay list event: {e}"); - } - } - } - - Ok(RelayState::NotConfigured) - }) - } - - /// 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(); - 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 - }) - } - - /// 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 the public key of a NIP-05 address pub fn get_address(&self, addr: Nip05Address, cx: &App) -> Task> { let client = self.client(); @@ -932,52 +693,6 @@ fn get_or_init_app_keys() -> Result { Ok(keys) } -async fn get_events_for_room(client: &Client, nip65: &Event) -> Result<(), Error> { - // Subscription options - let opts = SubscribeAutoCloseOptions::default() - .timeout(Some(Duration::from_secs(TIMEOUT))) - .exit_policy(ReqExitPolicy::ExitOnEOSE); - - // Extract write relays from event - let write_relays: Vec<&RelayUrl> = nip65::extract_relay_list(nip65) - .filter_map(|(url, metadata)| { - if metadata.is_none() || metadata == &Some(RelayMetadata::Write) { - Some(url) - } else { - None - } - }) - .collect(); - - // Ensure relay connections - for url in write_relays.iter() { - client.add_relay(*url).and_connect().await.ok(); - } - - // Construct filter for inbox relays - let inbox = Filter::new() - .kind(Kind::InboxRelays) - .author(nip65.pubkey) - .limit(1); - - // Construct filter for encryption announcement - let announcement = Filter::new() - .kind(Kind::Custom(10044)) - .author(nip65.pubkey) - .limit(1); - - // Construct target for subscription - let target: HashMap<&RelayUrl, Vec> = write_relays - .into_iter() - .map(|relay| (relay, vec![inbox.clone(), announcement.clone()])) - .collect(); - - // Subscribe to inbox relays and encryption announcements - client.subscribe(target).close_on(opts).await?; - - Ok(()) -} - fn default_relay_list() -> Vec<(RelayUrl, Option)> { vec![ (