This commit is contained in:
Ren Amamiya
2026-04-03 15:23:26 +07:00
parent fbc9883680
commit 163865eb46
4 changed files with 67 additions and 108 deletions

View File

@@ -41,8 +41,6 @@ pub enum ChatEvent {
CloseRoom(u64), CloseRoom(u64),
/// An event to notify UI about a new chat request /// An event to notify UI about a new chat request
Ping, Ping,
/// An event to notify UI that the chat registry has subscribed to messaging relays
Subscribed,
/// An error occurred /// An error occurred
Error(SharedString), Error(SharedString),
} }
@@ -78,6 +76,9 @@ impl Signal {
/// Chat Registry /// Chat Registry
#[derive(Debug)] #[derive(Debug)]
pub struct ChatRegistry { pub struct ChatRegistry {
/// Whether the chat registry is currently initializing.
pub initializing: bool,
/// Chat rooms /// Chat rooms
rooms: Vec<Entity<Room>>, rooms: Vec<Entity<Room>>,
@@ -130,9 +131,9 @@ impl ChatRegistry {
cx.subscribe(&nostr, |this, _state, event, cx| { cx.subscribe(&nostr, |this, _state, event, cx| {
if event == &StateEvent::SignerSet { if event == &StateEvent::SignerSet {
this.reset(cx); this.reset(cx);
this.get_rooms(cx);
this.get_contact_list(cx); this.get_contact_list(cx);
this.get_messages(cx); this.get_messages(cx);
this.get_rooms(cx);
}; };
}), }),
); );
@@ -145,6 +146,7 @@ impl ChatRegistry {
}); });
Self { Self {
initializing: true,
rooms: vec![], rooms: vec![],
trashes: cx.new(|_| BTreeSet::default()), trashes: cx.new(|_| BTreeSet::default()),
seens: Arc::new(RwLock::new(HashMap::default())), seens: Arc::new(RwLock::new(HashMap::default())),
@@ -327,13 +329,13 @@ impl ChatRegistry {
/// Get all messages for current user /// Get all messages for current user
pub fn get_messages(&mut self, cx: &mut Context<Self>) { pub fn get_messages(&mut self, cx: &mut Context<Self>) {
let task = self.subscribe(cx); let task = self.subscribe_gift_wrap_events(cx);
self.tasks.push(cx.spawn(async move |this, cx| { self.tasks.push(cx.spawn(async move |this, cx| {
match task.await { match task.await {
Ok(_) => { Ok(_) => {
this.update(cx, |_this, cx| { this.update(cx, |this, cx| {
cx.emit(ChatEvent::Subscribed); this.set_initializing(false, cx);
})?; })?;
} }
Err(e) => { Err(e) => {
@@ -354,6 +356,7 @@ impl ChatRegistry {
cx.background_spawn(async move { cx.background_spawn(async move {
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
let id = SubscriptionId::new("inbox-relay");
// Construct filter for inbox relays // Construct filter for inbox relays
let filter = Filter::new() let filter = Filter::new()
@@ -364,12 +367,12 @@ impl ChatRegistry {
// Stream events from user's write relays // Stream events from user's write relays
let mut stream = client let mut stream = client
.stream_events(filter) .stream_events(filter)
.with_id(id)
.timeout(Duration::from_secs(TIMEOUT)) .timeout(Duration::from_secs(TIMEOUT))
.await?; .await?;
while let Some((_url, res)) = stream.next().await { while let Some((_url, res)) = stream.next().await {
if let Ok(event) = res { if let Ok(event) = res {
log::debug!("Got event: {:?}", event);
let urls: Vec<RelayUrl> = nip17::extract_owned_relay_list(event).collect(); let urls: Vec<RelayUrl> = nip17::extract_owned_relay_list(event).collect();
return Ok(urls); return Ok(urls);
} }
@@ -380,7 +383,7 @@ impl ChatRegistry {
} }
/// Continuously get gift wrap events for the current user in their messaging relays /// Continuously get gift wrap events for the current user in their messaging relays
fn subscribe(&self, cx: &App) -> Task<Result<(), Error>> { fn subscribe_gift_wrap_events(&self, cx: &App) -> Task<Result<(), Error>> {
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();
@@ -414,6 +417,12 @@ impl ChatRegistry {
}) })
} }
/// Set the initializing status of the chat registry
fn set_initializing(&mut self, initializing: bool, cx: &mut Context<Self>) {
self.initializing = initializing;
cx.notify();
}
/// Get the loading status of the chat registry /// Get the loading status of the chat registry
pub fn loading(&self) -> bool { pub fn loading(&self) -> bool {
self.tracking_flag.load(Ordering::Acquire) self.tracking_flag.load(Ordering::Acquire)
@@ -557,7 +566,12 @@ impl ChatRegistry {
/// Reset the registry. /// Reset the registry.
pub fn reset(&mut self, cx: &mut Context<Self>) { pub fn reset(&mut self, cx: &mut Context<Self>) {
self.initializing = true;
self.rooms.clear(); self.rooms.clear();
self.trashes.update(cx, |this, cx| {
this.clear();
cx.notify();
});
cx.notify(); cx.notify();
} }

View File

@@ -177,16 +177,6 @@ impl Workspace {
window.push_notification(note, cx); window.push_notification(note, cx);
} }
DeviceEvent::NotSubscribe { reason } => {
let note = Notification::new()
.id::<DeviceNotifcation>()
.title("Cannot getting messages")
.message(reason)
.autohide(false)
.with_kind(NotificationKind::Error);
window.push_notification(note, cx);
}
DeviceEvent::Error(error) => { DeviceEvent::Error(error) => {
window.push_notification(Notification::error(error).autohide(false), cx); window.push_notification(Notification::error(error).autohide(false), cx);
} }
@@ -650,6 +640,12 @@ impl Workspace {
} }
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement { fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let chat = ChatRegistry::global(cx);
let initializing = chat.read(cx).initializing;
let device = DeviceRegistry::global(cx);
let device_initializing = device.read(cx).initializing;
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let signer = nostr.read(cx).signer(); let signer = nostr.read(cx).signer();
@@ -701,9 +697,13 @@ impl Workspace {
.tooltip("Decoupled encryption key") .tooltip("Decoupled encryption key")
.small() .small()
.ghost() .ghost()
.loading(device_initializing)
.when(device_initializing, |this| {
this.label("Dekey")
.xsmall()
.tooltip("Loading decoupled encryption key...")
})
.dropdown_menu(move |this, _window, cx| { .dropdown_menu(move |this, _window, cx| {
let device = DeviceRegistry::global(cx);
let subscribing = device.read(cx).subscribing;
let requesting = device.read(cx).requesting; let requesting = device.read(cx).requesting;
this.min_w(px(260.)) this.min_w(px(260.))
@@ -724,30 +724,6 @@ impl Workspace {
.child(SharedString::from("Waiting for approval...")) .child(SharedString::from("Waiting for approval..."))
})) }))
}) })
.item(PopupMenuItem::element(move |_window, cx| {
h_flex()
.px_1()
.w_full()
.gap_2()
.text_sm()
.when(!subscribing, |this| {
this.text_color(cx.theme().text_muted)
})
.child(div().size_1p5().rounded_full().map(|this| {
if subscribing {
this.bg(cx.theme().icon_accent)
} else {
this.bg(cx.theme().icon_muted)
}
}))
.map(|this| {
if subscribing {
this.child("Listening for messages")
} else {
this.child("Idle")
}
})
}))
.separator() .separator()
.menu_with_icon( .menu_with_icon(
"Backup", "Backup",
@@ -777,8 +753,13 @@ impl Workspace {
.icon(IconName::Inbox) .icon(IconName::Inbox)
.small() .small()
.ghost() .ghost()
.loading(initializing)
.when(initializing, |this| {
this.label("Inbox")
.xsmall()
.tooltip("Getting inbox messages...")
})
.dropdown_menu(move |this, _window, cx| { .dropdown_menu(move |this, _window, cx| {
let chat = ChatRegistry::global(cx);
let persons = PersonRegistry::global(cx); let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get(&public_key, cx); let profile = persons.read(cx).get(&public_key, cx);
@@ -832,12 +813,12 @@ impl Workspace {
Box::new(Command::RefreshMessagingRelays), Box::new(Command::RefreshMessagingRelays),
) )
.menu_with_icon( .menu_with_icon(
"Update gossip relays", "Manage gossip relays",
IconName::Relay, IconName::Relay,
Box::new(Command::ShowRelayList), Box::new(Command::ShowRelayList),
) )
.menu_with_icon( .menu_with_icon(
"Update messaging relays", "Manage messaging relays",
IconName::Settings, IconName::Settings,
Box::new(Command::ShowMessaging), Box::new(Command::ShowMessaging),
) )

View File

@@ -41,8 +41,6 @@ pub enum DeviceEvent {
Creating, Creating,
/// Encryption key is not set /// Encryption key is not set
NotSet { reason: SharedString }, NotSet { reason: SharedString },
/// An event to notify that Coop isn't subscribed to gift wrap events
NotSubscribe { reason: SharedString },
/// An error occurred /// An error occurred
Error(SharedString), Error(SharedString),
} }
@@ -55,15 +53,6 @@ impl DeviceEvent {
Self::Error(error.into()) Self::Error(error.into())
} }
pub fn not_subscribe<T>(reason: T) -> Self
where
T: Into<SharedString>,
{
Self::NotSubscribe {
reason: reason.into(),
}
}
pub fn not_set<T>(reason: T) -> Self pub fn not_set<T>(reason: T) -> Self
where where
T: Into<SharedString>, T: Into<SharedString>,
@@ -79,14 +68,14 @@ impl DeviceEvent {
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md /// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
#[derive(Debug)] #[derive(Debug)]
pub struct DeviceRegistry { pub struct DeviceRegistry {
/// Whether the registry is currently subscribing to gift wrap events /// Whether the registry is currently initializing
pub subscribing: bool, pub initializing: bool,
/// Whether the registry is waiting for encryption key approval from other devices /// Whether the registry is waiting for encryption key approval from other devices
pub requesting: bool, pub requesting: bool,
/// Whether there is a pending request for encryption key approval /// Whether there is a pending request for encryption key approval
pub has_pending_request: bool, pub pending_request: bool,
/// Async tasks /// Async tasks
tasks: Vec<Task<Result<(), Error>>>, tasks: Vec<Task<Result<(), Error>>>,
@@ -111,10 +100,13 @@ impl DeviceRegistry {
/// Create a new device registry instance /// Create a new device registry instance
fn new(window: &mut Window, 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);
// Subscribe to nostr state events
let subscription = cx.subscribe_in(&nostr, window, |this, _e, event, _window, cx| { let subscription = cx.subscribe_in(&nostr, window, |this, _e, event, _window, cx| {
if event == &StateEvent::SignerSet { if event == &StateEvent::SignerSet {
this.set_subscribing(false, cx); this.set_initializing(true, cx);
this.set_requesting(false, cx); this.set_requesting(false, cx);
this.get_announcement(cx);
}; };
}); });
@@ -123,9 +115,9 @@ impl DeviceRegistry {
}); });
Self { Self {
subscribing: false, initializing: true,
requesting: false, requesting: false,
has_pending_request: false, pending_request: false,
tasks: vec![], tasks: vec![],
_subscription: Some(subscription), _subscription: Some(subscription),
} }
@@ -139,7 +131,6 @@ impl DeviceRegistry {
self.tasks.push(cx.background_spawn(async move { self.tasks.push(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();
let mut found_relay_list = false;
while let Some(notification) = notifications.next().await { while let Some(notification) = notifications.next().await {
if let ClientNotification::Message { message, .. } = notification if let ClientNotification::Message { message, .. } = notification
@@ -151,17 +142,6 @@ impl DeviceRegistry {
} }
match event.kind { match event.kind {
Kind::RelayList => {
// Skip if the relay list has already been found
if found_relay_list {
continue;
}
// Verify the relay list event is signed by the user's signer
if verify_author(&client, event.as_ref()).await {
tx.send_async(event.into_owned()).await?;
found_relay_list = true;
}
}
Kind::Custom(4454) => { Kind::Custom(4454) => {
if verify_author(&client, event.as_ref()).await { if verify_author(&client, event.as_ref()).await {
tx.send_async(event.into_owned()).await?; tx.send_async(event.into_owned()).await?;
@@ -202,9 +182,9 @@ impl DeviceRegistry {
})); }));
} }
/// Set whether the registry is currently subscribing to gift wrap events /// Set whether the registry is currently initializing
fn set_subscribing(&mut self, subscribing: bool, cx: &mut Context<Self>) { fn set_initializing(&mut self, initializing: bool, cx: &mut Context<Self>) {
self.subscribing = subscribing; self.initializing = initializing;
cx.notify(); cx.notify();
} }
@@ -215,8 +195,8 @@ impl DeviceRegistry {
} }
/// Set whether there is a pending request for encryption key approval /// Set whether there is a pending request for encryption key approval
fn set_has_pending_request(&mut self, pending: bool, cx: &mut Context<Self>) { fn set_pending_request(&mut self, pending: bool, cx: &mut Context<Self>) {
self.has_pending_request = pending; self.pending_request = pending;
cx.notify(); cx.notify();
} }
@@ -243,16 +223,16 @@ impl DeviceRegistry {
/// Get all messages for encryption keys /// Get all messages for encryption keys
fn get_messages(&mut self, cx: &mut Context<Self>) { fn get_messages(&mut self, cx: &mut Context<Self>) {
let task = self.subscribe_to_giftwrap_events(cx); let task = self.subscribe_gift_wrap_events(cx);
self.tasks.push(cx.spawn(async move |this, cx| { self.tasks.push(cx.spawn(async move |this, cx| {
if let Err(e) = task.await { if let Err(e) = task.await {
this.update(cx, |_this, cx| { this.update(cx, |_this, cx| {
cx.emit(DeviceEvent::not_subscribe(e.to_string())); cx.emit(DeviceEvent::error(e.to_string()));
})?; })?;
} else { } else {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.set_subscribing(true, cx); this.set_initializing(false, cx);
})?; })?;
} }
Ok(()) Ok(())
@@ -260,7 +240,7 @@ impl DeviceRegistry {
} }
/// Continuously get gift wrap events for the current user in their messaging relays /// Continuously get gift wrap events for the current user in their messaging relays
fn subscribe_to_giftwrap_events(&self, cx: &App) -> Task<Result<(), Error>> { fn subscribe_gift_wrap_events(&self, cx: &App) -> Task<Result<(), Error>> {
let persons = PersonRegistry::global(cx); let persons = PersonRegistry::global(cx);
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
@@ -471,21 +451,16 @@ impl DeviceRegistry {
self.tasks.push(cx.background_spawn(async move { self.tasks.push(cx.background_spawn(async move {
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
let id = SubscriptionId::new("dekey-requests");
// Construct a filter for encryption key requests // Construct a filter for encryption key requests
let now = Filter::new() let filter = Filter::new()
.kind(Kind::Custom(4454)) .kind(Kind::Custom(4454))
.author(public_key) .author(public_key)
.since(Timestamp::now()); .since(Timestamp::now());
// Construct a filter for the last encryption key request
let last = Filter::new()
.kind(Kind::Custom(4454))
.author(public_key)
.limit(1);
// Subscribe to the device key requests on user's write relays // Subscribe to the device key requests on user's write relays
client.subscribe(vec![now, last]).await?; client.subscribe(vec![filter]).with_id(id).await?;
Ok(()) Ok(())
})); }));
@@ -687,10 +662,10 @@ impl DeviceRegistry {
/// Handle encryption request /// Handle encryption request
fn ask_for_approval(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) { fn ask_for_approval(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
// Ignore if there is already a pending request // Ignore if there is already a pending request
if self.has_pending_request { if self.pending_request {
return; return;
} }
self.set_has_pending_request(true, cx); self.set_pending_request(true, cx);
// Show notification // Show notification
let notification = self.notification(event, cx); let notification = self.notification(event, cx);

View File

@@ -133,14 +133,7 @@ impl NostrRegistry {
.signer(signer.clone()) .signer(signer.clone())
.database(lmdb) .database(lmdb)
.gossip(NostrGossipMemory::unbounded()) .gossip(NostrGossipMemory::unbounded())
.gossip_config(
GossipConfig::default()
.no_background_refresh()
.sync_idle_timeout(Duration::from_secs(TIMEOUT))
.sync_initial_timeout(Duration::from_millis(600)),
)
.automatic_authentication(false) .automatic_authentication(false)
.verify_subscriptions(true)
.connect_timeout(Duration::from_secs(10)) .connect_timeout(Duration::from_secs(10))
.sleep_when_idle(SleepWhenIdle::Enabled { .sleep_when_idle(SleepWhenIdle::Enabled {
timeout: Duration::from_secs(600), timeout: Duration::from_secs(600),
@@ -280,7 +273,7 @@ impl NostrRegistry {
let app_keys = self.app_keys.clone(); let app_keys = self.app_keys.clone();
if let Ok(payload) = std::fs::read_to_string(key_path) { if let Ok(payload) = std::fs::read_to_string(key_path) {
if payload.starts_with("nsec1") || payload.starts_with("bunker://") { if !payload.is_empty() {
cx.background_spawn(async move { cx.background_spawn(async move {
let decrypted = app_keys.nip44_decrypt(&public_key, &payload).await?; let decrypted = app_keys.nip44_decrypt(&public_key, &payload).await?;
let secret = SecretKey::parse(&decrypted)?; let secret = SecretKey::parse(&decrypted)?;
@@ -435,11 +428,7 @@ impl NostrRegistry {
let event = EventBuilder::nip17_relay_list(relays).sign(&signer).await?; let event = EventBuilder::nip17_relay_list(relays).sign(&signer).await?;
// Publish messaging relay list event // Publish messaging relay list event
client client.send_event(&event).to_nip65().await?;
.send_event(&event)
.to_nip65()
.ack_policy(AckPolicy::none())
.await?;
// Write user's credentials to the system keyring // Write user's credentials to the system keyring
write_secret.await?; write_secret.await?;