chore: follow up on nip-4e (#195)

* update deps

* .

* remove resend button

* clean up

* .

* .

* .

* .

* .
This commit is contained in:
reya
2025-10-28 14:37:30 +07:00
committed by GitHub
parent b5ed079a0e
commit b9297d3a01
21 changed files with 199 additions and 176 deletions

39
Cargo.lock generated
View File

@@ -1157,7 +1157,7 @@ dependencies = [
[[package]] [[package]]
name = "collections" name = "collections"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
@@ -1597,7 +1597,7 @@ dependencies = [
[[package]] [[package]]
name = "derive_refineable" name = "derive_refineable"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2494,7 +2494,7 @@ dependencies = [
[[package]] [[package]]
name = "gpui" name = "gpui"
version = "0.2.2" version = "0.2.2"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"as-raw-xcb-connection", "as-raw-xcb-connection",
@@ -2589,7 +2589,7 @@ dependencies = [
[[package]] [[package]]
name = "gpui_macros" name = "gpui_macros"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@@ -2600,7 +2600,7 @@ dependencies = [
[[package]] [[package]]
name = "gpui_tokio" name = "gpui_tokio"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"gpui", "gpui",
@@ -2829,7 +2829,7 @@ dependencies = [
[[package]] [[package]]
name = "http_client" name = "http_client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-compression", "async-compression",
@@ -2854,7 +2854,7 @@ dependencies = [
[[package]] [[package]]
name = "http_client_tls" name = "http_client_tls"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"rustls", "rustls",
"rustls-platform-verifier", "rustls-platform-verifier",
@@ -3672,7 +3672,7 @@ dependencies = [
[[package]] [[package]]
name = "media" name = "media"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bindgen 0.71.1", "bindgen 0.71.1",
@@ -4514,7 +4514,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "perf" name = "perf"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"collections", "collections",
"serde", "serde",
@@ -5130,7 +5130,7 @@ dependencies = [
[[package]] [[package]]
name = "refineable" name = "refineable"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"derive_refineable", "derive_refineable",
] ]
@@ -5237,7 +5237,7 @@ dependencies = [
[[package]] [[package]]
name = "reqwest_client" name = "reqwest_client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -5291,13 +5291,12 @@ dependencies = [
[[package]] [[package]]
name = "rope" name = "rope"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"log", "log",
"rayon", "rayon",
"regex", "regex",
"smallvec",
"sum_tree", "sum_tree",
"unicode-segmentation", "unicode-segmentation",
"util", "util",
@@ -5494,9 +5493,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.12.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
dependencies = [ dependencies = [
"web-time", "web-time",
"zeroize", "zeroize",
@@ -5759,7 +5758,7 @@ checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33"
[[package]] [[package]]
name = "semantic_version" name = "semantic_version"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"serde", "serde",
@@ -6132,6 +6131,8 @@ dependencies = [
"nostr-lmdb", "nostr-lmdb",
"nostr-sdk", "nostr-sdk",
"rustls", "rustls",
"serde",
"serde_json",
"smol", "smol",
"whoami", "whoami",
] ]
@@ -6203,7 +6204,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "sum_tree" name = "sum_tree"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"log", "log",
@@ -7239,7 +7240,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "util" name = "util"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-fs", "async-fs",
@@ -7274,7 +7275,7 @@ dependencies = [
[[package]] [[package]]
name = "util_macros" name = "util_macros"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#fd3ca0303ff1dfc552119878724eb151bc94b49c" source = "git+https://github.com/zed-industries/zed#f503c65924fff9799b359b6c83994b2eaf7af7c8"
dependencies = [ dependencies = [
"perf", "perf",
"quote", "quote",

View File

@@ -4,7 +4,7 @@ use cargo_packager_updater::{check_update, Config, Update};
use gpui::http_client::Url; use gpui::http_client::Url;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window}; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::constants::{APP_PUBKEY, APP_UPDATER_ENDPOINT}; use states::{APP_PUBKEY, APP_UPDATER_ENDPOINT};
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
AutoUpdater::set_global(cx.new(AutoUpdater::new), cx); AutoUpdater::set_global(cx.new(AutoUpdater::new), cx);

View File

@@ -6,7 +6,7 @@ use gpui::{Image, ImageFormat, SharedString, SharedUri};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use qrcode::render::svg; use qrcode::render::svg;
use qrcode::QrCode; use qrcode::QrCode;
use states::constants::IMAGE_RESIZE_SERVICE; use states::IMAGE_RESIZE_SERVICE;
const NOW: &str = "now"; const NOW: &str = "now";
const SECONDS_IN_MINUTE: i64 = 60; const SECONDS_IN_MINUTE: i64 = 60;

View File

@@ -21,9 +21,10 @@ use nostr_sdk::prelude::*;
use registry::{Registry, RegistryEvent}; use registry::{Registry, RegistryEvent};
use settings::AppSettings; use settings::AppSettings;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::constants::{BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH}; use states::{
use states::state::{Announcement, AuthRequest, Response, SignalKind, UnwrappingStatus}; app_state, default_nip17_relays, default_nip65_relays, Announcement, AuthRequest, Response,
use states::{app_state, default_nip17_relays, default_nip65_relays}; SignalKind, UnwrappingStatus, BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH,
};
use theme::{ActiveTheme, Theme, ThemeMode}; use theme::{ActiveTheme, Theme, ThemeMode};
use title_bar::TitleBar; use title_bar::TitleBar;
use ui::actions::{CopyPublicKey, OpenPublicKey}; use ui::actions::{CopyPublicKey, OpenPublicKey};
@@ -324,7 +325,7 @@ impl ChatSpace {
} }
fn load_encryption(&self, ann: Announcement, window: &Window, cx: &Context<Self>) { fn load_encryption(&self, ann: Announcement, window: &Window, cx: &Context<Self>) {
log::info!("Loading encryption keys: {ann:?}"); log::info!("Found encryption announcement: {ann:?}");
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
let state = app_state(); let state = app_state();

View File

@@ -6,8 +6,7 @@ use gpui::{
TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
WindowOptions, WindowOptions,
}; };
use states::app_state; use states::{app_state, APP_ID, CLIENT_NAME};
use states::constants::{APP_ID, CLIENT_NAME};
use ui::Root; use ui::Root;
use crate::actions::{load_embedded_fonts, quit, Quit}; use crate::actions::{load_embedded_fonts, quit, Quit};

View File

@@ -14,8 +14,7 @@ use key_store::KeyStore;
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
use registry::Registry; use registry::Registry;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::app_state; use states::{app_state, BUNKER_TIMEOUT};
use states::constants::BUNKER_TIMEOUT;
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};

View File

@@ -17,13 +17,13 @@ use indexset::{BTreeMap, BTreeSet};
use itertools::Itertools; use itertools::Itertools;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use registry::message::{Message, RenderedMessage}; use registry::message::{Message, RenderedMessage};
use registry::room::{Room, RoomKind, RoomSignal, SendOptions, SendReport, SignerKind}; use registry::room::{Room, RoomKind, RoomSignal, SendOptions, SendReport};
use registry::Registry; use registry::Registry;
use serde::Deserialize; use serde::Deserialize;
use settings::AppSettings; use settings::AppSettings;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use smol::fs; use smol::fs;
use states::app_state; use states::{app_state, SignerKind};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::actions::{CopyPublicKey, OpenPublicKey}; use ui::actions::{CopyPublicKey, OpenPublicKey};
use ui::avatar::Avatar; use ui::avatar::Avatar;
@@ -107,6 +107,15 @@ impl Chat {
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
let mut tasks = smallvec![]; let mut tasks = smallvec![];
tasks.push(
// Get messaging relays and encryption keys announcement for all members
cx.background_spawn(async move {
if let Err(e) = connect.await {
log::error!("Failed to initialize room: {e}");
}
}),
);
tasks.push( tasks.push(
// Load all messages belonging to this room // Load all messages belonging to this room
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
@@ -126,15 +135,6 @@ impl Chat {
}), }),
); );
tasks.push(
// Get messaging relays and encryption keys announcement for all members
cx.background_spawn(async move {
if let Err(e) = connect.await {
log::error!("Failed to initialize room: {e}");
}
}),
);
subscriptions.push( subscriptions.push(
// Subscribe to input events // Subscribe to input events
cx.subscribe_in( cx.subscribe_in(
@@ -346,6 +346,7 @@ impl Chat {
} }
/// Resend a failed message /// Resend a failed message
#[allow(dead_code)]
fn resend_message(&mut self, id: &EventId, window: &mut Window, cx: &mut Context<Self>) { fn resend_message(&mut self, id: &EventId, window: &mut Window, cx: &mut Context<Self>) {
if let Some(reports) = self.reports_by_id.get(id).cloned() { if let Some(reports) = self.reports_by_id.get(id).cloned() {
let id_clone = id.to_owned(); let id_clone = id.to_owned();
@@ -705,23 +706,7 @@ impl Chat {
}) })
.child(text) .child(text)
.when(is_sent_failed, |this| { .when(is_sent_failed, |this| {
this.child( this.child(self.render_message_reports(&id, cx))
h_flex()
.gap_1()
.child(self.render_message_reports(&id, cx))
.child(
Button::new(SharedString::from(id.to_hex()))
.label(t!("common.resend"))
.danger()
.xsmall()
.rounded()
.on_click(cx.listener(
move |this, _, window, cx| {
this.resend_message(&id, window, cx);
},
)),
),
)
}), }),
), ),
) )

View File

@@ -17,8 +17,7 @@ use registry::room::Room;
use registry::Registry; use registry::Registry;
use settings::AppSettings; use settings::AppSettings;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::app_state; use states::{app_state, BOOTSTRAP_RELAYS};
use states::constants::BOOTSTRAP_RELAYS;
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};

View File

@@ -11,8 +11,7 @@ use key_store::backend::KeyItem;
use key_store::KeyStore; use key_store::KeyStore;
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::app_state; use states::{app_state, BUNKER_TIMEOUT};
use states::constants::BUNKER_TIMEOUT;
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent}; use ui::dock_area::panel::{Panel, PanelEvent};

View File

@@ -12,8 +12,7 @@ use key_store::KeyStore;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use settings::AppSettings; use settings::AppSettings;
use smol::fs; use smol::fs;
use states::constants::BOOTSTRAP_RELAYS; use states::{app_state, default_nip17_relays, default_nip65_relays, BOOTSTRAP_RELAYS};
use states::{app_state, default_nip17_relays, default_nip65_relays};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};

View File

@@ -13,8 +13,7 @@ use key_store::backend::KeyItem;
use key_store::KeyStore; use key_store::KeyStore;
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::app_state; use states::{app_state, CLIENT_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT};
use states::constants::{CLIENT_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent}; use ui::dock_area::panel::{Panel, PanelEvent};

View File

@@ -13,8 +13,7 @@ use nostr_sdk::prelude::*;
use registry::Registry; use registry::Registry;
use settings::AppSettings; use settings::AppSettings;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::app_state; use states::{app_state, BOOTSTRAP_RELAYS};
use states::constants::BOOTSTRAP_RELAYS;
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};

View File

@@ -20,8 +20,7 @@ use registry::room::{Room, RoomKind};
use registry::{Registry, RegistryEvent}; use registry::{Registry, RegistryEvent};
use settings::AppSettings; use settings::AppSettings;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::app_state; use states::{app_state, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
use states::constants::{BOOTSTRAP_RELAYS, SEARCH_RELAYS};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent}; use ui::dock_area::panel::{Panel, PanelEvent};
@@ -626,7 +625,7 @@ impl Sidebar {
.name(this.display_name(cx)) .name(this.display_name(cx))
.avatar(this.display_image(proxy, cx)) .avatar(this.display_image(proxy, cx))
.created_at(this.created_at.to_ago()) .created_at(this.created_at.to_ago())
.public_key(this.members.iter().nth(0).unwrap().0) .public_key(&this.members[0])
.kind(this.kind) .kind(this.kind)
.on_click(handler), .on_click(handler),
) )

View File

@@ -8,7 +8,7 @@ use std::pin::Pin;
use anyhow::Result; use anyhow::Result;
use futures::FutureExt as _; use futures::FutureExt as _;
use gpui::AsyncApp; use gpui::AsyncApp;
use states::paths::config_dir; use states::config_dir;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyItem { pub enum KeyItem {

View File

@@ -245,7 +245,7 @@ impl Registry {
pub fn search_by_public_key(&self, public_key: PublicKey, cx: &App) -> Vec<Entity<Room>> { pub fn search_by_public_key(&self, public_key: PublicKey, cx: &App) -> Vec<Entity<Room>> {
self.rooms self.rooms
.iter() .iter()
.filter(|room| room.read(cx).members.contains_key(&public_key)) .filter(|room| room.read(cx).members.contains(&public_key))
.cloned() .cloned()
.collect() .collect()
} }

View File

@@ -8,20 +8,10 @@ use common::display::RenderedProfile;
use common::event::EventUtils; use common::event::EventUtils;
use gpui::{App, AppContext, Context, EventEmitter, SharedString, SharedUri, Task}; use gpui::{App, AppContext, Context, EventEmitter, SharedString, SharedUri, Task};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize}; use states::{app_state, SignerKind, SEND_RETRY};
use states::app_state;
use states::constants::SEND_RETRY;
use crate::Registry; use crate::Registry;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize, Serialize)]
pub enum SignerKind {
Encryption,
User,
#[default]
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct SendOptions { pub struct SendOptions {
pub backup: bool, pub backup: bool,
@@ -127,8 +117,6 @@ pub enum RoomKind {
Request, Request,
} }
type DevicePublicKey = PublicKey;
#[derive(Debug)] #[derive(Debug)]
pub struct Room { pub struct Room {
pub id: u64, pub id: u64,
@@ -136,7 +124,7 @@ pub struct Room {
/// Subject of the room /// Subject of the room
pub subject: Option<String>, pub subject: Option<String>,
/// All members of the room /// All members of the room
pub members: HashMap<PublicKey, Option<DevicePublicKey>>, pub members: Vec<PublicKey>,
/// Kind /// Kind
pub kind: RoomKind, pub kind: RoomKind,
} }
@@ -175,11 +163,7 @@ impl From<&Event> for Room {
let created_at = val.created_at; let created_at = val.created_at;
// Get the members from the event's tags and event's pubkey // Get the members from the event's tags and event's pubkey
let members: HashMap<PublicKey, Option<DevicePublicKey>> = val let members = val.all_pubkeys();
.all_pubkeys()
.into_iter()
.map(|public_key| (public_key, None))
.collect();
// Get subject from tags // Get subject from tags
let subject = val let subject = val
@@ -203,11 +187,7 @@ impl From<&UnsignedEvent> for Room {
let created_at = val.created_at; let created_at = val.created_at;
// Get the members from the event's tags and event's pubkey // Get the members from the event's tags and event's pubkey
let members: HashMap<PublicKey, Option<DevicePublicKey>> = val let members = val.all_pubkeys();
.all_pubkeys()
.into_iter()
.map(|public_key| (public_key, None))
.collect();
// Get subject from tags // Get subject from tags
let subject = val let subject = val
@@ -289,7 +269,7 @@ impl Room {
/// Returns the members of the room /// Returns the members of the room
pub fn members(&self) -> Vec<PublicKey> { pub fn members(&self) -> Vec<PublicKey> {
self.members.keys().cloned().collect() self.members.clone()
} }
/// Checks if the room has more than two members (group) /// Checks if the room has more than two members (group)
@@ -324,9 +304,9 @@ impl Room {
let target_member = self let target_member = self
.members .members
.keys() .iter()
.find(|&member| Some(member) != signer_pubkey.as_ref()) .find(|&member| Some(member) != signer_pubkey.as_ref())
.or_else(|| self.members.keys().next()) .or_else(|| self.members.first())
.expect("Room should have at least one member"); .expect("Room should have at least one member");
registry.read(cx).get_person(target_member, cx) registry.read(cx).get_person(target_member, cx)
@@ -339,7 +319,7 @@ impl Room {
if self.is_group() { if self.is_group() {
let profiles: Vec<Profile> = self let profiles: Vec<Profile> = self
.members .members
.keys() .iter()
.map(|public_key| registry.get_person(public_key, cx)) .map(|public_key| registry.get_person(public_key, cx))
.collect(); .collect();
@@ -447,7 +427,7 @@ impl Room {
// Add receivers // Add receivers
// //
// NOTE: current user will be removed from the list of receivers // NOTE: current user will be removed from the list of receivers
for (member, _) in self.members.iter() { for member in self.members.iter() {
// Get relay hint if available // Get relay hint if available
let relay_url = relay_cache let relay_url = relay_cache
.get(member) .get(member)
@@ -505,54 +485,75 @@ impl Room {
opts: &SendOptions, opts: &SendOptions,
cx: &App, cx: &App,
) -> Task<Result<Vec<SendReport>, Error>> { ) -> Task<Result<Vec<SendReport>, Error>> {
let mut members = self.members.clone();
let rumor = rumor.to_owned(); let rumor = rumor.to_owned();
let opts = opts.to_owned(); let opts = opts.to_owned();
// Get all members
let mut members = self.members();
cx.background_spawn(async move { cx.background_spawn(async move {
let state = app_state(); let state = app_state();
let client = state.client(); let client = state.client();
let signer_kind = opts.signer_kind;
let relay_cache = state.relay_cache.read().await; let relay_cache = state.relay_cache.read().await;
let device = state.device.read().await.encryption.clone(); let announcement_cache = state.announcement_cache.read().await;
let encryption = state.device.read().await.encryption.clone();
// Get the encryption public key
let encryption_pubkey = if let Some(signer) = encryption.as_ref() {
signer.get_public_key().await.ok()
} else {
None
};
let user_signer = client.signer().await?; let user_signer = client.signer().await?;
let user_pubkey = user_signer.get_public_key().await?; let user_pubkey = user_signer.get_public_key().await?;
// Remove the current user's public key from the list of receivers // Remove the current user's public key from the list of receivers
// Current user will be handled separately // the current user will be handled separately
let (public_key, device_pubkey) = members.remove_entry(&user_pubkey).unwrap(); members.retain(|&pk| pk != user_pubkey);
// Determine the signer will be used based on the provided options // Determine the signer will be used based on the provided options
let (signer, signer_kind) = let signer = Self::select_signer(&opts.signer_kind, user_signer, encryption)?;
Self::select_signer(&opts.signer_kind, device, user_signer)?;
// Collect the send reports // Collect the send reports
let mut reports: Vec<SendReport> = vec![]; let mut reports: Vec<SendReport> = vec![];
for (user, device_pubkey) in members.into_iter() { for member in members.into_iter() {
let urls = relay_cache.get(&user).cloned().unwrap_or_default(); // Get user's messaging relays
let urls = relay_cache.get(&member).cloned().unwrap_or_default();
// Check if there are any relays to send the message to // Check if there are any relays to send the message to
if urls.is_empty() { if urls.is_empty() {
reports.push(SendReport::new(user).relays_not_found()); reports.push(SendReport::new(member).relays_not_found());
continue; continue;
} }
// Skip sending if using encryption keys but device not found // Ensure connection to the relays
if device_pubkey.is_none() && matches!(opts.signer_kind, SignerKind::Encryption) { for url in urls.iter() {
reports.push(SendReport::new(user).device_not_found()); client.add_relay(url).await.ok();
client.connect_relay(url).await.ok();
}
// Get user's encryption public key if available
let encryption = announcement_cache
.get(&member)
.and_then(|a| a.to_owned().map(|a| a.public_key()));
// Skip sending if using encryption signer but receiver's encryption keys not found
if encryption.is_none() && matches!(signer_kind, SignerKind::Encryption) {
reports.push(SendReport::new(member).device_not_found());
continue; continue;
} }
// Determine the receiver based on the signer kind let receiver = Self::select_receiver(&signer_kind, member, encryption)?;
let receiver = Self::select_receiver(&opts.signer_kind, user, device_pubkey)?; let rumor = rumor.clone();
// Construct the gift wrap event // Construct the gift wrap event
let rumor = rumor.clone();
let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, []).await?; let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, []).await?;
// Send the event to the messaging relays // Send the gift wrap event to the messaging relays
match client.send_event_to(urls, &event).await { match client.send_event_to(urls, &event).await {
Ok(output) => { Ok(output) => {
let id = output.id().to_owned(); let id = output.id().to_owned();
@@ -590,33 +591,32 @@ impl Room {
} }
} }
// Select a signer based on previous usage, not from the options let receiver = Self::select_receiver(&signer_kind, user_pubkey, encryption_pubkey)?;
let receiver = Self::select_receiver(&signer_kind, public_key, device_pubkey)?;
// Construct a gift wrap to back up to current user's owned messaging relays
let rumor = rumor.clone(); let rumor = rumor.clone();
// Construct the gift-wrapped event
let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, []).await?; let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, []).await?;
// Only send a backup message to current user if sent successfully to others // Only send a backup message to current user if sent successfully to others
if opts.backup() && reports.iter().all(|r| r.is_sent_success()) { if opts.backup() && reports.iter().all(|r| r.is_sent_success()) {
let urls = relay_cache.get(&public_key).cloned().unwrap_or_default(); let urls = relay_cache.get(&user_pubkey).cloned().unwrap_or_default();
// Check if there are any relays to send the event to // Check if there are any relays to send the event to
if urls.is_empty() { if urls.is_empty() {
reports.push(SendReport::new(public_key).relays_not_found()); reports.push(SendReport::new(user_pubkey).relays_not_found());
} else { } else {
// Send the event to the messaging relays // Send the event to the messaging relays
match client.send_event_to(urls, &event).await { match client.send_event_to(urls, &event).await {
Ok(output) => { Ok(output) => {
reports.push(SendReport::new(public_key).status(output)); reports.push(SendReport::new(user_pubkey).status(output));
} }
Err(e) => { Err(e) => {
reports.push(SendReport::new(public_key).error(e.to_string())); reports.push(SendReport::new(user_pubkey).error(e.to_string()));
} }
} }
} }
} else { } else {
reports.push(SendReport::new(public_key).on_hold(event)); reports.push(SendReport::new(user_pubkey).on_hold(event));
} }
Ok(reports) Ok(reports)
@@ -683,37 +683,30 @@ impl Room {
}) })
} }
fn select_signer<T>( fn select_signer<T>(kind: &SignerKind, user: T, encryption: Option<T>) -> Result<T, Error>
kind: &SignerKind,
device: Option<T>,
user: T,
) -> Result<(T, SignerKind), Error>
where where
T: NostrSigner, T: NostrSigner,
{ {
match kind { match kind {
SignerKind::Auto => device SignerKind::Encryption => {
.map(|d| (d, SignerKind::Encryption)) Ok(encryption.ok_or_else(|| anyhow!("No encryption key found"))?)
.or(Some((user, SignerKind::User))) }
.ok_or_else(|| anyhow!("No signer available")), SignerKind::User => Ok(user),
SignerKind::Encryption => device SignerKind::Auto => Ok(encryption.unwrap_or(user)),
.map(|d| (d, SignerKind::Encryption))
.ok_or_else(|| anyhow!("No encryption key found")),
SignerKind::User => Ok((user, SignerKind::User)),
} }
} }
fn select_receiver( fn select_receiver(
kind: &SignerKind, kind: &SignerKind,
user: PublicKey, user: PublicKey,
device: Option<PublicKey>, encryption: Option<PublicKey>,
) -> Result<PublicKey, Error> { ) -> Result<PublicKey, Error> {
match kind { match kind {
SignerKind::Encryption => { SignerKind::Encryption => {
Ok(device.ok_or_else(|| anyhow!("Receiver's encryption key not found"))?) Ok(encryption.ok_or_else(|| anyhow!("Receiver's encryption key not found"))?)
} }
SignerKind::User => Ok(user), SignerKind::User => Ok(user),
SignerKind::Auto => Ok(device.unwrap_or(user)), SignerKind::Auto => Ok(encryption.unwrap_or(user)),
} }
} }
} }

View File

@@ -3,8 +3,7 @@ use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use states::app_state; use states::{app_state, SETTINGS_IDENTIFIER};
use states::constants::SETTINGS_IDENTIFIER;
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
let state = cx.new(AppSettings::new); let state = cx.new(AppSettings::new);

View File

@@ -13,6 +13,8 @@ smol.workspace = true
flume.workspace = true flume.workspace = true
log.workspace = true log.workspace = true
anyhow.workspace = true anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
whoami = "1.6.1" whoami = "1.6.1"
rustls = "0.23.23" rustls = "0.23.23"

View File

@@ -3,12 +3,13 @@ use std::sync::OnceLock;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use whoami::{devicename, platform}; use whoami::{devicename, platform};
use crate::constants::CLIENT_NAME; mod constants;
use crate::state::AppState; mod paths;
mod state;
pub mod constants; pub use constants::*;
pub mod paths; pub use paths::*;
pub mod state; pub use state::*;
static APP_STATE: OnceLock<AppState> = OnceLock::new(); static APP_STATE: OnceLock<AppState> = OnceLock::new();
static APP_NAME: OnceLock<String> = OnceLock::new(); static APP_NAME: OnceLock<String> = OnceLock::new();

View File

@@ -1,6 +1,15 @@
use std::sync::Arc; use std::sync::Arc;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize, Serialize)]
pub enum SignerKind {
Encryption,
#[default]
User,
Auto,
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Device { pub struct Device {

View File

@@ -15,7 +15,6 @@ use crate::constants::{
BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, QUERY_TIMEOUT, SEARCH_RELAYS, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, QUERY_TIMEOUT, SEARCH_RELAYS,
}; };
use crate::paths::config_dir; use crate::paths::config_dir;
use crate::state::device::Device;
use crate::state::ingester::Ingester; use crate::state::ingester::Ingester;
use crate::state::tracker::EventTracker; use crate::state::tracker::EventTracker;
@@ -24,6 +23,7 @@ mod ingester;
mod signal; mod signal;
mod tracker; mod tracker;
pub use device::*;
pub use signal::*; pub use signal::*;
#[derive(Debug)] #[derive(Debug)]
@@ -43,6 +43,9 @@ pub struct AppState {
/// Cache of messaging relays for each public key /// Cache of messaging relays for each public key
pub relay_cache: RwLock<HashMap<PublicKey, HashSet<RelayUrl>>>, pub relay_cache: RwLock<HashMap<PublicKey, HashSet<RelayUrl>>>,
/// Cache of device announcement for each public key
pub announcement_cache: RwLock<HashMap<PublicKey, Option<Announcement>>>,
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md /// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
pub device: RwLock<Device>, pub device: RwLock<Device>,
@@ -83,6 +86,7 @@ impl AppState {
let device = RwLock::new(Device::default()); let device = RwLock::new(Device::default());
let event_tracker = RwLock::new(EventTracker::default()); let event_tracker = RwLock::new(EventTracker::default());
let relay_cache = RwLock::new(HashMap::default()); let relay_cache = RwLock::new(HashMap::default());
let announcement_cache = RwLock::new(HashMap::default());
let signal = Signal::default(); let signal = Signal::default();
let ingester = Ingester::default(); let ingester = Ingester::default();
@@ -92,6 +96,7 @@ impl AppState {
device, device,
event_tracker, event_tracker,
relay_cache, relay_cache,
announcement_cache,
signal, signal,
ingester, ingester,
initialized_at: Timestamp::now(), initialized_at: Timestamp::now(),
@@ -138,6 +143,9 @@ impl AppState {
// Get user's gossip relays // Get user's gossip relays
self.get_nip65(pk).await.ok(); self.get_nip65(pk).await.ok();
// Initialize the relay and announcement caches
self.init_cache().await.ok();
// Initialize client keys // Initialize client keys
self.init_client_keys().await.ok(); self.init_client_keys().await.ok();
@@ -236,12 +244,16 @@ impl AppState {
match event.kind { match event.kind {
// Encryption Keys announcement event // Encryption Keys announcement event
Kind::Custom(10044) => { Kind::Custom(10044) => {
if let Ok(true) = self.is_self_authored(&event).await {
if let Ok(announcement) = self.extract_announcement(&event) { if let Ok(announcement) = self.extract_announcement(&event) {
if let Ok(true) = self.is_self_authored(&event).await {
self.signal self.signal
.send(SignalKind::EncryptionSet(announcement)) .send(SignalKind::EncryptionSet(announcement.clone()))
.await; .await;
} }
// Cache the announcement for further queries
let mut announcement_cache = self.announcement_cache.write().await;
announcement_cache.insert(event.pubkey, Some(announcement));
} }
} }
// Encryption Keys request event // Encryption Keys request event
@@ -572,6 +584,33 @@ impl AppState {
Ok(()) Ok(())
} }
/// Initialize the relay and announcement caches with events from the local database
pub async fn init_cache(&self) -> Result<(), Error> {
let filter = Filter::new().kind(Kind::InboxRelays);
let events = self.client.database().query(filter).await?;
let mut relay_cache = self.relay_cache.write().await;
for event in events.into_iter() {
let relays: Vec<RelayUrl> =
nip17::extract_relay_list(&event).take(3).cloned().collect();
// Push all relays to the relay cache
relay_cache.entry(event.pubkey).or_default().extend(relays);
}
let filter = Filter::new().kind(Kind::Custom(10044));
let events = self.client.database().query(filter).await?;
let mut announcement_cache = self.announcement_cache.write().await;
for event in events.into_iter() {
if let Ok(announcement) = self.extract_announcement(&event) {
announcement_cache.insert(event.pubkey, Some(announcement));
}
}
Ok(())
}
/// Initialize the client keys to communicate between clients /// Initialize the client keys to communicate between clients
/// ///
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md /// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
@@ -961,7 +1000,7 @@ impl AppState {
// Subscribe to gift wrap events // Subscribe to gift wrap events
self.client self.client
.subscribe_with_id_to(urls, id, filter, None) .subscribe_with_id_to(&urls, id, filter, None)
.await?; .await?;
Ok(()) Ok(())
@@ -1037,7 +1076,7 @@ impl AppState {
} }
// Try to unwrap with the available signer // Try to unwrap with the available signer
if let Ok(unwrapped) = self.try_unwrap_gift_wrap(gift_wrap).await { let unwrapped = self.try_unwrap_gift_wrap(gift_wrap).await?;
let sender = unwrapped.sender; let sender = unwrapped.sender;
let mut rumor_unsigned = unwrapped.rumor; let mut rumor_unsigned = unwrapped.rumor;
@@ -1048,11 +1087,11 @@ impl AppState {
// Generate event id for the rumor if it doesn't have one // Generate event id for the rumor if it doesn't have one
rumor_unsigned.ensure_id(); rumor_unsigned.ensure_id();
// Cache the rumor
self.set_rumor(gift_wrap.id, &rumor_unsigned).await?; self.set_rumor(gift_wrap.id, &rumor_unsigned).await?;
self.process_rumor(gift_wrap.id, rumor_unsigned).await?;
return Ok(()); // Process the rumor
} self.process_rumor(gift_wrap.id, rumor_unsigned).await?;
Ok(()) Ok(())
} }
@@ -1062,20 +1101,21 @@ impl AppState {
// Try to unwrap with the device's encryption keys first // Try to unwrap with the device's encryption keys first
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
if let Some(signer) = self.device.read().await.encryption.as_ref() { if let Some(signer) = self.device.read().await.encryption.as_ref() {
if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(signer, gift_wrap).await { match UnwrappedGift::from_gift_wrap(signer, gift_wrap).await {
Ok(unwrapped) => {
return Ok(unwrapped); return Ok(unwrapped);
} }
Err(e) => {
log::error!("Failed to unwrap with the encryption key: {e}")
}
}
} }
// Get user's signer
let signer = self.client.signer().await?;
// Try to unwrap with the user's signer // Try to unwrap with the user's signer
if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await { let signer = self.client.signer().await?;
return Ok(unwrapped); let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?;
}
Err(anyhow!("No signer available")) Ok(unwrapped)
} }
/// Process a rumor event. /// Process a rumor event.