From a39725b1d309b59d1235a956235541f3493d3632 Mon Sep 17 00:00:00 2001 From: reya Date: Tue, 27 Jan 2026 13:38:00 +0700 Subject: [PATCH] git add . --- crates/chat/src/lib.rs | 75 +++++++++++++++++++------------ crates/coop/src/panels/connect.rs | 5 ++- crates/coop/src/panels/greeter.rs | 7 ++- crates/coop/src/panels/import.rs | 42 +++++++++++++---- crates/device/src/lib.rs | 39 ++++++++++++---- crates/dock/src/lib.rs | 20 +++++---- crates/dock/src/panel.rs | 1 + crates/dock/src/tab_panel.rs | 2 +- crates/state/src/identity.rs | 6 +++ crates/state/src/lib.rs | 1 + 10 files changed, 143 insertions(+), 55 deletions(-) diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index 729de8c..16989a5 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -16,7 +16,7 @@ use gpui::{ }; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; -use state::{tracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION}; +use state::{tracker, NostrRegistry, RelayState, GIFTWRAP_SUBSCRIPTION}; mod message; mod room; @@ -63,14 +63,17 @@ pub struct ChatRegistry { /// Loading status of the registry loading: bool, - /// Tracking the status of unwrapping gift wrap events. - tracking_flag: Arc, - /// Channel's sender for communication between nostr and gpui sender: Sender, + /// Tracking the status of unwrapping gift wrap events. + tracking_flag: Arc, + + /// Handle tracking asynchronous task + tracking: Option>>, + /// Handle notifications asynchronous task - notifications: Option>>, + notifications: Option>, /// Tasks for asynchronous operations tasks: Vec>, @@ -112,7 +115,7 @@ impl ChatRegistry { subscriptions.push( // Observe the identity cx.observe(&identity, |this, state, cx| { - if state.read(cx).has_public_key() { + if state.read(cx).messaging_relays_state() == RelayState::Set { // Handle nostr notifications this.handle_notifications(cx); // Track unwrapping progress @@ -162,8 +165,9 @@ impl ChatRegistry { Self { rooms: vec![], loading: true, - tracking_flag, sender: tx.clone(), + tracking_flag, + tracking: None, notifications: None, tasks, _subscriptions: subscriptions, @@ -181,7 +185,7 @@ impl ChatRegistry { let status = self.tracking_flag.clone(); let tx = self.sender.clone(); - self.tasks.push(cx.background_spawn(async move { + self.notifications = Some(cx.background_spawn(async move { let initialized_at = Timestamp::now(); let subscription_id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION); @@ -229,7 +233,7 @@ impl ChatRegistry { } }, Err(e) => { - log::warn!("Failed to unwrap: {e}"); + log::warn!("Failed to unwrap the gift wrap event: {e}"); } } } @@ -252,7 +256,7 @@ impl ChatRegistry { let status = self.tracking_flag.clone(); let tx = self.sender.clone(); - self.notifications = Some(cx.background_spawn(async move { + self.tracking = Some(cx.background_spawn(async move { let loop_duration = Duration::from_secs(12); let mut total_loops = 0; @@ -607,29 +611,44 @@ impl ChatRegistry { device_signer: &Option>, gift_wrap: &Event, ) -> Result { - if let Some(signer) = device_signer.as_ref() { - let seal = signer - .nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content) - .await?; + // Try with the device signer first + if let Some(signer) = device_signer { + if let Ok(unwrapped) = Self::try_unwrap_with(gift_wrap, signer).await { + return Ok(unwrapped); + }; + }; - let seal: Event = Event::from_json(seal)?; - seal.verify_with_ctx(&SECP256K1)?; - - let rumor = signer.nip44_decrypt(&seal.pubkey, &seal.content).await?; - let rumor = UnsignedEvent::from_json(rumor)?; - - return Ok(UnwrappedGift { - sender: seal.pubkey, - rumor, - }); - } - - let signer = client.signer().await?; - let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?; + // Try with the user's signer + let user_signer = client.signer().await?; + let unwrapped = UnwrappedGift::from_gift_wrap(&user_signer, gift_wrap).await?; Ok(unwrapped) } + /// Attempts to unwrap a gift wrap event with a given signer. + async fn try_unwrap_with( + gift_wrap: &Event, + signer: &Arc, + ) -> Result { + // Get the sealed event + let seal = signer + .nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content) + .await?; + + // Verify the sealed event + let seal: Event = Event::from_json(seal)?; + seal.verify_with_ctx(&SECP256K1)?; + + // Get the rumor event + let rumor = signer.nip44_decrypt(&seal.pubkey, &seal.content).await?; + let rumor = UnsignedEvent::from_json(rumor)?; + + Ok(UnwrappedGift { + sender: seal.pubkey, + rumor, + }) + } + /// Stores an unwrapped event in local database with reference to original async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Result<(), Error> { let rumor_id = rumor.id.context("Rumor is missing an event id")?; diff --git a/crates/coop/src/panels/connect.rs b/crates/coop/src/panels/connect.rs index 993a77b..5a8ea69 100644 --- a/crates/coop/src/panels/connect.rs +++ b/crates/coop/src/panels/connect.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use common::TextUtils; use dock::panel::{Panel, PanelEvent}; +use dock::ClosePanel; use gpui::prelude::FluentBuilder; use gpui::{ div, img, px, relative, AnyElement, App, AppContext, Context, Entity, EventEmitter, @@ -51,6 +52,8 @@ impl ConnectPanel { Ok(uri) => { this.persist_bunker(uri, cx); this.set_signer(signer, true, cx); + // Close the current panel after setting the signer + window.dispatch_action(Box::new(ClosePanel), cx); } Err(e) => { window.push_notification(Notification::error(e.to_string()), cx); @@ -94,7 +97,7 @@ impl Render for ConnectPanel { .size_full() .items_center() .justify_center() - .gap_3() + .gap_10() .child( v_flex() .justify_center() diff --git a/crates/coop/src/panels/greeter.rs b/crates/coop/src/panels/greeter.rs index e351efb..e0eb227 100644 --- a/crates/coop/src/panels/greeter.rs +++ b/crates/coop/src/panels/greeter.rs @@ -77,11 +77,13 @@ impl Render for GreeterPanel { .justify_center() .child( v_flex() - .gap_4() + .gap_10() + .h_full() .items_center() .justify_center() .child( h_flex() + .w_96() .gap_2() .child( svg() @@ -109,9 +111,11 @@ impl Render for GreeterPanel { .child( v_flex() .gap_2() + .w_96() .child( h_flex() .gap_1() + .w_full() .text_sm() .font_semibold() .text_color(cx.theme().text_muted) @@ -120,6 +124,7 @@ impl Render for GreeterPanel { ) .child( v_flex() + .w_full() .items_start() .justify_start() .gap_2() diff --git a/crates/coop/src/panels/import.rs b/crates/coop/src/panels/import.rs index b2b347c..f73ef3a 100644 --- a/crates/coop/src/panels/import.rs +++ b/crates/coop/src/panels/import.rs @@ -2,6 +2,7 @@ use std::time::Duration; use anyhow::anyhow; use dock::panel::{Panel, PanelEvent}; +use dock::ClosePanel; use gpui::prelude::FluentBuilder; use gpui::{ div, relative, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, @@ -91,17 +92,19 @@ impl ImportPanel { } if value.starts_with("ncryptsec1") { - self.login_with_password(&value, &password, cx); + self.login_with_password(&value, &password, window, cx); return; } if let Ok(secret) = SecretKey::parse(&value) { let keys = Keys::new(secret); let nostr = NostrRegistry::global(cx); - + // Update the signer nostr.update(cx, |this, cx| { this.set_signer(keys, true, cx); }); + // Close the current panel after setting the signer + window.dispatch_action(Box::new(ClosePanel), cx); } else { self.set_error("Invalid", cx); } @@ -152,6 +155,8 @@ impl ImportPanel { Ok(uri) => { this.persist_bunker(uri, cx); this.set_signer(signer, true, cx); + // Close the current panel after setting the signer + window.dispatch_action(Box::new(ClosePanel), cx); } Err(e) => { window.push_notification(Notification::error(e.to_string()), cx); @@ -163,7 +168,13 @@ impl ImportPanel { .detach(); } - pub fn login_with_password(&mut self, content: &str, pwd: &str, cx: &mut Context) { + pub fn login_with_password( + &mut self, + content: &str, + pwd: &str, + window: &mut Window, + cx: &mut Context, + ) { if pwd.is_empty() { self.set_error("Password is required", cx); return; @@ -185,16 +196,19 @@ impl ImportPanel { } }); - cx.spawn(async move |this, cx| { + cx.spawn_in(window, async move |this, cx| { let result = task.await; - this.update(cx, |this, cx| { + this.update_in(cx, |this, window, cx| { match result { Ok(keys) => { let nostr = NostrRegistry::global(cx); + // Update the signer nostr.update(cx, |this, cx| { this.set_signer(keys, true, cx); }); + // Close the current panel after setting the signer + window.dispatch_action(Box::new(ClosePanel), cx); } Err(e) => { this.set_error(e.to_string(), cx); @@ -270,11 +284,15 @@ impl Focusable for ImportPanel { impl Render for ImportPanel { fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context) -> impl IntoElement { + const SECRET_WARN: &str = "* Coop doesn't store your secret key. \ + It will be cleared when you close the app. \ + To persist your identity, please connect via Nostr Connect."; + v_flex() .size_full() .items_center() .justify_center() - .gap_3() + .gap_10() .child( div() .text_center() @@ -284,8 +302,8 @@ impl Render for ImportPanel { ) .child( v_flex() + .w_112() .gap_2() - .w_96() .text_sm() .child( v_flex() @@ -338,7 +356,15 @@ impl Render for ImportPanel { .text_color(cx.theme().danger_foreground) .child(error.clone()), ) - }), + }) + .child( + div() + .mt_2() + .italic() + .text_xs() + .text_color(cx.theme().text_muted) + .child(SharedString::from(SECRET_WARN)), + ), ) } } diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 2c587ea..c5d07c3 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -72,13 +72,18 @@ impl DeviceRegistry { subscriptions.push( // Observe the identity entity cx.observe(&identity, |this, state, cx| { - if state.read(cx).has_public_key() { - if state.read(cx).relay_list_state() == RelayState::Set { + match state.read(cx).relay_list_state() { + RelayState::Initial => { + this.reset(cx); + } + RelayState::Set => { this.get_announcement(cx); + + if state.read(cx).messaging_relays_state() == RelayState::Set { + this.get_messages(cx); + } } - if state.read(cx).messaging_relays_state() == RelayState::Set { - this.get_messages(cx); - } + _ => {} } }), ); @@ -193,7 +198,9 @@ impl DeviceRegistry { let filter = Filter::new() .kind(Kind::ApplicationSpecificData) - .identifier(IDENTIFIER); + .identifier(IDENTIFIER) + .author(public_key) + .limit(1); if let Some(event) = client.database().query(filter).await?.first() { let content = signer.nip44_decrypt(&public_key, &event.content).await?; @@ -206,6 +213,22 @@ impl DeviceRegistry { } } + /// Reset the device state + pub fn reset(&mut self, cx: &mut Context) { + self.requests.update(cx, |this, cx| { + this.clear(); + cx.notify(); + }); + + self.device_signer.update(cx, |this, cx| { + *this = None; + cx.notify(); + }); + + self.state = DeviceState::Initial; + cx.notify(); + } + /// Returns the device signer entity pub fn signer(&self, cx: &App) -> Option> { self.device_signer.read(cx).clone() @@ -256,8 +279,8 @@ impl DeviceRegistry { // Construct a filter to get dekey messages if available if let Some(signer) = device_signer.as_ref() { - if let Ok(pubkey) = signer.get_public_key().await { - filters.push(Filter::new().kind(Kind::GiftWrap).pubkey(pubkey)); + if let Ok(pkey) = signer.get_public_key().await { + filters.push(Filter::new().kind(Kind::GiftWrap).pubkey(pkey)); } } diff --git a/crates/dock/src/lib.rs b/crates/dock/src/lib.rs index 5c78663..198c221 100644 --- a/crates/dock/src/lib.rs +++ b/crates/dock/src/lib.rs @@ -657,31 +657,35 @@ impl DockArea { cx.subscribe_in( view, window, - move |_, panel, event, window, cx| match event { + move |_this, panel, event, window, cx| match event { PanelEvent::ZoomIn => { let panel = panel.clone(); cx.spawn_in(window, async move |view, window| { - _ = view.update_in(window, |view, window, cx| { + view.update_in(window, |view, window, cx| { view.set_zoomed_in(panel, window, cx); cx.notify(); - }); + }) + .ok(); }) .detach(); } - PanelEvent::ZoomOut => cx - .spawn_in(window, async move |view, window| { + PanelEvent::ZoomOut => { + cx.spawn_in(window, async move |view, window| { _ = view.update_in(window, |view, window, cx| { view.set_zoomed_out(window, cx); }); }) - .detach(), + .detach(); + } PanelEvent::LayoutChanged => { cx.spawn_in(window, async move |view, window| { - _ = view.update_in(window, |view, window, cx| { + view.update_in(window, |view, window, cx| { view.update_toggle_button_tab_panels(window, cx) - }); + }) + .ok(); }) .detach(); + // Emit layout changed event for dock cx.emit(DockEvent::LayoutChanged); } }, diff --git a/crates/dock/src/panel.rs b/crates/dock/src/panel.rs index f1fe9d3..00bec24 100644 --- a/crates/dock/src/panel.rs +++ b/crates/dock/src/panel.rs @@ -5,6 +5,7 @@ use gpui::{ use ui::button::Button; use ui::popup_menu::PopupMenu; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PanelEvent { ZoomIn, ZoomOut, diff --git a/crates/dock/src/tab_panel.rs b/crates/dock/src/tab_panel.rs index 55e891d..3895556 100644 --- a/crates/dock/src/tab_panel.rs +++ b/crates/dock/src/tab_panel.rs @@ -1113,7 +1113,7 @@ impl TabPanel { fn on_action_close_panel( &mut self, - _: &ClosePanel, + _ev: &ClosePanel, window: &mut Window, cx: &mut Context, ) { diff --git a/crates/state/src/identity.rs b/crates/state/src/identity.rs index bc72f2c..81a652b 100644 --- a/crates/state/src/identity.rs +++ b/crates/state/src/identity.rs @@ -46,6 +46,12 @@ impl Identity { } } + /// Resets the relay states to their default values. + pub fn reset_relay_state(&mut self) { + self.relay_list = RelayState::default(); + self.messaging_relays = RelayState::default(); + } + /// Sets the state of the NIP-65 relays. pub fn set_relay_list_state(&mut self, state: RelayState) { self.relay_list = state; diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 5ebaa28..31fdc60 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -429,6 +429,7 @@ impl NostrRegistry { Ok(public_key) => { identity.update(cx, |this, cx| { this.set_public_key(public_key); + this.reset_relay_state(); this.set_owned(owned); cx.notify(); })?;