This commit is contained in:
2026-01-05 08:43:28 +07:00
parent 067f88dfa6
commit 23f8cc49c6
12 changed files with 142 additions and 264 deletions

View File

@@ -253,6 +253,11 @@ impl ChatRegistry {
} }
} }
/// Get the loading status of the chat registry
pub fn loading(&self) -> bool {
self.loading
}
/// Set the loading status of the chat registry /// Set the loading status of the chat registry
pub fn set_loading(&mut self, loading: bool, cx: &mut Context<Self>) { pub fn set_loading(&mut self, loading: bool, cx: &mut Context<Self>) {
self.loading = loading; self.loading = loading;
@@ -511,7 +516,7 @@ impl ChatRegistry {
} }
// Set this room is ongoing if the new message is from current user // Set this room is ongoing if the new message is from current user
if author == nostr.read(cx).identity(cx).public_key() { if author == nostr.read(cx).identity().read(cx).public_key() {
this.set_ongoing(cx); this.set_ongoing(cx);
} }

View File

@@ -263,7 +263,7 @@ impl Room {
pub fn display_member(&self, cx: &App) -> Profile { pub fn display_member(&self, cx: &App) -> Profile {
let persons = PersonRegistry::global(cx); let persons = PersonRegistry::global(cx);
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let public_key = nostr.read(cx).identity(cx).public_key(); let public_key = nostr.read(cx).identity().read(cx).public_key();
let target_member = self let target_member = self
.members .members
@@ -369,7 +369,7 @@ impl Room {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
// Get current user // Get current user
let public_key = nostr.read(cx).identity(cx).public_key(); let public_key = nostr.read(cx).identity().read(cx).public_key();
// Get room's subject // Get room's subject
let subject = self.subject.clone(); let subject = self.subject.clone();
@@ -438,7 +438,7 @@ impl Room {
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
// Get current user's public key and relays // Get current user's public key and relays
let current_user = nostr.read(cx).identity(cx).public_key(); let current_user = nostr.read(cx).identity().read(cx).public_key();
let current_user_relays = nostr.read(cx).messaging_relays(&current_user, cx); let current_user_relays = nostr.read(cx).messaging_relays(&current_user, cx);
let rumor = rumor.to_owned(); let rumor = rumor.to_owned();

View File

@@ -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, SendOptions, SendReport}; use chat::{Message, RenderedMessage, Room, RoomKind, RoomSignal, 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::{
@@ -59,7 +59,6 @@ pub struct ChatPanel {
// New Message // New Message
input: Entity<InputState>, input: Entity<InputState>,
options: Entity<SendOptions>,
replies_to: Entity<HashSet<EventId>>, replies_to: Entity<HashSet<EventId>>,
// Media Attachment // Media Attachment
@@ -87,7 +86,6 @@ impl ChatPanel {
let attachments = cx.new(|_| vec![]); let attachments = cx.new(|_| vec![]);
let replies_to = cx.new(|_| HashSet::new()); let replies_to = cx.new(|_| HashSet::new());
let options = cx.new(|_| SendOptions::default());
let id = room.read(cx).id.to_string().into(); let id = room.read(cx).id.to_string().into();
let messages = BTreeSet::from([Message::system()]); let messages = BTreeSet::from([Message::system()]);
@@ -145,18 +143,11 @@ impl ChatPanel {
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)) => { RoomSignal::NewMessage((gift_wrap_id, event)) => {
let nostr = NostrRegistry::global(cx);
let tracker = nostr.read(cx).tracker();
let gift_wrap_id = gift_wrap_id.to_owned();
let message = Message::user(event.clone()); let message = Message::user(event.clone());
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
let tracker = tracker.read().await;
this.update_in(cx, |this, _window, cx| { this.update_in(cx, |this, _window, cx| {
if !tracker.sent_ids().contains(&gift_wrap_id) { this.insert_message(message, false, cx);
this.insert_message(message, false, cx);
}
}) })
.ok(); .ok();
}) })
@@ -189,7 +180,6 @@ impl ChatPanel {
input, input,
replies_to, replies_to,
attachments, attachments,
options,
rendered_texts_by_id: BTreeMap::new(), rendered_texts_by_id: BTreeMap::new(),
reports_by_id: BTreeMap::new(), reports_by_id: BTreeMap::new(),
uploading: false, uploading: false,
@@ -271,14 +261,13 @@ impl ChatPanel {
// Get the current room entity // Get the current room entity
let room = self.room.read(cx); let room = self.room.read(cx);
let opts = self.options.read(cx);
// Create a temporary message for optimistic update // Create a temporary message for optimistic update
let rumor = room.create_message(&content, replies.as_ref(), cx); let rumor = room.create_message(&content, replies.as_ref(), cx);
let rumor_id = rumor.id.unwrap(); let rumor_id = rumor.id.unwrap();
// Create a task for sending the message in the background // Create a task for sending the message in the background
let send_message = room.send_message(&rumor, opts, cx); let send_message = room.send_message(&rumor, cx);
// Optimistically update message list // Optimistically update message list
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
@@ -440,10 +429,6 @@ impl ChatPanel {
persons.read(cx).get_person(public_key, cx) persons.read(cx).get_person(public_key, cx)
} }
fn signer_kind(&self, cx: &App) -> SignerKind {
self.options.read(cx).signer_kind
}
fn scroll_to(&self, id: EventId) { fn scroll_to(&self, id: EventId) {
if let Some(ix) = self.messages.iter().position(|m| { if let Some(ix) = self.messages.iter().position(|m| {
if let Message::User(msg) = m { if let Message::User(msg) = m {
@@ -1235,71 +1220,6 @@ impl ChatPanel {
}); });
}) })
} }
fn on_open_seen_on(&mut self, ev: &SeenOn, window: &mut Window, cx: &mut Context<Self>) {
let id = ev.0;
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let tracker = nostr.read(cx).tracker();
let task: Task<Result<Vec<RelayUrl>, Error>> = cx.background_spawn(async move {
let tracker = tracker.read().await;
let mut relays: Vec<RelayUrl> = vec![];
let filter = Filter::new()
.kind(Kind::ApplicationSpecificData)
.event(id)
.limit(1);
if let Some(event) = client.database().query(filter).await?.first_owned() {
if let Some(Ok(id)) = event.tags.identifier().map(EventId::parse) {
if let Some(urls) = tracker.seen_on_relays.get(&id).cloned() {
relays.extend(urls);
}
}
}
Ok(relays)
});
cx.spawn_in(window, async move |_, cx| {
if let Ok(urls) = task.await {
cx.update(|window, cx| {
window.open_modal(cx, move |this, _window, cx| {
this.show_close(true)
.title(SharedString::from("Seen on"))
.child(v_flex().pb_4().gap_2().children({
let mut items = Vec::with_capacity(urls.len());
for url in urls.clone().into_iter() {
items.push(
h_flex()
.h_8()
.px_2()
.bg(cx.theme().elevated_surface_background)
.rounded(cx.theme().radius)
.font_semibold()
.text_xs()
.child(SharedString::from(url.to_string())),
)
}
items
}))
});
})
.ok();
}
})
.detach();
}
fn on_set_encryption(&mut self, ev: &SetSigner, _: &mut Window, cx: &mut Context<Self>) {
self.options.update(cx, move |this, cx| {
this.signer_kind = ev.0;
cx.notify();
});
}
} }
impl Panel for ChatPanel { impl Panel for ChatPanel {
@@ -1339,11 +1259,7 @@ impl Focusable for ChatPanel {
impl Render for ChatPanel { impl Render for ChatPanel {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let kind = self.signer_kind(cx);
v_flex() v_flex()
.on_action(cx.listener(Self::on_open_seen_on))
.on_action(cx.listener(Self::on_set_encryption))
.image_cache(self.image_cache.clone()) .image_cache(self.image_cache.clone())
.size_full() .size_full()
.child( .child(
@@ -1398,31 +1314,7 @@ impl Render for ChatPanel {
.large(), .large(),
), ),
) )
.child(TextInput::new(&self.input)) .child(TextInput::new(&self.input)),
.child(
Button::new("encryptions")
.icon(IconName::Encryption)
.ghost()
.large()
.popup_menu(move |this, _window, _cx| {
this.label("Encrypt by:")
.menu_with_check(
"Encryption Key",
matches!(kind, SignerKind::Encryption),
Box::new(SetSigner(SignerKind::Encryption)),
)
.menu_with_check(
"User's Identity",
matches!(kind, SignerKind::User),
Box::new(SetSigner(SignerKind::User)),
)
.menu_with_check(
"Auto",
matches!(kind, SignerKind::Auto),
Box::new(SetSigner(SignerKind::Auto)),
)
}),
),
), ),
), ),
) )

View File

@@ -1,12 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use account::Account;
use auto_update::{AutoUpdateStatus, AutoUpdater}; use auto_update::{AutoUpdateStatus, AutoUpdater};
use chat::{ChatEvent, ChatRegistry}; use chat::{ChatEvent, ChatRegistry};
use chat_ui::{CopyPublicKey, OpenPublicKey}; use chat_ui::{CopyPublicKey, OpenPublicKey};
use common::{RenderedProfile, DEFAULT_SIDEBAR_WIDTH}; use common::{RenderedProfile, DEFAULT_SIDEBAR_WIDTH};
use encryption::Encryption;
use encryption_ui::EncryptionPanel;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
deferred, div, px, relative, rems, App, AppContext, Axis, ClipboardItem, Context, Entity, deferred, div, px, relative, rems, App, AppContext, Axis, ClipboardItem, Context, Entity,
@@ -19,6 +16,7 @@ use person::PersonRegistry;
use relay_auth::RelayAuth; use relay_auth::RelayAuth;
use settings::AppSettings; use settings::AppSettings;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use state::NostrRegistry;
use theme::{ActiveTheme, Theme, ThemeMode, ThemeRegistry}; use theme::{ActiveTheme, Theme, ThemeMode, ThemeRegistry};
use title_bar::TitleBar; use title_bar::TitleBar;
use ui::avatar::Avatar; use ui::avatar::Avatar;
@@ -27,7 +25,6 @@ use ui::dock_area::dock::DockPlacement;
use ui::dock_area::panel::PanelView; use ui::dock_area::panel::PanelView;
use ui::dock_area::{ClosePanel, DockArea, DockItem}; use ui::dock_area::{ClosePanel, DockArea, DockItem};
use ui::modal::ModalButtonProps; use ui::modal::ModalButtonProps;
use ui::popover::{Popover, PopoverContent};
use ui::popup_menu::PopupMenuExt; use ui::popup_menu::PopupMenuExt;
use ui::{h_flex, v_flex, ContextModal, IconName, Root, Sizable, StyledExt}; use ui::{h_flex, v_flex, ContextModal, IconName, Root, Sizable, StyledExt};
@@ -61,9 +58,6 @@ pub struct ChatSpace {
/// App's Dock Area /// App's Dock Area
dock: Entity<DockArea>, dock: Entity<DockArea>,
/// App's Encryption Panel
encryption_panel: Entity<EncryptionPanel>,
/// Determines if the chat space is ready to use /// Determines if the chat space is ready to use
ready: bool, ready: bool,
@@ -73,13 +67,14 @@ pub struct ChatSpace {
impl ChatSpace { impl ChatSpace {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let keystore = KeyStore::global(cx); let keystore = KeyStore::global(cx);
let account = Account::global(cx);
let title_bar = cx.new(|_| TitleBar::new()); let title_bar = cx.new(|_| TitleBar::new());
let dock = cx.new(|cx| DockArea::new(window, cx)); let dock = cx.new(|cx| DockArea::new(window, cx));
let encryption_panel = encryption_ui::init(window, cx);
let identity = nostr.read(cx).identity();
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
@@ -92,8 +87,8 @@ impl ChatSpace {
subscriptions.push( subscriptions.push(
// Observe account entity changes // Observe account entity changes
cx.observe_in(&account, window, move |this, state, window, cx| { cx.observe_in(&identity, window, move |this, state, window, cx| {
if !this.ready && state.read(cx).has_account() { if !this.ready && state.read(cx).has_public_key() {
this.set_default_layout(window, cx); this.set_default_layout(window, cx);
// Load all chat room in the database if available // Load all chat room in the database if available
@@ -175,7 +170,6 @@ impl ChatSpace {
Self { Self {
dock, dock,
title_bar, title_bar,
encryption_panel,
ready: false, ready: false,
_subscriptions: subscriptions, _subscriptions: subscriptions,
} }
@@ -447,11 +441,11 @@ impl ChatSpace {
} }
fn titlebar_left(&mut self, _window: &mut Window, cx: &Context<Self>) -> impl IntoElement { fn titlebar_left(&mut self, _window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
let account = Account::global(cx); let nostr = NostrRegistry::global(cx);
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let status = chat.read(cx).loading; let status = chat.read(cx).loading();
if !account.read(cx).has_account() { if !nostr.read(cx).identity().read(cx).has_public_key() {
return div(); return div();
} }
@@ -479,10 +473,12 @@ impl ChatSpace {
fn titlebar_right(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn titlebar_right(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let proxy = AppSettings::get_proxy_user_avatars(cx); let proxy = AppSettings::get_proxy_user_avatars(cx);
let auto_update = AutoUpdater::global(cx); let auto_update = AutoUpdater::global(cx);
let account = Account::global(cx);
let relay_auth = RelayAuth::global(cx); let relay_auth = RelayAuth::global(cx);
let pending_requests = relay_auth.read(cx).pending_requests(cx); let pending_requests = relay_auth.read(cx).pending_requests(cx);
let encryption_panel = self.encryption_panel.downgrade();
let nostr = NostrRegistry::global(cx);
let identity = nostr.read(cx).identity();
h_flex() h_flex()
.gap_2() .gap_2()
@@ -542,16 +538,10 @@ impl ChatSpace {
}), }),
) )
}) })
.when(account.read(cx).has_account(), |this| { .when_some(identity.read(cx).option_public_key(), |this, public_key| {
let account = Account::global(cx);
let public_key = account.read(cx).public_key();
let persons = PersonRegistry::global(cx); let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get_person(&public_key, cx); let profile = persons.read(cx).get_person(&public_key, cx);
let encryption = Encryption::global(cx);
let has_encryption = encryption.read(cx).has_encryption(cx);
let keystore = KeyStore::global(cx); let keystore = KeyStore::global(cx);
let is_using_file_keystore = keystore.read(cx).is_using_file_keystore(); let is_using_file_keystore = keystore.read(cx).is_using_file_keystore();
@@ -562,82 +552,38 @@ impl ChatSpace {
}; };
this.child( this.child(
h_flex() Button::new("user")
.gap_1() .small()
.child( .reverse()
Popover::new("encryption") .transparent()
.trigger( .icon(IconName::CaretDown)
Button::new("encryption-trigger") .child(Avatar::new(profile.avatar(proxy)).size(rems(1.45)))
.tooltip("Manage Encryption Key") .popup_menu(move |this, _window, _cx| {
.icon(IconName::Encryption) this.label(profile.display_name())
.rounded() .menu_with_icon(
.small() "Profile",
.cta() IconName::EmojiFill,
.map(|this| match has_encryption { Box::new(ViewProfile),
true => this.ghost_alt(),
false => this.warning(),
}),
) )
.content(move |window, cx| { .menu_with_icon(
let encryption_panel = encryption_panel.clone(); "Messaging Relays",
IconName::Server,
cx.new(|cx| { Box::new(ViewRelays),
PopoverContent::new(window, cx, move |_window, _cx| { )
if let Some(view) = encryption_panel.upgrade() { .separator()
view.clone().into_any_element() .label(SharedString::from("Keyring Service"))
} else { .menu_with_icon_and_disabled(
div().into_any_element() keyring_label.clone(),
} IconName::Encryption,
}) Box::new(KeyringPopup),
}) !is_using_file_keystore,
}), )
) .separator()
.child( .menu_with_icon("Dark Mode", IconName::Sun, Box::new(DarkMode))
Button::new("user") .menu_with_icon("Themes", IconName::Moon, Box::new(Themes))
.small() .menu_with_icon("Settings", IconName::Settings, Box::new(Settings))
.reverse() .menu_with_icon("Sign Out", IconName::Logout, Box::new(Logout))
.transparent() }),
.icon(IconName::CaretDown)
.child(Avatar::new(profile.avatar(proxy)).size(rems(1.45)))
.popup_menu(move |this, _window, _cx| {
this.label(profile.display_name())
.menu_with_icon(
"Profile",
IconName::EmojiFill,
Box::new(ViewProfile),
)
.menu_with_icon(
"Messaging Relays",
IconName::Server,
Box::new(ViewRelays),
)
.separator()
.label(SharedString::from("Keyring Service"))
.menu_with_icon_and_disabled(
keyring_label.clone(),
IconName::Encryption,
Box::new(KeyringPopup),
!is_using_file_keystore,
)
.separator()
.menu_with_icon(
"Dark Mode",
IconName::Sun,
Box::new(DarkMode),
)
.menu_with_icon("Themes", IconName::Moon, Box::new(Themes))
.menu_with_icon(
"Settings",
IconName::Settings,
Box::new(Settings),
)
.menu_with_icon(
"Sign Out",
IconName::Logout,
Box::new(Logout),
)
}),
),
) )
}) })
} }

View File

@@ -96,12 +96,6 @@ fn main() {
// Initialize settings // Initialize settings
settings::init(cx); settings::init(cx);
// Initialize account state
account::init(cx);
// Initialize encryption state
encryption::init(cx);
// Initialize app registry // Initialize app registry
chat::init(cx); chat::init(cx);

View File

@@ -1,7 +1,6 @@
use std::ops::Range; use std::ops::Range;
use std::time::Duration; use std::time::Duration;
use account::Account;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use chat::{ChatEvent, ChatRegistry, Room, RoomKind}; use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
use common::{DebouncedDelay, RenderedTimestamp, TextUtils, BOOTSTRAP_RELAYS, SEARCH_RELAYS}; use common::{DebouncedDelay, RenderedTimestamp, TextUtils, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
@@ -199,11 +198,9 @@ impl Sidebar {
} }
fn search_by_nip50(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) { fn search_by_nip50(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
let account = Account::global(cx);
let public_key = account.read(cx).public_key();
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key();
let query = query.to_owned(); let query = query.to_owned();
@@ -597,7 +594,7 @@ impl Focusable for Sidebar {
impl Render for Sidebar { impl Render for Sidebar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let loading = chat.read(cx).loading; let loading = chat.read(cx).loading();
// Get rooms from either search results or the chat registry // Get rooms from either search results or the chat registry
let rooms = if let Some(results) = self.search_results.read(cx).as_ref() { let rooms = if let Some(results) = self.search_results.read(cx).as_ref() {

View File

@@ -259,17 +259,11 @@ impl UserProfile {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let gossip = nostr.read(cx).gossip(); let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
cx.background_spawn(async move { cx.background_spawn(async move {
let signer = client.signer().await?; let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let gossip = gossip.read().await;
let write_relays = gossip.inbox_relays(&public_key);
// Ensure connections to the write relays
gossip.ensure_connections(&client, &write_relays).await;
// Sign the new metadata event // Sign the new metadata event
let event = EventBuilder::metadata(&new_metadata).sign(&signer).await?; let event = EventBuilder::metadata(&new_metadata).sign(&signer).await?;

View File

@@ -1,7 +1,6 @@
use std::ops::Range; use std::ops::Range;
use std::time::Duration; use std::time::Duration;
use account::Account;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use chat::{ChatRegistry, Room}; use chat::{ChatRegistry, Room};
use common::{nip05_profile, RenderedProfile, TextUtils, BOOTSTRAP_RELAYS}; use common::{nip05_profile, RenderedProfile, TextUtils, BOOTSTRAP_RELAYS};
@@ -312,9 +311,8 @@ impl Compose {
fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let nostr = NostrRegistry::global(cx);
let account = Account::global(cx); let public_key = nostr.read(cx).identity().read(cx).public_key();
let public_key = account.read(cx).public_key();
let receivers: Vec<PublicKey> = self.selected(cx); let receivers: Vec<PublicKey> = self.selected(cx);
let subject_input = self.title_input.read(cx).value(); let subject_input = self.title_input.read(cx).value();

View File

@@ -158,17 +158,13 @@ impl SetupRelay {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let gossip = nostr.read(cx).gossip(); let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let relays = self.relays.clone(); let relays = self.relays.clone();
let task: Task<Result<(), Error>> = cx.background_spawn(async move { let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let signer = client.signer().await?; let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let gossip = gossip.read().await;
let write_relays = gossip.inbox_relays(&public_key);
// Ensure connections to the write relays
gossip.ensure_connections(&client, &write_relays).await;
let tags: Vec<Tag> = relays let tags: Vec<Tag> = relays
.iter() .iter()

View File

@@ -29,11 +29,16 @@ pub fn init(cre: Credential, window: &mut Window, cx: &mut App) -> Entity<Startu
/// Startup /// Startup
#[derive(Debug)] #[derive(Debug)]
pub struct Startup { pub struct Startup {
credential: Credential,
loading: bool,
name: SharedString, name: SharedString,
focus_handle: FocusHandle, focus_handle: FocusHandle,
/// Local user credentials
credential: Credential,
/// Whether the loadng is in progress
loading: bool,
/// Image cache
image_cache: Entity<RetainAllImageCache>, image_cache: Entity<RetainAllImageCache>,
/// Event subscriptions /// Event subscriptions
@@ -164,15 +169,12 @@ impl Startup {
} }
fn login_with_keys(&mut self, secret: SecretKey, cx: &mut Context<Self>) { fn login_with_keys(&mut self, secret: SecretKey, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let keys = Keys::new(secret); let keys = Keys::new(secret);
let nostr = NostrRegistry::global(cx);
// Update the signer nostr.update(cx, |this, cx| {
cx.background_spawn(async move { this.set_signer(keys, cx);
client.set_signer(keys).await;
}) })
.detach();
} }
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) { fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {

View File

@@ -75,6 +75,11 @@ impl Identity {
self.public_key = None; self.public_key = None;
} }
/// Returns the public key of the identity.
pub fn option_public_key(&self) -> Option<PublicKey> {
self.public_key
}
/// Returns the public key of the identity. /// Returns the public key of the identity.
pub fn public_key(&self) -> PublicKey { pub fn public_key(&self) -> PublicKey {
// This method is safe to unwrap because the public key is always called when the identity is created. // This method is safe to unwrap because the public key is always called when the identity is created.

View File

@@ -286,8 +286,8 @@ impl NostrRegistry {
} }
/// Get current identity /// Get current identity
pub fn identity(&self, cx: &App) -> Identity { pub fn identity(&self) -> Entity<Identity> {
self.identity.read(cx).clone() self.identity.clone()
} }
/// Get a relay hint (messaging relay) for a given public key /// Get a relay hint (messaging relay) for a given public key
@@ -299,9 +299,58 @@ impl NostrRegistry {
.cloned() .cloned()
} }
/// Get a list of write relays for a given public key
pub fn write_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<RelayUrl> {
let client = self.client();
let relays = self.gossip.read(cx).write_relays(public_key);
let async_relays = relays.clone();
// Ensure relay connections
cx.background_spawn(async move {
for url in async_relays.iter() {
client.add_relay(url).await.ok();
client.connect_relay(url).await.ok();
}
})
.detach();
relays
}
/// Get a list of read relays for a given public key
pub fn read_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<RelayUrl> {
let client = self.client();
let relays = self.gossip.read(cx).read_relays(public_key);
let async_relays = relays.clone();
// Ensure relay connections
cx.background_spawn(async move {
for url in async_relays.iter() {
client.add_relay(url).await.ok();
client.connect_relay(url).await.ok();
}
})
.detach();
relays
}
/// Get a list of messaging relays for a given public key /// Get a list of messaging relays for a given public key
pub fn messaging_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<RelayUrl> { pub fn messaging_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<RelayUrl> {
self.gossip.read(cx).messaging_relays(public_key) let client = self.client();
let relays = self.gossip.read(cx).messaging_relays(public_key);
let async_relays = relays.clone();
// Ensure relay connections
cx.background_spawn(async move {
for url in async_relays.iter() {
client.add_relay(url).await.ok();
client.connect_relay(url).await.ok();
}
})
.detach();
relays
} }
/// Set the signer for the nostr client and verify the public key /// Set the signer for the nostr client and verify the public key
@@ -370,7 +419,7 @@ impl NostrRegistry {
fn get_relay_list(&mut self, cx: &mut Context<Self>) { fn get_relay_list(&mut self, cx: &mut Context<Self>) {
let client = self.client(); let client = self.client();
let async_identity = self.identity.downgrade(); let async_identity = self.identity.downgrade();
let public_key = self.identity(cx).public_key(); let public_key = self.identity().read(cx).public_key();
let task: Task<Result<RelayState, Error>> = cx.background_spawn(async move { let task: Task<Result<RelayState, Error>> = cx.background_spawn(async move {
let filter = Filter::new() let filter = Filter::new()
@@ -415,8 +464,8 @@ impl NostrRegistry {
fn get_messaging_relays(&mut self, cx: &mut Context<Self>) { fn get_messaging_relays(&mut self, cx: &mut Context<Self>) {
let client = self.client(); let client = self.client();
let async_identity = self.identity.downgrade(); let async_identity = self.identity.downgrade();
let public_key = self.identity(cx).public_key(); let public_key = self.identity().read(cx).public_key();
let write_relays = self.gossip.read(cx).write_relays(&public_key); let write_relays = self.write_relays(&public_key, cx);
let task: Task<Result<RelayState, Error>> = cx.background_spawn(async move { let task: Task<Result<RelayState, Error>> = cx.background_spawn(async move {
let filter = Filter::new() let filter = Filter::new()
@@ -460,8 +509,8 @@ impl NostrRegistry {
/// Continuously get gift wrap events for the current user in their messaging relays /// Continuously get gift wrap events for the current user in their messaging relays
fn get_messages(&mut self, cx: &mut Context<Self>) { fn get_messages(&mut self, cx: &mut Context<Self>) {
let client = self.client(); let client = self.client();
let public_key = self.identity(cx).public_key(); let public_key = self.identity().read(cx).public_key();
let messaging_relays = self.gossip.read(cx).messaging_relays(&public_key); let messaging_relays = self.messaging_relays(&public_key, cx);
cx.background_spawn(async move { cx.background_spawn(async move {
let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION); let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
@@ -480,7 +529,7 @@ impl NostrRegistry {
/// Publish an event to author's write relays /// Publish an event to author's write relays
pub fn publish(&self, event: Event, cx: &App) -> Task<Result<Output<EventId>, Error>> { pub fn publish(&self, event: Event, cx: &App) -> Task<Result<Output<EventId>, Error>> {
let client = self.client(); let client = self.client();
let write_relays = self.gossip.read(cx).write_relays(&event.pubkey); let write_relays = self.write_relays(&event.pubkey, cx);
cx.background_spawn(async move { Ok(client.send_event_to(&write_relays, &event).await?) }) cx.background_spawn(async move { Ok(client.send_event_to(&write_relays, &event).await?) })
} }
@@ -491,7 +540,7 @@ impl NostrRegistry {
I: Into<Vec<Kind>>, I: Into<Vec<Kind>>,
{ {
let client = self.client(); let client = self.client();
let write_relays = self.gossip.read(cx).write_relays(&author); let write_relays = self.write_relays(&author, cx);
// Construct filters based on event kinds // Construct filters based on event kinds
let filters: Vec<Filter> = kinds let filters: Vec<Filter> = kinds