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", "indexset",
"itertools 0.13.0", "itertools 0.13.0",
"log", "log",
"nostr-connect",
"nostr-sdk", "nostr-sdk",
"oneshot", "oneshot",
"person", "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); let task = cx.read_credentials(MASTER_KEYRING);
cx.spawn(async move |_, cx| { cx.spawn(async move |cx| {
let (keys, new_key) = match task.await { let (keys, new_key) = match task.await {
Ok(Some((_user, secret))) => match SecretKey::from_slice(&secret) { Ok(Some((_user, secret))) => match SecretKey::from_slice(&secret) {
Ok(secret_key) => (Keys::new(secret_key), false), Ok(secret_key) => (Keys::new(secret_key), false),

View File

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

View File

@@ -6,8 +6,8 @@ use gpui::{
AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
Subscription, Task, Window, div, Subscription, Task, Window, div,
}; };
use nostr_sdk::prelude::*; use nostr_connect::prelude::*;
use state::NostrRegistry; use state::{CoopAuthUrlHandler, NostrRegistry, USER_KEYRING};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::input::{Input, InputEvent, InputState}; use ui::input::{Input, InputEvent, InputState};
@@ -66,6 +66,18 @@ impl ImportIdentity {
return; 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) { if let Ok(secret) = SecretKey::parse(&value) {
let keys = Keys::new(secret); let keys = Keys::new(secret);
let nostr = NostrRegistry::global(cx); 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>) fn set_error<S>(&mut self, message: S, cx: &mut Context<Self>)
where where
S: Into<SharedString>, S: Into<SharedString>,
@@ -154,34 +193,40 @@ impl ImportIdentity {
impl Render for ImportIdentity { impl Render for ImportIdentity {
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement { 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() v_flex()
.size_full() .size_full()
.gap_2() .gap_2()
.text_sm()
.child( .child(
v_flex() v_flex()
.gap_1() .gap_1()
.text_sm() .text_sm()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child("nsec or ncryptsec://") .child("Continue with existing key or bunker connection")
.child(Input::new(&self.key_input)), .child(Input::new(&self.key_input)),
) )
.when( .when(require_password, |this| {
self.key_input.read(cx).value().starts_with("ncryptsec1"), this.child(
|this| { v_flex()
this.child( .gap_1()
v_flex() .text_sm()
.gap_1() .text_color(cx.theme().text_muted)
.text_sm() .child("Decrypt Password:")
.text_color(cx.theme().text_muted) .child(Input::new(&self.pass_input)),
.child("Password:") )
.child(Input::new(&self.pass_input)), })
) .when(key_warning, |this| {
}, this.child(
) div()
.child(div().text_xs().text_color(cx.theme().text_muted).child(MSG)) .text_xs()
.text_color(cx.theme().warning_active)
.child(MSG),
)
})
.child( .child(
Button::new("login") Button::new("login")
.label("Continue") .label("Continue")