wip
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m27s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m33s

This commit is contained in:
2026-01-21 12:04:23 +07:00
parent 311af51bee
commit dd6b93bd79
5 changed files with 139 additions and 22 deletions

View File

@@ -12,8 +12,6 @@ use ui::Root;
use crate::actions::Quit;
mod actions;
mod login;
mod new_identity;
mod sidebar;
mod user;
mod views;

View File

@@ -2,5 +2,4 @@ pub mod compose;
pub mod preferences;
pub mod screening;
pub mod setup_relay;
pub mod startup;
pub mod welcome;

View File

@@ -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> {
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<Self> {
cx.new(|cx| Self::view(window, cx))
}
fn view(_window: &mut Window, cx: &mut Context<Self>) -> 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");
}),

View File

@@ -20,6 +20,9 @@ pub struct Identity {
/// The public key of the account
pub public_key: Option<PublicKey>,
/// 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;
}
}

View File

@@ -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<T>(&mut self, signer: T, cx: &mut Context<Self>)
pub fn set_signer<T>(&mut self, signer: T, owned: bool, cx: &mut Context<Self>)
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<Self>) {
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<Self>) {
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<Self>) {
let client = self.client();
let app_keys = self.app_keys().clone();
let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT);
let task: Task<Result<NostrConnect, Error>> = 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)
}