.
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m26s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m22s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled

This commit is contained in:
2026-03-02 06:20:10 +07:00
parent f8e6b3ff7a
commit 703c4923ca
18 changed files with 767 additions and 641 deletions

View File

@@ -25,7 +25,7 @@ pub const FIND_LIMIT: usize = 20;
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
/// Default Nostr Connect relay
pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nip46.com";
/// Default subscription id for device gift wrap events
pub const DEVICE_GIFTWRAP: &str = "device-gift-wraps";

View File

@@ -5,7 +5,7 @@ use std::time::Duration;
use anyhow::{anyhow, Context as AnyhowContext, Error};
use common::config_dir;
use gpui::{App, AppContext, Context, Entity, Global, SharedString, Task, Window};
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, SharedString, Task, Window};
use nostr_connect::prelude::*;
use nostr_lmdb::prelude::*;
use nostr_sdk::prelude::*;
@@ -42,6 +42,16 @@ struct GlobalNostrRegistry(Entity<NostrRegistry>);
impl Global for GlobalNostrRegistry {}
/// Signer event.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum SignerEvent {
/// A new signer has been set
Set,
/// An error occurred
Error(String),
}
/// Nostr Registry
#[derive(Debug)]
pub struct NostrRegistry {
@@ -54,22 +64,22 @@ pub struct NostrRegistry {
/// Local public keys
npubs: Entity<Vec<PublicKey>>,
/// App keys
///
/// Used for Nostr Connect and NIP-4e operations
app_keys: Keys,
/// Custom gossip implementation
gossip: Entity<Gossip>,
/// App keys
///
/// Used for Nostr Connect and NIP-4e operations
pub app_keys: Keys,
/// Relay list state
relay_list_state: RelayState,
pub relay_list_state: RelayState,
/// Whether Coop is connected to all bootstrap relays
connected: bool,
pub connected: bool,
/// Whether Coop is creating a new signer
creating: bool,
pub creating: bool,
/// Tasks for asynchronous operations
tasks: Vec<Task<Result<(), Error>>>,
@@ -146,29 +156,9 @@ impl NostrRegistry {
self.signer.clone()
}
/// Get the app keys
pub fn app_keys(&self) -> &Keys {
&self.app_keys
}
/// Get the connected status of the client
pub fn connected(&self) -> bool {
self.connected
}
/// Get the creating status
pub fn creating(&self) -> bool {
self.creating
}
/// Get the relay list state
pub fn relay_list_state(&self) -> RelayState {
self.relay_list_state.clone()
}
/// Get all relays for a given public key without ensuring connections
pub fn read_only_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<SharedString> {
self.gossip.read(cx).read_only_relays(public_key)
/// Get the npubs entity
pub fn npubs(&self) -> Entity<Vec<PublicKey>> {
self.npubs.clone()
}
/// Set the connected status of the client
@@ -304,10 +294,11 @@ impl NostrRegistry {
Ok(public_keys) => match public_keys.is_empty() {
true => {
this.update(cx, |this, cx| {
this.create_new_signer(cx);
this.create_identity(cx);
})?;
}
false => {
// TODO: auto login
npubs.update(cx, |this, cx| {
this.extend(public_keys);
cx.notify();
@@ -317,10 +308,11 @@ impl NostrRegistry {
Err(e) => {
log::error!("Failed to get npubs: {e}");
this.update(cx, |this, cx| {
this.create_new_signer(cx);
this.create_identity(cx);
})?;
}
}
Ok(())
}));
}
@@ -332,7 +324,7 @@ impl NostrRegistry {
}
/// Create a new identity
pub fn create_new_signer(&mut self, cx: &mut Context<Self>) {
fn create_identity(&mut self, cx: &mut Context<Self>) {
let client = self.client();
let keys = Keys::generate();
let async_keys = keys.clone();
@@ -411,16 +403,17 @@ impl NostrRegistry {
}));
}
// Get the signer in keyring by username
/// Get the signer in keyring by username
pub fn get_signer(
&mut self,
username: &str,
cx: &mut Context<Self>,
&self,
public_key: &PublicKey,
cx: &App,
) -> Task<Result<Arc<dyn NostrSigner>, Error>> {
let app_keys = self.app_keys().clone();
let read_credential = cx.read_credentials(username);
let username = public_key.to_bech32().unwrap();
let app_keys = self.app_keys.clone();
let read_credential = cx.read_credentials(&username);
cx.spawn(async move |_this, _cx| {
cx.spawn(async move |_cx| {
let (_, secret) = read_credential
.await
.map_err(|_| anyhow!("Failed to get signer"))?
@@ -439,7 +432,7 @@ impl NostrRegistry {
let uri =
NostrConnectUri::parse(&sec).map_err(|_| anyhow!("Failed to parse NIP-46 URI"))?;
let timeout = Duration::from_secs(120);
let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT);
let nip46 = NostrConnect::new(uri, app_keys, timeout, None)?;
Ok(nip46.into_nostr_signer())
@@ -478,30 +471,37 @@ impl NostrRegistry {
});
self.tasks.push(cx.spawn(async move |this, cx| {
// set signer
let public_key = task.await?;
match task.await {
Ok(public_key) => {
// Update states
this.update(cx, |this, cx| {
// Add public key to npubs if not already present
this.npubs.update(cx, |this, cx| {
if !this.contains(&public_key) {
this.push(public_key);
cx.notify();
}
});
// Update states
this.update(cx, |this, cx| {
this.npubs.update(cx, |this, cx| {
if !this.contains(&public_key) {
this.push(public_key);
cx.notify();
}
});
this.ensure_relay_list(cx);
})?;
// Ensure relay list for the user
this.ensure_relay_list(cx);
// Emit signer changed event
cx.emit(SignerEvent::Set);
})?;
}
Err(e) => {
this.update(cx, |_this, cx| {
cx.emit(SignerEvent::Error(e.to_string()));
})?;
}
}
Ok(())
}));
}
/// Add a key signer to keyring
pub fn add_key_signer(
&mut self,
keys: &Keys,
cx: &mut Context<Self>,
) -> Task<Result<(), Error>> {
pub fn add_key_signer(&mut self, keys: &Keys, cx: &mut Context<Self>) {
let keys = keys.clone();
let username = keys.public_key().to_bech32().unwrap();
let secret = keys.secret_key().to_secret_bytes();
@@ -509,25 +509,26 @@ impl NostrRegistry {
// Write the credential to the keyring
let write_credential = cx.write_credentials(&username, &username, &secret);
cx.spawn(async move |this, cx| {
self.tasks.push(cx.spawn(async move |this, cx| {
match write_credential.await {
Ok(_) => {
this.update(cx, |this, cx| {
this.set_signer(keys, cx);
})?;
}
Err(e) => return Err(anyhow!("Failed to write credential: {e}")),
Err(e) => {
this.update(cx, |_this, cx| {
cx.emit(SignerEvent::Error(e.to_string()));
})?;
}
}
Ok(())
})
}));
}
/// Add a nostr connect signer to keyring
pub fn add_nip46_signer(
&mut self,
nip46: &NostrConnect,
cx: &mut Context<Self>,
) -> Task<Result<(), Error>> {
pub fn add_nip46_signer(&mut self, nip46: &NostrConnect, cx: &mut Context<Self>) {
let nip46 = nip46.clone();
let async_nip46 = nip46.clone();
@@ -540,7 +541,7 @@ impl NostrRegistry {
Ok((public_key, uri))
});
cx.spawn(async move |this, cx| {
self.tasks.push(cx.spawn(async move |this, cx| {
match task.await {
Ok((public_key, uri)) => {
let username = public_key.to_bech32().unwrap();
@@ -554,13 +555,22 @@ impl NostrRegistry {
this.set_signer(nip46, cx);
})?;
}
Err(e) => return Err(anyhow!("Failed to write credential: {e}")),
Err(e) => {
this.update(cx, |_this, cx| {
cx.emit(SignerEvent::Error(e.to_string()));
})?;
}
}
}
Err(e) => return Err(anyhow!("Failed to connect to the remote signer: {e}")),
Err(e) => {
this.update(cx, |_this, cx| {
cx.emit(SignerEvent::Error(e.to_string()));
})?;
}
}
Ok(())
})
}));
}
/// Set the state of the relay list
@@ -716,9 +726,14 @@ impl NostrRegistry {
})
}
/// Get all relays for a given public key without ensuring connections
pub fn read_only_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<SharedString> {
self.gossip.read(cx).read_only_relays(public_key)
}
/// Generate a direct nostr connection initiated by the client
pub fn nostr_connect(&self, relay: Option<RelayUrl>) -> (NostrConnect, NostrConnectUri) {
let app_keys = self.app_keys();
let app_keys = self.app_keys.clone();
let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT);
// Determine the relay will be used for Nostr Connect
@@ -894,6 +909,8 @@ impl NostrRegistry {
}
}
impl EventEmitter<SignerEvent> for NostrRegistry {}
/// Get or create a new app keys
fn get_or_init_app_keys() -> Result<Keys, Error> {
let dir = config_dir().join(".app_keys");