diff --git a/CHANGELOG.md b/CHANGELOG.md index 960b79926..625c2f7d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ * nostr: change `TagStandard::Relay` variant inner type ([Yuki Kishimoto]) * pool: switch from async to sync message sending for `Relay` ([Yuki Kishimoto]) * connect: refactor `NostrConnectRemoteSigner` to use distinct keys for signer and user ([Yuki Kishimoto]) +* connect: refactor `NostrConnectRemoteSigner` to use synchronous constructors ([Yuki Kishimoto]) * sdk: disable all default features ([Yuki Kishimoto]) * sdk: set `Client::from_builder` as private ([Yuki Kishimoto]) * ffi: convert `NostrSigner` trait to an object ([Yuki Kishimoto]) diff --git a/bindings/nostr-sdk-ffi/src/connect.rs b/bindings/nostr-sdk-ffi/src/connect.rs index 10a5cb365..aaa0cc432 100644 --- a/bindings/nostr-sdk-ffi/src/connect.rs +++ b/bindings/nostr-sdk-ffi/src/connect.rs @@ -141,9 +141,8 @@ pub struct NostrConnectRemoteSigner { #[uniffi::export(async_runtime = "tokio")] impl NostrConnectRemoteSigner { - // TODO: change again to `new` (currently python not support async constructor) #[uniffi::constructor(default(secret = None, opts = None))] - pub async fn init( + pub fn new( keys: NostrConnectKeys, relays: Vec, secret: Option, @@ -155,14 +154,13 @@ impl NostrConnectRemoteSigner { relays, secret, opts.map(|o| o.as_ref().deref().clone()), - ) - .await?, + )?, }) } /// Construct remote signer from client URI (`nostrconnect://..`) #[uniffi::constructor(default(secret = None, opts = None))] - pub async fn from_uri( + pub fn from_uri( uri: &NostrConnectURI, keys: NostrConnectKeys, secret: Option, @@ -174,24 +172,18 @@ impl NostrConnectRemoteSigner { keys.into(), secret, opts.map(|o| o.as_ref().deref().clone()), - ) - .await?, + )?, }) } /// Get signer relays - pub async fn relays(&self) -> Vec { - self.inner - .relays() - .await - .into_iter() - .map(|r| r.to_string()) - .collect() + pub fn relays(&self) -> Vec { + self.inner.relays().iter().map(|r| r.to_string()).collect() } /// Get `bunker` URI - pub async fn bunker_uri(&self) -> NostrConnectURI { - self.inner.bunker_uri().await.into() + pub fn bunker_uri(&self) -> NostrConnectURI { + self.inner.bunker_uri().into() } /// Serve signer diff --git a/crates/nostr-cli/src/main.rs b/crates/nostr-cli/src/main.rs index e06e63dca..880eb86ae 100644 --- a/crates/nostr-cli/src/main.rs +++ b/crates/nostr-cli/src/main.rs @@ -137,16 +137,13 @@ async fn run() -> Result<()> { let signer: NostrConnectRemoteSigner = match uri { Some(uri) => { let uri: NostrConnectURI = NostrConnectURI::parse(&uri)?; - NostrConnectRemoteSigner::from_uri(uri, keys, None, None).await? - } - None => { - NostrConnectRemoteSigner::new(keys, ["wss://relay.nsec.app"], None, None) - .await? + NostrConnectRemoteSigner::from_uri(uri, keys, None, None)? } + None => NostrConnectRemoteSigner::new(keys, ["wss://relay.nsec.app"], None, None)?, }; // Print bunker URI - let uri: NostrConnectURI = signer.bunker_uri().await; + let uri: NostrConnectURI = signer.bunker_uri(); println!("\nBunker URI: {uri}\n"); // Serve signer diff --git a/crates/nostr-connect/examples/nostr-connect-signer.rs b/crates/nostr-connect/examples/nostr-connect-signer.rs index 4ce56dc6c..afa4d2a80 100644 --- a/crates/nostr-connect/examples/nostr-connect-signer.rs +++ b/crates/nostr-connect/examples/nostr-connect-signer.rs @@ -19,14 +19,14 @@ async fn main() -> Result<()> { }; // Compose signer - let signer = NostrConnectRemoteSigner::new(keys, ["wss://relay.nsec.app"], None, None).await?; + let signer = NostrConnectRemoteSigner::new(keys, ["wss://relay.nsec.app"], None, None)?; // Compose signer from URI // let uri = NostrConnectURI::parse("nostrconnect://...")?; - // let signer = NostrConnectRemoteSigner::from_uri(uri, keys, None, None).await?; + // let signer = NostrConnectRemoteSigner::from_uri(uri, keys, None, None)?; // Print bunker URI - let uri = signer.bunker_uri().await; + let uri = signer.bunker_uri(); println!("\n{uri}\n"); // Serve signer diff --git a/crates/nostr-connect/src/error.rs b/crates/nostr-connect/src/error.rs index 3a4d8a6c6..b655a2019 100644 --- a/crates/nostr-connect/src/error.rs +++ b/crates/nostr-connect/src/error.rs @@ -4,6 +4,8 @@ //! Nostr Connect error +use std::convert::Infallible; + use nostr::event::builder; use nostr::nips::{nip04, nip46}; use nostr::PublicKey; @@ -52,4 +54,7 @@ pub enum Error { /// The local set user public key local: Box, }, + /// Infallible + #[error(transparent)] + Infallible(#[from] Infallible), } diff --git a/crates/nostr-connect/src/signer.rs b/crates/nostr-connect/src/signer.rs index b328ed9b3..eb2003a89 100644 --- a/crates/nostr-connect/src/signer.rs +++ b/crates/nostr-connect/src/signer.rs @@ -4,6 +4,8 @@ //! Nostr Connect signer +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::time::Duration; use nostr::nips::nip46::{Message, Request, ResponseResult}; @@ -30,15 +32,19 @@ pub struct NostrConnectKeys { #[derive(Debug, Clone)] pub struct NostrConnectRemoteSigner { keys: NostrConnectKeys, + relays: Vec, pool: RelayPool, + opts: RelayOptions, secret: Option, + nostr_connect_client_public_key: Option, + bootstrapped: Arc, } impl NostrConnectRemoteSigner { /// Construct new remote signer - pub async fn new( + pub fn new( keys: NostrConnectKeys, - relays: I, + urls: I, secret: Option, opts: Option, ) -> Result @@ -47,21 +53,28 @@ impl NostrConnectRemoteSigner { U: TryIntoUrl, pool::Error: From<::Err>, { - // Compose pool - let pool: RelayPool = RelayPool::default(); - - let opts: RelayOptions = opts.unwrap_or_default(); - for url in relays.into_iter() { - pool.add_relay(url, opts.clone()).await?; + let mut relays = Vec::new(); + for relay in urls.into_iter() { + relays.push( + relay + .try_into_url() + .map_err(|e| Error::Pool(pool::Error::from(e)))?, + ); } - pool.connect(Some(Duration::from_secs(10))).await; - - Ok(Self { keys, pool, secret }) + Ok(Self { + keys, + relays, + pool: RelayPool::default(), + opts: opts.unwrap_or_default(), + secret, + nostr_connect_client_public_key: None, + bootstrapped: Arc::new(AtomicBool::new(false)), + }) } /// Construct remote signer from client URI (`nostrconnect://..`) - pub async fn from_uri( + pub fn from_uri( uri: NostrConnectURI, keys: NostrConnectKeys, secret: Option, @@ -71,24 +84,24 @@ impl NostrConnectRemoteSigner { NostrConnectURI::Client { public_key, relays, .. } => { - let this = Self::new(keys, relays, secret, opts).await?; - this.send_connect_ack(public_key).await?; - Ok(this) + let mut signer = Self::new(keys, relays, secret, opts)?; + signer.nostr_connect_client_public_key = Some(public_key); + Ok(signer) } NostrConnectURI::Bunker { .. } => Err(Error::UnexpectedUri), } } /// Get signer relays - pub async fn relays(&self) -> Vec { - self.pool.relays().await.into_keys().collect() + pub fn relays(&self) -> &[Url] { + &self.relays } /// Get `bunker` URI - pub async fn bunker_uri(&self) -> NostrConnectURI { + pub fn bunker_uri(&self) -> NostrConnectURI { NostrConnectURI::Bunker { remote_signer_public_key: self.keys.signer.public_key(), - relays: self.relays().await, + relays: self.relays().to_vec(), secret: self.secret.clone(), } } @@ -104,11 +117,22 @@ impl NostrConnectRemoteSigner { Ok(()) } - async fn subscribe(&self) -> Result<(), Error> { - let public_key: PublicKey = self.keys.signer.public_key(); + async fn bootstrap(&self) -> Result<(), Error> { + // Check if already bootstrapped + if self.bootstrapped.load(Ordering::SeqCst) { + return Ok(()); + } + + // Add relays to pool + for url in self.relays.iter() { + self.pool.add_relay(url, self.opts.clone()).await?; + } + + // Connect + self.pool.connect(Some(Duration::from_secs(10))).await; let filter = Filter::new() - .pubkey(public_key) + .pubkey(self.keys.signer.public_key()) .kind(Kind::NostrConnect) .since(Timestamp::now()); @@ -117,6 +141,9 @@ impl NostrConnectRemoteSigner { .subscribe(vec![filter], SubscribeOptions::default()) .await?; + // Mark as bootstrapped + self.bootstrapped.store(true, Ordering::SeqCst); + Ok(()) } @@ -125,7 +152,12 @@ impl NostrConnectRemoteSigner { where T: NostrConnectSignerActions, { - self.subscribe().await?; + self.bootstrap().await?; + + // TODO: move into bootstrap method? + if let Some(public_key) = self.nostr_connect_client_public_key { + self.send_connect_ack(public_key).await?; + } self.pool .handle_notifications(|notification| async {