wip: update nostr sdk
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m35s

This commit is contained in:
2026-02-05 09:29:47 +07:00
parent 0e756fb6c3
commit fce4c1bbcd
18 changed files with 391 additions and 252 deletions

View File

@@ -15,9 +15,9 @@ impl RelayState {
}
/// Identity
#[derive(Debug, Clone, Default)]
#[derive(Debug, Default)]
pub struct Identity {
/// The public key of the account
/// Signer's public key
pub public_key: Option<PublicKey>,
/// Whether the identity is owned by the user

View File

@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::os::unix::fs::PermissionsExt;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Error};
@@ -14,12 +15,14 @@ mod event;
mod gossip;
mod identity;
mod nip05;
mod signer;
pub use device::*;
pub use event::*;
pub use gossip::*;
pub use identity::*;
pub use nip05::*;
pub use signer::*;
use crate::identity::Identity;
@@ -68,6 +71,9 @@ pub struct NostrRegistry {
/// Nostr client
client: Client,
/// Nostr signer
signer: Arc<CoopSigner>,
/// App keys
///
/// Used for Nostr Connect and NIP-4e operations
@@ -108,14 +114,6 @@ impl NostrRegistry {
.install_default()
.ok();
// Construct the nostr client options
let opts = ClientOptions::new()
.automatic_authentication(false)
.verify_subscriptions(false)
.sleep_when_idle(SleepWhenIdle::Enabled {
timeout: Duration::from_secs(600),
});
// Construct the lmdb
let lmdb = cx.foreground_executor().block_on(async move {
NostrLmdb::open(config_dir().join("nostr"))
@@ -123,9 +121,23 @@ impl NostrRegistry {
.expect("Failed to initialize database")
});
// Construct the nostr signer
let keys = Keys::generate();
let signer = Arc::new(CoopSigner::new(keys));
// Construct the nostr client
let client = ClientBuilder::default().database(lmdb).opts(opts).build();
let _ = tracker();
let client = ClientBuilder::default()
.signer(signer.clone())
.database(lmdb)
.automatic_authentication(false)
.verify_subscriptions(false)
.sleep_when_idle(SleepWhenIdle::Enabled {
timeout: Duration::from_secs(600),
})
.build();
// Construct the event tracker
let _tracker = tracker();
// Get the app keys
let app_keys = Self::create_or_init_app_keys().unwrap();
@@ -135,7 +147,7 @@ impl NostrRegistry {
let async_gossip = gossip.downgrade();
// Construct the identity entity
let identity = cx.new(|_| Identity::default());
let identity = cx.new(|_| Identity::new());
// Channel for communication between nostr and gpui
let (tx, rx) = flume::bounded::<Event>(2048);
@@ -207,6 +219,7 @@ impl NostrRegistry {
Self {
client,
signer,
app_keys,
identity,
gossip,
@@ -239,8 +252,8 @@ impl NostrRegistry {
let mut notifications = client.notifications();
let mut processed_events = HashSet::new();
while let Ok(notification) = notifications.recv().await {
if let RelayPoolNotification::Message { message, relay_url } = notification {
while let Some(notification) = notifications.next().await {
if let ClientNotification::Message { message, relay_url } = notification {
match message {
RelayMessage::Event {
event,
@@ -325,9 +338,13 @@ impl NostrRegistry {
.author(event.pubkey)
.limit(1);
client
.subscribe_to(write_relays, vec![inbox, announcement], Some(opts))
.await?;
// Construct target for subscription
let target = write_relays
.into_iter()
.map(|relay| (relay, vec![inbox.clone(), announcement.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).close_on(opts).await?;
Ok(())
}
@@ -433,21 +450,21 @@ impl NostrRegistry {
}
/// Set the signer for the nostr client and verify the public key
pub fn set_signer<T>(&mut self, signer: T, owned: bool, cx: &mut Context<Self>)
pub fn set_signer<T>(&mut self, new: T, owned: bool, cx: &mut Context<Self>)
where
T: NostrSigner + 'static,
{
let client = self.client();
let identity = self.identity.downgrade();
let signer = self.signer.clone();
// Create a task to update the signer and verify the public key
let task: Task<Result<PublicKey, Error>> = cx.background_spawn(async move {
// Update signer
client.set_signer(signer).await;
signer.switch(new).await;
// Verify signer
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
log::info!("test: {public_key:?}");
Ok(public_key)
});
@@ -471,31 +488,6 @@ impl NostrRegistry {
}));
}
/// Unset the current signer
pub fn unset_signer(&mut self, cx: &mut Context<Self>) {
let client = self.client();
let async_identity = self.identity.downgrade();
self.tasks.push(cx.spawn(async move |_this, cx| {
// Unset the signer from nostr client
cx.background_executor()
.await_on_background(async move {
client.unset_signer().await;
})
.await;
// Unset the current identity
async_identity
.update(cx, |this, cx| {
this.unset_public_key();
cx.notify();
})
.ok();
Ok(())
}));
}
// Get relay list for current user
fn get_relay_list(&mut self, cx: &mut Context<Self>) {
let client = self.client();
@@ -508,8 +500,15 @@ impl NostrRegistry {
.author(public_key)
.limit(1);
// Construct targets for subscription
let target = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
let mut stream = client
.stream_events_from(BOOTSTRAP_RELAYS, vec![filter], Duration::from_secs(TIMEOUT))
.stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?;
while let Some((_url, res)) = stream.next().await {
@@ -523,10 +522,14 @@ impl NostrRegistry {
.author(public_key)
.since(Timestamp::now());
// Construct targets for subscription
let target = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Subscribe to the relay list events
client
.subscribe_to(BOOTSTRAP_RELAYS, vec![filter], None)
.await?;
client.subscribe(target).await?;
return Ok(RelayState::Set);
}
@@ -565,8 +568,7 @@ impl NostrRegistry {
let write_relays = self.write_relays(&public_key, cx);
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let mut urls = vec![];
urls.extend(write_relays.await);
let mut urls = write_relays.await;
urls.extend(
BOOTSTRAP_RELAYS
.iter()
@@ -590,9 +592,13 @@ impl NostrRegistry {
.limit(1)
.author(public_key);
client
.subscribe_to(urls, vec![metadata, contact_list], Some(opts))
.await?;
// Construct targets for subscription
let target = urls
.into_iter()
.map(|relay| (relay, vec![metadata.clone(), contact_list.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).close_on(opts).await?;
Ok(())
});
@@ -616,9 +622,16 @@ impl NostrRegistry {
.author(public_key)
.limit(1);
// Construct targets for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Stream events from the write relays
let mut stream = client
.stream_events_from(&urls, vec![filter], Duration::from_secs(TIMEOUT))
.stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?;
while let Some((_url, res)) = stream.next().await {
@@ -632,8 +645,14 @@ impl NostrRegistry {
.author(public_key)
.since(Timestamp::now());
// Construct targets for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Subscribe to the relay list events
client.subscribe_to(&urls, vec![filter], None).await?;
client.subscribe(target).await?;
return Ok(RelayState::Set);
}
@@ -687,13 +706,13 @@ impl NostrRegistry {
cx.background_spawn(async move {
let urls = write_relays.await;
let signer = client.signer().await?;
// Sign the new metadata event
let event = EventBuilder::metadata(&metadata).sign(&signer).await?;
// Build and sign the metadata event
let builder = EventBuilder::metadata(&metadata);
let event = client.sign_event_builder(builder).await?;
// Send event to user's write relayss
client.send_event_to(urls, &event).await?;
client.send_event(&event).to(urls).await?;
Ok(())
})
@@ -728,9 +747,6 @@ impl NostrRegistry {
/// Create a new identity
fn create_identity(&mut self, cx: &mut Context<Self>) {
let client = self.client();
// Generate new keys
let keys = Keys::generate();
// Get write credential task
@@ -743,23 +759,25 @@ impl NostrRegistry {
// Update the signer
self.set_signer(keys, false, cx);
// Spawn a task to set metadata and write the credentials
// Generate a unique name and avatar for the identity
let name = petname::petname(2, "-").unwrap_or("Cooper".to_string());
let avatar = Url::parse(DEFAULT_AVATAR).unwrap();
// Construct metadata for the identity
let metadata = Metadata::new()
.display_name(&name)
.name(&name)
.picture(avatar);
// Update user's metadata
let task = self.set_metadata(&metadata, cx);
// Spawn a task to write the credentials
cx.background_spawn(async move {
let name = petname::petname(2, "-").unwrap_or("Cooper".to_string());
let avatar = Url::parse(DEFAULT_AVATAR).unwrap();
// Construct metadata for the identity
let metadata = Metadata::new()
.display_name(&name)
.name(&name)
.picture(avatar);
// Set metadata for the identity
if let Err(e) = client.set_metadata(&metadata).await {
log::error!("Failed to set metadata: {}", e);
if let Err(e) = task.await {
log::error!("Failed to update metadata: {}", e);
}
// Write the credentials
if let Err(e) = write_credential.await {
log::error!("Failed to write credentials: {}", e);
}
@@ -874,10 +892,13 @@ impl NostrRegistry {
.author(public_key)
.limit(1);
// Subscribe to bootstrap relays
client
.subscribe_to(BOOTSTRAP_RELAYS, vec![filter], Some(opts))
.await?;
// Construct target for subscription
let target = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).close_on(opts).await?;
Ok(public_key)
})
@@ -897,9 +918,16 @@ impl NostrRegistry {
.kind(Kind::Metadata)
.limit(FIND_LIMIT);
// Construct target for subscription
let target = SEARCH_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Stream events from the search relays
let mut stream = client
.stream_events_from(SEARCH_RELAYS, vec![filter], Duration::from_secs(3))
.stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?;
// Collect the results
@@ -923,28 +951,31 @@ impl NostrRegistry {
let query = query.to_string();
cx.background_spawn(async move {
let signer = client.signer().await?;
// Construct a vertex request event
let event = EventBuilder::new(Kind::Custom(5315), "")
.tags(vec![
Tag::custom(TagKind::custom("param"), vec!["search", &query]),
Tag::custom(TagKind::custom("param"), vec!["limit", "10"]),
])
.sign(&signer)
.await?;
let builder = EventBuilder::new(Kind::Custom(5315), "").tags(vec![
Tag::custom(TagKind::custom("param"), vec!["search", &query]),
Tag::custom(TagKind::custom("param"), vec!["limit", "10"]),
]);
let event = client.sign_event_builder(builder).await?;
// Send the event to vertex relays
let output = client.send_event_to(WOT_RELAYS, &event).await?;
let output = client.send_event(&event).to(WOT_RELAYS).await?;
// Construct a filter to get the response or error from vertex
let filter = Filter::new()
.kinds(vec![Kind::Custom(6315), Kind::Custom(7000)])
.event(output.id().to_owned());
// Stream events from the search relays
// Construct target for subscription
let target = WOT_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Stream events from the wot relays
let mut stream = client
.stream_events_from(WOT_RELAYS, vec![filter], Duration::from_secs(3))
.stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?;
while let Some((_url, res)) = stream.next().await {

View File

@@ -0,0 +1,88 @@
use std::borrow::Cow;
use std::sync::Arc;
use nostr_sdk::prelude::*;
use smol::lock::RwLock;
#[derive(Debug)]
pub struct CoopSigner {
signer: RwLock<Arc<dyn NostrSigner>>,
}
impl CoopSigner {
pub fn new<T>(signer: T) -> Self
where
T: IntoNostrSigner,
{
Self {
signer: RwLock::new(signer.into_nostr_signer()),
}
}
async fn get(&self) -> Arc<dyn NostrSigner> {
self.signer.read().await.clone()
}
pub async fn switch<T>(&self, new: T)
where
T: IntoNostrSigner,
{
let mut signer = self.signer.write().await;
*signer = new.into_nostr_signer();
}
}
impl NostrSigner for CoopSigner {
fn backend(&self) -> SignerBackend {
SignerBackend::Custom(Cow::Borrowed("custom"))
}
fn get_public_key(&self) -> BoxedFuture<Result<PublicKey, SignerError>> {
Box::pin(async move { Ok(self.get().await.get_public_key().await?) })
}
fn sign_event(
&self,
unsigned: UnsignedEvent,
) -> BoxedFuture<std::result::Result<Event, SignerError>> {
Box::pin(async move { Ok(self.get().await.sign_event(unsigned).await?) })
}
fn nip04_encrypt<'a>(
&'a self,
public_key: &'a PublicKey,
content: &'a str,
) -> BoxedFuture<'a, std::result::Result<String, SignerError>> {
Box::pin(async move { Ok(self.get().await.nip04_encrypt(public_key, content).await?) })
}
fn nip04_decrypt<'a>(
&'a self,
public_key: &'a PublicKey,
encrypted_content: &'a str,
) -> BoxedFuture<'a, std::result::Result<String, SignerError>> {
Box::pin(async move {
Ok(self
.get()
.await
.nip04_decrypt(public_key, encrypted_content)
.await?)
})
}
fn nip44_encrypt<'a>(
&'a self,
public_key: &'a PublicKey,
content: &'a str,
) -> BoxedFuture<'a, std::result::Result<String, SignerError>> {
Box::pin(async move { Ok(self.get().await.nip44_encrypt(public_key, content).await?) })
}
fn nip44_decrypt<'a>(
&'a self,
public_key: &'a PublicKey,
payload: &'a str,
) -> BoxedFuture<'a, std::result::Result<String, SignerError>> {
Box::pin(async move { Ok(self.get().await.nip44_decrypt(public_key, payload).await?) })
}
}