lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

client.rs (7960B)


      1 #![forbid(unsafe_code)]
      2 
      3 use core::ops::Deref;
      4 use core::time::Duration;
      5 use std::collections::HashMap;
      6 #[cfg(not(target_arch = "wasm32"))]
      7 use std::net::SocketAddr;
      8 
      9 use nostr_sdk::{Client, ClientBuilder, ClientOptions};
     10 use radroots_identity::RadrootsIdentity;
     11 
     12 use crate::error::RadrootsNostrError;
     13 use crate::types::RadrootsNostrMetadata;
     14 use crate::types::{
     15     RadrootsNostrEvent, RadrootsNostrEventBuilder, RadrootsNostrEventId, RadrootsNostrFilter,
     16     RadrootsNostrKeys, RadrootsNostrMonitor, RadrootsNostrOutput, RadrootsNostrRelay,
     17     RadrootsNostrRelayUrl, RadrootsNostrSubscribeAutoCloseOptions, RadrootsNostrSubscriptionId,
     18 };
     19 
     20 #[derive(Clone)]
     21 pub struct RadrootsNostrClient {
     22     inner: Client,
     23 }
     24 
     25 #[derive(Debug, Clone, Default)]
     26 pub struct RadrootsNostrClientOptions {
     27     automatic_authentication: Option<bool>,
     28     max_avg_latency_ms: Option<u64>,
     29     verify_subscriptions: Option<bool>,
     30     ban_relay_on_mismatch: Option<bool>,
     31     #[cfg(not(target_arch = "wasm32"))]
     32     proxy: Option<SocketAddr>,
     33 }
     34 
     35 impl RadrootsNostrClientOptions {
     36     pub fn new() -> Self {
     37         Self::default()
     38     }
     39 
     40     pub fn automatic_authentication(mut self, enabled: bool) -> Self {
     41         self.automatic_authentication = Some(enabled);
     42         self
     43     }
     44 
     45     pub fn max_avg_latency_ms(mut self, max_ms: u64) -> Self {
     46         self.max_avg_latency_ms = Some(max_ms);
     47         self
     48     }
     49 
     50     pub fn verify_subscriptions(mut self, enabled: bool) -> Self {
     51         self.verify_subscriptions = Some(enabled);
     52         self
     53     }
     54 
     55     pub fn ban_relay_on_mismatch(mut self, enabled: bool) -> Self {
     56         self.ban_relay_on_mismatch = Some(enabled);
     57         self
     58     }
     59 
     60     #[cfg(not(target_arch = "wasm32"))]
     61     pub fn proxy_addr(mut self, addr: SocketAddr) -> Self {
     62         self.proxy = Some(addr);
     63         self
     64     }
     65 
     66     #[cfg(not(target_arch = "wasm32"))]
     67     pub fn proxy_str(mut self, addr: &str) -> Result<Self, RadrootsNostrError> {
     68         let parsed: SocketAddr = addr.parse().map_err(|err: std::net::AddrParseError| {
     69             RadrootsNostrError::ClientConfigError(err.to_string())
     70         })?;
     71         self.proxy = Some(parsed);
     72         Ok(self)
     73     }
     74 
     75     fn to_client_options(&self) -> Result<ClientOptions, RadrootsNostrError> {
     76         let mut opts = ClientOptions::new();
     77         if let Some(enabled) = self.automatic_authentication {
     78             opts = opts.automatic_authentication(enabled);
     79         }
     80         if let Some(max_ms) = self.max_avg_latency_ms {
     81             opts = opts.max_avg_latency(Duration::from_millis(max_ms));
     82         }
     83         if let Some(enabled) = self.verify_subscriptions {
     84             opts = opts.verify_subscriptions(enabled);
     85         }
     86         if let Some(enabled) = self.ban_relay_on_mismatch {
     87             opts = opts.ban_relay_on_mismatch(enabled);
     88         }
     89         #[cfg(not(target_arch = "wasm32"))]
     90         if let Some(proxy) = self.proxy {
     91             let connection = nostr_sdk::client::options::Connection::new().proxy(proxy);
     92             opts = opts.connection(connection);
     93         }
     94         Ok(opts)
     95     }
     96 }
     97 
     98 impl RadrootsNostrClient {
     99     pub fn new_signerless() -> Self {
    100         Self {
    101             inner: Client::default(),
    102         }
    103     }
    104 
    105     pub fn new_signerless_with_options(
    106         options: RadrootsNostrClientOptions,
    107     ) -> Result<Self, RadrootsNostrError> {
    108         let opts = options.to_client_options()?;
    109         let inner = ClientBuilder::new().opts(opts).build();
    110         Ok(Self { inner })
    111     }
    112 
    113     pub fn new(keys: RadrootsNostrKeys) -> Self {
    114         Self {
    115             inner: Client::new(keys),
    116         }
    117     }
    118 
    119     pub fn from_keys_with_options(
    120         keys: RadrootsNostrKeys,
    121         options: RadrootsNostrClientOptions,
    122     ) -> Result<Self, RadrootsNostrError> {
    123         let opts = options.to_client_options()?;
    124         let inner = ClientBuilder::new().signer(keys).opts(opts).build();
    125         Ok(Self { inner })
    126     }
    127 
    128     pub fn new_with_monitor(keys: RadrootsNostrKeys, monitor: RadrootsNostrMonitor) -> Self {
    129         let inner = Client::builder().signer(keys).monitor(monitor).build();
    130         Self { inner }
    131     }
    132 
    133     pub fn from_identity(identity: &RadrootsIdentity) -> Self {
    134         Self::new(identity.keys().clone())
    135     }
    136 
    137     pub fn from_identity_owned(identity: RadrootsIdentity) -> Self {
    138         Self::new(identity.into_keys())
    139     }
    140 
    141     pub fn from_inner(inner: Client) -> Self {
    142         Self { inner }
    143     }
    144 
    145     pub fn into_inner(self) -> Client {
    146         self.inner
    147     }
    148 
    149     pub async fn add_relay(&self, url: &str) -> Result<bool, RadrootsNostrError> {
    150         Ok(self.inner.add_relay(url).await?)
    151     }
    152 
    153     pub async fn add_write_relay(&self, url: &str) -> Result<bool, RadrootsNostrError> {
    154         Ok(self.inner.add_write_relay(url).await?)
    155     }
    156 
    157     pub async fn add_read_relay(&self, url: &str) -> Result<bool, RadrootsNostrError> {
    158         Ok(self.inner.add_read_relay(url).await?)
    159     }
    160 
    161     pub async fn remove_relay(&self, url: &str) -> Result<(), RadrootsNostrError> {
    162         self.inner.force_remove_relay(url).await?;
    163         Ok(())
    164     }
    165 
    166     pub async fn relays(&self) -> HashMap<RadrootsNostrRelayUrl, RadrootsNostrRelay> {
    167         self.inner.relays().await
    168     }
    169 
    170     pub async fn fetch_events(
    171         &self,
    172         filter: RadrootsNostrFilter,
    173         timeout: Duration,
    174     ) -> Result<Vec<RadrootsNostrEvent>, RadrootsNostrError> {
    175         let events = self.inner.fetch_events(filter, timeout).await?;
    176         Ok(events.to_vec())
    177     }
    178 
    179     pub async fn subscribe(
    180         &self,
    181         filter: RadrootsNostrFilter,
    182         opts: Option<RadrootsNostrSubscribeAutoCloseOptions>,
    183     ) -> Result<RadrootsNostrOutput<RadrootsNostrSubscriptionId>, RadrootsNostrError> {
    184         Ok(self.inner.subscribe(filter, opts).await?)
    185     }
    186 
    187     pub async fn send_event_builder(
    188         &self,
    189         event: RadrootsNostrEventBuilder,
    190     ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> {
    191         Ok(self.inner.send_event_builder(event).await?)
    192     }
    193 
    194     pub async fn send_event(
    195         &self,
    196         event: &RadrootsNostrEvent,
    197     ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> {
    198         Ok(self.inner.send_event(event).await?)
    199     }
    200 
    201     pub async fn set_metadata(
    202         &self,
    203         md: &RadrootsNostrMetadata,
    204     ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> {
    205         Ok(self.inner.set_metadata(md).await?)
    206     }
    207 }
    208 
    209 impl Deref for RadrootsNostrClient {
    210     type Target = Client;
    211 
    212     fn deref(&self) -> &Self::Target {
    213         &self.inner
    214     }
    215 }
    216 
    217 pub async fn radroots_nostr_send_event(
    218     client: &RadrootsNostrClient,
    219     event: RadrootsNostrEventBuilder,
    220 ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> {
    221     client.send_event_builder(event).await
    222 }
    223 
    224 pub async fn radroots_nostr_fetch_event_by_id(
    225     client: &RadrootsNostrClient,
    226     id: &str,
    227 ) -> Result<RadrootsNostrEvent, RadrootsNostrError> {
    228     let event_id = RadrootsNostrEventId::parse(id)?;
    229     let filter = RadrootsNostrFilter::new().id(event_id);
    230     let events = client.fetch_events(filter, Duration::from_secs(10)).await?;
    231     let event = events
    232         .first()
    233         .ok_or_else(|| RadrootsNostrError::EventNotFound(event_id.to_hex()))?;
    234     Ok(event.clone())
    235 }
    236 
    237 #[cfg(test)]
    238 mod tests {
    239     use super::{RadrootsNostrClient, RadrootsNostrClientOptions};
    240 
    241     #[tokio::test]
    242     async fn signerless_client_has_no_signer() {
    243         let client = RadrootsNostrClient::new_signerless();
    244 
    245         assert!(!client.has_signer().await);
    246     }
    247 
    248     #[tokio::test]
    249     async fn signerless_client_with_options_has_no_signer() {
    250         let client = RadrootsNostrClient::new_signerless_with_options(
    251             RadrootsNostrClientOptions::new()
    252                 .automatic_authentication(true)
    253                 .verify_subscriptions(true),
    254         )
    255         .expect("signerless client");
    256 
    257         assert!(!client.has_signer().await);
    258     }
    259 }