clean up
This commit is contained in:
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -6024,23 +6024,6 @@ dependencies = [
|
|||||||
"smol",
|
"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]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ use flume::Sender;
|
|||||||
use fuzzy_matcher::skim::SkimMatcherV2;
|
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||||
use fuzzy_matcher::FuzzyMatcher;
|
use fuzzy_matcher::FuzzyMatcher;
|
||||||
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task};
|
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task};
|
||||||
pub use message::*;
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
pub use room::*;
|
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use state::{tracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION};
|
use state::{tracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION};
|
||||||
@@ -21,6 +19,9 @@ use state::{tracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION};
|
|||||||
mod message;
|
mod message;
|
||||||
mod room;
|
mod room;
|
||||||
|
|
||||||
|
pub use message::*;
|
||||||
|
pub use room::*;
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
ChatRegistry::set_global(cx.new(ChatRegistry::new), cx);
|
ChatRegistry::set_global(cx.new(ChatRegistry::new), cx);
|
||||||
}
|
}
|
||||||
@@ -42,17 +43,22 @@ pub struct ChatRegistry {
|
|||||||
_tasks: SmallVec<[Task<()>; 4]>,
|
_tasks: SmallVec<[Task<()>; 4]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Chat event.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum ChatEvent {
|
pub enum ChatEvent {
|
||||||
OpenRoom(u64),
|
OpenRoom(u64),
|
||||||
CloseRoom(u64),
|
CloseRoom(u64),
|
||||||
NewChatRequest(RoomKind),
|
NewRequest(RoomKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Channel signal.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Signal {
|
enum Signal {
|
||||||
Loading(bool),
|
/// Message received from relay pool
|
||||||
Message(NewMessage),
|
Message(NewMessage),
|
||||||
|
/// Loading status of the registry
|
||||||
|
Loading(bool),
|
||||||
|
/// Eose received from relay pool
|
||||||
Eose,
|
Eose,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,7 +513,6 @@ impl ChatRegistry {
|
|||||||
if let Some(room) = self.rooms.iter().find(|room| room.read(cx).id == id) {
|
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 is_new_event = message.rumor.created_at > room.read(cx).created_at;
|
||||||
let created_at = message.rumor.created_at;
|
let created_at = message.rumor.created_at;
|
||||||
let event_for_emit = message.rumor.clone();
|
|
||||||
|
|
||||||
// Update room
|
// Update room
|
||||||
room.update(cx, |this, cx| {
|
room.update(cx, |this, cx| {
|
||||||
@@ -521,7 +526,7 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Emit the new message to the room
|
// 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)
|
// 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);
|
self.add_room(cx.new(|_| Room::from(&message.rumor)), cx);
|
||||||
|
|
||||||
// Notify the UI about the new room
|
// Notify the UI about the new room
|
||||||
cx.emit(ChatEvent::NewChatRequest(RoomKind::default()));
|
cx.emit(ChatEvent::NewRequest(RoomKind::default()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::hash::Hash;
|
|||||||
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
|
/// New message.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct NewMessage {
|
pub struct NewMessage {
|
||||||
pub gift_wrap: EventId,
|
pub gift_wrap: EventId,
|
||||||
@@ -14,6 +15,7 @@ impl NewMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Message.
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
User(RenderedMessage),
|
User(RenderedMessage),
|
||||||
@@ -22,11 +24,17 @@ pub enum Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
pub fn user(user: impl Into<RenderedMessage>) -> Self {
|
pub fn user<I>(user: I) -> Self
|
||||||
|
where
|
||||||
|
I: Into<RenderedMessage>,
|
||||||
|
{
|
||||||
Self::User(user.into())
|
Self::User(user.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn warning(content: impl Into<String>) -> Self {
|
pub fn warning<I>(content: I) -> Self
|
||||||
|
where
|
||||||
|
I: Into<String>,
|
||||||
|
{
|
||||||
Self::Warning(content.into(), Timestamp::now())
|
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 {
|
impl Ord for Message {
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
@@ -63,6 +83,7 @@ impl PartialOrd for Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rendered message.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RenderedMessage {
|
pub struct RenderedMessage {
|
||||||
pub id: EventId,
|
pub id: EventId,
|
||||||
@@ -78,48 +99,53 @@ pub struct RenderedMessage {
|
|||||||
pub replies_to: Vec<EventId>,
|
pub replies_to: Vec<EventId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Event> for RenderedMessage {
|
impl From<&Event> for RenderedMessage {
|
||||||
fn from(inner: Event) -> Self {
|
fn from(val: &Event) -> Self {
|
||||||
let mentions = extract_mentions(&inner.content);
|
let mentions = extract_mentions(&val.content);
|
||||||
let replies_to = extract_reply_ids(&inner.tags);
|
let replies_to = extract_reply_ids(&val.tags);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id: inner.id,
|
id: val.id,
|
||||||
author: inner.pubkey,
|
author: val.pubkey,
|
||||||
content: inner.content,
|
content: val.content.clone(),
|
||||||
created_at: inner.created_at,
|
created_at: val.created_at,
|
||||||
mentions,
|
mentions,
|
||||||
replies_to,
|
replies_to,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UnsignedEvent> for RenderedMessage {
|
impl From<&UnsignedEvent> for RenderedMessage {
|
||||||
fn from(inner: UnsignedEvent) -> Self {
|
fn from(val: &UnsignedEvent) -> Self {
|
||||||
let mentions = extract_mentions(&inner.content);
|
let mentions = extract_mentions(&val.content);
|
||||||
let replies_to = extract_reply_ids(&inner.tags);
|
let replies_to = extract_reply_ids(&val.tags);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
// Event ID must be known
|
// Event ID must be known
|
||||||
id: inner.id.unwrap(),
|
id: val.id.unwrap(),
|
||||||
author: inner.pubkey,
|
author: val.pubkey,
|
||||||
content: inner.content,
|
content: val.content.clone(),
|
||||||
created_at: inner.created_at,
|
created_at: val.created_at,
|
||||||
mentions,
|
mentions,
|
||||||
replies_to,
|
replies_to,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Box<Event>> for RenderedMessage {
|
impl From<&NewMessage> for RenderedMessage {
|
||||||
fn from(inner: Box<Event>) -> Self {
|
fn from(val: &NewMessage) -> Self {
|
||||||
(*inner).into()
|
let mentions = extract_mentions(&val.rumor.content);
|
||||||
}
|
let replies_to = extract_reply_ids(&val.rumor.tags);
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Box<Event>> for RenderedMessage {
|
Self {
|
||||||
fn from(inner: &Box<Event>) -> Self {
|
// Event ID must be known
|
||||||
inner.to_owned().into()
|
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<PublicKey> {
|
fn extract_mentions(content: &str) -> Vec<PublicKey> {
|
||||||
let parser = NostrParser::new();
|
let parser = NostrParser::new();
|
||||||
let tokens = parser.parse(content);
|
let tokens = parser.parse(content);
|
||||||
@@ -165,6 +192,7 @@ fn extract_mentions(content: &str) -> Vec<PublicKey> {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts all reply (ids) from the event tags.
|
||||||
fn extract_reply_ids(inner: &Tags) -> Vec<EventId> {
|
fn extract_reply_ids(inner: &Tags) -> Vec<EventId> {
|
||||||
let mut replies_to = vec![];
|
let mut replies_to = vec![];
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use nostr_sdk::prelude::*;
|
|||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use state::{tracker, NostrRegistry};
|
use state::{tracker, NostrRegistry};
|
||||||
|
|
||||||
|
use crate::NewMessage;
|
||||||
|
|
||||||
const SEND_RETRY: usize = 10;
|
const SEND_RETRY: usize = 10;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -80,17 +82,21 @@ impl SendReport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// Room event.
|
||||||
pub enum RoomSignal {
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
NewMessage((EventId, UnsignedEvent)),
|
pub enum RoomEvent {
|
||||||
Refresh,
|
/// Incoming message.
|
||||||
|
Incoming(NewMessage),
|
||||||
|
/// Reloads the current room's messages.
|
||||||
|
Reload,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Room kind.
|
||||||
#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
|
#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||||
pub enum RoomKind {
|
pub enum RoomKind {
|
||||||
Ongoing,
|
|
||||||
#[default]
|
#[default]
|
||||||
Request,
|
Request,
|
||||||
|
Ongoing,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -133,7 +139,7 @@ impl Hash for Room {
|
|||||||
|
|
||||||
impl Eq for Room {}
|
impl Eq for Room {}
|
||||||
|
|
||||||
impl EventEmitter<RoomSignal> for Room {}
|
impl EventEmitter<RoomEvent> for Room {}
|
||||||
|
|
||||||
impl From<&UnsignedEvent> for Room {
|
impl From<&UnsignedEvent> for Room {
|
||||||
fn from(val: &UnsignedEvent) -> Self {
|
fn from(val: &UnsignedEvent) -> Self {
|
||||||
@@ -304,13 +310,13 @@ impl Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Emits a new message signal to the current room
|
/// Emits a new message signal to the current room
|
||||||
pub fn emit_message(&self, id: EventId, event: UnsignedEvent, cx: &mut Context<Self>) {
|
pub fn emit_message(&self, message: NewMessage, cx: &mut Context<Self>) {
|
||||||
cx.emit(RoomSignal::NewMessage((id, event)));
|
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<Self>) {
|
pub fn emit_refresh(&mut self, cx: &mut Context<Self>) {
|
||||||
cx.emit(RoomSignal::Refresh);
|
cx.emit(RoomEvent::Reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get gossip relays for each member
|
/// Get gossip relays for each member
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub use actions::*;
|
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 common::{nip96_upload, RenderedProfile, RenderedTimestamp};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -114,7 +114,7 @@ impl ChatPanel {
|
|||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
match result {
|
match result {
|
||||||
Ok(events) => {
|
Ok(events) => {
|
||||||
this.insert_messages(events, cx);
|
this.insert_messages(&events, cx);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
window.push_notification(e.to_string(), cx);
|
window.push_notification(e.to_string(), cx);
|
||||||
@@ -142,18 +142,10 @@ impl ChatPanel {
|
|||||||
// Subscribe to room events
|
// Subscribe to room events
|
||||||
cx.subscribe_in(&room, window, move |this, _, signal, window, cx| {
|
cx.subscribe_in(&room, window, move |this, _, signal, window, cx| {
|
||||||
match signal {
|
match signal {
|
||||||
RoomSignal::NewMessage((gift_wrap_id, event)) => {
|
RoomEvent::Incoming(message) => {
|
||||||
let message = Message::user(event.clone());
|
this.insert_message(message, false, cx);
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
|
||||||
this.update_in(cx, |this, _window, cx| {
|
|
||||||
this.insert_message(message, false, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
RoomSignal::Refresh => {
|
RoomEvent::Reload => {
|
||||||
this.load_messages(window, cx);
|
this.load_messages(window, cx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -202,7 +194,7 @@ impl ChatPanel {
|
|||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
match result {
|
match result {
|
||||||
Ok(events) => {
|
Ok(events) => {
|
||||||
this.insert_messages(events, cx);
|
this.insert_messages(&events, cx);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
window.push_notification(Notification::error(e.to_string()), cx);
|
window.push_notification(Notification::error(e.to_string()), cx);
|
||||||
@@ -278,14 +270,16 @@ impl ChatPanel {
|
|||||||
|
|
||||||
// Update the message list and reset the states
|
// Update the message list and reset the states
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.insert_message(Message::user(rumor), true, cx);
|
|
||||||
this.remove_all_replies(cx);
|
this.remove_all_replies(cx);
|
||||||
this.remove_all_attachments(cx);
|
this.remove_all_attachments(cx);
|
||||||
|
// Reset the input to its default state
|
||||||
this.input.update(cx, |this, cx| {
|
this.input.update(cx, |this, cx| {
|
||||||
this.set_loading(false, cx);
|
this.set_loading(false, cx);
|
||||||
this.set_disabled(false, cx);
|
this.set_disabled(false, cx);
|
||||||
this.set_value("", window, cx);
|
this.set_value("", window, cx);
|
||||||
});
|
});
|
||||||
|
// Update the message list
|
||||||
|
this.insert_message(&rumor, true, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
@@ -378,11 +372,10 @@ impl ChatPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert and insert a vector of nostr events into the chat panel
|
/// Convert and insert a vector of nostr events into the chat panel
|
||||||
fn insert_messages(&mut self, events: Vec<UnsignedEvent>, cx: &mut Context<Self>) {
|
fn insert_messages(&mut self, events: &[UnsignedEvent], cx: &mut Context<Self>) {
|
||||||
for event in events {
|
for event in events.iter() {
|
||||||
let m = Message::user(event);
|
|
||||||
// Bulk inserting messages, so no need to scroll to the latest message
|
// Bulk inserting messages, so no need to scroll to the latest message
|
||||||
self.insert_message(m, false, cx);
|
self.insert_message(event, false, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ impl Sidebar {
|
|||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Subscribe for registry new events
|
// Subscribe for registry new events
|
||||||
cx.subscribe_in(&chat, window, move |this, _, event, _window, cx| {
|
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.indicator.update(cx, |this, cx| {
|
||||||
*this = Some(kind.to_owned());
|
*this = Some(kind.to_owned());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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<NostrRegistry>);
|
|
||||||
|
|
||||||
impl Global for GlobalNostrRegistry {}
|
|
||||||
|
|
||||||
/// Nostr Registry
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NostrRegistry {
|
|
||||||
/// Nostr Client
|
|
||||||
client: Client,
|
|
||||||
|
|
||||||
/// Custom gossip implementation
|
|
||||||
gossip: Arc<RwLock<Gossip>>,
|
|
||||||
|
|
||||||
/// Tracks activity related to Nostr events
|
|
||||||
tracker: Arc<RwLock<EventTracker>>,
|
|
||||||
|
|
||||||
/// Tasks for asynchronous operations
|
|
||||||
_tasks: SmallVec<[Task<()>; 1]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NostrRegistry {
|
|
||||||
/// Retrieve the global nostr state
|
|
||||||
pub fn global(cx: &App) -> Entity<Self> {
|
|
||||||
cx.global::<GlobalNostrRegistry>().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the global nostr instance
|
|
||||||
fn set_global(state: Entity<Self>, cx: &mut App) {
|
|
||||||
cx.set_global(GlobalNostrRegistry(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new nostr instance
|
|
||||||
fn new(cx: &mut Context<Self>) -> 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<RwLock<Gossip>>,
|
|
||||||
tracker: &Arc<RwLock<EventTracker>>,
|
|
||||||
) {
|
|
||||||
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<RelayUrl> = 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<RelayUrl> =
|
|
||||||
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<PublicKey> =
|
|
||||||
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<PublicKey>) -> 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<RelayUrl> {
|
|
||||||
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<RelayUrl> {
|
|
||||||
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<Announcement, Error> {
|
|
||||||
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<Response, Error> {
|
|
||||||
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<RwLock<EventTracker>> {
|
|
||||||
Arc::clone(&self.tracker)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the cache manager.
|
|
||||||
pub fn gossip(&self) -> Arc<RwLock<Gossip>> {
|
|
||||||
Arc::clone(&self.gossip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Announcement {
|
|
||||||
pub fn new(id: EventId, client_name: Option<String>, 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<PublicKey, HashSet<(RelayUrl, Option<RelayMetadata>)>>,
|
|
||||||
|
|
||||||
/// Messaging relays for each public key
|
|
||||||
messaging_relays: HashMap<PublicKey, HashSet<RelayUrl>>,
|
|
||||||
|
|
||||||
/// Encryption announcement for each public key
|
|
||||||
announcements: HashMap<PublicKey, Option<Announcement>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gossip {
|
|
||||||
/// Get inbox relays for a public key
|
|
||||||
pub fn inbox_relays(&self, public_key: &PublicKey) -> Vec<RelayUrl> {
|
|
||||||
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<RelayUrl> {
|
|
||||||
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<RelayUrl> {
|
|
||||||
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<Announcement> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
|
|
||||||
static INITIALIZED_AT: OnceLock<Timestamp> = 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<Output<EventId>>,
|
|
||||||
|
|
||||||
/// Temporarily store events that need to be resent later
|
|
||||||
pub resend_queue: HashMap<EventId, RelayUrl>,
|
|
||||||
|
|
||||||
/// Tracking events sent by Coop in the current session
|
|
||||||
pub sent_ids: HashSet<EventId>,
|
|
||||||
|
|
||||||
/// Tracking events seen on which relays in the current session
|
|
||||||
pub seen_on_relays: HashMap<EventId, HashSet<RelayUrl>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventTracker {
|
|
||||||
pub fn resent_ids(&self) -> &Vec<Output<EventId>> {
|
|
||||||
&self.resent_ids
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resend_queue(&self) -> &HashMap<EventId, RelayUrl> {
|
|
||||||
&self.resend_queue
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sent_ids(&self) -> &HashSet<EventId> {
|
|
||||||
&self.sent_ids
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seen_on_relays(&self) -> &HashMap<EventId, HashSet<RelayUrl>> {
|
|
||||||
&self.seen_on_relays
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user