re-add nostr connect

This commit is contained in:
2026-07-24 10:42:24 +07:00
parent f1e4428109
commit d79d03b30e
4 changed files with 70 additions and 21 deletions

1
Cargo.lock generated
View File

@@ -9217,6 +9217,7 @@ dependencies = [
"indexset",
"itertools 0.13.0",
"log",
"nostr-connect",
"nostr-sdk",
"oneshot",
"person",

View File

@@ -282,10 +282,11 @@ impl NostrRegistry {
}));
}
fn get_master_key(&mut self, cx: &mut Context<Self>) -> Task<Keys> {
/// Get the master key that used for Nostr Connect
pub fn get_master_key(&self, cx: &App) -> Task<Keys> {
let task = cx.read_credentials(MASTER_KEYRING);
cx.spawn(async move |_, cx| {
cx.spawn(async move |cx| {
let (keys, new_key) = match task.await {
Ok(Some((_user, secret))) => match SecretKey::from_slice(&secret) {
Ok(secret_key) => (Keys::new(secret_key), false),

View File

@@ -18,6 +18,8 @@ person = { path = "../person" }
gpui.workspace = true
nostr-sdk.workspace = true
nostr-connect.workspace = true
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -6,8 +6,8 @@ use gpui::{
AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
Subscription, Task, Window, div,
};
use nostr_sdk::prelude::*;
use state::NostrRegistry;
use nostr_connect::prelude::*;
use state::{CoopAuthUrlHandler, NostrRegistry, USER_KEYRING};
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::input::{Input, InputEvent, InputState};
@@ -66,6 +66,18 @@ impl ImportIdentity {
return;
}
if value.starts_with("bunker://") {
match NostrConnectUri::parse(value) {
Ok(uri) => {
self.bunker(uri, window, cx);
}
Err(e) => {
self.set_error(e.to_string(), cx);
}
}
return;
}
if let Ok(secret) = SecretKey::parse(&value) {
let keys = Keys::new(secret);
let nostr = NostrRegistry::global(cx);
@@ -126,6 +138,33 @@ impl ImportIdentity {
}));
}
fn bunker(&mut self, uri: NostrConnectUri, window: &mut Window, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let master_keys = nostr.read(cx).get_master_key(cx);
let password = uri.to_string();
let save = cx.write_credentials(USER_KEYRING, "bunker", password.as_bytes());
cx.spawn_in(window, async move |_this, cx| {
let keys = master_keys.await;
let timeout = Duration::from_secs(30);
// Construct the nostr connect signer
let mut signer = NostrConnect::new(uri, keys, timeout, None)?;
// Handle auth url with the default browser
signer.auth_url_handler(CoopAuthUrlHandler);
nostr.update(cx, |this, cx| {
this.set_signer(signer, cx);
cx.background_spawn(async move { save.await.ok() }).detach();
cx.notify();
});
Ok::<(), anyhow::Error>(())
})
.detach();
}
fn set_error<S>(&mut self, message: S, cx: &mut Context<Self>)
where
S: Into<SharedString>,
@@ -154,34 +193,40 @@ impl ImportIdentity {
impl Render for ImportIdentity {
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
const MSG: &str = "Coop isn't stored your identity secret in local device. Everything will be reset on the next login.";
const MSG: &str = "Coop won't stored your identity key on the local device. Use Nostr Connect for persistent login.";
let require_password = self.key_input.read(cx).value().starts_with("ncryptsec1");
let key_warning = self.key_input.read(cx).value().starts_with("nsec1") || require_password;
v_flex()
.size_full()
.gap_2()
.text_sm()
.child(
v_flex()
.gap_1()
.text_sm()
.text_color(cx.theme().text_muted)
.child("nsec or ncryptsec://")
.child("Continue with existing key or bunker connection")
.child(Input::new(&self.key_input)),
)
.when(
self.key_input.read(cx).value().starts_with("ncryptsec1"),
|this| {
.when(require_password, |this| {
this.child(
v_flex()
.gap_1()
.text_sm()
.text_color(cx.theme().text_muted)
.child("Password:")
.child("Decrypt Password:")
.child(Input::new(&self.pass_input)),
)
},
})
.when(key_warning, |this| {
this.child(
div()
.text_xs()
.text_color(cx.theme().warning_active)
.child(MSG),
)
.child(div().text_xs().text_color(cx.theme().text_muted).child(MSG))
})
.child(
Button::new("login")
.label("Continue")