feat: refactor to use gpui event instead of local state #18

Merged
reya merged 8 commits from new-backend into master 2026-03-10 08:19:03 +00:00
7 changed files with 71 additions and 97 deletions
Showing only changes of commit 8850f158ab - Show all commits

View File

@@ -1,5 +1,4 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::time::Duration; use std::time::Duration;
@@ -10,7 +9,7 @@ use itertools::Itertools;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use person::{Person, PersonRegistry}; use person::{Person, PersonRegistry};
use settings::{RoomConfig, SignerKind}; use settings::{RoomConfig, SignerKind};
use state::{NostrRegistry, BOOTSTRAP_RELAYS, TIMEOUT}; use state::{NostrRegistry, TIMEOUT};
use crate::NewMessage; use crate::NewMessage;
@@ -333,9 +332,6 @@ impl Room {
let signer = nostr.read(cx).signer(); let signer = nostr.read(cx).signer();
let sender = signer.public_key(); let sender = signer.public_key();
// Get room's id
let id = self.id;
// Get all members, excluding the sender // Get all members, excluding the sender
let members: Vec<PublicKey> = self let members: Vec<PublicKey> = self
.members .members
@@ -345,30 +341,27 @@ impl Room {
.collect(); .collect();
cx.background_spawn(async move { cx.background_spawn(async move {
let id = SubscriptionId::new(format!("room-{id}"));
let opts = SubscribeAutoCloseOptions::default() let opts = SubscribeAutoCloseOptions::default()
.exit_policy(ReqExitPolicy::ExitOnEOSE) .exit_policy(ReqExitPolicy::ExitOnEOSE)
.timeout(Some(Duration::from_secs(TIMEOUT))); .timeout(Some(Duration::from_secs(TIMEOUT)));
// Construct filters for each member for public_key in members.into_iter() {
let filters: Vec<Filter> = members let inbox = Filter::new()
.into_iter()
.map(|public_key| {
Filter::new()
.author(public_key) .author(public_key)
.kind(Kind::RelayList) .kind(Kind::InboxRelays)
.limit(1) .limit(1);
})
.collect();
// Construct target for subscription let announcement = Filter::new()
let target: HashMap<&str, Vec<Filter>> = BOOTSTRAP_RELAYS .author(public_key)
.into_iter() .kind(Kind::Custom(10044))
.map(|relay| (relay, filters.clone())) .limit(1);
.collect();
// Subscribe to the target // Subscribe to the target
client.subscribe(target).close_on(opts).with_id(id).await?; client
.subscribe(vec![inbox, announcement])
.close_on(opts)
.await?;
}
Ok(()) Ok(())
}) })
@@ -491,15 +484,9 @@ impl Room {
// Process each member // Process each member
for member in members { for member in members {
let relays = member.messaging_relays();
let announcement = member.announcement(); let announcement = member.announcement();
let public_key = member.public_key(); let public_key = member.public_key();
if relays.is_empty() {
reports.push(SendReport::new(public_key).error("No messaging relays"));
continue;
}
// Handle encryption signer requirements // Handle encryption signer requirements
if signer_kind.encryption() { if signer_kind.encryption() {
if announcement.is_none() { if announcement.is_none() {
@@ -535,8 +522,7 @@ impl Room {
SignerKind::User => (member.public_key(), user_signer.clone()), SignerKind::User => (member.public_key(), user_signer.clone()),
}; };
match send_gift_wrap(&client, &signer, &receiver, &rumor, relays, public_key).await match send_gift_wrap(&client, &signer, &receiver, &rumor, public_key).await {
{
Ok((report, _)) => { Ok((report, _)) => {
reports.push(report); reports.push(report);
sents += 1; sents += 1;
@@ -549,12 +535,10 @@ impl Room {
// Send backup to current user if needed // Send backup to current user if needed
if backup && sents >= 1 { if backup && sents >= 1 {
let relays = sender.messaging_relays();
let public_key = sender.public_key(); let public_key = sender.public_key();
let signer = encryption_signer.as_ref().unwrap_or(&user_signer); let signer = encryption_signer.as_ref().unwrap_or(&user_signer);
match send_gift_wrap(&client, signer, &public_key, &rumor, relays, public_key).await match send_gift_wrap(&client, signer, &public_key, &rumor, public_key).await {
{
Ok((report, _)) => reports.push(report), Ok((report, _)) => reports.push(report),
Err(report) => reports.push(report), Err(report) => reports.push(report),
} }
@@ -571,22 +555,16 @@ async fn send_gift_wrap<T>(
signer: &T, signer: &T,
receiver: &PublicKey, receiver: &PublicKey,
rumor: &UnsignedEvent, rumor: &UnsignedEvent,
relays: &[RelayUrl],
public_key: PublicKey, public_key: PublicKey,
) -> Result<(SendReport, bool), SendReport> ) -> Result<(SendReport, bool), SendReport>
where where
T: NostrSigner + 'static, T: NostrSigner + 'static,
{ {
// Ensure relay connections
for url in relays {
client.add_relay(url).and_connect().await.ok();
}
match EventBuilder::gift_wrap(signer, receiver, rumor.clone(), []).await { match EventBuilder::gift_wrap(signer, receiver, rumor.clone(), []).await {
Ok(event) => { Ok(event) => {
match client match client
.send_event(&event) .send_event(&event)
.to(relays) .to_nip17()
.ack_policy(AckPolicy::none()) .ack_policy(AckPolicy::none())
.await .await
{ {

View File

@@ -2,9 +2,9 @@ use std::sync::{Arc, Mutex};
use assets::Assets; use assets::Assets;
use gpui::{ use gpui::{
actions, point, px, size, App, AppContext, Bounds, KeyBinding, Menu, MenuItem, SharedString, App, AppContext, Bounds, KeyBinding, Menu, MenuItem, SharedString, TitlebarOptions,
TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions,
WindowOptions, actions, point, px, size,
}; };
use gpui_platform::application; use gpui_platform::application;
use state::{APP_ID, CLIENT_NAME}; use state::{APP_ID, CLIENT_NAME};
@@ -86,7 +86,7 @@ fn main() {
state::init(window, cx); state::init(window, cx);
// Initialize person registry // Initialize person registry
person::init(cx); person::init(window, cx);
// Initialize relay auth registry // Initialize relay auth registry
relay_auth::init(window, cx); relay_auth::init(window, cx);

View File

@@ -5,7 +5,7 @@ use std::time::Duration;
use anyhow::{Error, anyhow}; use anyhow::{Error, anyhow};
use common::EventUtils; use common::EventUtils;
use gpui::{App, AppContext, Context, Entity, Global, Task}; use gpui::{App, AppContext, Context, Entity, Global, Task, Window};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{Announcement, BOOTSTRAP_RELAYS, NostrRegistry, TIMEOUT}; use state::{Announcement, BOOTSTRAP_RELAYS, NostrRegistry, TIMEOUT};
@@ -14,8 +14,8 @@ mod person;
pub use person::*; pub use person::*;
pub fn init(cx: &mut App) { pub fn init(window: &mut Window, cx: &mut App) {
PersonRegistry::set_global(cx.new(PersonRegistry::new), cx); PersonRegistry::set_global(cx.new(|cx| PersonRegistry::new(window, cx)), cx);
} }
struct GlobalPersonRegistry(Entity<PersonRegistry>); struct GlobalPersonRegistry(Entity<PersonRegistry>);
@@ -42,7 +42,7 @@ pub struct PersonRegistry {
sender: flume::Sender<PublicKey>, sender: flume::Sender<PublicKey>,
/// Tasks for asynchronous operations /// Tasks for asynchronous operations
_tasks: SmallVec<[Task<()>; 4]>, tasks: SmallVec<[Task<()>; 4]>,
} }
impl PersonRegistry { impl PersonRegistry {
@@ -57,7 +57,7 @@ impl PersonRegistry {
} }
/// Create a new person registry instance /// Create a new person registry instance
fn new(cx: &mut Context<Self>) -> Self { fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
@@ -111,33 +111,16 @@ impl PersonRegistry {
}), }),
); );
tasks.push(
// Load all user profiles from the database // Load all user profiles from the database
cx.spawn(async move |this, cx| { cx.defer_in(window, |this, _window, cx| {
let result = cx this.load(cx);
.background_executor() });
.await_on_background(async move { load_persons(&client).await })
.await;
match result {
Ok(persons) => {
this.update(cx, |this, cx| {
this.bulk_inserts(persons, cx);
})
.ok();
}
Err(e) => {
log::error!("Failed to load all persons from the database: {e}");
}
};
}),
);
Self { Self {
persons: HashMap::new(), persons: HashMap::new(),
seens: Rc::new(RefCell::new(HashSet::new())), seens: Rc::new(RefCell::new(HashSet::new())),
sender: mta_tx, sender: mta_tx,
_tasks: tasks, tasks,
} }
} }
@@ -163,25 +146,21 @@ impl PersonRegistry {
let metadata = Metadata::from_json(&event.content).unwrap_or_default(); let metadata = Metadata::from_json(&event.content).unwrap_or_default();
let person = Person::new(event.pubkey, metadata); let person = Person::new(event.pubkey, metadata);
let val = Box::new(person); let val = Box::new(person);
// Send // Send
tx.send_async(Dispatch::Person(val)).await.ok(); tx.send_async(Dispatch::Person(val)).await.ok();
} }
Kind::ContactList => { Kind::ContactList => {
let public_keys = event.extract_public_keys(); let public_keys = event.extract_public_keys();
// Get metadata for all public keys // Get metadata for all public keys
get_metadata(client, public_keys).await.ok(); get_metadata(client, public_keys).await.ok();
} }
Kind::InboxRelays => { Kind::InboxRelays => {
let val = Box::new(event.into_owned()); let val = Box::new(event.into_owned());
// Send // Send
tx.send_async(Dispatch::Relays(val)).await.ok(); tx.send_async(Dispatch::Relays(val)).await.ok();
} }
Kind::Custom(10044) => { Kind::Custom(10044) => {
let val = Box::new(event.into_owned()); let val = Box::new(event.into_owned());
// Send // Send
tx.send_async(Dispatch::Announcement(val)).await.ok(); tx.send_async(Dispatch::Announcement(val)).await.ok();
} }
@@ -198,7 +177,7 @@ impl PersonRegistry {
loop { loop {
match flume::Selector::new() match flume::Selector::new()
.recv(rx, |result| result.ok()) .recv(rx, |result| result.ok())
.wait_timeout(Duration::from_secs(2)) .wait_timeout(Duration::from_secs(TIMEOUT))
{ {
Ok(Some(public_key)) => { Ok(Some(public_key)) => {
batch.insert(public_key); batch.insert(public_key);
@@ -216,6 +195,35 @@ impl PersonRegistry {
} }
} }
/// Load all user profiles from the database
fn load(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let task: Task<Result<Vec<Person>, Error>> = cx.background_spawn(async move {
let filter = Filter::new().kind(Kind::Metadata).limit(200);
let events = client.database().query(filter).await?;
let persons = events
.into_iter()
.map(|event| {
let metadata = Metadata::from_json(event.content).unwrap_or_default();
Person::new(event.pubkey, metadata)
})
.collect();
Ok(persons)
});
self.tasks.push(cx.spawn(async move |this, cx| {
if let Ok(persons) = task.await {
this.update(cx, |this, cx| {
this.bulk_inserts(persons, cx);
})
.ok();
}
}));
}
/// Set profile encryption keys announcement /// Set profile encryption keys announcement
fn set_announcement(&mut self, event: &Event, cx: &mut App) { fn set_announcement(&mut self, event: &Event, cx: &mut App) {
let announcement = Announcement::from(event); let announcement = Announcement::from(event);
@@ -334,19 +342,3 @@ where
Ok(()) Ok(())
} }
/// Load all user profiles from the database
async fn load_persons(client: &Client) -> Result<Vec<Person>, Error> {
let filter = Filter::new().kind(Kind::Metadata).limit(200);
let events = client.database().query(filter).await?;
let mut persons = vec![];
for event in events.into_iter() {
let metadata = Metadata::from_json(event.content).unwrap_or_default();
let person = Person::new(event.pubkey, metadata);
persons.push(person);
}
Ok(persons)
}

View File

@@ -283,7 +283,10 @@ impl RelayAuth {
}); });
window.push_notification( window.push_notification(
Notification::success(format!("{} has been authenticated", url)), Notification::success(format!(
"{} has been authenticated",
url.domain().unwrap_or_default()
)),
cx, cx,
); );
} }

View File

@@ -40,7 +40,8 @@ pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://search.nos.today"]; pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://search.nos.today"];
/// Default bootstrap relays /// Default bootstrap relays
pub const BOOTSTRAP_RELAYS: [&str; 3] = [ pub const BOOTSTRAP_RELAYS: [&str; 4] = [
"wss://relay.damus.io",
"wss://relay.primal.net", "wss://relay.primal.net",
"wss://indexer.coracle.social", "wss://indexer.coracle.social",
"wss://user.kindpag.es", "wss://user.kindpag.es",

View File

@@ -185,7 +185,7 @@ impl NostrRegistry {
} }
// Connect to all added relays // Connect to all added relays
client.connect().and_wait(Duration::from_secs(2)).await; client.connect().await;
Ok(()) Ok(())
}); });

View File

@@ -405,12 +405,12 @@ impl Render for Notification {
} }
} }
} else { } else {
let opacity = delta;
let y_offset = match placement { let y_offset = match placement {
placement if placement.is_top() => px(-45.) + delta * px(45.), placement if placement.is_top() => px(-45.) + delta * px(45.),
placement if placement.is_bottom() => px(45.) - delta * px(45.), placement if placement.is_bottom() => px(45.) - delta * px(45.),
_ => px(0.), _ => px(0.),
}; };
let opacity = delta;
this.top(px(0.) + y_offset) this.top(px(0.) + y_offset)
.opacity(opacity) .opacity(opacity)
.when(opacity < 0.85, |this| this.shadow_none()) .when(opacity < 0.85, |this| this.shadow_none())