From dd6b93bd79a0e17996afd772352a54f4a47cb3a3 Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 21 Jan 2026 12:04:23 +0700 Subject: [PATCH] wip --- crates/coop/src/main.rs | 2 - crates/coop/src/views/mod.rs | 1 - crates/coop/src/views/welcome.rs | 26 +++---- crates/state/src/identity.rs | 9 +++ crates/state/src/lib.rs | 123 +++++++++++++++++++++++++++++-- 5 files changed, 139 insertions(+), 22 deletions(-) diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index 903d912..34ddd96 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -12,8 +12,6 @@ use ui::Root; use crate::actions::Quit; mod actions; -mod login; -mod new_identity; mod sidebar; mod user; mod views; diff --git a/crates/coop/src/views/mod.rs b/crates/coop/src/views/mod.rs index 2b146b7..e615adf 100644 --- a/crates/coop/src/views/mod.rs +++ b/crates/coop/src/views/mod.rs @@ -2,5 +2,4 @@ pub mod compose; pub mod preferences; pub mod screening; pub mod setup_relay; -pub mod startup; pub mod welcome; diff --git a/crates/coop/src/views/welcome.rs b/crates/coop/src/views/welcome.rs index ff5aff9..f21f27d 100644 --- a/crates/coop/src/views/welcome.rs +++ b/crates/coop/src/views/welcome.rs @@ -5,28 +5,20 @@ use gpui::{ }; use theme::ActiveTheme; use ui::dock_area::panel::{Panel, PanelEvent}; -use ui::{v_flex, StyledExt}; +use ui::{h_flex, v_flex, StyledExt}; pub fn init(window: &mut Window, cx: &mut App) -> Entity { - Welcome::new(window, cx) + cx.new(|cx| Welcome::new(window, cx)) } pub struct Welcome { name: SharedString, - version: SharedString, focus_handle: FocusHandle, } impl Welcome { - pub fn new(window: &mut Window, cx: &mut App) -> Entity { - cx.new(|cx| Self::view(window, cx)) - } - - fn view(_window: &mut Window, cx: &mut Context) -> Self { - let version = SharedString::from(format!("Version: {}", env!("CARGO_PKG_VERSION"))); - + fn new(_window: &mut Window, cx: &mut App) -> Self { Self { - version, name: "Welcome".into(), focus_handle: cx.focus_handle(), } @@ -39,12 +31,19 @@ impl Panel for Welcome { } fn title(&self, cx: &App) -> AnyElement { - div() + h_flex() + .gap_1p5() .child( svg() .path("brand/coop.svg") .size_4() - .text_color(cx.theme().element_background), + .text_color(cx.theme().text_muted), + ) + .child( + div() + .text_sm() + .text_color(cx.theme().text_muted) + .child(self.name.clone()), ) .into_any_element() } @@ -92,7 +91,6 @@ impl Render for Welcome { .id("version") .text_color(cx.theme().text_placeholder) .text_xs() - .child(self.version.clone()) .on_click(|_, _window, cx| { cx.open_url("https://github.com/lumehq/coop/releases"); }), diff --git a/crates/state/src/identity.rs b/crates/state/src/identity.rs index 8c59c18..418e836 100644 --- a/crates/state/src/identity.rs +++ b/crates/state/src/identity.rs @@ -20,6 +20,9 @@ pub struct Identity { /// The public key of the account pub public_key: Option, + /// Whether the identity is owned by the user + pub owned: bool, + /// Status of the current user NIP-65 relays relay_list: RelayState, @@ -37,6 +40,7 @@ impl Identity { pub fn new() -> Self { Self { public_key: None, + owned: false, relay_list: RelayState::default(), messaging_relays: RelayState::default(), } @@ -83,4 +87,9 @@ impl Identity { pub fn unset_public_key(&mut self) { self.public_key = None; } + + /// Sets whether the identity is owned by the user. + pub fn set_owned(&mut self, owned: bool) { + self.owned = owned; + } } diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 4030f5a..a83e5de 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -1,7 +1,8 @@ use std::collections::HashSet; +use std::os::unix::fs::PermissionsExt; use std::time::Duration; -use anyhow::Error; +use anyhow::{anyhow, Error}; use common::{config_dir, BOOTSTRAP_RELAYS, CLIENT_NAME, SEARCH_RELAYS}; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task}; use nostr_connect::prelude::*; @@ -173,6 +174,14 @@ impl NostrRegistry { }), ); + cx.defer(|cx| { + let nostr = NostrRegistry::global(cx); + + nostr.update(cx, |this, cx| { + this.get_identity(cx); + }); + }); + Self { client, app_keys, @@ -305,9 +314,15 @@ impl NostrRegistry { let keys = Keys::generate(); let secret_key = keys.secret_key(); + // Create directory and write secret key std::fs::create_dir_all(dir.parent().unwrap())?; std::fs::write(&dir, secret_key.to_secret_bytes())?; + // Set permissions to readonly + let mut perms = std::fs::metadata(&dir)?.permissions(); + perms.set_mode(0o400); + std::fs::set_permissions(&dir, perms)?; + return Ok(keys); } }; @@ -390,7 +405,7 @@ impl NostrRegistry { } /// Set the signer for the nostr client and verify the public key - pub fn set_signer(&mut self, signer: T, cx: &mut Context) + pub fn set_signer(&mut self, signer: T, owned: bool, cx: &mut Context) where T: NostrSigner + 'static, { @@ -414,6 +429,7 @@ impl NostrRegistry { Ok(public_key) => { identity.update(cx, |this, cx| { this.set_public_key(public_key); + this.set_owned(owned); cx.notify(); })?; } @@ -598,8 +614,102 @@ impl NostrRegistry { })); } - /// Store a connection for future uses - pub fn persit_connection(&mut self, uri: NostrConnectUri, cx: &mut App) { + /// Get local stored identity + fn get_identity(&mut self, cx: &mut Context) { + let read_credential = cx.read_credentials(CLIENT_NAME); + + self.tasks.push(cx.spawn(async move |this, cx| { + match read_credential.await { + Ok(Some((_, secret))) => { + let secret = SecretKey::from_slice(&secret)?; + let keys = Keys::new(secret); + + this.update(cx, |this, cx| { + this.set_signer(keys, false, cx); + }) + .ok(); + } + _ => { + this.update(cx, |this, cx| { + this.get_bunker(cx); + }) + .ok(); + } + } + + Ok(()) + })); + } + + /// Create a new identity + fn create_identity(&mut self, cx: &mut Context) { + let keys = Keys::generate(); + let write_credential = cx.write_credentials( + CLIENT_NAME, + &keys.public_key().to_hex(), + &keys.secret_key().to_secret_bytes(), + ); + + // Update the signer + self.set_signer(keys, false, cx); + + // Spawn a task to write the credentials + cx.background_spawn(async move { + if let Err(e) = write_credential.await { + log::error!("Failed to write credentials: {}", e); + } + }) + .detach(); + } + + /// Get local stored bunker connection + fn get_bunker(&mut self, cx: &mut Context) { + let client = self.client(); + let app_keys = self.app_keys().clone(); + let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT); + + let task: Task> = cx.background_spawn(async move { + log::info!("Getting bunker connection"); + + let filter = Filter::new() + .kind(Kind::ApplicationSpecificData) + .identifier("coop:account") + .limit(1); + + if let Some(event) = client.database().query(filter).await?.first_owned() { + let uri = NostrConnectUri::parse(event.content)?; + let signer = NostrConnect::new(uri.clone(), app_keys.clone(), timeout, None)?; + + Ok(signer) + } else { + Err(anyhow!("No account found")) + } + }); + + self.tasks.push(cx.spawn(async move |this, cx| { + match task.await { + Ok(signer) => { + this.update(cx, |this, cx| { + this.set_signer(signer, true, cx); + }) + .ok(); + } + Err(e) => { + log::warn!("Failed to get bunker: {e}"); + // Create a new identity if no stored bunker exists + this.update(cx, |this, cx| { + this.create_identity(cx); + }) + .ok(); + } + } + + Ok(()) + })); + } + + /// Store the bunker connection for the next login + pub fn persist_bunker(&mut self, uri: NostrConnectUri, cx: &mut App) { let client = self.client(); let rng_keys = Keys::generate(); @@ -632,7 +742,10 @@ impl NostrRegistry { let uri = NostrConnectUri::client(app_keys.public_key(), vec![relay], CLIENT_NAME); // Generate the nostr connect - let signer = NostrConnect::new(uri.clone(), app_keys.clone(), timeout, None).unwrap(); + let mut signer = NostrConnect::new(uri.clone(), app_keys.clone(), timeout, None).unwrap(); + + // Handle the auth request + signer.auth_url_handler(CoopAuthUrlHandler); (signer, uri) }