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 }