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

View File

@@ -4,7 +4,7 @@ use cargo_packager_updater::{check_update, Config, Update};
use gpui::http_client::Url;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
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) {
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 qrcode::render::svg;
use qrcode::QrCode;
use states::constants::IMAGE_RESIZE_SERVICE;
use states::IMAGE_RESIZE_SERVICE;
const NOW: &str = "now";
const SECONDS_IN_MINUTE: i64 = 60;

View File

@@ -21,9 +21,10 @@ use nostr_sdk::prelude::*;
use registry::{Registry, RegistryEvent};
use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use states::constants::{BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH};
use states::state::{Announcement, AuthRequest, Response, SignalKind, UnwrappingStatus};
use states::{app_state, default_nip17_relays, default_nip65_relays};
use states::{
app_state, default_nip17_relays, default_nip65_relays, Announcement, AuthRequest, Response,
SignalKind, UnwrappingStatus, BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH,
};
use theme::{ActiveTheme, Theme, ThemeMode};
use title_bar::TitleBar;
use ui::actions::{CopyPublicKey, OpenPublicKey};
@@ -324,7 +325,7 @@ impl ChatSpace {
}
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| {
let state = app_state();

View File

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

View File

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

View File

@@ -17,13 +17,13 @@ use indexset::{BTreeMap, BTreeSet};
use itertools::Itertools;
use nostr_sdk::prelude::*;
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 serde::Deserialize;
use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use smol::fs;
use states::app_state;
use states::{app_state, SignerKind};
use theme::ActiveTheme;
use ui::actions::{CopyPublicKey, OpenPublicKey};
use ui::avatar::Avatar;
@@ -107,6 +107,15 @@ impl Chat {
let mut subscriptions = 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(
// Load all messages belonging to this room
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(
// Subscribe to input events
cx.subscribe_in(
@@ -346,6 +346,7 @@ impl Chat {
}
/// Resend a failed message
#[allow(dead_code)]
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() {
let id_clone = id.to_owned();
@@ -705,23 +706,7 @@ impl Chat {
})
.child(text)
.when(is_sent_failed, |this| {
this.child(
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);
},
)),
),
)
this.child(self.render_message_reports(&id, cx))
}),
),
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ use std::pin::Pin;
use anyhow::Result;
use futures::FutureExt as _;
use gpui::AsyncApp;
use states::paths::config_dir;
use states::config_dir;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
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>> {
self.rooms
.iter()
.filter(|room| room.read(cx).members.contains_key(&public_key))
.filter(|room| room.read(cx).members.contains(&public_key))
.cloned()
.collect()
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,15 @@
use std::sync::Arc;
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)]
pub struct Device {

View File

@@ -15,7 +15,6 @@ use crate::constants::{
BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, QUERY_TIMEOUT, SEARCH_RELAYS,
};
use crate::paths::config_dir;
use crate::state::device::Device;
use crate::state::ingester::Ingester;
use crate::state::tracker::EventTracker;
@@ -24,6 +23,7 @@ mod ingester;
mod signal;
mod tracker;
pub use device::*;
pub use signal::*;
#[derive(Debug)]
@@ -43,6 +43,9 @@ pub struct AppState {
/// Cache of messaging relays for each public key
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
pub device: RwLock<Device>,
@@ -83,6 +86,7 @@ impl AppState {
let device = RwLock::new(Device::default());
let event_tracker = RwLock::new(EventTracker::default());
let relay_cache = RwLock::new(HashMap::default());
let announcement_cache = RwLock::new(HashMap::default());
let signal = Signal::default();
let ingester = Ingester::default();
@@ -92,6 +96,7 @@ impl AppState {
device,
event_tracker,
relay_cache,
announcement_cache,
signal,
ingester,
initialized_at: Timestamp::now(),
@@ -138,6 +143,9 @@ impl AppState {
// Get user's gossip relays
self.get_nip65(pk).await.ok();
// Initialize the relay and announcement caches
self.init_cache().await.ok();
// Initialize client keys
self.init_client_keys().await.ok();
@@ -236,12 +244,16 @@ impl AppState {
match event.kind {
// Encryption Keys announcement event
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
.send(SignalKind::EncryptionSet(announcement))
.send(SignalKind::EncryptionSet(announcement.clone()))
.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
@@ -572,6 +584,33 @@ impl AppState {
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
///
/// 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
self.client
.subscribe_with_id_to(urls, id, filter, None)
.subscribe_with_id_to(&urls, id, filter, None)
.await?;
Ok(())
@@ -1037,22 +1076,22 @@ impl AppState {
}
// Try to unwrap with the available signer
if let Ok(unwrapped) = self.try_unwrap_gift_wrap(gift_wrap).await {
let sender = unwrapped.sender;
let mut rumor_unsigned = unwrapped.rumor;
let unwrapped = self.try_unwrap_gift_wrap(gift_wrap).await?;
let sender = unwrapped.sender;
let mut rumor_unsigned = unwrapped.rumor;
if !self.verify_sender(sender, &rumor_unsigned).await {
return Err(anyhow!("Invalid rumor"));
};
if !self.verify_sender(sender, &rumor_unsigned).await {
return Err(anyhow!("Invalid rumor"));
};
// Generate event id for the rumor if it doesn't have one
rumor_unsigned.ensure_id();
// Generate event id for the rumor if it doesn't have one
rumor_unsigned.ensure_id();
self.set_rumor(gift_wrap.id, &rumor_unsigned).await?;
self.process_rumor(gift_wrap.id, rumor_unsigned).await?;
// Cache the rumor
self.set_rumor(gift_wrap.id, &rumor_unsigned).await?;
return Ok(());
}
// Process the rumor
self.process_rumor(gift_wrap.id, rumor_unsigned).await?;
Ok(())
}
@@ -1062,20 +1101,21 @@ impl AppState {
// Try to unwrap with the device's encryption keys first
// 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 Ok(unwrapped) = UnwrappedGift::from_gift_wrap(signer, gift_wrap).await {
return Ok(unwrapped);
match UnwrappedGift::from_gift_wrap(signer, gift_wrap).await {
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
if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await {
return Ok(unwrapped);
}
let signer = self.client.signer().await?;
let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?;
Err(anyhow!("No signer available"))
Ok(unwrapped)
}
/// Process a rumor event.