lib

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

net.rs (7715B)


      1 use serde::Serialize;
      2 use std::sync::{Arc, Mutex, MutexGuard};
      3 
      4 use crate::error::{NetError, Result};
      5 #[cfg(feature = "nostr-client")]
      6 use crate::nostr_client::{NostrClientManager, NostrConnectionSnapshot};
      7 #[cfg(feature = "nostr-client")]
      8 use radroots_nostr_accounts::prelude::RadrootsNostrAccountsManager;
      9 #[cfg(feature = "nostr-client")]
     10 use radroots_nostr_signer::prelude::RadrootsNostrSignerCapability;
     11 
     12 #[derive(Debug, Clone, Serialize)]
     13 pub struct BuildInfo {
     14     pub crate_name: &'static str,
     15     pub crate_version: &'static str,
     16     #[serde(skip_serializing_if = "Option::is_none")]
     17     pub rustc: Option<&'static str>,
     18     #[serde(skip_serializing_if = "Option::is_none")]
     19     pub profile: Option<&'static str>,
     20     #[serde(skip_serializing_if = "Option::is_none")]
     21     pub git_sha: Option<&'static str>,
     22     #[serde(skip_serializing_if = "Option::is_none")]
     23     pub build_time_unix: Option<u64>,
     24 }
     25 
     26 #[derive(Debug, Clone, Serialize)]
     27 pub struct NetInfo {
     28     pub build: BuildInfo,
     29 }
     30 
     31 pub struct Net {
     32     pub info: NetInfo,
     33     pub config: crate::config::NetConfig,
     34 
     35     #[cfg(feature = "nostr-client")]
     36     pub accounts: RadrootsNostrAccountsManager,
     37 
     38     #[cfg(feature = "nostr-client")]
     39     pub signer: Option<RadrootsNostrSignerCapability>,
     40 
     41     #[cfg(feature = "nostr-client")]
     42     pub nostr: Option<NostrClientManager>,
     43 
     44     #[cfg(feature = "rt")]
     45     pub rt: Option<tokio::runtime::Runtime>,
     46 }
     47 
     48 impl Net {
     49     pub fn new(cfg: crate::config::NetConfig) -> Self {
     50         Self {
     51             info: NetInfo {
     52                 build: BuildInfo {
     53                     crate_name: env!("CARGO_PKG_NAME"),
     54                     crate_version: env!("CARGO_PKG_VERSION"),
     55                     rustc: option_env!("RUSTC_VERSION"),
     56                     profile: option_env!("PROFILE"),
     57                     git_sha: option_env!("GIT_HASH"),
     58                     build_time_unix: option_env!("BUILD_TIME_UNIX").and_then(|s| s.parse().ok()),
     59                 },
     60             },
     61             config: cfg,
     62             #[cfg(feature = "nostr-client")]
     63             accounts: RadrootsNostrAccountsManager::new_in_memory(),
     64             #[cfg(feature = "nostr-client")]
     65             signer: None,
     66             #[cfg(feature = "nostr-client")]
     67             nostr: None,
     68             #[cfg(feature = "rt")]
     69             rt: None,
     70         }
     71     }
     72 
     73     #[cfg(feature = "rt")]
     74     pub fn init_managed_runtime(&mut self, worker_threads: Option<usize>) -> Result<()> {
     75         if self.rt.is_some() {
     76             return Ok(());
     77         }
     78 
     79         let threads = worker_threads.unwrap_or_else(|| {
     80             std::thread::available_parallelism()
     81                 .map(|n| n.get())
     82                 .unwrap_or(1)
     83                 .max(1)
     84         });
     85 
     86         let rt = tokio::runtime::Builder::new_multi_thread()
     87             .worker_threads(threads)
     88             .enable_all()
     89             .build()
     90             .map_err(|e| NetError::msg(format!("failed to build tokio runtime: {e}")))?;
     91 
     92         self.rt = Some(rt);
     93         Ok(())
     94     }
     95 
     96     #[cfg(feature = "nostr-client")]
     97     pub fn nostr_set_default_relays(&mut self, urls: &[String]) -> Result<()> {
     98         if self.nostr.is_none() {
     99             let keys = self.selected_nostr_keys().ok_or(NetError::MissingKey)?;
    100             let rt = self
    101                 .rt
    102                 .as_ref()
    103                 .ok_or_else(|| NetError::msg("tokio runtime missing"))?;
    104             self.nostr = Some(NostrClientManager::new(keys, rt.handle().clone()));
    105         }
    106         if let Some(n) = &self.nostr {
    107             n.set_relays(urls);
    108         }
    109         Ok(())
    110     }
    111 
    112     #[cfg(feature = "nostr-client")]
    113     pub fn nostr_connect_if_key_present(&mut self) -> Result<()> {
    114         let Some(keys) = self.selected_nostr_keys() else {
    115             return Ok(());
    116         };
    117         let rt = self
    118             .rt
    119             .as_ref()
    120             .ok_or_else(|| NetError::msg("tokio runtime missing"))?;
    121         if self.nostr.is_none() {
    122             self.nostr = Some(NostrClientManager::new(keys, rt.handle().clone()));
    123         }
    124         if let Some(n) = &self.nostr {
    125             n.connect()?;
    126         }
    127         Ok(())
    128     }
    129 
    130     #[cfg(feature = "nostr-client")]
    131     pub fn nostr_connection_snapshot(&self) -> Option<NostrConnectionSnapshot> {
    132         self.nostr.as_ref().map(|n| n.snapshot())
    133     }
    134 
    135     #[cfg(feature = "nostr-client")]
    136     pub fn set_nostr_signer(&mut self, signer: Option<RadrootsNostrSignerCapability>) {
    137         self.signer = signer;
    138     }
    139 
    140     #[cfg(feature = "nostr-client")]
    141     pub fn selected_nostr_signer(&self) -> Option<RadrootsNostrSignerCapability> {
    142         self.signer
    143             .clone()
    144             .or_else(|| self.accounts.default_signer_capability().ok().flatten())
    145     }
    146 
    147     #[cfg(feature = "nostr-client")]
    148     pub fn selected_nostr_keys(&self) -> Option<radroots_nostr::prelude::RadrootsNostrKeys> {
    149         let signer = self.selected_nostr_signer()?;
    150         self.accounts
    151             .resolve_signing_identity_for_signer(&signer)
    152             .ok()
    153             .flatten()
    154             .map(|identity| identity.into_keys())
    155     }
    156 }
    157 
    158 #[derive(Clone)]
    159 pub struct NetHandle(Arc<Mutex<Net>>);
    160 
    161 impl NetHandle {
    162     pub fn from_inner(inner: Net) -> Self {
    163         Self(Arc::new(Mutex::new(inner)))
    164     }
    165 
    166     pub fn lock(&self) -> Result<MutexGuard<'_, Net>> {
    167         self.0.lock().map_err(|_| NetError::Poisoned)
    168     }
    169 }
    170 
    171 #[cfg(test)]
    172 mod tests {
    173     use crate::builder::NetBuilder;
    174     #[cfg(feature = "nostr-client")]
    175     use radroots_identity::RadrootsIdentity;
    176     #[cfg(feature = "nostr-client")]
    177     use radroots_nostr_signer::prelude::{
    178         RadrootsNostrRemoteSessionSignerCapability, RadrootsNostrSignerCapability,
    179         RadrootsNostrSignerConnectionId,
    180     };
    181 
    182     #[test]
    183     fn builds_minimal() {
    184         let cfg = crate::config::NetConfig::default();
    185         let handle = NetBuilder::new().config(cfg).build();
    186         assert!(handle.is_ok());
    187     }
    188 
    189     #[test]
    190     fn lock_is_ok() {
    191         let cfg = crate::config::NetConfig::default();
    192         let handle = NetBuilder::new().config(cfg).build().unwrap();
    193         let guard = handle.lock();
    194         assert!(guard.is_ok());
    195     }
    196 
    197     #[cfg(feature = "rt")]
    198     #[test]
    199     fn builds_with_managed_rt() {
    200         let cfg = crate::config::NetConfig::default();
    201         let handle = crate::builder::NetBuilder::new()
    202             .config(cfg)
    203             .manage_runtime(true)
    204             .build()
    205             .expect("build with runtime");
    206 
    207         let rt_present = handle.lock().unwrap().rt.is_some();
    208         assert!(rt_present);
    209     }
    210 
    211     #[cfg(feature = "nostr-client")]
    212     #[test]
    213     fn selected_nostr_keys_reflects_selected_signing_account() {
    214         let cfg = crate::config::NetConfig::default();
    215         let mut net = crate::Net::new(cfg);
    216         assert!(net.selected_nostr_keys().is_none());
    217         assert!(net.selected_nostr_signer().is_none());
    218 
    219         net.accounts
    220             .generate_identity(Some("primary".into()), true)
    221             .expect("generate account");
    222         assert!(net.selected_nostr_keys().is_some());
    223         assert!(net.selected_nostr_signer().is_some());
    224 
    225         let remote = RadrootsNostrSignerCapability::RemoteSession(Box::new(
    226             RadrootsNostrRemoteSessionSignerCapability::new(
    227                 RadrootsNostrSignerConnectionId::new_v7(),
    228                 RadrootsIdentity::generate().to_public(),
    229                 RadrootsIdentity::generate().to_public(),
    230             ),
    231         ));
    232         net.set_nostr_signer(Some(remote.clone()));
    233         assert_eq!(net.selected_nostr_signer(), Some(remote));
    234         assert!(net.selected_nostr_keys().is_none());
    235 
    236         net.set_nostr_signer(None);
    237         assert!(net.selected_nostr_keys().is_some());
    238     }
    239 }