Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b349656c56 | ||
|
|
8bbb472103 |
+123
-137
@@ -1,10 +1,8 @@
|
|||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
use std::sync::Arc;
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
use std::sync::RwLock;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, LazyLock, RwLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{Context as AnyhowContext, Error, anyhow};
|
use anyhow::{Context as AnyhowContext, Error, anyhow};
|
||||||
@@ -17,8 +15,6 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use smol::lock::RwLock;
|
|
||||||
use state::{DEVICE_GIFTWRAP, NostrRegistry, USER_GIFTWRAP, UniversalSigner};
|
use state::{DEVICE_GIFTWRAP, NostrRegistry, USER_GIFTWRAP, UniversalSigner};
|
||||||
|
|
||||||
mod message;
|
mod message;
|
||||||
@@ -27,6 +23,9 @@ mod room;
|
|||||||
pub use message::*;
|
pub use message::*;
|
||||||
pub use room::*;
|
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) {
|
pub fn init(window: &mut Window, cx: &mut App) {
|
||||||
ChatRegistry::set_global(cx.new(|cx| ChatRegistry::new(window, cx)), cx);
|
ChatRegistry::set_global(cx.new(|cx| ChatRegistry::new(window, cx)), cx);
|
||||||
}
|
}
|
||||||
@@ -103,6 +102,12 @@ pub struct ChatRegistry {
|
|||||||
/// Async tasks
|
/// Async tasks
|
||||||
tasks: SmallVec<[Task<Result<(), Error>>; 2]>,
|
tasks: SmallVec<[Task<Result<(), Error>>; 2]>,
|
||||||
|
|
||||||
|
/// Notification listener task (cancelled on signer change)
|
||||||
|
notification_listener: Option<Task<Result<(), Error>>>,
|
||||||
|
|
||||||
|
/// Signal consumer task (cancelled on signer change)
|
||||||
|
signal_consumer: Option<Task<Result<(), Error>>>,
|
||||||
|
|
||||||
/// Subscriptions
|
/// Subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 2]>,
|
_subscriptions: SmallVec<[Subscription; 2]>,
|
||||||
}
|
}
|
||||||
@@ -153,12 +158,18 @@ impl ChatRegistry {
|
|||||||
signal_rx: rx,
|
signal_rx: rx,
|
||||||
signal_tx: tx,
|
signal_tx: tx,
|
||||||
tasks: smallvec![],
|
tasks: smallvec![],
|
||||||
|
notification_listener: None,
|
||||||
|
signal_consumer: None,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle nostr notifications
|
/// Handle nostr notifications
|
||||||
fn handle_notifications(&mut self, cx: &mut Context<Self>) {
|
fn handle_notifications(&mut self, cx: &mut Context<Self>) {
|
||||||
|
// Cancel previous notification tasks before spawning new ones
|
||||||
|
self.notification_listener = None;
|
||||||
|
self.signal_consumer = None;
|
||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
@@ -176,7 +187,7 @@ impl ChatRegistry {
|
|||||||
let tx = self.signal_tx.clone();
|
let tx = self.signal_tx.clone();
|
||||||
let rx = self.signal_rx.clone();
|
let rx = self.signal_rx.clone();
|
||||||
|
|
||||||
self.tasks.push(cx.background_spawn(async move {
|
self.notification_listener = Some(cx.background_spawn(async move {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
@@ -209,7 +220,7 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
// Keep track of which relays have seen this event
|
// Keep track of which relays have seen this event
|
||||||
{
|
{
|
||||||
let mut seens = seens.write().await;
|
let mut seens = seens.write().unwrap();
|
||||||
seens.entry(event.id).or_default().insert(relay_url);
|
seens.entry(event.id).or_default().insert(relay_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,9 +228,13 @@ impl ChatRegistry {
|
|||||||
match extract_rumor(&client, &signer, event.as_ref()).await {
|
match extract_rumor(&client, &signer, event.as_ref()).await {
|
||||||
Ok(rumor) => {
|
Ok(rumor) => {
|
||||||
// Map the rumor id to the gift wrap event id for later lookup
|
// Map the rumor id to the gift wrap event id for later lookup
|
||||||
|
let Some(rumor_id) = rumor.id else {
|
||||||
|
log::error!("Rumor missing id after ensure_id");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
{
|
{
|
||||||
let mut event_map = event_map.write().await;
|
let mut event_map = event_map.write().unwrap();
|
||||||
event_map.insert(rumor.id.unwrap(), event.id);
|
event_map.insert(rumor_id, event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the rumor has a recipient
|
// Check if the rumor has a recipient
|
||||||
@@ -256,7 +271,7 @@ impl ChatRegistry {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.signal_consumer = Some(cx.spawn(async move |this, cx| {
|
||||||
while let Ok(message) = rx.recv_async().await {
|
while let Ok(message) = rx.recv_async().await {
|
||||||
match message {
|
match message {
|
||||||
Signal::Message(message) => {
|
Signal::Message(message) => {
|
||||||
@@ -287,21 +302,23 @@ impl ChatRegistry {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tracking the status of unwrapping gift wrap events.
|
/// Check periodically whether old gift-wrap events have finished processing,
|
||||||
|
/// and refresh rooms once the backlog is caught up.
|
||||||
fn tracking(&mut self, cx: &mut Context<Self>) {
|
fn tracking(&mut self, cx: &mut Context<Self>) {
|
||||||
let status = self.tracking.clone();
|
let status = self.tracking.clone();
|
||||||
let tx = self.signal_tx.clone();
|
let tx = self.signal_tx.clone();
|
||||||
|
let check_interval = Duration::from_secs(15);
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |_, cx| {
|
self.tasks.push(cx.spawn(async move |_, cx| {
|
||||||
let loop_duration = Duration::from_secs(15);
|
|
||||||
loop {
|
loop {
|
||||||
if status.load(Ordering::Acquire) {
|
cx.background_executor().timer(check_interval).await;
|
||||||
_ = status.compare_exchange(true, false, Ordering::Release, Ordering::Relaxed);
|
|
||||||
_ = tx.send_async(Signal::Eose).await;
|
// Only trigger a room refresh if old events were being tracked
|
||||||
} else {
|
// (i.e. the notification handler set the flag while catching up).
|
||||||
|
// `swap` atomically reads and clears the flag.
|
||||||
|
if status.swap(false, Ordering::AcqRel) {
|
||||||
_ = tx.send_async(Signal::Eose).await;
|
_ = tx.send_async(Signal::Eose).await;
|
||||||
}
|
}
|
||||||
cx.background_executor().timer(loop_duration).await;
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -315,55 +332,51 @@ impl ChatRegistry {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.tasks.push(cx.background_spawn(async move {
|
let subscribe = cx.background_spawn({
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let client = client.clone();
|
||||||
|
|
||||||
// Construct filter for msg relays
|
async move {
|
||||||
let msg_relays = Filter::new()
|
let opts =
|
||||||
|
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
|
let msg_relays = Filter::new()
|
||||||
|
.kind(Kind::InboxRelays)
|
||||||
|
.author(public_key)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
let contact_list = Filter::new()
|
||||||
|
.kind(Kind::ContactList)
|
||||||
|
.author(public_key)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
client
|
||||||
|
.subscribe(vec![msg_relays, contact_list])
|
||||||
|
.close_on(opts)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
.kind(Kind::InboxRelays)
|
.kind(Kind::InboxRelays)
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
// Construct filter for contact list
|
let found = client
|
||||||
let contact_list = Filter::new()
|
.database()
|
||||||
.kind(Kind::ContactList)
|
.query(filter)
|
||||||
.author(public_key)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
// Subscribe
|
|
||||||
client
|
|
||||||
.subscribe(vec![msg_relays, contact_list])
|
|
||||||
.close_on(opts)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
|
|
||||||
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| {
|
|
||||||
cx.background_executor().timer(Duration::from_secs(5)).await;
|
|
||||||
|
|
||||||
if !cx
|
|
||||||
.background_spawn(async move {
|
|
||||||
// Construct inbox relays filter
|
|
||||||
let filter = Filter::new()
|
|
||||||
.kind(Kind::InboxRelays)
|
|
||||||
.author(public_key)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
// Check the latest inbox relays event in database
|
|
||||||
client
|
|
||||||
.database()
|
|
||||||
.query(filter)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
.first_owned()
|
|
||||||
.is_some()
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
{
|
.unwrap_or_default()
|
||||||
|
.first_owned()
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
if !found {
|
||||||
this.update(cx, |_this, cx| {
|
this.update(cx, |_this, cx| {
|
||||||
cx.emit(ChatEvent::InboxRelayNotFound);
|
cx.emit(ChatEvent::InboxRelayNotFound);
|
||||||
})?;
|
})?;
|
||||||
@@ -379,52 +392,46 @@ impl ChatRegistry {
|
|||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let signer = nostr.read(cx).signer();
|
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| {
|
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 {
|
if let Err(e) = task.await {
|
||||||
this.update(cx, |_this, cx| {
|
this.update(cx, |_this, cx| {
|
||||||
cx.emit(ChatEvent::Error(e.to_string()));
|
cx.emit(ChatEvent::Error(e.to_string()));
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -469,7 +476,8 @@ impl ChatRegistry {
|
|||||||
/// Count the number of messages seen by a given relay.
|
/// Count the number of messages seen by a given relay.
|
||||||
pub fn count_messages(&self, relay_url: &RelayUrl) -> usize {
|
pub fn count_messages(&self, relay_url: &RelayUrl) -> usize {
|
||||||
self.seens
|
self.seens
|
||||||
.read_blocking()
|
.read()
|
||||||
|
.unwrap()
|
||||||
.values()
|
.values()
|
||||||
.filter(|seen| seen.contains(relay_url))
|
.filter(|seen| seen.contains(relay_url))
|
||||||
.count()
|
.count()
|
||||||
@@ -488,7 +496,8 @@ impl ChatRegistry {
|
|||||||
/// Get the relays that have seen a given rumor id.
|
/// Get the relays that have seen a given rumor id.
|
||||||
pub fn rumor_seen_on(&self, id: &EventId) -> Option<HashSet<RelayUrl>> {
|
pub fn rumor_seen_on(&self, id: &EventId) -> Option<HashSet<RelayUrl>> {
|
||||||
self.event_map
|
self.event_map
|
||||||
.read_blocking()
|
.read()
|
||||||
|
.unwrap()
|
||||||
.get(id)
|
.get(id)
|
||||||
.map(|id| self.seen_on(id))
|
.map(|id| self.seen_on(id))
|
||||||
}
|
}
|
||||||
@@ -496,7 +505,8 @@ impl ChatRegistry {
|
|||||||
/// Get the relays that have seen a given gift wrap id.
|
/// Get the relays that have seen a given gift wrap id.
|
||||||
pub fn seen_on(&self, id: &EventId) -> HashSet<RelayUrl> {
|
pub fn seen_on(&self, id: &EventId) -> HashSet<RelayUrl> {
|
||||||
self.seens
|
self.seens
|
||||||
.read_blocking()
|
.read()
|
||||||
|
.unwrap()
|
||||||
.get(id)
|
.get(id)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@@ -505,25 +515,18 @@ impl ChatRegistry {
|
|||||||
/// Add a new room to the start of list.
|
/// Add a new room to the start of list.
|
||||||
pub fn add_room<I>(&mut self, room: I, cx: &mut Context<Self>)
|
pub fn add_room<I>(&mut self, room: I, cx: &mut Context<Self>)
|
||||||
where
|
where
|
||||||
I: Into<Room> + 'static,
|
I: Into<Room>,
|
||||||
{
|
{
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
|
||||||
let Some(public_key) = nostr.read(cx).current_user() else {
|
let Some(public_key) = nostr.read(cx).current_user() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
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| {
|
cx.emit(ChatEvent::Ping);
|
||||||
this.rooms.insert(0, cx.new(|_| room));
|
cx.notify();
|
||||||
cx.emit(ChatEvent::Ping);
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit an open room event.
|
/// Emit an open room event.
|
||||||
@@ -635,7 +638,9 @@ impl ChatRegistry {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to load rooms: {}", e);
|
this.update(cx, |_, cx| {
|
||||||
|
cx.emit(ChatEvent::Error(e.to_string()));
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -775,7 +780,7 @@ async fn extract_rumor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to unwrap with the available signer
|
// 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;
|
let mut rumor = unwrapped.rumor;
|
||||||
|
|
||||||
// Generate event id for the rumor if it doesn't have one
|
// Generate event id for the rumor if it doesn't have one
|
||||||
@@ -789,25 +794,6 @@ async fn extract_rumor(
|
|||||||
Ok(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.
|
/// Attempts to unwrap a gift wrap event with a given signer.
|
||||||
async fn try_unwrap_with(
|
async fn try_unwrap_with(
|
||||||
signer: &UniversalSigner,
|
signer: &UniversalSigner,
|
||||||
@@ -866,7 +852,7 @@ async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Resul
|
|||||||
// Construct the event
|
// Construct the event
|
||||||
let event = EventBuilder::new(Kind::ApplicationSpecificData, content)
|
let event = EventBuilder::new(Kind::ApplicationSpecificData, content)
|
||||||
.tags(tags)
|
.tags(tags)
|
||||||
.finalize_async(&Keys::generate())
|
.finalize_async(&*LOCAL_KEYS)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Save the event to the database
|
// Save the event to the database
|
||||||
|
|||||||
+43
-63
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||||
use std::sync::Arc;
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock as AsyncRwLock;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
pub use actions::*;
|
pub use actions::*;
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
@@ -21,7 +21,7 @@ use person::{Person, PersonRegistry};
|
|||||||
use settings::{AppSettings, SignerKind};
|
use settings::{AppSettings, SignerKind};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use smol::lock::RwLock;
|
use smol::lock::RwLock as AsyncRwLock;
|
||||||
use state::{NostrRegistry, upload};
|
use state::{NostrRegistry, upload};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
@@ -63,7 +63,7 @@ pub struct ChatPanel {
|
|||||||
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
|
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
|
||||||
|
|
||||||
/// Mapping message (rumor event) ids to their reports
|
/// Mapping message (rumor event) ids to their reports
|
||||||
reports_by_id: Entity<BTreeMap<EventId, Vec<SendReport>>>,
|
reports_by_id: Arc<RwLock<BTreeMap<EventId, Vec<SendReport>>>>,
|
||||||
|
|
||||||
/// Chat input state
|
/// Chat input state
|
||||||
input: Entity<InputState>,
|
input: Entity<InputState>,
|
||||||
@@ -75,7 +75,7 @@ pub struct ChatPanel {
|
|||||||
subject_bar: Entity<bool>,
|
subject_bar: Entity<bool>,
|
||||||
|
|
||||||
/// Sent message ids
|
/// Sent message ids
|
||||||
sent_ids: Arc<RwLock<Vec<EventId>>>,
|
sent_ids: Arc<AsyncRwLock<Vec<EventId>>>,
|
||||||
|
|
||||||
/// Replies to
|
/// Replies to
|
||||||
replies_to: Entity<HashSet<EventId>>,
|
replies_to: Entity<HashSet<EventId>>,
|
||||||
@@ -98,7 +98,7 @@ impl ChatPanel {
|
|||||||
// Define attachments and replies_to entities
|
// Define attachments and replies_to entities
|
||||||
let attachments = cx.new(|_| vec![]);
|
let attachments = cx.new(|_| vec![]);
|
||||||
let replies_to = cx.new(|_| HashSet::new());
|
let replies_to = cx.new(|_| HashSet::new());
|
||||||
let reports_by_id = cx.new(|_| BTreeMap::new());
|
let reports_by_id = Arc::new(RwLock::new(BTreeMap::new()));
|
||||||
|
|
||||||
// Define list of messages
|
// Define list of messages
|
||||||
let messages = BTreeSet::default();
|
let messages = BTreeSet::default();
|
||||||
@@ -172,7 +172,7 @@ impl ChatPanel {
|
|||||||
attachments,
|
attachments,
|
||||||
rendered_texts_by_id: BTreeMap::new(),
|
rendered_texts_by_id: BTreeMap::new(),
|
||||||
reports_by_id,
|
reports_by_id,
|
||||||
sent_ids: Arc::new(RwLock::new(Vec::new())),
|
sent_ids: Arc::new(AsyncRwLock::new(Vec::new())),
|
||||||
uploading: false,
|
uploading: false,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
tasks: vec![],
|
tasks: vec![],
|
||||||
@@ -192,7 +192,7 @@ impl ChatPanel {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let sent_ids = self.sent_ids.clone();
|
let sent_ids = self.sent_ids.clone();
|
||||||
let reports = self.reports_by_id.downgrade();
|
let reports = self.reports_by_id.clone();
|
||||||
|
|
||||||
let (tx, rx) = flume::bounded::<Arc<SendStatus>>(256);
|
let (tx, rx) = flume::bounded::<Arc<SendStatus>>(256);
|
||||||
|
|
||||||
@@ -223,10 +223,11 @@ impl ChatPanel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |_this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
while let Ok(status) = rx.recv_async().await {
|
while let Ok(status) = rx.recv_async().await {
|
||||||
reports.update(cx, |this, cx| {
|
{
|
||||||
for reports in this.values_mut() {
|
let mut map = reports.write().unwrap();
|
||||||
|
for reports in map.values_mut() {
|
||||||
for report in reports.iter_mut() {
|
for report in reports.iter_mut() {
|
||||||
let Some(output) = report.output.as_mut() else {
|
let Some(output) = report.output.as_mut() else {
|
||||||
continue;
|
continue;
|
||||||
@@ -243,10 +244,10 @@ impl ChatPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.notify();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})?;
|
}
|
||||||
|
this.update(cx, |_, cx| cx.notify()).ok();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
@@ -345,69 +346,46 @@ impl ChatPanel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get room entity
|
|
||||||
let room = self.room.clone();
|
let room = self.room.clone();
|
||||||
|
|
||||||
// Get content and replies
|
|
||||||
let replies: Vec<EventId> = self.replies_to.read(cx).iter().copied().collect();
|
let replies: Vec<EventId> = self.replies_to.read(cx).iter().copied().collect();
|
||||||
let content = value.to_string();
|
let content = value.to_string();
|
||||||
|
|
||||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
|
||||||
let room = room.upgrade().context("Room is not available")?;
|
|
||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
|
||||||
match room.read(cx).rumor(content, replies, cx) {
|
|
||||||
Some(rumor) => {
|
|
||||||
this.insert_message(&rumor, true, cx);
|
|
||||||
this.send_and_wait(rumor, window, cx);
|
|
||||||
this.clear(window, cx);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
window.push_notification("Failed to create message", cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message in the background and wait for the response
|
|
||||||
fn send_and_wait(&mut self, rumor: UnsignedEvent, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let sent_ids = self.sent_ids.clone();
|
let sent_ids = self.sent_ids.clone();
|
||||||
|
|
||||||
// This can't fail, because we already ensured that the ID is set
|
// Upgrade room and create the rumor synchronously
|
||||||
let id = rumor.id.unwrap();
|
let Some(room_entity) = room.upgrade() else {
|
||||||
|
return;
|
||||||
// Add empty reports
|
};
|
||||||
self.insert_reports(id, vec![], cx);
|
let Some(rumor) = room_entity.read(cx).rumor(content.clone(), replies, cx) else {
|
||||||
|
window.push_notification("Failed to create message", cx);
|
||||||
// Upgrade room reference
|
|
||||||
let Some(room) = self.room.upgrade() else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the send message task
|
let id = rumor.id.expect("rumor must have an id");
|
||||||
let Some(task) = room.read(cx).send(rumor, cx) else {
|
|
||||||
|
// Insert optimistic message and clear input
|
||||||
|
self.insert_message(&rumor, true, cx);
|
||||||
|
self.insert_reports(id, vec![], cx);
|
||||||
|
self.clear(window, cx);
|
||||||
|
|
||||||
|
// Get the send task
|
||||||
|
let Some(send_task) = room_entity.read(cx).send(rumor, cx) else {
|
||||||
window.push_notification("Failed to send message", cx);
|
window.push_notification("Failed to send message", cx);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Spawn a single task to await the send and update reports
|
||||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
// Send and get reports
|
let outputs = send_task.await;
|
||||||
let outputs = task.await;
|
|
||||||
|
|
||||||
// Add sent IDs to the list
|
|
||||||
let mut sent_ids = sent_ids.write().await;
|
let mut sent_ids = sent_ids.write().await;
|
||||||
sent_ids.extend(outputs.iter().filter_map(|output| output.gift_wrap_id));
|
sent_ids.extend(outputs.iter().filter_map(|output| output.gift_wrap_id));
|
||||||
|
|
||||||
// Update the state
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.insert_reports(id, outputs, cx);
|
this.insert_reports(id, outputs, cx);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the input field, attachments, and replies
|
/// Clear the input field, attachments, and replies
|
||||||
@@ -429,10 +407,13 @@ impl ChatPanel {
|
|||||||
|
|
||||||
/// Insert reports
|
/// Insert reports
|
||||||
fn insert_reports(&mut self, id: EventId, reports: Vec<SendReport>, cx: &mut Context<Self>) {
|
fn insert_reports(&mut self, id: EventId, reports: Vec<SendReport>, cx: &mut Context<Self>) {
|
||||||
self.reports_by_id.update(cx, |this, cx| {
|
self.reports_by_id
|
||||||
this.entry(id).or_default().extend(reports);
|
.write()
|
||||||
cx.notify();
|
.unwrap()
|
||||||
});
|
.entry(id)
|
||||||
|
.or_default()
|
||||||
|
.extend(reports);
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a message into the chat panel
|
/// Insert a message into the chat panel
|
||||||
@@ -466,13 +447,12 @@ impl ChatPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a message has any reports
|
/// Check if a message has any reports
|
||||||
fn has_reports(&self, id: &EventId, cx: &App) -> bool {
|
fn has_reports(&self, id: &EventId, _cx: &App) -> bool {
|
||||||
self.reports_by_id.read(cx).get(id).is_some()
|
self.reports_by_id.read().unwrap().get(id).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all sent reports for a message by its ID
|
fn sent_reports(&self, id: &EventId, _cx: &App) -> Option<Vec<SendReport>> {
|
||||||
fn sent_reports(&self, id: &EventId, cx: &App) -> Option<Vec<SendReport>> {
|
self.reports_by_id.read().unwrap().get(id).cloned()
|
||||||
self.reports_by_id.read(cx).get(id).cloned()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a message by its ID
|
/// Get a message by its ID
|
||||||
|
|||||||
+36
-44
@@ -258,28 +258,20 @@ impl DeviceRegistry {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
let announcement_existed = self.announcement_existed.clone();
|
let announcement_existed = self.announcement_existed.clone();
|
||||||
let executor = cx.background_executor().clone();
|
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
if !cx
|
// Wait for 5 seconds
|
||||||
.background_spawn(async move {
|
cx.background_executor().timer(Duration::from_secs(5)).await;
|
||||||
// Wait for 5 seconds
|
|
||||||
executor.timer(Duration::from_secs(5)).await;
|
|
||||||
|
|
||||||
// Then check if the msg relays have been found
|
// Then check if the msg relays have been found
|
||||||
if !announcement_existed.load(Ordering::Acquire) {
|
if announcement_existed.load(Ordering::Acquire) {
|
||||||
return true;
|
return Ok(());
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
this.update(cx, |_this, cx| {
|
|
||||||
cx.emit(DeviceEvent::NotSet);
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.update(cx, |_this, cx| {
|
||||||
|
cx.emit(DeviceEvent::NotSet);
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -404,11 +396,10 @@ impl DeviceRegistry {
|
|||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
|
|
||||||
let Ok(app_keys) = get_or_init_app_keys(cx) else {
|
let app_keys_task = get_or_init_app_keys(cx);
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let task: Task<Result<Option<Event>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Option<Event>, Error>> = cx.background_spawn(async move {
|
||||||
|
let app_keys = app_keys_task.await?;
|
||||||
let app_pubkey = app_keys.public_key();
|
let app_pubkey = app_keys.public_key();
|
||||||
let public_key = signer.get_public_key_async().await?;
|
let public_key = signer.get_public_key_async().await?;
|
||||||
|
|
||||||
@@ -489,11 +480,10 @@ impl DeviceRegistry {
|
|||||||
|
|
||||||
/// Parse the approval event to get encryption key then set it
|
/// Parse the approval event to get encryption key then set it
|
||||||
fn extract_encryption(&mut self, event: Event, cx: &mut Context<Self>) {
|
fn extract_encryption(&mut self, event: Event, cx: &mut Context<Self>) {
|
||||||
let Ok(app_keys) = get_or_init_app_keys(cx) else {
|
let app_keys_task = get_or_init_app_keys(cx);
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
||||||
|
let app_keys = app_keys_task.await?;
|
||||||
let master = event
|
let master = event
|
||||||
.tags
|
.tags
|
||||||
.iter()
|
.iter()
|
||||||
@@ -573,7 +563,7 @@ impl DeviceRegistry {
|
|||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn_in(window, async move |_this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |_this, cx| {
|
||||||
match task.await {
|
match task.await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
@@ -591,8 +581,9 @@ impl DeviceRegistry {
|
|||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
|
||||||
.detach();
|
Ok(())
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle encryption request
|
/// Handle encryption request
|
||||||
@@ -715,33 +706,34 @@ impl DeviceRegistry {
|
|||||||
|
|
||||||
struct DeviceNotification;
|
struct DeviceNotification;
|
||||||
|
|
||||||
/// Get or create new app keys
|
/// Get or create new app keys (async, returns a task)
|
||||||
fn get_or_init_app_keys(cx: &App) -> Result<Keys, Error> {
|
fn get_or_init_app_keys(cx: &App) -> Task<Result<Keys, Error>> {
|
||||||
let read = cx.read_credentials(CLIENT_NAME);
|
let read = cx.read_credentials(CLIENT_NAME);
|
||||||
let stored_keys: Option<Keys> = cx.foreground_executor().block_on(async move {
|
|
||||||
if let Ok(Some((_, secret))) = read.await {
|
|
||||||
SecretKey::from_slice(&secret).map(Keys::new).ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(keys) = stored_keys {
|
cx.spawn(async move |cx| {
|
||||||
Ok(keys)
|
if let Ok(Some((_, secret))) = read.await
|
||||||
} else {
|
&& let Ok(keys) = SecretKey::from_slice(&secret).map(Keys::new)
|
||||||
|
{
|
||||||
|
return Ok(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No stored keys found or invalid — generate new ones
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let user = keys.public_key().to_hex();
|
let user = keys.public_key().to_hex();
|
||||||
let secret = keys.secret_key().to_secret_bytes();
|
let secret = keys.secret_key().to_secret_bytes();
|
||||||
let write = cx.write_credentials(CLIENT_NAME, &user, &secret);
|
|
||||||
|
|
||||||
cx.foreground_executor().block_on(async move {
|
cx.update(|cx| {
|
||||||
if let Err(e) = write.await {
|
let write = cx.write_credentials(CLIENT_NAME, &user, &secret);
|
||||||
log::error!("Keyring not available or panic: {e}")
|
cx.background_spawn(async move {
|
||||||
}
|
if let Err(e) = write.await {
|
||||||
|
log::error!("Keyring not available or panic: {e}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(keys)
|
Ok(keys)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt and store device keys in the local database.
|
/// Encrypt and store device keys in the local database.
|
||||||
|
|||||||
+57
-65
@@ -1,6 +1,5 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::rc::Rc;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{Error, anyhow};
|
use anyhow::{Error, anyhow};
|
||||||
@@ -24,9 +23,9 @@ impl Global for GlobalPersonRegistry {}
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Dispatch {
|
enum Dispatch {
|
||||||
Person(Box<Person>),
|
Person(Person),
|
||||||
Announcement(Box<Event>),
|
Announcement(Event),
|
||||||
Relays(Box<Event>),
|
Relays(Event),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Person Registry
|
/// Person Registry
|
||||||
@@ -36,7 +35,7 @@ pub struct PersonRegistry {
|
|||||||
persons: HashMap<PublicKey, Entity<Person>>,
|
persons: HashMap<PublicKey, Entity<Person>>,
|
||||||
|
|
||||||
/// Set of public keys that have been seen
|
/// Set of public keys that have been seen
|
||||||
seens: Rc<RefCell<HashSet<PublicKey>>>,
|
seens: RefCell<HashSet<PublicKey>>,
|
||||||
|
|
||||||
/// Sender for requesting metadata
|
/// Sender for requesting metadata
|
||||||
sender: flume::Sender<PublicKey>,
|
sender: flume::Sender<PublicKey>,
|
||||||
@@ -67,49 +66,38 @@ impl PersonRegistry {
|
|||||||
|
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(cx.background_spawn({
|
||||||
// Handle nostr notifications
|
let client = client.clone();
|
||||||
cx.background_spawn({
|
async move {
|
||||||
let client = client.clone();
|
Self::handle_notifications(&client, &tx).await;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
async move {
|
tasks.push(cx.background_spawn({
|
||||||
Self::handle_notifications(&client, &tx).await;
|
let client = client.clone();
|
||||||
}
|
async move {
|
||||||
}),
|
Self::handle_requests(&client, &mta_rx).await;
|
||||||
);
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(cx.spawn(async move |this, cx| {
|
||||||
// Handle metadata requests
|
while let Ok(event) = rx.recv_async().await {
|
||||||
cx.background_spawn({
|
this.update(cx, |this, cx| {
|
||||||
let client = client.clone();
|
match event {
|
||||||
|
Dispatch::Person(person) => {
|
||||||
async move {
|
this.insert(person, cx);
|
||||||
Self::handle_requests(&client, &mta_rx).await;
|
}
|
||||||
}
|
Dispatch::Announcement(event) => {
|
||||||
}),
|
this.set_announcement(&event, cx);
|
||||||
);
|
}
|
||||||
|
Dispatch::Relays(event) => {
|
||||||
tasks.push(
|
this.set_messaging_relays(&event, cx);
|
||||||
// Update GPUI state
|
}
|
||||||
cx.spawn(async move |this, cx| {
|
};
|
||||||
while let Ok(event) = rx.recv_async().await {
|
})
|
||||||
this.update(cx, |this, cx| {
|
.ok();
|
||||||
match event {
|
}
|
||||||
Dispatch::Person(person) => {
|
}));
|
||||||
this.insert(*person, cx);
|
|
||||||
}
|
|
||||||
Dispatch::Announcement(event) => {
|
|
||||||
this.set_announcement(&event, cx);
|
|
||||||
}
|
|
||||||
Dispatch::Relays(event) => {
|
|
||||||
this.set_messaging_relays(&event, cx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Load all user profiles from the database
|
// Load all user profiles from the database
|
||||||
cx.defer_in(window, |this, _window, cx| {
|
cx.defer_in(window, |this, _window, cx| {
|
||||||
@@ -118,7 +106,7 @@ impl PersonRegistry {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
persons: HashMap::new(),
|
persons: HashMap::new(),
|
||||||
seens: Rc::new(RefCell::new(HashSet::new())),
|
seens: RefCell::new(HashSet::new()),
|
||||||
sender: mta_tx,
|
sender: mta_tx,
|
||||||
tasks,
|
tasks,
|
||||||
}
|
}
|
||||||
@@ -145,24 +133,25 @@ impl PersonRegistry {
|
|||||||
Kind::Metadata => {
|
Kind::Metadata => {
|
||||||
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);
|
if tx.send_async(Dispatch::Person(person)).await.is_err() {
|
||||||
// Send
|
log::warn!("PersonRegistry channel closed, dropping metadata event");
|
||||||
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
|
if let Err(e) = get_metadata(client, public_keys).await {
|
||||||
get_metadata(client, public_keys).await.ok();
|
log::warn!("Failed to get metadata for contact list: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Kind::InboxRelays => {
|
Kind::InboxRelays => {
|
||||||
let val = Box::new(event.into_owned());
|
tx.send_async(Dispatch::Relays(event.into_owned()))
|
||||||
// Send
|
.await
|
||||||
tx.send_async(Dispatch::Relays(val)).await.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
Kind::Custom(10044) => {
|
Kind::Custom(10044) => {
|
||||||
let val = Box::new(event.into_owned());
|
tx.send_async(Dispatch::Announcement(event.into_owned()))
|
||||||
// Send
|
.await
|
||||||
tx.send_async(Dispatch::Announcement(val)).await.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -182,13 +171,17 @@ impl PersonRegistry {
|
|||||||
Ok(Some(public_key)) => {
|
Ok(Some(public_key)) => {
|
||||||
batch.insert(public_key);
|
batch.insert(public_key);
|
||||||
// Process the batch if it's full
|
// Process the batch if it's full
|
||||||
if batch.len() >= 20 {
|
if batch.len() >= 20
|
||||||
get_metadata(client, std::mem::take(&mut batch)).await.ok();
|
&& let Err(e) = get_metadata(client, std::mem::take(&mut batch)).await
|
||||||
|
{
|
||||||
|
log::warn!("Failed to get metadata batch: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if !batch.is_empty() {
|
if !batch.is_empty()
|
||||||
get_metadata(client, std::mem::take(&mut batch)).await.ok();
|
&& let Err(e) = get_metadata(client, std::mem::take(&mut batch)).await
|
||||||
|
{
|
||||||
|
log::warn!("Failed to get metadata batch: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,15 +283,14 @@ impl PersonRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let public_key = *public_key;
|
let public_key = *public_key;
|
||||||
let mut seen = self.seens.borrow_mut();
|
|
||||||
|
|
||||||
if seen.insert(public_key) {
|
if self.seens.borrow_mut().insert(public_key) {
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
|
|
||||||
// Spawn background task to request metadata
|
// Spawn background task to request metadata
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
if let Err(e) = sender.send_async(public_key).await {
|
if let Err(e) = sender.send_async(public_key).await {
|
||||||
log::warn!("Failed to send public key for metadata request: {}", e);
|
log::warn!("Failed to send public key for metadata request: {e}");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ impl NostrRegistry {
|
|||||||
<T as AsyncSignEvent>::Error: std::error::Error + Send + Sync + 'static,
|
<T as AsyncSignEvent>::Error: std::error::Error + Send + Sync + 'static,
|
||||||
<T as AsyncNip44>::Error: std::error::Error + Send + Sync + 'static,
|
<T as AsyncNip44>::Error: std::error::Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
cx.spawn(async move |this, cx| {
|
let task = cx.spawn(async move |this, cx| {
|
||||||
match new_signer.get_public_key_async().await {
|
match new_signer.get_public_key_async().await {
|
||||||
Ok(public_key) => {
|
Ok(public_key) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
@@ -181,9 +181,9 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok::<(), anyhow::Error>(())
|
Ok(())
|
||||||
})
|
});
|
||||||
.detach();
|
self.tasks.push(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to the bootstrapping relays
|
/// Connect to the bootstrapping relays
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ impl ImportIdentity {
|
|||||||
let password = uri.to_string();
|
let password = uri.to_string();
|
||||||
let save = cx.write_credentials(USER_KEYRING, "bunker", password.as_bytes());
|
let save = cx.write_credentials(USER_KEYRING, "bunker", password.as_bytes());
|
||||||
|
|
||||||
cx.spawn_in(window, async move |_this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |_this, cx| {
|
||||||
let keys = master_keys.await;
|
let keys = master_keys.await;
|
||||||
let timeout = Duration::from_secs(30);
|
let timeout = Duration::from_secs(30);
|
||||||
|
|
||||||
@@ -162,9 +162,8 @@ impl ImportIdentity {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok::<(), anyhow::Error>(())
|
Ok(())
|
||||||
})
|
}));
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ::settings::AppSettings;
|
use ::settings::AppSettings;
|
||||||
|
use anyhow::Error;
|
||||||
use chat::{ChatEvent, ChatRegistry};
|
use chat::{ChatEvent, ChatRegistry};
|
||||||
use common::{CoopImageCache, download_dir};
|
use common::{CoopImageCache, download_dir};
|
||||||
use device::{DeviceEvent, DeviceRegistry};
|
use device::{DeviceEvent, DeviceRegistry};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, App, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
|
Action, App, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
|
||||||
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, div,
|
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Window, div,
|
||||||
image_cache, px, relative,
|
image_cache, px, relative,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
@@ -65,6 +66,9 @@ pub struct Workspace {
|
|||||||
/// App's Image Cache
|
/// App's Image Cache
|
||||||
image_cache: Entity<CoopImageCache>,
|
image_cache: Entity<CoopImageCache>,
|
||||||
|
|
||||||
|
/// Async tasks
|
||||||
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
|
|
||||||
/// Event subscriptions
|
/// Event subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 6]>,
|
_subscriptions: SmallVec<[Subscription; 6]>,
|
||||||
}
|
}
|
||||||
@@ -245,6 +249,7 @@ impl Workspace {
|
|||||||
Self {
|
Self {
|
||||||
dock,
|
dock,
|
||||||
image_cache,
|
image_cache,
|
||||||
|
tasks: vec![],
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,7 +395,7 @@ impl Workspace {
|
|||||||
let device = DeviceRegistry::global(cx).downgrade();
|
let device = DeviceRegistry::global(cx).downgrade();
|
||||||
let save_dialog = cx.prompt_for_new_path(download_dir(), Some("encryption.txt"));
|
let save_dialog = cx.prompt_for_new_path(download_dir(), Some("encryption.txt"));
|
||||||
|
|
||||||
cx.spawn_in(window, async move |_this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |_this, cx| {
|
||||||
// Get the output path from the save dialog
|
// Get the output path from the save dialog
|
||||||
let output_path = match save_dialog.await {
|
let output_path = match save_dialog.await {
|
||||||
Ok(Ok(Some(path))) => path,
|
Ok(Ok(Some(path))) => path,
|
||||||
@@ -417,9 +422,8 @@ impl Workspace {
|
|||||||
cx.open_with_system(output_path.as_path());
|
cx.open_with_system(output_path.as_path());
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok(())
|
||||||
})
|
}));
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
Command::ImportEncryption => {
|
Command::ImportEncryption => {
|
||||||
self.import_encryption(window, cx);
|
self.import_encryption(window, cx);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
use anyhow::Error;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
IntoElement, ParentElement, Render, SharedString, Styled, Window, div, svg,
|
IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, div, svg,
|
||||||
};
|
};
|
||||||
use state::NostrRegistry;
|
use state::NostrRegistry;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
@@ -18,6 +19,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<GreeterPanel> {
|
|||||||
pub struct GreeterPanel {
|
pub struct GreeterPanel {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GreeterPanel {
|
impl GreeterPanel {
|
||||||
@@ -25,6 +27,7 @@ impl GreeterPanel {
|
|||||||
Self {
|
Self {
|
||||||
name: "Onboarding".into(),
|
name: "Onboarding".into(),
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
|
tasks: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +35,7 @@ impl GreeterPanel {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
|
||||||
if let Some(public_key) = nostr.read(cx).current_user() {
|
if let Some(public_key) = nostr.read(cx).current_user() {
|
||||||
cx.spawn_in(window, async move |_this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |_this, cx| {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
profile::init(public_key, window, cx),
|
profile::init(public_key, window, cx),
|
||||||
@@ -42,8 +45,9 @@ impl GreeterPanel {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
|
||||||
.detach();
|
Ok(())
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ impl ProfilePanel {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
if status {
|
if status {
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||||
|
|
||||||
// Reset the copied state after a delay
|
// Reset the copied state after a delay
|
||||||
@@ -143,8 +143,9 @@ impl ProfilePanel {
|
|||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
|
||||||
.detach();
|
Ok(())
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user