From 1d8e3724a8c9e583a1617bdb3650f373afc91ffc Mon Sep 17 00:00:00 2001 From: reya Date: Tue, 17 Feb 2026 07:54:46 +0700 Subject: [PATCH] wip --- Cargo.lock | 2 +- crates/chat/src/lib.rs | 17 +-- crates/chat/src/room.rs | 10 +- crates/chat_ui/Cargo.toml | 2 +- crates/chat_ui/src/lib.rs | 206 ++++++++++++++++++----------------- crates/relay_auth/src/lib.rs | 199 +++++++++++++++++++-------------- crates/state/src/event.rs | 46 -------- crates/state/src/lib.rs | 5 - 8 files changed, 239 insertions(+), 248 deletions(-) delete mode 100644 crates/state/src/event.rs diff --git a/Cargo.lock b/Cargo.lock index ea3a59a..7188f48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,9 +1009,9 @@ dependencies = [ "chat", "common", "dock", + "flume", "gpui", "gpui_tokio", - "indexset", "itertools 0.13.0", "log", "nostr-sdk", diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index 49f0da7..e84eb75 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -14,7 +14,7 @@ use gpui::{ }; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; -use state::{tracker, NostrRegistry, RelayState, DEVICE_GIFTWRAP, USER_GIFTWRAP}; +use state::{NostrRegistry, RelayState, DEVICE_GIFTWRAP, USER_GIFTWRAP}; mod message; mod room; @@ -153,19 +153,10 @@ impl ChatRegistry { match Self::extract_rumor(&client, &device_signer, event.as_ref()).await { Ok(rumor) => match rumor.created_at >= initialized_at { true => { - // Check if the event is sent by coop - let sent_by_coop = { - let tracker = tracker().read().await; - tracker.is_sent_by_coop(&event.id) - }; - // No need to emit if sent by coop - // the event is already emitted - if !sent_by_coop { - let new_message = NewMessage::new(event.id, rumor); - let signal = Signal::Message(new_message); + let new_message = NewMessage::new(event.id, rumor); + let signal = Signal::Message(new_message); - tx.send_async(signal).await.ok(); - } + tx.send_async(signal).await.ok(); } false => { status.store(true, Ordering::Release); diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index 1f9dc93..cde8ed8 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -30,11 +30,13 @@ impl SendReport { } } + /// Set the output. pub fn output(mut self, output: Output) -> Self { self.output = Some(output); self } + /// Set the error message. pub fn error(mut self, error: T) -> Self where T: Into, @@ -43,11 +45,13 @@ impl SendReport { self } - pub fn is_relay_error(&self) -> bool { - self.error.is_some() + /// Returns true if the send is pending. + pub fn pending(&self) -> bool { + self.output.is_none() && self.error.is_none() } - pub fn is_sent_success(&self) -> bool { + /// Returns true if the send was successful. + pub fn success(&self) -> bool { if let Some(output) = self.output.as_ref() { !output.success.is_empty() } else { diff --git a/crates/chat_ui/Cargo.toml b/crates/chat_ui/Cargo.toml index 43a3157..fef8a71 100644 --- a/crates/chat_ui/Cargo.toml +++ b/crates/chat_ui/Cargo.toml @@ -22,10 +22,10 @@ anyhow.workspace = true itertools.workspace = true smallvec.workspace = true smol.workspace = true +flume.workspace = true log.workspace = true serde.workspace = true serde_json.workspace = true -indexset = "0.12.3" once_cell = "1.19.0" regex = "1" diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index 3fdb087..f686fdb 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -1,4 +1,5 @@ -use std::collections::HashSet; +use std::collections::{BTreeMap, BTreeSet, HashSet}; +use std::sync::Arc; pub use actions::*; use anyhow::{Context as AnyhowContext, Error}; @@ -7,25 +8,26 @@ use common::{nip96_upload, RenderedTimestamp}; use dock::panel::{Panel, PanelEvent}; use gpui::prelude::FluentBuilder; use gpui::{ - div, img, list, px, red, relative, rems, svg, white, AnyElement, App, AppContext, + deferred, div, img, list, px, red, relative, rems, svg, white, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ListAlignment, ListOffset, ListState, MouseButton, ObjectFit, ParentElement, PathPromptOptions, Render, SharedString, StatefulInteractiveElement, Styled, StyledImage, Subscription, Task, WeakEntity, Window, }; use gpui_tokio::Tokio; -use indexset::{BTreeMap, BTreeSet}; use itertools::Itertools; use nostr_sdk::prelude::*; use person::{Person, PersonRegistry}; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use smol::fs; +use smol::lock::RwLock; use state::NostrRegistry; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; use ui::context_menu::ContextMenuExt; +use ui::indicator::Indicator; use ui::input::{InputEvent, InputState, TextInput}; use ui::notification::Notification; use ui::popup_menu::PopupMenuExt; @@ -61,7 +63,7 @@ pub struct ChatPanel { rendered_texts_by_id: BTreeMap, /// Mapping message (rumor event) ids to their reports - reports_by_id: BTreeMap>, + reports_by_id: Arc>>>, /// Input state input: Entity, @@ -76,7 +78,7 @@ pub struct ChatPanel { uploading: bool, /// Async operations - tasks: SmallVec<[Task>; 2]>, + tasks: Vec>>, /// Event subscriptions subscriptions: SmallVec<[Subscription; 2]>, @@ -125,6 +127,7 @@ impl ChatPanel { cx.defer_in(window, |this, window, cx| { this.subscribe_room_events(window, cx); this.connect(window, cx); + this.handle_notifications(cx); this.get_messages(window, cx); }); @@ -138,13 +141,46 @@ impl ChatPanel { replies_to, attachments, rendered_texts_by_id: BTreeMap::new(), - reports_by_id: BTreeMap::new(), + reports_by_id: Arc::new(RwLock::new(BTreeMap::new())), uploading: false, subscriptions, - tasks: smallvec![], + tasks: vec![], } } + /// Handle nostr notifications + fn handle_notifications(&mut self, cx: &mut Context) { + let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); + let reports = self.reports_by_id.clone(); + + self.tasks.push(cx.background_spawn(async move { + let mut notifications = client.notifications(); + + while let Some(notification) = notifications.next().await { + if let ClientNotification::Message { + message: RelayMessage::Ok { event_id, .. }, + relay_url, + } = notification + { + let mut writer = reports.write().await; + + for reports in writer.values_mut() { + for report in reports.iter_mut() { + if let Some(output) = report.output.as_mut() { + if output.id() == &event_id { + output.success.insert(relay_url.clone()); + } + } + } + } + } + } + + Ok(()) + })); + } + fn subscribe_room_events(&mut self, window: &mut Window, cx: &mut Context) { let Some(room) = self.room.upgrade() else { return; @@ -240,6 +276,7 @@ impl ChatPanel { // Get room entity let room = self.room.clone(); + // Get content and replies let replies: Vec = self.replies_to.read(cx).iter().copied().collect(); let content = value.to_string(); @@ -251,6 +288,7 @@ impl ChatPanel { 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); @@ -262,7 +300,11 @@ impl ChatPanel { })); } + /// Send message in the background and wait for the response fn send_and_wait(&mut self, rumor: UnsignedEvent, window: &mut Window, cx: &mut Context) { + // This can't fail, because we already ensured that the ID is set + let id = rumor.id.unwrap(); + let Some(room) = self.room.upgrade() else { return; }; @@ -274,14 +316,36 @@ impl ChatPanel { self.tasks.push(cx.spawn_in(window, async move |this, cx| { let outputs = task.await; - log::info!("Message sent successfully: {outputs:?}"); + + // Update the state + this.update(cx, |this, cx| { + this.insert_reports(id, outputs, cx); + })?; Ok(()) })) } + /// Clear the input field, attachments, and replies + /// + /// Only run after sending a message + fn clear(&mut self, window: &mut Window, cx: &mut Context) { + self.input.update(cx, |this, cx| { + this.set_value("", window, cx); + }); + self.attachments.update(cx, |this, cx| { + this.clear(); + cx.notify(); + }); + self.replies_to.update(cx, |this, cx| { + this.clear(); + cx.notify(); + }) + } + + /// Synchronously insert reports fn insert_reports(&mut self, id: EventId, reports: Vec, cx: &mut Context) { - self.reports_by_id.insert(id, reports); + self.reports_by_id.write_blocking().insert(id, reports); cx.notify(); } @@ -315,23 +379,25 @@ impl ChatPanel { } } - /// Check if a message failed to send by its ID - fn is_sent_failed(&self, id: &EventId) -> bool { + /// Check if a message is pending + fn sent_pending(&self, id: &EventId) -> bool { self.reports_by_id + .read_blocking() .get(id) - .is_some_and(|reports| reports.iter().all(|r| !r.is_sent_success())) + .is_some_and(|reports| reports.iter().any(|r| r.pending())) } /// Check if a message was sent successfully by its ID - fn is_sent_success(&self, id: &EventId) -> Option { + fn sent_success(&self, id: &EventId) -> bool { self.reports_by_id + .read_blocking() .get(id) - .map(|reports| reports.iter().all(|r| r.is_sent_success())) + .is_some_and(|reports| reports.iter().all(|r| r.success())) } - /// Get the sent reports for a message by its ID - fn sent_reports(&self, id: &EventId) -> Option<&Vec> { - self.reports_by_id.get(id) + /// Get all sent reports for a message by its ID + fn sent_reports(&self, id: &EventId) -> Option> { + self.reports_by_id.read_blocking().get(id).cloned() } /// Get a message by its ID @@ -380,13 +446,6 @@ impl ChatPanel { }); } - fn remove_all_replies(&mut self, cx: &mut Context) { - self.replies_to.update(cx, |this, cx| { - this.clear(); - cx.notify(); - }); - } - fn upload(&mut self, window: &mut Window, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); @@ -401,9 +460,9 @@ impl ChatPanel { prompt: None, }); - cx.spawn_in(window, async move |this, cx| { - let mut paths = path.await.ok()?.ok()??; - let path = paths.pop()?; + self.tasks.push(cx.spawn_in(window, async move |this, cx| { + let mut paths = path.await??.context("Not found")?; + let path = paths.pop().context("No path")?; let upload = Tokio::spawn(cx, async move { let file = fs::read(path).await.ok()?; @@ -432,9 +491,8 @@ impl ChatPanel { .ok(); } - Some(()) - }) - .detach(); + Ok(()) + })); } fn set_uploading(&mut self, uploading: bool, cx: &mut Context) { @@ -458,13 +516,6 @@ impl ChatPanel { }); } - fn remove_all_attachments(&mut self, cx: &mut Context) { - self.attachments.update(cx, |this, cx| { - this.clear(); - cx.notify(); - }); - } - fn profile(&self, public_key: &PublicKey, cx: &Context) -> Person { let persons = PersonRegistry::global(cx); persons.read(cx).get(public_key, cx) @@ -530,7 +581,7 @@ impl ChatPanel { window: &mut Window, cx: &mut Context, ) -> AnyElement { - if let Some(message) = self.messages.get_index(ix) { + if let Some(message) = self.messages.iter().nth(ix) { match message { Message::User(rendered) => { let text = self @@ -555,7 +606,7 @@ impl ChatPanel { &self, ix: usize, message: &RenderedMessage, - text: AnyElement, + rendered_text: AnyElement, cx: &Context, ) -> AnyElement { let id = message.id; @@ -566,10 +617,10 @@ impl ChatPanel { let has_replies = !replies.is_empty(); // Check if message is sent failed - let is_sent_failed = self.is_sent_failed(&id); + let sent_pending = self.sent_pending(&id); // Check if message is sent successfully - let is_sent_success = self.is_sent_success(&id); + let sent_success = self.sent_success(&id); // Hide avatar setting let hide_avatar = AppSettings::get_hide_avatar(cx); @@ -617,18 +668,19 @@ impl ChatPanel { .child(author.name()), ) .child(message.created_at.to_human_time()) - .when_some(is_sent_success, |this, status| { - this.when(status, |this| { - this.child(self.render_message_sent(&id, cx)) - }) + .when(sent_pending, |this| { + this.child(deferred(Indicator::new().small())) + }) + .when(sent_success, |this| { + this.child(deferred(self.render_sent_indicator(&id, cx))) }), ) .when(has_replies, |this| { this.children(self.render_message_replies(replies, cx)) }) - .child(text) - .when(is_sent_failed, |this| { - this.child(self.render_message_reports(&id, cx)) + .child(rendered_text) + .when(!sent_success, |this| { + this.child(deferred(self.render_message_reports(&id, cx))) }), ), ) @@ -693,11 +745,11 @@ impl ChatPanel { items } - fn render_message_sent(&self, id: &EventId, _cx: &Context) -> impl IntoElement { + fn render_sent_indicator(&self, id: &EventId, _cx: &Context) -> impl IntoElement { div() .id(SharedString::from(id.to_hex())) .child(SharedString::from("• Sent")) - .when_some(self.sent_reports(id).cloned(), |this, reports| { + .when_some(self.sent_reports(id), |this, reports| { this.on_click(move |_e, window, cx| { let reports = reports.clone(); @@ -708,8 +760,7 @@ impl ChatPanel { let mut items = Vec::with_capacity(reports.len()); for report in reports.iter() { - //items.push(Self::render_report(report, cx)) - items.push(div()) + items.push(Self::render_report(report, cx)) } items @@ -730,7 +781,7 @@ impl ChatPanel { .child(SharedString::from( "Failed to send message. Click to see details.", )) - .when_some(self.sent_reports(id).cloned(), |this, reports| { + .when_some(self.sent_reports(id), |this, reports| { this.on_click(move |_e, window, cx| { let reports = reports.clone(); @@ -741,8 +792,7 @@ impl ChatPanel { let mut items = Vec::with_capacity(reports.len()); for report in reports.iter() { - //items.push(Self::render_report(report, cx)) - items.push(div()) + items.push(Self::render_report(report, cx)) } items @@ -752,7 +802,6 @@ impl ChatPanel { }) } - /* fn render_report(report: &SendReport, cx: &App) -> impl IntoElement { let persons = PersonRegistry::global(cx); let profile = persons.read(cx).get(&report.receiver, cx); @@ -775,48 +824,6 @@ impl ChatPanel { .child(name.clone()), ), ) - .when(report.relays_not_found, |this| { - this.child( - h_flex() - .flex_wrap() - .justify_center() - .p_2() - .h_20() - .w_full() - .text_sm() - .rounded(cx.theme().radius) - .bg(cx.theme().danger_background) - .text_color(cx.theme().danger_foreground) - .child( - div() - .flex_1() - .w_full() - .text_center() - .child(SharedString::from("Messaging Relays not found")), - ), - ) - }) - .when(report.device_not_found, |this| { - this.child( - h_flex() - .flex_wrap() - .justify_center() - .p_2() - .h_20() - .w_full() - .text_sm() - .rounded(cx.theme().radius) - .bg(cx.theme().danger_background) - .text_color(cx.theme().danger_foreground) - .child( - div() - .flex_1() - .w_full() - .text_center() - .child(SharedString::from("Encryption Key not found")), - ), - ) - }) .when_some(report.error.clone(), |this, error| { this.child( h_flex() @@ -832,7 +839,7 @@ impl ChatPanel { .child(div().flex_1().w_full().text_center().child(error)), ) }) - .when_some(report.status.clone(), |this, output| { + .when_some(report.output.clone(), |this, output| { this.child( v_flex() .gap_2() @@ -902,7 +909,6 @@ impl ChatPanel { ) }) } - */ fn render_border(&self, cx: &Context) -> impl IntoElement { div() diff --git a/crates/relay_auth/src/lib.rs b/crates/relay_auth/src/lib.rs index 8f26d4d..2f3ac6c 100644 --- a/crates/relay_auth/src/lib.rs +++ b/crates/relay_auth/src/lib.rs @@ -13,7 +13,7 @@ use gpui::{ use nostr_sdk::prelude::*; use settings::{AppSettings, AuthMode}; use smallvec::{smallvec, SmallVec}; -use state::{tracker, NostrRegistry}; +use state::NostrRegistry; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::notification::Notification; @@ -28,7 +28,7 @@ pub fn init(window: &mut Window, cx: &mut App) { /// Authentication request #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct AuthRequest { +struct AuthRequest { url: RelayUrl, challenge: String, } @@ -56,6 +56,12 @@ impl AuthRequest { } } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +enum Signal { + Auth(Arc), + Pending((EventId, RelayUrl)), +} + struct GlobalRelayAuth(Entity); impl Global for GlobalRelayAuth {} @@ -63,6 +69,9 @@ impl Global for GlobalRelayAuth {} // Relay authentication #[derive(Debug)] pub struct RelayAuth { + /// Pending events waiting for resend after authentication + pending_events: HashSet<(EventId, RelayUrl)>, + /// Tasks for asynchronous operations tasks: SmallVec<[Task<()>; 2]>, @@ -84,89 +93,113 @@ impl RelayAuth { /// Create a new relay auth instance fn new(window: &mut Window, cx: &mut Context) -> Self { let nostr = NostrRegistry::global(cx); - // Channel for communication between nostr and gpui - let (tx, rx) = flume::bounded::>(100); - let mut subscriptions = smallvec![]; - let mut tasks = smallvec![]; subscriptions.push( - // Observe the current state - cx.observe(&nostr, move |this, state, cx| { + // Observe the nostr state + cx.observe_in(&nostr, window, move |this, state, window, cx| { if state.read(cx).connected() { - this.handle_notifications(tx.clone(), cx) - } - }), - ); - - tasks.push( - // Update GPUI states - cx.spawn_in(window, async move |this, cx| { - while let Ok(req) = rx.recv_async().await { - this.update_in(cx, |this, window, cx| { - this.handle_auth(&req, window, cx); - }) - .ok(); + this.handle_notifications(window, cx) } }), ); Self { - tasks, + pending_events: HashSet::default(), + tasks: smallvec![], _subscriptions: subscriptions, } } - // Handle nostr notifications - fn handle_notifications( - &mut self, - tx: flume::Sender>, - cx: &mut Context, - ) { + /// Handle nostr notifications + fn handle_notifications(&mut self, window: &mut Window, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let task = cx.background_spawn(async move { + // Channel for communication between nostr and gpui + let (tx, rx) = flume::bounded::(256); + + cx.background_spawn(async move { let mut notifications = client.notifications(); let mut challenges: HashSet> = HashSet::default(); while let Some(notification) = notifications.next().await { - match notification { - ClientNotification::Message { relay_url, message } => { - match message { - RelayMessage::Auth { challenge } => { - if challenges.insert(challenge.clone()) { - let request = AuthRequest::new(challenge, relay_url); - tx.send_async(Arc::new(request)).await.ok(); - } - } - RelayMessage::Ok { - event_id, message, .. - } => { - let msg = MachineReadablePrefix::parse(&message); - let mut tracker = tracker().write().await; + if let ClientNotification::Message { relay_url, message } = notification { + match message { + RelayMessage::Auth { challenge } => { + if challenges.insert(challenge.clone()) { + let request = AuthRequest::new(challenge, relay_url); + let signal = Signal::Auth(Arc::new(request)); - // Handle authentication messages - if let Some(MachineReadablePrefix::AuthRequired) = msg { - // Keep track of events that need to be resent after authentication - tracker.add_to_pending(event_id, relay_url); - } else { - // Keep track of events sent by Coop - tracker.sent(event_id) - } + tx.send_async(signal).await.ok(); } - _ => {} } + RelayMessage::Ok { + event_id, message, .. + } => { + let msg = MachineReadablePrefix::parse(&message); + + // Handle authentication messages + if let Some(MachineReadablePrefix::AuthRequired) = msg { + let signal = Signal::Pending((event_id, relay_url)); + + tx.send_async(signal).await.ok(); + } + } + _ => {} } - ClientNotification::Shutdown => break, - _ => {} } } - }); + }) + .detach(); - self.tasks.push(task); + self.tasks.push(cx.spawn_in(window, async move |this, cx| { + while let Ok(signal) = rx.recv_async().await { + match signal { + Signal::Auth(req) => { + this.update_in(cx, |this, window, cx| { + this.handle_auth(&req, window, cx); + }) + .ok(); + } + Signal::Pending((event_id, relay_url)) => { + this.update_in(cx, |this, _window, cx| { + this.insert_pending_event(event_id, relay_url, cx); + }) + .ok(); + } + } + } + })); } + /// Insert a pending event waiting for resend after authentication + fn insert_pending_event(&mut self, id: EventId, relay: RelayUrl, cx: &mut Context) { + self.pending_events.insert((id, relay)); + cx.notify(); + } + + /// Get all pending events for a specific relay, + fn get_pending_events(&self, relay: &RelayUrl, _cx: &App) -> Vec { + let pending_events: Vec = self + .pending_events + .iter() + .filter(|(_, pending_relay)| pending_relay == relay) + .map(|(id, _relay)| id) + .cloned() + .collect(); + + pending_events + } + + /// Clear all pending events for a specific relay, + fn clear_pending_events(&mut self, relay: &RelayUrl, cx: &mut Context) { + self.pending_events + .retain(|(_, pending_relay)| pending_relay != relay); + cx.notify(); + } + + /// Handle authentication request fn handle_auth(&mut self, req: &Arc, window: &mut Window, cx: &mut Context) { let settings = AppSettings::global(cx); let trusted_relay = settings.read(cx).trusted_relay(req.url(), cx); @@ -181,29 +214,25 @@ impl RelayAuth { } } - /// Respond to an authentication request. - fn response(&self, req: &Arc, window: &Window, cx: &Context) { - let settings = AppSettings::global(cx); + /// Send auth response and wait for confirmation + fn auth(&self, req: &Arc, cx: &App) -> Task> { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let req = req.clone(); - let challenge = req.challenge().to_string(); - let async_req = req.clone(); - let task: Task> = cx.background_spawn(async move { + // Get all pending events for the relay + let pending_events = self.get_pending_events(req.url(), cx); + + cx.background_spawn(async move { // Construct event - let builder = EventBuilder::auth(async_req.challenge(), async_req.url().clone()); + let builder = EventBuilder::auth(req.challenge(), req.url().clone()); let event = client.sign_event_builder(builder).await?; // Get the event ID let id = event.id; // Get the relay - let relay = client - .relay(async_req.url()) - .await? - .context("Relay not found")?; + let relay = client.relay(req.url()).await?.context("Relay not found")?; // Subscribe to notifications let mut notifications = relay.notifications(); @@ -219,17 +248,18 @@ impl RelayAuth { message: RelayMessage::Ok { event_id, .. }, } => { if id == event_id { - // Re-subscribe to previous subscription - // relay.resubscribe().await?; + // Get all subscriptions + let subscriptions = relay.subscriptions().await; - // Get all pending events that need to be resent - let mut tracker = tracker().write().await; - let ids: Vec = tracker.pending_resend(relay.url()); + // Re-subscribe to previous subscriptions + for (id, filters) in subscriptions.into_iter() { + relay.subscribe(filters).with_id(id).await?; + } - for id in ids.into_iter() { + // Re-send pending events + for id in pending_events { if let Some(event) = client.database().event_by_id(&id).await? { - let event_id = relay.send_event(&event).await?; - tracker.sent(event_id); + relay.send_event(&event).await?; } } @@ -242,22 +272,33 @@ impl RelayAuth { } Err(anyhow!("Authentication failed")) - }); + }) + } + + /// Respond to an authentication request. + fn response(&self, req: &Arc, window: &Window, cx: &Context) { + let settings = AppSettings::global(cx); + let req = req.clone(); + let challenge = req.challenge().to_string(); + + // Create a task for authentication + let task = self.auth(&req, cx); cx.spawn_in(window, async move |this, cx| { let result = task.await; let url = req.url(); - this.update_in(cx, |_this, window, cx| { + this.update_in(cx, |this, window, cx| { window.clear_notification(challenge, cx); match result { Ok(_) => { + // Clear pending events for the authenticated relay + this.clear_pending_events(url, cx); // Save the authenticated relay to automatically authenticate future requests settings.update(cx, |this, cx| { this.add_trusted_relay(url, cx); }); - window.push_notification(format!("{} has been authenticated", url), cx); } Err(e) => { diff --git a/crates/state/src/event.rs b/crates/state/src/event.rs deleted file mode 100644 index e7de936..0000000 --- a/crates/state/src/event.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::collections::HashSet; -use std::sync::{Arc, OnceLock}; - -use nostr_sdk::prelude::*; -use smol::lock::RwLock; - -static TRACKER: OnceLock>> = OnceLock::new(); - -pub fn tracker() -> &'static Arc> { - TRACKER.get_or_init(|| Arc::new(RwLock::new(EventTracker::default()))) -} - -/// Event tracker -#[derive(Debug, Clone, Default)] -pub struct EventTracker { - /// Tracking events sent by Coop in the current session - sent_ids: HashSet, - - /// Events that need to be resent later - pending_resend: HashSet<(EventId, RelayUrl)>, -} - -impl EventTracker { - /// Check if an event was sent by Coop in the current session. - pub fn is_sent_by_coop(&self, id: &EventId) -> bool { - self.sent_ids.contains(id) - } - - /// Mark an event as sent by Coop. - pub fn sent(&mut self, id: EventId) { - self.sent_ids.insert(id); - } - - /// Get all events that need to be resent later for a specific relay. - pub fn pending_resend(&mut self, relay: &RelayUrl) -> Vec { - self.pending_resend - .extract_if(|(_id, url)| url == relay) - .map(|(id, _url)| id) - .collect() - } - - /// Add an event (id and relay url) to the pending resend set. - pub fn add_to_pending(&mut self, id: EventId, url: RelayUrl) { - self.pending_resend.insert((id, url)); - } -} diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index b967083..8247443 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -12,12 +12,10 @@ use nostr_lmdb::NostrLmdb; use nostr_sdk::prelude::*; mod constants; -mod event; mod nip05; mod signer; pub use constants::*; -pub use event::*; pub use nip05::*; pub use signer::*; @@ -32,9 +30,6 @@ pub fn init(cx: &mut App) { // Initialize the tokio runtime gpui_tokio::init(cx); - // Initialize the event tracker - let _tracker = tracker(); - NostrRegistry::set_global(cx.new(NostrRegistry::new), cx); }