diff --git a/Cargo.lock b/Cargo.lock index 5c039d7..f363450 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6024,23 +6024,6 @@ dependencies = [ "smol", ] -[[package]] -name = "state_old" -version = "0.3.0" -dependencies = [ - "anyhow", - "common", - "gpui", - "log", - "nostr-lmdb", - "nostr-sdk", - "rustls", - "serde", - "serde_json", - "smallvec", - "smol", -] - [[package]] name = "static_assertions" version = "1.1.0" diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index bf398ab..5e96710 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -11,9 +11,7 @@ use flume::Sender; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task}; -pub use message::*; use nostr_sdk::prelude::*; -pub use room::*; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use state::{tracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION}; @@ -21,6 +19,9 @@ use state::{tracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION}; mod message; mod room; +pub use message::*; +pub use room::*; + pub fn init(cx: &mut App) { ChatRegistry::set_global(cx.new(ChatRegistry::new), cx); } @@ -42,17 +43,22 @@ pub struct ChatRegistry { _tasks: SmallVec<[Task<()>; 4]>, } +/// Chat event. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ChatEvent { OpenRoom(u64), CloseRoom(u64), - NewChatRequest(RoomKind), + NewRequest(RoomKind), } +/// Channel signal. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Signal { - Loading(bool), +enum Signal { + /// Message received from relay pool Message(NewMessage), + /// Loading status of the registry + Loading(bool), + /// Eose received from relay pool Eose, } @@ -507,7 +513,6 @@ impl ChatRegistry { if let Some(room) = self.rooms.iter().find(|room| room.read(cx).id == id) { let is_new_event = message.rumor.created_at > room.read(cx).created_at; let created_at = message.rumor.created_at; - let event_for_emit = message.rumor.clone(); // Update room room.update(cx, |this, cx| { @@ -521,7 +526,7 @@ impl ChatRegistry { } // Emit the new message to the room - this.emit_message(message.gift_wrap, event_for_emit.clone(), cx); + this.emit_message(message, cx); }); // Resort all rooms in the registry by their created at (after updated) @@ -533,7 +538,7 @@ impl ChatRegistry { self.add_room(cx.new(|_| Room::from(&message.rumor)), cx); // Notify the UI about the new room - cx.emit(ChatEvent::NewChatRequest(RoomKind::default())); + cx.emit(ChatEvent::NewRequest(RoomKind::default())); } } diff --git a/crates/chat/src/message.rs b/crates/chat/src/message.rs index b0e34ef..c4cfef6 100644 --- a/crates/chat/src/message.rs +++ b/crates/chat/src/message.rs @@ -2,6 +2,7 @@ use std::hash::Hash; use nostr_sdk::prelude::*; +/// New message. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct NewMessage { pub gift_wrap: EventId, @@ -14,6 +15,7 @@ impl NewMessage { } } +/// Message. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Message { User(RenderedMessage), @@ -22,11 +24,17 @@ pub enum Message { } impl Message { - pub fn user(user: impl Into) -> Self { + pub fn user(user: I) -> Self + where + I: Into, + { Self::User(user.into()) } - pub fn warning(content: impl Into) -> Self { + pub fn warning(content: I) -> Self + where + I: Into, + { Self::Warning(content.into(), Timestamp::now()) } @@ -43,6 +51,18 @@ impl Message { } } +impl From<&NewMessage> for Message { + fn from(val: &NewMessage) -> Self { + Self::User(val.into()) + } +} + +impl From<&UnsignedEvent> for Message { + fn from(val: &UnsignedEvent) -> Self { + Self::User(val.into()) + } +} + impl Ord for Message { fn cmp(&self, other: &Self) -> std::cmp::Ordering { match (self, other) { @@ -63,6 +83,7 @@ impl PartialOrd for Message { } } +/// Rendered message. #[derive(Debug, Clone)] pub struct RenderedMessage { pub id: EventId, @@ -78,48 +99,53 @@ pub struct RenderedMessage { pub replies_to: Vec, } -impl From for RenderedMessage { - fn from(inner: Event) -> Self { - let mentions = extract_mentions(&inner.content); - let replies_to = extract_reply_ids(&inner.tags); +impl From<&Event> for RenderedMessage { + fn from(val: &Event) -> Self { + let mentions = extract_mentions(&val.content); + let replies_to = extract_reply_ids(&val.tags); Self { - id: inner.id, - author: inner.pubkey, - content: inner.content, - created_at: inner.created_at, + id: val.id, + author: val.pubkey, + content: val.content.clone(), + created_at: val.created_at, mentions, replies_to, } } } -impl From for RenderedMessage { - fn from(inner: UnsignedEvent) -> Self { - let mentions = extract_mentions(&inner.content); - let replies_to = extract_reply_ids(&inner.tags); +impl From<&UnsignedEvent> for RenderedMessage { + fn from(val: &UnsignedEvent) -> Self { + let mentions = extract_mentions(&val.content); + let replies_to = extract_reply_ids(&val.tags); Self { // Event ID must be known - id: inner.id.unwrap(), - author: inner.pubkey, - content: inner.content, - created_at: inner.created_at, + id: val.id.unwrap(), + author: val.pubkey, + content: val.content.clone(), + created_at: val.created_at, mentions, replies_to, } } } -impl From> for RenderedMessage { - fn from(inner: Box) -> Self { - (*inner).into() - } -} +impl From<&NewMessage> for RenderedMessage { + fn from(val: &NewMessage) -> Self { + let mentions = extract_mentions(&val.rumor.content); + let replies_to = extract_reply_ids(&val.rumor.tags); -impl From<&Box> for RenderedMessage { - fn from(inner: &Box) -> Self { - inner.to_owned().into() + Self { + // Event ID must be known + id: val.rumor.id.unwrap(), + author: val.rumor.pubkey, + content: val.rumor.content.clone(), + created_at: val.rumor.created_at, + mentions, + replies_to, + } } } @@ -149,6 +175,7 @@ impl Hash for RenderedMessage { } } +/// Extracts all mentions (public keys) from a content string. fn extract_mentions(content: &str) -> Vec { let parser = NostrParser::new(); let tokens = parser.parse(content); @@ -165,6 +192,7 @@ fn extract_mentions(content: &str) -> Vec { .collect::>() } +/// Extracts all reply (ids) from the event tags. fn extract_reply_ids(inner: &Tags) -> Vec { let mut replies_to = vec![]; diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index d4e0960..427d950 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -11,6 +11,8 @@ use nostr_sdk::prelude::*; use person::PersonRegistry; use state::{tracker, NostrRegistry}; +use crate::NewMessage; + const SEND_RETRY: usize = 10; #[derive(Debug, Clone)] @@ -80,17 +82,21 @@ impl SendReport { } } -#[derive(Debug, Clone)] -pub enum RoomSignal { - NewMessage((EventId, UnsignedEvent)), - Refresh, +/// Room event. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum RoomEvent { + /// Incoming message. + Incoming(NewMessage), + /// Reloads the current room's messages. + Reload, } +/// Room kind. #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum RoomKind { - Ongoing, #[default] Request, + Ongoing, } #[derive(Debug)] @@ -133,7 +139,7 @@ impl Hash for Room { impl Eq for Room {} -impl EventEmitter for Room {} +impl EventEmitter for Room {} impl From<&UnsignedEvent> for Room { fn from(val: &UnsignedEvent) -> Self { @@ -304,13 +310,13 @@ impl Room { } /// Emits a new message signal to the current room - pub fn emit_message(&self, id: EventId, event: UnsignedEvent, cx: &mut Context) { - cx.emit(RoomSignal::NewMessage((id, event))); + pub fn emit_message(&self, message: NewMessage, cx: &mut Context) { + cx.emit(RoomEvent::Incoming(message)); } - /// Emits a signal to refresh the current room's messages. + /// Emits a signal to reload the current room's messages. pub fn emit_refresh(&mut self, cx: &mut Context) { - cx.emit(RoomSignal::Refresh); + cx.emit(RoomEvent::Reload); } /// Get gossip relays for each member diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index 6d038f6..36f6609 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::time::Duration; pub use actions::*; -use chat::{Message, RenderedMessage, Room, RoomKind, RoomSignal, SendReport}; +use chat::{Message, RenderedMessage, Room, RoomEvent, RoomKind, SendReport}; use common::{nip96_upload, RenderedProfile, RenderedTimestamp}; use gpui::prelude::FluentBuilder; use gpui::{ @@ -114,7 +114,7 @@ impl ChatPanel { this.update_in(cx, |this, window, cx| { match result { Ok(events) => { - this.insert_messages(events, cx); + this.insert_messages(&events, cx); } Err(e) => { window.push_notification(e.to_string(), cx); @@ -142,18 +142,10 @@ impl ChatPanel { // Subscribe to room events cx.subscribe_in(&room, window, move |this, _, signal, window, cx| { match signal { - RoomSignal::NewMessage((gift_wrap_id, event)) => { - let message = Message::user(event.clone()); - - cx.spawn_in(window, async move |this, cx| { - this.update_in(cx, |this, _window, cx| { - this.insert_message(message, false, cx); - }) - .ok(); - }) - .detach(); + RoomEvent::Incoming(message) => { + this.insert_message(message, false, cx); } - RoomSignal::Refresh => { + RoomEvent::Reload => { this.load_messages(window, cx); } }; @@ -202,7 +194,7 @@ impl ChatPanel { this.update_in(cx, |this, window, cx| { match result { Ok(events) => { - this.insert_messages(events, cx); + this.insert_messages(&events, cx); } Err(e) => { window.push_notification(Notification::error(e.to_string()), cx); @@ -278,14 +270,16 @@ impl ChatPanel { // Update the message list and reset the states this.update_in(cx, |this, window, cx| { - this.insert_message(Message::user(rumor), true, cx); this.remove_all_replies(cx); this.remove_all_attachments(cx); + // Reset the input to its default state this.input.update(cx, |this, cx| { this.set_loading(false, cx); this.set_disabled(false, cx); this.set_value("", window, cx); }); + // Update the message list + this.insert_message(&rumor, true, cx); }) .ok(); }) @@ -378,11 +372,10 @@ impl ChatPanel { } /// Convert and insert a vector of nostr events into the chat panel - fn insert_messages(&mut self, events: Vec, cx: &mut Context) { - for event in events { - let m = Message::user(event); + fn insert_messages(&mut self, events: &[UnsignedEvent], cx: &mut Context) { + for event in events.iter() { // Bulk inserting messages, so no need to scroll to the latest message - self.insert_message(m, false, cx); + self.insert_message(event, false, cx); } } diff --git a/crates/coop/src/sidebar/mod.rs b/crates/coop/src/sidebar/mod.rs index e7e5c39..b4b5671 100644 --- a/crates/coop/src/sidebar/mod.rs +++ b/crates/coop/src/sidebar/mod.rs @@ -84,7 +84,7 @@ impl Sidebar { subscriptions.push( // Subscribe for registry new events cx.subscribe_in(&chat, window, move |this, _, event, _window, cx| { - if let ChatEvent::NewChatRequest(kind) = event { + if let ChatEvent::NewRequest(kind) = event { this.indicator.update(cx, |this, cx| { *this = Some(kind.to_owned()); cx.notify(); diff --git a/crates/state_old/Cargo.toml b/crates/state_old/Cargo.toml deleted file mode 100644 index 5f668cd..0000000 --- a/crates/state_old/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "state_old" -version.workspace = true -edition.workspace = true -publish.workspace = true - -[dependencies] -common = { path = "../common" } - -nostr-sdk.workspace = true -nostr-lmdb.workspace = true - -gpui.workspace = true -smol.workspace = true -smallvec.workspace = true -log.workspace = true -anyhow.workspace = true -serde.workspace = true -serde_json.workspace = true - -rustls = "0.23.23" diff --git a/crates/state_old/src/lib.rs b/crates/state_old/src/lib.rs deleted file mode 100644 index de10ecd..0000000 --- a/crates/state_old/src/lib.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::collections::HashSet; -use std::sync::Arc; -use std::time::Duration; - -use anyhow::{anyhow, Context as AnyhowContext, Error}; -use common::{config_dir, BOOTSTRAP_RELAYS, SEARCH_RELAYS}; -use gpui::{App, AppContext, Context, Entity, Global, Task}; -use nostr_lmdb::NostrLmdb; -use nostr_sdk::prelude::*; -use smallvec::{smallvec, SmallVec}; -use smol::lock::RwLock; -pub use storage::*; -pub use tracker::*; - -mod storage; -mod tracker; - -pub const GIFTWRAP_SUBSCRIPTION: &str = "gift-wrap-events"; - -pub fn init(cx: &mut App) { - NostrRegistry::set_global(cx.new(NostrRegistry::new), cx); -} - -struct GlobalNostrRegistry(Entity); - -impl Global for GlobalNostrRegistry {} - -/// Nostr Registry -#[derive(Debug)] -pub struct NostrRegistry { - /// Nostr Client - client: Client, - - /// Custom gossip implementation - gossip: Arc>, - - /// Tracks activity related to Nostr events - tracker: Arc>, - - /// Tasks for asynchronous operations - _tasks: SmallVec<[Task<()>; 1]>, -} - -impl NostrRegistry { - /// Retrieve the global nostr state - pub fn global(cx: &App) -> Entity { - cx.global::().0.clone() - } - - /// Set the global nostr instance - fn set_global(state: Entity, cx: &mut App) { - cx.set_global(GlobalNostrRegistry(state)); - } - - /// Create a new nostr instance - fn new(cx: &mut Context) -> Self { - // rustls uses the `aws_lc_rs` provider by default - // This only errors if the default provider has already - // been installed. We can ignore this `Result`. - rustls::crypto::aws_lc_rs::default_provider() - .install_default() - .ok(); - - // Construct the nostr client options - let opts = ClientOptions::new() - .automatic_authentication(false) - .verify_subscriptions(false) - .sleep_when_idle(SleepWhenIdle::Enabled { - timeout: Duration::from_secs(600), - }); - - // Construct the lmdb - let lmdb = cx.background_executor().block(async move { - let path = config_dir().join("nostr"); - NostrLmdb::open(path) - .await - .expect("Failed to initialize database") - }); - - // Construct the nostr client - let client = ClientBuilder::default().database(lmdb).opts(opts).build(); - - let tracker = Arc::new(RwLock::new(EventTracker::default())); - let gossip = Arc::new(RwLock::new(Gossip::default())); - - let mut tasks = smallvec![]; - - tasks.push( - // Establish connection to the bootstrap relays - // - // And handle notifications from the nostr relay pool channel - cx.background_spawn({ - let client = client.clone(); - let gossip = Arc::clone(&gossip); - let tracker = Arc::clone(&tracker); - let _ = initialized_at(); - - async move { - // Connect to the bootstrap relays - Self::connect(&client).await; - - // Handle notifications from the relay pool - Self::handle_notifications(&client, &gossip, &tracker).await; - } - }), - ); - - Self { - client, - tracker, - gossip, - _tasks: tasks, - } - } - - /// Establish connection to the bootstrap relays - async fn connect(client: &Client) { - // Get all bootstrapping relays - let mut urls = vec![]; - urls.extend(BOOTSTRAP_RELAYS); - urls.extend(SEARCH_RELAYS); - - // Add relay to the relay pool - for url in urls.into_iter() { - client.add_relay(url).await.ok(); - } - - // Connect to all added relays - client.connect().await; - } - - async fn handle_notifications( - client: &Client, - gossip: &Arc>, - tracker: &Arc>, - ) { - let mut notifications = client.notifications(); - let mut processed_events = HashSet::new(); - - while let Ok(notification) = notifications.recv().await { - let RelayPoolNotification::Message { message, relay_url } = notification else { - // Skip if the notification is not a message - continue; - }; - - match message { - RelayMessage::Event { event, .. } => { - if !processed_events.insert(event.id) { - // Skip if the event has already been processed - continue; - } - - match event.kind { - Kind::RelayList => { - let mut gossip = gossip.write().await; - gossip.insert_relays(&event); - - let urls: Vec = Self::extract_write_relays(&event); - let author = event.pubkey; - - log::info!("Write relays: {urls:?}"); - - // Fetch user's encryption announcement event - Self::get(client, &urls, author, Kind::Custom(10044)).await; - // Fetch user's messaging relays event - Self::get(client, &urls, author, Kind::InboxRelays).await; - - // Verify if the event is belonging to the current user - if Self::is_self_authored(client, &event).await { - // Fetch user's metadata event - Self::get(client, &urls, author, Kind::Metadata).await; - // Fetch user's contact list event - Self::get(client, &urls, author, Kind::ContactList).await; - } - } - Kind::InboxRelays => { - let mut gossip = gossip.write().await; - gossip.insert_messaging_relays(&event); - - if Self::is_self_authored(client, &event).await { - // Extract user's messaging relays - let urls: Vec = - nip17::extract_relay_list(&event).cloned().collect(); - - // Fetch user's inbox messages in the extracted relays - Self::get_messages(client, event.pubkey, &urls).await; - } - } - Kind::Custom(10044) => { - let mut gossip = gossip.write().await; - gossip.insert_announcement(&event); - } - Kind::ContactList => { - if Self::is_self_authored(client, &event).await { - let public_keys: Vec = - event.tags.public_keys().copied().collect(); - - if let Err(e) = - Self::get_metadata_for_list(client, public_keys).await - { - log::error!("Failed to get metadata for list: {e}"); - } - } - } - _ => {} - }; - } - RelayMessage::Ok { - event_id, message, .. - } => { - let msg = MachineReadablePrefix::parse(&message); - let mut tracker = tracker.write().await; - - // Message that need to be authenticated will be handled separately - if let Some(MachineReadablePrefix::AuthRequired) = msg { - // Keep track of events that need to be resent after authentication - tracker.resend_queue.insert(event_id, relay_url); - } else { - // Keep track of events sent by Coop - tracker.sent_ids.insert(event_id); - } - } - _ => {} - } - } - } - - /// Check if event is published by current user - pub async fn is_self_authored(client: &Client, event: &Event) -> bool { - if let Ok(signer) = client.signer().await { - if let Ok(public_key) = signer.get_public_key().await { - return public_key == event.pubkey; - } - } - false - } - - /// Get event that match the given kind for a given author - async fn get(client: &Client, urls: &[RelayUrl], author: PublicKey, kind: Kind) { - // Skip if no relays are provided - if urls.is_empty() { - return; - } - - // Ensure relay connections - for url in urls.iter() { - client.add_relay(url).await.ok(); - client.connect_relay(url).await.ok(); - } - - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); - let filter = Filter::new().author(author).kind(kind).limit(1); - - // Subscribe to filters from the user's write relays - if let Err(e) = client.subscribe_to(urls, filter, Some(opts)).await { - log::error!("Failed to subscribe: {}", e); - } - } - - /// Get all gift wrap events in the messaging relays for a given public key - pub async fn get_messages(client: &Client, public_key: PublicKey, urls: &[RelayUrl]) { - // Verify that there are relays provided - if urls.is_empty() { - return; - } - - // Ensure relay connection - for url in urls.iter() { - client.add_relay(url).await.ok(); - client.connect_relay(url).await.ok(); - } - - let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION); - let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); - - // Unsubscribe from the previous subscription - client.unsubscribe(&id).await; - - // Subscribe to filters to user's messaging relays - if let Err(e) = client.subscribe_with_id_to(urls, id, filter, None).await { - log::error!("Failed to subscribe: {}", e); - } else { - log::info!("Subscribed to gift wrap events for public key {public_key}",); - } - } - - /// Get metadata for a list of public keys - async fn get_metadata_for_list(client: &Client, pubkeys: Vec) -> Result<(), Error> { - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); - let kinds = vec![Kind::Metadata, Kind::ContactList]; - - // Return if the list is empty - if pubkeys.is_empty() { - return Err(anyhow!("You need at least one public key".to_string(),)); - } - - let filter = Filter::new() - .limit(pubkeys.len() * kinds.len()) - .authors(pubkeys) - .kinds(kinds); - - // Subscribe to filters to the bootstrap relays - client - .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) - .await?; - - Ok(()) - } - - pub fn extract_read_relays(event: &Event) -> Vec { - nip65::extract_relay_list(event) - .filter_map(|(url, metadata)| { - if metadata.is_none() || metadata == &Some(RelayMetadata::Read) { - Some(url.to_owned()) - } else { - None - } - }) - .take(3) - .collect() - } - - pub fn extract_write_relays(event: &Event) -> Vec { - nip65::extract_relay_list(event) - .filter_map(|(url, metadata)| { - if metadata.is_none() || metadata == &Some(RelayMetadata::Write) { - Some(url.to_owned()) - } else { - None - } - }) - .take(3) - .collect() - } - - /// Extract an encryption keys announcement from an event. - pub fn extract_announcement(event: &Event) -> Result { - let public_key = event - .tags - .iter() - .find(|tag| tag.kind().as_str() == "n" || tag.kind().as_str() == "pubkey") - .and_then(|tag| tag.content()) - .and_then(|c| PublicKey::parse(c).ok()) - .context("Cannot parse public key from the event's tags")?; - - let client_name = event - .tags - .find(TagKind::Client) - .and_then(|tag| tag.content()) - .map(|c| c.to_string()); - - Ok(Announcement::new(event.id, client_name, public_key)) - } - - /// Extract an encryption keys response from an event. - pub async fn extract_response(client: &Client, event: &Event) -> Result { - let signer = client.signer().await?; - let public_key = signer.get_public_key().await?; - - if event.pubkey != public_key { - return Err(anyhow!("Event does not belong to current user")); - } - - let client_pubkey = event - .tags - .find(TagKind::custom("P")) - .and_then(|tag| tag.content()) - .and_then(|c| PublicKey::parse(c).ok()) - .context("Cannot parse public key from the event's tags")?; - - Ok(Response::new(event.content.clone(), client_pubkey)) - } - - /// Returns a reference to the nostr client. - pub fn client(&self) -> Client { - self.client.clone() - } - - /// Returns a reference to the event tracker. - pub fn tracker(&self) -> Arc> { - Arc::clone(&self.tracker) - } - - /// Returns a reference to the cache manager. - pub fn gossip(&self) -> Arc> { - Arc::clone(&self.gossip) - } -} diff --git a/crates/state_old/src/storage.rs b/crates/state_old/src/storage.rs deleted file mode 100644 index 1601965..0000000 --- a/crates/state_old/src/storage.rs +++ /dev/null @@ -1,189 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use gpui::SharedString; -use nostr_sdk::prelude::*; - -use crate::NostrRegistry; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Announcement { - id: EventId, - public_key: PublicKey, - client_name: Option, -} - -impl Announcement { - pub fn new(id: EventId, client_name: Option, public_key: PublicKey) -> Self { - Self { - id, - client_name, - public_key, - } - } - - pub fn id(&self) -> EventId { - self.id - } - - pub fn public_key(&self) -> PublicKey { - self.public_key - } - - pub fn client_name(&self) -> SharedString { - self.client_name - .as_ref() - .map(SharedString::from) - .unwrap_or(SharedString::from("Unknown")) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Response { - payload: String, - public_key: PublicKey, -} - -impl Response { - pub fn new(payload: String, public_key: PublicKey) -> Self { - Self { - payload, - public_key, - } - } - - pub fn public_key(&self) -> PublicKey { - self.public_key - } - - pub fn payload(&self) -> &str { - self.payload.as_str() - } -} - -#[derive(Debug, Clone, Default)] -pub struct Gossip { - /// Gossip relays for each public key - relays: HashMap)>>, - - /// Messaging relays for each public key - messaging_relays: HashMap>, - - /// Encryption announcement for each public key - announcements: HashMap>, -} - -impl Gossip { - /// Get inbox relays for a public key - pub fn inbox_relays(&self, public_key: &PublicKey) -> Vec { - self.relays - .get(public_key) - .map(|relays| { - relays - .iter() - .filter_map(|(url, metadata)| { - if metadata.is_none() || metadata == &Some(RelayMetadata::Read) { - Some(url.to_owned()) - } else { - None - } - }) - .collect() - }) - .unwrap_or_default() - } - - /// Get outbox relays for a public key - pub fn outbox_relays(&self, public_key: &PublicKey) -> Vec { - self.relays - .get(public_key) - .map(|relays| { - relays - .iter() - .filter_map(|(url, metadata)| { - if metadata.is_none() || metadata == &Some(RelayMetadata::Write) { - Some(url.to_owned()) - } else { - None - } - }) - .collect() - }) - .unwrap_or_default() - } - - /// Insert gossip relays for a public key - pub fn insert_relays(&mut self, event: &Event) { - self.relays.entry(event.pubkey).or_default().extend( - event - .tags - .iter() - .filter_map(|tag| { - if let Some(TagStandard::RelayMetadata { - relay_url, - metadata, - }) = tag.clone().to_standardized() - { - Some((relay_url, metadata)) - } else { - None - } - }) - .take(3), - ); - } - - /// Get messaging relays for a public key - pub fn messaging_relays(&self, public_key: &PublicKey) -> Vec { - self.messaging_relays - .get(public_key) - .cloned() - .unwrap_or_default() - .into_iter() - .collect() - } - - /// Insert messaging relays for a public key - pub fn insert_messaging_relays(&mut self, event: &Event) { - self.messaging_relays - .entry(event.pubkey) - .or_default() - .extend( - event - .tags - .iter() - .filter_map(|tag| { - if let Some(TagStandard::Relay(url)) = tag.as_standardized() { - Some(url.to_owned()) - } else { - None - } - }) - .take(3), - ); - } - - /// Ensure connections for the given relay list - pub async fn ensure_connections(&self, client: &Client, urls: &[RelayUrl]) { - for url in urls { - client.add_relay(url).await.ok(); - client.connect_relay(url).await.ok(); - } - } - - /// Get announcement for a public key - pub fn announcement(&self, public_key: &PublicKey) -> Option { - self.announcements - .get(public_key) - .cloned() - .unwrap_or_default() - } - - /// Insert announcement for a public key - pub fn insert_announcement(&mut self, event: &Event) { - let announcement = NostrRegistry::extract_announcement(event).ok(); - - self.announcements - .entry(event.pubkey) - .or_insert(announcement); - } -} diff --git a/crates/state_old/src/tracker.rs b/crates/state_old/src/tracker.rs deleted file mode 100644 index 739265d..0000000 --- a/crates/state_old/src/tracker.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::sync::OnceLock; - -use nostr_sdk::prelude::*; - -static INITIALIZED_AT: OnceLock = OnceLock::new(); - -pub fn initialized_at() -> &'static Timestamp { - INITIALIZED_AT.get_or_init(Timestamp::now) -} - -#[derive(Debug, Clone, Default)] -pub struct EventTracker { - /// Tracking events that have been resent by Coop in the current session - pub resent_ids: Vec>, - - /// Temporarily store events that need to be resent later - pub resend_queue: HashMap, - - /// Tracking events sent by Coop in the current session - pub sent_ids: HashSet, - - /// Tracking events seen on which relays in the current session - pub seen_on_relays: HashMap>, -} - -impl EventTracker { - pub fn resent_ids(&self) -> &Vec> { - &self.resent_ids - } - - pub fn resend_queue(&self) -> &HashMap { - &self.resend_queue - } - - pub fn sent_ids(&self) -> &HashSet { - &self.sent_ids - } - - pub fn seen_on_relays(&self) -> &HashMap> { - &self.seen_on_relays - } -}