chore: rewrite the backend (not tested) (#203)

* wip: refactor

* refactor

* clean up

* .

* rename

* add relay auth

* .

* .

* optimize

* .

* clean up

* add encryption crate

* .

* .

* .

* .

* .

* add encryption crate

* .

* refactor nip4e

* .

* fix endless loop

* fix metadata fetching
This commit is contained in:
reya
2025-11-11 09:09:33 +07:00
committed by GitHub
parent a1a0a7ecd4
commit 512834b640
68 changed files with 3503 additions and 3194 deletions

View File

@@ -32,14 +32,17 @@ ui = { path = "../ui" }
title_bar = { path = "../title_bar" }
theme = { path = "../theme" }
common = { path = "../common" }
states = { path = "../states" }
state = { path = "../state" }
key_store = { path = "../key_store" }
chat = { path = "../chat" }
chat_ui = { path = "../chat_ui" }
settings = { path = "../settings" }
auto_update = { path = "../auto_update" }
account = { path = "../account" }
encryption = { path = "../encryption" }
encryption_ui = { path = "../encryption_ui" }
person = { path = "../person" }
relay_auth = { path = "../relay_auth" }
rust-i18n.workspace = true
i18n.workspace = true
@@ -49,19 +52,16 @@ reqwest_client.workspace = true
nostr-connect.workspace = true
nostr-sdk.workspace = true
nostr.workspace = true
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
itertools.workspace = true
dirs.workspace = true
log.workspace = true
smallvec.workspace = true
smol.workspace = true
futures.workspace = true
oneshot.workspace = true
flume.workspace = true
webbrowser.workspace = true
indexset = "0.12.3"

View File

@@ -1,12 +1,11 @@
use std::sync::Mutex;
use gpui::{actions, App};
use key_store::backend::KeyItem;
use key_store::KeyStore;
use key_store::{KeyItem, KeyStore};
use nostr_connect::prelude::*;
use states::app_state;
use state::NostrRegistry;
actions!(coop, [ReloadMetadata, DarkMode, Settings, Logout, Quit]);
actions!(coop, [KeyringPopup, DarkMode, Settings, Logout, Quit]);
actions!(sidebar, [Reload, RelayStatus]);
#[derive(Debug, Clone)]
@@ -49,10 +48,9 @@ pub fn load_embedded_fonts(cx: &App) {
pub fn reset(cx: &mut App) {
let backend = KeyStore::global(cx).read(cx).backend();
let client = NostrRegistry::global(cx).read(cx).client();
cx.spawn(async move |cx| {
let client = app_state().client();
// Remove the signer
client.unset_signer().await;

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
use std::sync::Arc;
use assets::Assets;
use common::{APP_ID, CLIENT_NAME};
use gpui::{
point, px, size, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, SharedString,
TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
WindowOptions,
};
use states::{app_state, APP_ID, BOOTSTRAP_RELAYS, CLIENT_NAME, SEARCH_RELAYS};
use ui::Root;
use crate::actions::{load_embedded_fonts, quit, Quit};
@@ -26,29 +26,6 @@ fn main() {
.with_assets(Assets)
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()));
// Initialize app state
let app_state = app_state();
// Connect to relays
app.background_executor()
.spawn(async move {
let client = app_state.client();
// Get all bootstrapping relays
let mut urls = vec![];
urls.extend(BOOTSTRAP_RELAYS);
urls.extend(SEARCH_RELAYS);
// Add relay to the relay pool
for url in urls.into_iter() {
client.add_relay(url).await.ok();
}
// Establish connection to relays
client.connect().await;
})
.detach();
// Run application
app.run(move |cx| {
// Load embedded fonts in assets/fonts
@@ -102,18 +79,30 @@ fn main() {
// Initialize components
ui::init(cx);
// Initialize app registry
chat::init(cx);
// Initialize backend for keys storage
key_store::init(cx);
// Initialize the nostr client
state::init(cx);
// Initialize person registry
person::init(cx);
// Initialize backend for keys storage
key_store::init(cx);
// Initialize settings
settings::init(cx);
// Initialize account state
account::init(cx);
// Initialize encryption state
encryption::init(cx);
// Initialize app registry
chat::init(cx);
// Initialize relay auth registry
relay_auth::init(window, cx);
// Initialize auto update
auto_update::init(cx);

View File

@@ -1,7 +1,7 @@
use std::fs;
use std::time::Duration;
use dirs::document_dir;
use common::home_dir;
use gpui::{
div, AppContext, ClipboardItem, Context, Entity, Flatten, IntoElement, ParentElement, Render,
SharedString, Styled, Task, Window,
@@ -46,9 +46,8 @@ impl BackupKeys {
}
pub fn backup(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<Task<()>> {
let document_dir = document_dir().expect("Failed to get document directory");
let path = cx.prompt_for_new_path(&document_dir, Some("My Nostr Account"));
let dir = home_dir();
let path = cx.prompt_for_new_path(dir, Some("My Nostr Account"));
let nsec = self.secret_input.read(cx).value().to_string();
Some(cx.spawn_in(window, async move |this, cx| {

View File

@@ -1,11 +1,10 @@
use std::ops::Range;
use std::time::Duration;
use account::Account;
use anyhow::{anyhow, Error};
use chat::room::Room;
use chat::ChatRegistry;
use common::display::{RenderedProfile, TextUtils};
use common::nip05::nip05_profile;
use chat::{ChatRegistry, Room};
use common::{nip05_profile, RenderedProfile, TextUtils, BOOTSTRAP_RELAYS};
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, relative, rems, uniform_list, App, AppContext, Context, Entity, InteractiveElement,
@@ -18,7 +17,7 @@ use nostr_sdk::prelude::*;
use person::PersonRegistry;
use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use states::{app_state, BOOTSTRAP_RELAYS};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
@@ -115,7 +114,10 @@ pub struct Compose {
}
impl Compose {
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 client = nostr.read(cx).client();
let contacts = cx.new(|_| vec![]);
let error_message = cx.new(|_| None);
@@ -129,7 +131,6 @@ impl Compose {
let mut tasks = smallvec![];
let get_contacts: Task<Result<Vec<Contact>, Error>> = cx.background_spawn(async move {
let client = app_state().client();
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let profiles = client.database().contacts(public_key).await?;
@@ -194,10 +195,7 @@ impl Compose {
}
}
async fn request_metadata(public_key: PublicKey) -> Result<(), Error> {
let states = app_state();
let client = states.client();
async fn request_metadata(client: &Client, public_key: PublicKey) -> Result<(), Error> {
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
@@ -220,11 +218,13 @@ impl Compose {
}
fn push_contact(&mut self, contact: Contact, window: &mut Window, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let pk = contact.public_key;
if !self.contacts.read(cx).iter().any(|c| c.public_key == pk) {
self._tasks.push(cx.background_spawn(async move {
Self::request_metadata(pk).await.ok();
Self::request_metadata(&client, pk).await.ok();
}));
cx.defer_in(window, |this, window, cx| {
@@ -313,6 +313,10 @@ impl Compose {
fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let chat = ChatRegistry::global(cx);
let account = Account::global(cx);
let public_key = account.read(cx).public_key();
let receivers: Vec<PublicKey> = self.selected(cx);
let subject_input = self.title_input.read(cx).value();
let subject = (!subject_input.is_empty()).then(|| subject_input.to_string());
@@ -322,25 +326,11 @@ impl Compose {
return;
};
cx.spawn_in(window, async move |this, cx| {
let result = Room::new(subject, receivers).await;
chat.update(cx, |this, cx| {
this.push_room(cx.new(|_| Room::new(subject, public_key, receivers)), cx);
});
this.update_in(cx, |this, window, cx| {
match result {
Ok(room) => {
chat.update(cx, |this, cx| {
this.push_room(cx.new(|_| room), cx);
});
window.close_modal(cx);
}
Err(e) => {
this.set_error(e.to_string(), cx);
}
};
})
.ok();
})
.detach();
window.close_modal(cx);
}
fn set_error(&mut self, error: impl Into<SharedString>, cx: &mut Context<Self>) {

View File

@@ -2,7 +2,7 @@ use std::str::FromStr;
use std::time::Duration;
use anyhow::Error;
use common::nip96::nip96_upload;
use common::nip96_upload;
use gpui::prelude::FluentBuilder;
use gpui::{
div, img, App, AppContext, Context, Entity, Flatten, IntoElement, ParentElement,
@@ -12,7 +12,7 @@ use i18n::{shared_t, t};
use nostr_sdk::prelude::*;
use settings::AppSettings;
use smol::fs;
use states::app_state;
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::input::{InputState, TextInput};
@@ -34,12 +34,18 @@ pub struct EditProfile {
impl EditProfile {
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let name_input =
cx.new(|cx| InputState::new(window, cx).placeholder(t!("profile.placeholder_name")));
let avatar_input =
cx.new(|cx| InputState::new(window, cx).placeholder("https://example.com/avatar.jpg"));
let website_input =
cx.new(|cx| InputState::new(window, cx).placeholder("https://your-website.com"));
let bio_input = cx.new(|cx| {
InputState::new(window, cx)
.multi_line()
@@ -58,7 +64,6 @@ impl EditProfile {
};
let task: Task<Result<Option<Metadata>, Error>> = cx.background_spawn(async move {
let client = app_state().client();
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let metadata = client
@@ -104,8 +109,12 @@ impl EditProfile {
}
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let nip96 = AppSettings::get_media_server(cx);
let avatar_input = self.avatar_input.downgrade();
let paths = cx.prompt_for_paths(PathPromptOptions {
files: true,
directories: false,
@@ -125,9 +134,7 @@ impl EditProfile {
let (tx, rx) = oneshot::channel::<Url>();
nostr_sdk::async_utility::task::spawn(async move {
if let Ok(url) =
nip96_upload(app_state().client(), &nip96, file_data).await
{
if let Ok(url) = nip96_upload(&client, &nip96, file_data).await {
_ = tx.send(url);
}
});
@@ -168,6 +175,9 @@ impl EditProfile {
}
pub fn set_metadata(&mut self, cx: &mut Context<Self>) -> Task<Result<Profile, Error>> {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let avatar = self.avatar_input.read(cx).value().to_string();
let name = self.name_input.read(cx).value().to_string();
let bio = self.bio_input.read(cx).value().to_string();
@@ -190,7 +200,6 @@ impl EditProfile {
}
cx.background_spawn(async move {
let client = app_state().client();
let signer = client.signer().await?;
// Sign the new metadata event

View File

@@ -1,17 +1,17 @@
use std::time::Duration;
use anyhow::anyhow;
use common::BUNKER_TIMEOUT;
use gpui::prelude::FluentBuilder;
use gpui::{
div, relative, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Window,
};
use i18n::{shared_t, t};
use key_store::backend::KeyItem;
use key_store::KeyStore;
use key_store::{KeyItem, KeyStore};
use nostr_connect::prelude::*;
use smallvec::{smallvec, SmallVec};
use states::{app_state, BUNKER_TIMEOUT};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent};
@@ -213,8 +213,10 @@ impl Login {
}
fn connect(&mut self, signer: NostrConnect, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
cx.background_spawn(async move {
let client = app_state().client();
client.set_signer(signer).await;
})
.detach();
@@ -262,6 +264,10 @@ impl Login {
pub fn login_with_keys(&mut self, keys: Keys, cx: &mut Context<Self>) {
let keystore = KeyStore::global(cx).read(cx).backend();
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let username = keys.public_key().to_hex();
let secret = keys.secret_key().to_secret_hex().into_bytes();
@@ -281,7 +287,6 @@ impl Login {
// Update the signer
cx.background_spawn(async move {
let client = app_state().client();
client.set_signer(keys).await;
})
.detach();

View File

@@ -1,4 +1,3 @@
pub mod account;
pub mod backup_keys;
pub mod compose;
pub mod edit_profile;
@@ -9,5 +8,6 @@ pub mod preferences;
pub mod screening;
pub mod setup_relay;
pub mod sidebar;
pub mod startup;
pub mod user_profile;
pub mod welcome;

View File

@@ -1,5 +1,5 @@
use anyhow::{anyhow, Error};
use common::nip96::nip96_upload;
use common::{default_nip17_relays, default_nip65_relays, nip96_upload, BOOTSTRAP_RELAYS};
use gpui::{
div, relative, rems, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity,
EventEmitter, Flatten, FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions,
@@ -7,12 +7,11 @@ use gpui::{
};
use gpui_tokio::Tokio;
use i18n::{shared_t, t};
use key_store::backend::KeyItem;
use key_store::KeyStore;
use key_store::{KeyItem, KeyStore};
use nostr_sdk::prelude::*;
use settings::AppSettings;
use smol::fs;
use states::{app_state, default_nip17_relays, default_nip65_relays, BOOTSTRAP_RELAYS};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
@@ -106,6 +105,9 @@ impl NewAccount {
pub fn set_signer(&mut self, cx: &mut Context<Self>) {
let keystore = KeyStore::global(cx).read(cx).backend();
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let keys = self.temp_keys.read(cx).clone();
let username = keys.public_key().to_hex();
let secret = keys.secret_key().to_secret_hex().into_bytes();
@@ -130,8 +132,6 @@ impl NewAccount {
// Update the signer
// Set the client's signer with the current keys
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let client = app_state().client();
// Set the client's signer with the current keys
client.set_signer(keys).await;
@@ -178,6 +178,9 @@ impl NewAccount {
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.uploading(true, cx);
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
// Get the user's configured NIP96 server
let nip96_server = AppSettings::get_media_server(cx);
@@ -194,7 +197,7 @@ impl NewAccount {
Ok(Some(mut paths)) => {
if let Some(path) = paths.pop() {
let file = fs::read(path).await?;
let url = nip96_upload(app_state().client(), &nip96_server, file).await?;
let url = nip96_upload(&client, &nip96_server, file).await?;
Ok(url)
} else {

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use std::time::Duration;
use common::display::TextUtils;
use common::{TextUtils, CLIENT_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT};
use gpui::prelude::FluentBuilder;
use gpui::{
div, img, px, relative, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter,
@@ -9,11 +9,10 @@ use gpui::{
SharedString, StatefulInteractiveElement, Styled, Task, Window,
};
use i18n::{shared_t, t};
use key_store::backend::KeyItem;
use key_store::KeyStore;
use key_store::{KeyItem, KeyStore};
use nostr_connect::prelude::*;
use smallvec::{smallvec, SmallVec};
use states::{app_state, CLIENT_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent};
@@ -165,8 +164,10 @@ impl Onboarding {
}
fn connect(&mut self, signer: NostrConnect, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
cx.background_spawn(async move {
let client = app_state().client();
client.set_signer(signer).await;
})
.detach();
@@ -223,7 +224,7 @@ impl Focusable for Onboarding {
}
impl Render for Onboarding {
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.size_full()
.child(

View File

@@ -1,5 +1,5 @@
use account::Account;
use common::display::RenderedProfile;
use common::RenderedProfile;
use gpui::http_client::Url;
use gpui::{
div, px, relative, rems, App, AppContext, Context, Entity, InteractiveElement, IntoElement,

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use std::time::Duration;
use common::display::{shorten_pubkey, RenderedProfile, RenderedTimestamp};
use common::nip05::nip05_verify;
use common::{nip05_verify, shorten_pubkey, RenderedProfile, RenderedTimestamp, BOOTSTRAP_RELAYS};
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity,
@@ -13,7 +13,7 @@ use nostr_sdk::prelude::*;
use person::PersonRegistry;
use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use states::{app_state, BOOTSTRAP_RELAYS};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
@@ -35,14 +35,17 @@ pub struct Screening {
impl Screening {
pub fn new(public_key: PublicKey, window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get_person(&public_key, cx);
let mut tasks = smallvec![];
let contact_check: Task<Result<(bool, Vec<Profile>), Error>> =
cx.background_spawn(async move {
let client = app_state().client();
let contact_check: Task<Result<(bool, Vec<Profile>), Error>> = cx.background_spawn({
let client = Arc::clone(&client);
async move {
let signer = client.signer().await?;
let signer_pubkey = signer.get_public_key().await?;
@@ -64,10 +67,10 @@ impl Screening {
}
Ok((followed, mutual_contacts))
});
}
});
let activity_check = cx.background_spawn(async move {
let client = app_state().client();
let filter = Filter::new().author(public_key).limit(1);
let mut activity: Option<Timestamp> = None;
@@ -153,12 +156,12 @@ impl Screening {
}
fn report(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let public_key = self.profile.public_key();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let client = app_state().client();
let signer = client.signer().await?;
let tag = Tag::public_key_report(public_key, Report::Impersonation);
let event = EventBuilder::report(vec![tag], "").sign(&signer).await?;

View File

@@ -4,15 +4,14 @@ use std::time::Duration;
use anyhow::{anyhow, Error};
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, uniform_list, App, AppContext, AsyncWindowContext, Context, Entity,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
Task, TextAlign, UniformList, Window,
div, px, uniform_list, App, AppContext, Context, Entity, InteractiveElement, IntoElement,
ParentElement, Render, SharedString, Styled, Subscription, Task, TextAlign, UniformList,
Window,
};
use i18n::{shared_t, t};
use itertools::Itertools;
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use states::app_state;
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::input::{InputEvent, InputState, TextInput};
@@ -39,6 +38,9 @@ pub struct SetupRelay {
impl SetupRelay {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let input = cx.new(|cx| InputState::new(window, cx).placeholder("wss://example.com"));
let mut subscriptions = smallvec![];
@@ -47,7 +49,11 @@ impl SetupRelay {
tasks.push(
// Load user's relays in the local database
cx.spawn_in(window, async move |this, cx| {
if let Ok(relays) = Self::load(cx).await {
let result = cx
.background_spawn(async move { Self::load(&client).await })
.await;
if let Ok(relays) = result {
this.update(cx, |this, cx| {
this.relays.extend(relays);
cx.notify();
@@ -79,24 +85,21 @@ impl SetupRelay {
}
}
fn load(cx: &AsyncWindowContext) -> Task<Result<Vec<RelayUrl>, Error>> {
cx.background_spawn(async move {
let client = app_state().client();
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
async fn load(client: &Client) -> Result<Vec<RelayUrl>, Error> {
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
if let Some(event) = client.database().query(filter).await?.first_owned() {
let urls = nip17::extract_owned_relay_list(event).collect();
Ok(urls)
} else {
Err(anyhow!("Not found."))
}
})
if let Some(event) = client.database().query(filter).await?.first_owned() {
let urls = nip17::extract_owned_relay_list(event).collect();
Ok(urls)
} else {
Err(anyhow!("Not found."))
}
}
fn add(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -150,13 +153,12 @@ impl SetupRelay {
return;
};
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let relays = self.relays.clone();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let states = app_state();
let client = states.client();
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let tags: Vec<Tag> = relays
.iter()
@@ -177,12 +179,6 @@ impl SetupRelay {
client.connect_relay(relay).await.ok();
}
// Fetch gift wrap events
states
.get_messages(public_key, &relays.into_iter().collect_vec())
.await
.ok();
Ok(())
});

View File

@@ -1,12 +1,11 @@
use std::rc::Rc;
use chat::room::RoomKind;
use chat::ChatRegistry;
use chat::{ChatRegistry, RoomKind};
use chat_ui::{CopyPublicKey, OpenPublicKey};
use gpui::prelude::FluentBuilder;
use gpui::{
div, rems, App, ClickEvent, InteractiveElement, IntoElement, ParentElement as _, RenderOnce,
SharedString, SharedUri, StatefulInteractiveElement, Styled, Window,
SharedString, StatefulInteractiveElement, Styled, Window,
};
use i18n::t;
use nostr_sdk::prelude::*;
@@ -26,7 +25,7 @@ pub struct RoomListItem {
room_id: Option<u64>,
public_key: Option<PublicKey>,
name: Option<SharedString>,
avatar: Option<SharedUri>,
avatar: Option<SharedString>,
created_at: Option<SharedString>,
kind: Option<RoomKind>,
#[allow(clippy::type_complexity)]
@@ -62,7 +61,7 @@ impl RoomListItem {
self
}
pub fn avatar(mut self, avatar: impl Into<SharedUri>) -> Self {
pub fn avatar(mut self, avatar: impl Into<SharedString>) -> Self {
self.avatar = Some(avatar.into());
self
}

View File

@@ -3,10 +3,8 @@ use std::ops::Range;
use std::time::Duration;
use anyhow::{anyhow, Error};
use chat::room::{Room, RoomKind};
use chat::{ChatEvent, ChatRegistry};
use common::debounced_delay::DebouncedDelay;
use common::display::{RenderedTimestamp, TextUtils};
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
use common::{DebouncedDelay, RenderedTimestamp, TextUtils, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
use gpui::prelude::FluentBuilder;
use gpui::{
deferred, div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity,
@@ -20,7 +18,7 @@ use list_item::RoomListItem;
use nostr_sdk::prelude::*;
use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use states::{app_state, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent};
@@ -137,8 +135,7 @@ impl Sidebar {
}
}
async fn request_metadata(public_key: PublicKey) -> Result<(), Error> {
let client = app_state().client();
async fn request_metadata(client: &Client, public_key: PublicKey) -> Result<(), Error> {
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
@@ -152,18 +149,7 @@ impl Sidebar {
Ok(())
}
async fn create_temp_room(receiver: PublicKey) -> Result<Room, Error> {
// Request to get user's metadata
Self::request_metadata(receiver).await?;
// Create a temporary room
let room = Room::new(None, vec![receiver]).await?;
Ok(room)
}
async fn nip50(query: &str) -> Result<BTreeSet<Room>, Error> {
let client = app_state().client();
async fn nip50(client: &Client, query: &str) -> Result<BTreeSet<Room>, Error> {
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
@@ -186,10 +172,13 @@ impl Sidebar {
continue;
}
// Return a temporary room
if let Ok(room) = Self::create_temp_room(event.pubkey).await {
rooms.insert(room);
}
// Request metadata event's author
Self::request_metadata(client, event.pubkey).await?;
// Construct room
let room = Room::new(None, public_key, vec![event.pubkey]);
rooms.insert(room);
}
}
@@ -212,11 +201,13 @@ impl Sidebar {
window: &mut Window,
cx: &mut Context<Self>,
) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let query = query.to_owned();
let query_cloned = query.clone();
let task = smol::future::or(
Tokio::spawn(cx, async move { Self::nip50(&query).await.ok() }),
Tokio::spawn(cx, async move { Self::nip50(&client, &query).await.ok() }),
Tokio::spawn(cx, async move {
let _ = rx.recv().await.is_ok();
None
@@ -263,13 +254,20 @@ impl Sidebar {
}
fn search_by_nip05(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let address = query.to_owned();
let task = Tokio::spawn(cx, async move {
if let Ok(profile) = common::nip05::nip05_profile(&address).await {
Self::create_temp_room(profile.public_key).await
} else {
Err(anyhow!(t!("sidebar.addr_error")))
match common::nip05_profile(&address).await {
Ok(profile) => {
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let room = Room::new(None, public_key, vec![profile.public_key]);
Ok(room)
}
Err(e) => Err(anyhow!(e)),
}
});
@@ -310,6 +308,9 @@ impl Sidebar {
}
fn search_by_pubkey(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let Ok(public_key) = query.to_public_key() else {
window.push_notification(t!("common.pubkey_invalid"), cx);
self.set_finding(false, window, cx);
@@ -317,8 +318,13 @@ impl Sidebar {
};
let task: Task<Result<Room, Error>> = cx.background_spawn(async move {
// Create a gift wrap event to represent as room
Self::create_temp_room(public_key).await
let signer = client.signer().await?;
let author = signer.get_public_key().await?;
let room = Room::new(None, author, vec![public_key]);
Self::request_metadata(&client, public_key).await?;
Ok(room)
});
cx.spawn_in(window, async move |this, cx| {
@@ -521,14 +527,16 @@ impl Sidebar {
fn on_reload(&mut self, _ev: &Reload, window: &mut Window, cx: &mut Context<Self>) {
ChatRegistry::global(cx).update(cx, |this, cx| {
this.load_rooms(window, cx);
this.get_rooms(cx);
});
window.push_notification(t!("common.refreshed"), cx);
}
fn on_manage(&mut self, _ev: &RelayStatus, window: &mut Window, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let task: Task<Result<Vec<Relay>, Error>> = cx.background_spawn(async move {
let client = app_state().client();
let subscription = client.subscription(&SubscriptionId::new("inbox")).await;
let mut relays: Vec<Relay> = vec![];

View File

@@ -1,6 +1,6 @@
use std::time::Duration;
use common::display::RenderedProfile;
use common::{RenderedProfile, BUNKER_TIMEOUT};
use gpui::prelude::FluentBuilder;
use gpui::{
div, relative, rems, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter,
@@ -9,12 +9,11 @@ use gpui::{
Window,
};
use i18n::{shared_t, t};
use key_store::backend::KeyItem;
use key_store::KeyStore;
use key_store::{Credential, KeyItem, KeyStore};
use nostr_connect::prelude::*;
use person::PersonRegistry;
use smallvec::{smallvec, SmallVec};
use states::{app_state, BUNKER_TIMEOUT};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
@@ -24,18 +23,14 @@ use ui::{h_flex, v_flex, ContextModal, Sizable, StyledExt};
use crate::actions::{reset, CoopAuthUrlHandler};
pub fn init(
public_key: PublicKey,
secret: String,
window: &mut Window,
cx: &mut App,
) -> Entity<Account> {
cx.new(|cx| Account::new(public_key, secret, window, cx))
pub fn init(cre: Credential, window: &mut Window, cx: &mut App) -> Entity<Startup> {
cx.new(|cx| Startup::new(cre, window, cx))
}
pub struct Account {
public_key: PublicKey,
secret: String,
/// Startup
#[derive(Debug)]
pub struct Startup {
credential: Credential,
loading: bool,
name: SharedString,
@@ -49,13 +44,8 @@ pub struct Account {
_tasks: SmallVec<[Task<()>; 1]>,
}
impl Account {
fn new(
public_key: PublicKey,
secret: String,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
impl Startup {
fn new(credential: Credential, window: &mut Window, cx: &mut Context<Self>) -> Self {
let tasks = smallvec![];
let mut subscriptions = smallvec![];
@@ -69,8 +59,7 @@ impl Account {
);
Self {
public_key,
secret,
credential,
loading: false,
name: "Account".into(),
focus_handle: cx.focus_handle(),
@@ -83,9 +72,11 @@ impl Account {
fn login(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.set_loading(true, cx);
let secret = self.credential.secret();
// Try to login with bunker
if self.secret.starts_with("bunker://") {
match NostrConnectURI::parse(&self.secret) {
if secret.starts_with("bunker://") {
match NostrConnectURI::parse(secret) {
Ok(uri) => {
self.login_with_bunker(uri, window, cx);
}
@@ -98,7 +89,7 @@ impl Account {
};
// Fall back to login with keys
match SecretKey::parse(&self.secret) {
match SecretKey::parse(secret) {
Ok(secret) => {
self.login_with_keys(secret, cx);
}
@@ -115,6 +106,8 @@ impl Account {
window: &mut Window,
cx: &mut Context<Self>,
) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let keystore = KeyStore::global(cx).read(cx).backend();
// Handle connection in the background
@@ -138,8 +131,6 @@ impl Account {
this._tasks.push(
// Handle connection in the background
cx.spawn_in(window, async move |this, cx| {
let client = app_state().client();
match signer.bunker_uri().await {
Ok(_) => {
client.set_signer(signer).await;
@@ -171,11 +162,12 @@ impl Account {
}
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);
// Update the signer
cx.background_spawn(async move {
let client = app_state().client();
client.set_signer(keys).await;
})
.detach();
@@ -187,7 +179,7 @@ impl Account {
}
}
impl Panel for Account {
impl Panel for Startup {
fn panel_id(&self) -> SharedString {
self.name.clone()
}
@@ -197,19 +189,21 @@ impl Panel for Account {
}
}
impl EventEmitter<PanelEvent> for Account {}
impl EventEmitter<PanelEvent> for Startup {}
impl Focusable for Account {
impl Focusable for Startup {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Account {
impl Render for Startup {
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get_person(&self.public_key, cx);
let bunker = self.secret.starts_with("bunker://");
let bunker = self.credential.secret().starts_with("bunker://");
let profile = persons
.read(cx)
.get_person(&self.credential.public_key(), cx);
v_flex()
.image_cache(self.image_cache.clone())

View File

@@ -1,7 +1,6 @@
use std::time::Duration;
use common::display::RenderedProfile;
use common::nip05::nip05_verify;
use common::{nip05_verify, RenderedProfile};
use gpui::prelude::FluentBuilder;
use gpui::{
div, relative, rems, App, AppContext, ClipboardItem, Context, Entity, IntoElement,
@@ -13,7 +12,7 @@ use nostr_sdk::prelude::*;
use person::PersonRegistry;
use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use states::app_state;
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
@@ -33,13 +32,15 @@ pub struct UserProfile {
impl UserProfile {
pub fn new(target: PublicKey, window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get_person(&target, cx);
let mut tasks = smallvec![];
let check_follow: Task<Result<bool, Error>> = cx.background_spawn(async move {
let client = app_state().client();
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let contact_list = client.database().contacts_public_keys(public_key).await?;