.
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m35s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m37s

This commit is contained in:
2026-02-12 14:11:55 +07:00
parent 836d9a99e1
commit 58571a806e
10 changed files with 226 additions and 190 deletions

View File

@@ -37,13 +37,12 @@ pub const USER_GIFTWRAP: &str = "user-gift-wraps";
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
/// Default search relays
pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://relay.noswhere.com"];
pub const SEARCH_RELAYS: [&str; 1] = ["wss://antiprimal.net"];
/// Default bootstrap relays
pub const BOOTSTRAP_RELAYS: [&str; 4] = [
pub const BOOTSTRAP_RELAYS: [&str; 3] = [
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://relay.nos.social",
"wss://user.kindpag.es",
];

View File

@@ -48,14 +48,15 @@ pub struct NostrRegistry {
/// Nostr client
client: Client,
/// Whether the bootstrapping relays is connected
connected: bool,
/// Whether coop is creating a default signer
creating_signer: bool,
/// Nostr signer
signer: Arc<CoopSigner>,
/// By default, Coop generates a new signer for new users.
///
/// This flag indicates whether the signer is user-owned or Coop-generated.
owned_signer: bool,
/// NIP-65 relay state
nip65: Entity<RelayState>,
@@ -129,16 +130,17 @@ impl NostrRegistry {
cx.defer(|cx| {
let nostr = NostrRegistry::global(cx);
// Connect to the bootstrapping relays
nostr.update(cx, |this, cx| {
this.connect(cx);
this.get_identity(cx);
});
});
Self {
client,
connected: false,
creating_signer: false,
signer,
owned_signer: false,
nip65,
nip17,
app_keys,
@@ -151,19 +153,29 @@ impl NostrRegistry {
fn connect(&mut self, cx: &mut Context<Self>) {
let client = self.client();
self.tasks.push(cx.background_spawn(async move {
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
// Add search relay to the relay pool
for url in SEARCH_RELAYS.into_iter() {
client.add_relay(url).await?;
client.add_relay(url).and_connect().await?;
}
// Add bootstrap relay to the relay pool
for url in BOOTSTRAP_RELAYS.into_iter() {
client.add_relay(url).await?;
client.add_relay(url).and_connect().await?;
}
// Connect to all added relays
client.connect().await;
Ok(())
});
self.tasks.push(cx.spawn(async move |this, cx| {
// Wait for the task to complete
task.await?;
// Update the state
this.update(cx, |this, cx| {
this.set_connected(cx);
this.get_signer(cx);
})?;
Ok(())
}));
@@ -174,22 +186,16 @@ impl NostrRegistry {
self.client.clone()
}
/// Get the nostr signer
pub fn signer(&self) -> Arc<CoopSigner> {
self.signer.clone()
}
/// Get the app keys
pub fn app_keys(&self) -> &Keys {
&self.app_keys
}
/// Returns whether the current signer is owned by user
pub fn owned_signer(&self) -> bool {
self.owned_signer
}
/// Set whether the current signer is owned by user
pub fn set_owned_signer(&mut self, owned: bool, cx: &mut Context<Self>) {
self.owned_signer = owned;
cx.notify();
}
/// Get the NIP-65 state
pub fn nip65_state(&self) -> Entity<RelayState> {
self.nip65.clone()
@@ -200,16 +206,26 @@ impl NostrRegistry {
self.nip17.clone()
}
/// Get current signer's public key
pub fn signer_pkey(&self, cx: &App) -> Task<Result<PublicKey, Error>> {
let client = self.client();
/// Get the connected state
pub fn connected(&self) -> bool {
self.connected
}
cx.background_spawn(async move {
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
/// Set the connected state
fn set_connected(&mut self, cx: &mut Context<Self>) {
self.connected = true;
cx.notify();
}
Ok(public_key)
})
/// Get the creating signer status
pub fn creating_signer(&self) -> bool {
self.creating_signer
}
/// Set the creating signer status
fn set_creating_signer(&mut self, status: bool, cx: &mut Context<Self>) {
self.creating_signer = status;
cx.notify();
}
/// Get a relay hint (messaging relay) for a given public key
@@ -290,18 +306,17 @@ impl NostrRegistry {
T: NostrSigner + 'static,
{
let client = self.client();
let signer = self.signer.clone();
let signer = self.signer();
// Create a task to update the signer and verify the public key
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
// Update signer
signer.switch(new).await;
signer.switch(new, owned).await;
// Verify signer
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
log::info!("Signer's public key: {public_key}");
log::info!("Signer's public key: {}", public_key);
Ok(())
});
@@ -313,8 +328,6 @@ impl NostrRegistry {
// Update states
this.update(cx, |this, cx| {
this.reset_relay_states(cx);
this.get_relay_list(cx);
this.set_owned_signer(owned, cx);
})?;
Ok(())
@@ -615,83 +628,61 @@ impl NostrRegistry {
})
}
/// Get local stored identity
fn get_identity(&mut self, cx: &mut Context<Self>) {
let read_credential = cx.read_credentials(KEYRING);
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>) {
fn set_default_signer(&mut self, cx: &mut Context<Self>) {
let client = self.client();
let keys = Keys::generate();
let async_keys = keys.clone();
// Get write credential task
// Create a write credential task
let write_credential = cx.write_credentials(
KEYRING,
&keys.public_key().to_hex(),
&keys.secret_key().to_secret_bytes(),
);
// Update the signer
self.set_signer(keys, false, cx);
// Set the creating signer status
self.set_creating_signer(true, cx);
// Run async tasks in background
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
// Build and sign the relay list event
let signer = client.signer().context("Signer not found")?;
// Get default relay list
let relay_list = default_relay_list();
let event = EventBuilder::relay_list(relay_list)
.sign(&async_keys)
.await?;
// Publish relay list event
client.send_event(&event).await?;
let event = EventBuilder::relay_list(relay_list).sign(signer).await?;
let output = client.send_event(&event).broadcast().await?;
log::info!("Published relay list event: {:?}", output.id());
// Build and sign the metadata event
// Construct the default metadata
let name = petname::petname(2, "-").unwrap_or("Cooper".to_string());
let avatar = Url::parse(&format!("https://avatar.vercel.sh/{name}")).unwrap();
let metadata = Metadata::new().display_name(&name).picture(avatar);
let event = EventBuilder::metadata(&metadata).sign(&async_keys).await?;
// Publish metadata event
client.send_event(&event).await?;
let event = EventBuilder::metadata(&metadata).sign(signer).await?;
let output = client.send_event(&event).broadcast().await?;
log::info!("Published metadata event: {:?}", output.id());
// Build and sign the contact list event
// Construct the default contact list
let contacts = vec![Contact::new(PublicKey::parse(COOP_PUBKEY).unwrap())];
let event = EventBuilder::contact_list(contacts)
.sign(&async_keys)
.await?;
// Publish contact list event
client.send_event(&event).await?;
let event = EventBuilder::contact_list(contacts).sign(signer).await?;
let output = client.send_event(&event).broadcast().await?;
log::info!("Published contact list event: {:?}", output.id());
// Build and sign the messaging relay list event
// Construct the default messaging relay list
let relays = default_messaging_relays();
let event = EventBuilder::nip17_relay_list(relays)
.sign(&async_keys)
.await?;
// Publish messaging relay list event
client.send_event(&event).await?;
let event = EventBuilder::nip17_relay_list(relays).sign(signer).await?;
let output = client.send_event(&event).to_nip65().await?;
log::info!("Published messaging relay list event: {:?}", output.id());
// Write user's credentials to the system keyring
write_credential.await?;
@@ -703,15 +694,41 @@ impl NostrRegistry {
// Wait for the task to complete
task.await?;
// Update the signer
this.update(cx, |this, cx| {
this.set_signer(keys, false, cx);
this.set_creating_signer(false, cx);
this.get_relay_list(cx);
})?;
Ok(())
}));
}
/// Get local stored signer
fn get_signer(&mut self, cx: &mut Context<Self>) {
let read_credential = cx.read_credentials(KEYRING);
self.tasks.push(cx.spawn(async move |this, cx| {
match read_credential.await {
Ok(Some((_user, secret))) => {
let secret = SecretKey::from_slice(&secret)?;
let keys = Keys::new(secret);
this.update(cx, |this, cx| {
this.set_signer(keys, false, cx);
this.get_relay_list(cx);
})?;
}
_ => {
this.update(cx, |this, cx| {
this.get_bunker(cx);
})?;
}
}
Ok(())
}));
}
/// Get local stored bunker connection
fn get_bunker(&mut self, cx: &mut Context<Self>) {
let client = self.client();
@@ -741,6 +758,7 @@ impl NostrRegistry {
Ok(signer) => {
this.update(cx, |this, cx| {
this.set_signer(signer, true, cx);
this.get_relay_list(cx);
})
.ok();
}
@@ -748,7 +766,7 @@ impl NostrRegistry {
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);
this.set_default_signer(cx);
})
.ok();
}

View File

@@ -1,5 +1,6 @@
use std::borrow::Cow;
use std::result::Result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use nostr_sdk::prelude::*;
@@ -8,6 +9,17 @@ use smol::lock::RwLock;
#[derive(Debug)]
pub struct CoopSigner {
signer: RwLock<Arc<dyn NostrSigner>>,
/// Signer's public key
signer_pkey: RwLock<Option<PublicKey>>,
/// Whether coop is creating a new identity
creating: AtomicBool,
/// By default, Coop generates a new signer for new users.
///
/// This flag indicates whether the signer is user-owned or Coop-generated.
owned: AtomicBool,
}
impl CoopSigner {
@@ -17,6 +29,9 @@ impl CoopSigner {
{
Self {
signer: RwLock::new(signer.into_nostr_signer()),
signer_pkey: RwLock::new(None),
creating: AtomicBool::new(false),
owned: AtomicBool::new(false),
}
}
@@ -25,13 +40,39 @@ impl CoopSigner {
self.signer.read().await.clone()
}
/// Get public key
pub fn public_key(&self) -> Option<PublicKey> {
self.signer_pkey.read_blocking().to_owned()
}
/// Get the flag indicating whether the signer is creating a new identity.
pub fn creating(&self) -> bool {
self.creating.load(Ordering::SeqCst)
}
/// Get the flag indicating whether the signer is user-owned.
pub fn owned(&self) -> bool {
self.owned.load(Ordering::SeqCst)
}
/// Switch the current signer to a new signer.
pub async fn switch<T>(&self, new: T)
pub async fn switch<T>(&self, new: T, owned: bool)
where
T: IntoNostrSigner,
{
let new_signer = new.into_nostr_signer();
let public_key = new_signer.get_public_key().await.ok();
let mut signer = self.signer.write().await;
*signer = new.into_nostr_signer();
let mut signer_pkey = self.signer_pkey.write().await;
// Switch to the new signer
*signer = new_signer;
// Update the public key
*signer_pkey = public_key;
// Update the owned flag
self.owned.store(owned, Ordering::SeqCst);
}
}