This commit is contained in:
2026-07-27 10:46:10 +07:00
parent 8bbb472103
commit b349656c56
3 changed files with 168 additions and 226 deletions

View File

@@ -2,7 +2,7 @@ use std::cmp::Reverse;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::sync::{Arc, LazyLock, RwLock};
use std::time::Duration;
use anyhow::{Context as AnyhowContext, Error, anyhow};
@@ -23,6 +23,9 @@ mod room;
pub use message::*;
pub use room::*;
/// A static keypair used only for signing locally-cached rumor events.
static LOCAL_KEYS: LazyLock<Keys> = LazyLock::new(Keys::generate);
pub fn init(window: &mut Window, cx: &mut App) {
ChatRegistry::set_global(cx.new(|cx| ChatRegistry::new(window, cx)), cx);
}
@@ -329,43 +332,42 @@ impl ChatRegistry {
return;
};
self.tasks.push(cx.background_spawn(async move {
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let subscribe = cx.background_spawn({
let client = client.clone();
// Construct filter for msg relays
let msg_relays = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
async move {
let opts =
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
// Construct filter for contact list
let contact_list = Filter::new()
.kind(Kind::ContactList)
.author(public_key)
.limit(1);
let msg_relays = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
// Subscribe
client
.subscribe(vec![msg_relays, contact_list])
.close_on(opts)
.await?;
let contact_list = Filter::new()
.kind(Kind::ContactList)
.author(public_key)
.limit(1);
Ok(())
}));
client
.subscribe(vec![msg_relays, contact_list])
.close_on(opts)
.await
}
});
let client = nostr.read(cx).client();
// Spawn a task to verify user inbox relays after 5 seconds
self.tasks.push(cx.spawn(async move |this, cx| {
// Wait for subscription to complete (or fail silently)
_ = subscribe.await;
// Give relays time to respond
cx.background_executor().timer(Duration::from_secs(5)).await;
// Construct inbox relays filter
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
// Check the latest inbox relays event in database
let found = client
.database()
.query(filter)
@@ -390,52 +392,46 @@ impl ChatRegistry {
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let public_key = signer.get_public_key_async().await?;
// Construct inbox relays filter
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
// Get the latest inbox relays event in database
let event = client
.database()
.query(filter)
.await?
.first_owned()
.ok_or(anyhow::anyhow!("No inbox relays found"))?;
// Extract relay list from event
let relays: Vec<RelayUrl> = nip17::extract_relay_list(&event).collect();
// Ensure relay connections
for url in relays.iter() {
client.add_relay(url).and_connect().await?;
}
// Construct gift wrap event filter
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
let id = SubscriptionId::new(format!("{}-msg", public_key.to_hex()));
// Construct target for subscription
let target: HashMap<RelayUrl, Filter> = relays
.into_iter()
.map(|relay| (relay, filter.clone()))
.collect();
client.subscribe(target).with_id(id).await?;
Ok(())
});
self.tasks.push(cx.spawn(async move |this, cx| {
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let public_key = signer.get_public_key_async().await?;
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let event = client
.database()
.query(filter)
.await?
.first_owned()
.ok_or(anyhow::anyhow!("No inbox relays found"))?;
let relays: Vec<RelayUrl> = nip17::extract_relay_list(&event).collect();
for url in relays.iter() {
client.add_relay(url).and_connect().await?;
}
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
let id = SubscriptionId::new(format!("{}-msg", public_key.to_hex()));
let target: HashMap<RelayUrl, Filter> = relays
.into_iter()
.map(|relay| (relay, filter.clone()))
.collect();
client.subscribe(target).with_id(id).await?;
Ok(())
});
if let Err(e) = task.await {
this.update(cx, |_this, cx| {
cx.emit(ChatEvent::Error(e.to_string()));
})?;
}
Ok(())
}));
}
@@ -519,25 +515,18 @@ impl ChatRegistry {
/// Add a new room to the start of list.
pub fn add_room<I>(&mut self, room: I, cx: &mut Context<Self>)
where
I: Into<Room> + 'static,
I: Into<Room>,
{
let nostr = NostrRegistry::global(cx);
let Some(public_key) = nostr.read(cx).current_user() else {
return;
};
self.tasks.push(cx.spawn(async move |this, cx| {
let room: Room = room.into().organize(&public_key);
let room: Room = room.into().organize(&public_key);
self.rooms.insert(0, cx.new(|_| room));
this.update(cx, |this, cx| {
this.rooms.insert(0, cx.new(|_| room));
cx.emit(ChatEvent::Ping);
cx.notify();
})?;
Ok(())
}));
cx.emit(ChatEvent::Ping);
cx.notify();
}
/// Emit an open room event.
@@ -791,7 +780,7 @@ async fn extract_rumor(
}
// Try to unwrap with the available signer
let unwrapped = try_unwrap(signer, gift_wrap).await?;
let unwrapped = try_unwrap_with(signer, gift_wrap).await?;
let mut rumor = unwrapped.rumor;
// Generate event id for the rumor if it doesn't have one
@@ -805,25 +794,6 @@ async fn extract_rumor(
Ok(rumor)
}
/// Helper method to try unwrapping with different signers
async fn try_unwrap(signer: &UniversalSigner, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
/*
* // Try with the device signer first
if let Some(signer) = signer.get_encryption_signer().await {
log::info!("trying with encryption key");
if let Ok(unwrapped) = try_unwrap_with(gift_wrap, &signer).await {
return Ok(unwrapped);
}
}
// Fallback to the user's signer
let user_signer = signer.get().await;
*/
let unwrapped = try_unwrap_with(signer, gift_wrap).await?;
Ok(unwrapped)
}
/// Attempts to unwrap a gift wrap event with a given signer.
async fn try_unwrap_with(
signer: &UniversalSigner,
@@ -882,7 +852,7 @@ async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Resul
// Construct the event
let event = EventBuilder::new(Kind::ApplicationSpecificData, content)
.tags(tags)
.finalize_async(&Keys::generate())
.finalize_async(&*LOCAL_KEYS)
.await?;
// Save the event to the database