lib

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

commit 2969de4edb2eb70fecd5288d048d2ffdaf2730ac
parent 107e8a4f7248720cff7e87d3f865f84d644ef7f0
Author: triesap <tyson@radroots.org>
Date:   Sun,  4 Jan 2026 16:28:31 +0000

nostr: add client options and filter tag helper


- Introduce configurable RadrootsNostrClientOptions builder
- Support proxy and connection tuning outside wasm targets
- Add validated single-letter filter tag helper
- Extend error enum for client config and filter tags

Diffstat:
Mnostr/Cargo.toml | 1+
Mnostr/src/client.rs | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mnostr/src/error.rs | 7+++++++
Mnostr/src/filter.rs | 20++++++++++++++++++++
Mnostr/src/lib.rs | 2++
5 files changed, 115 insertions(+), 1 deletion(-)

diff --git a/nostr/Cargo.toml b/nostr/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true [features] default = ["std"] std = [] +os-rng = ["nostr/os-rng"] client = ["std", "dep:nostr-sdk", "dep:radroots-identity"] codec = ["dep:radroots-events", "dep:radroots-events-codec", "radroots-events-codec/nostr"] events = ["dep:radroots-events", "radroots-events/std", "radroots-events/serde"] diff --git a/nostr/src/client.rs b/nostr/src/client.rs @@ -3,8 +3,10 @@ use core::ops::Deref; use core::time::Duration; use std::collections::HashMap; +#[cfg(not(target_arch = "wasm32"))] +use std::net::SocketAddr; -use nostr_sdk::Client; +use nostr_sdk::{Client, ClientBuilder, ClientOptions}; use radroots_identity::RadrootsIdentity; use crate::error::RadrootsNostrError; @@ -28,6 +30,79 @@ pub struct RadrootsNostrClient { inner: Client, } +#[derive(Debug, Clone, Default)] +pub struct RadrootsNostrClientOptions { + automatic_authentication: Option<bool>, + max_avg_latency_ms: Option<u64>, + verify_subscriptions: Option<bool>, + ban_relay_on_mismatch: Option<bool>, + #[cfg(not(target_arch = "wasm32"))] + proxy: Option<SocketAddr>, +} + +impl RadrootsNostrClientOptions { + pub fn new() -> Self { + Self::default() + } + + pub fn automatic_authentication(mut self, enabled: bool) -> Self { + self.automatic_authentication = Some(enabled); + self + } + + pub fn max_avg_latency_ms(mut self, max_ms: u64) -> Self { + self.max_avg_latency_ms = Some(max_ms); + self + } + + pub fn verify_subscriptions(mut self, enabled: bool) -> Self { + self.verify_subscriptions = Some(enabled); + self + } + + pub fn ban_relay_on_mismatch(mut self, enabled: bool) -> Self { + self.ban_relay_on_mismatch = Some(enabled); + self + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn proxy_addr(mut self, addr: SocketAddr) -> Self { + self.proxy = Some(addr); + self + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn proxy_str(mut self, addr: &str) -> Result<Self, RadrootsNostrError> { + let parsed: SocketAddr = addr + .parse() + .map_err(|err| RadrootsNostrError::ClientConfigError(err.to_string()))?; + self.proxy = Some(parsed); + Ok(self) + } + + fn to_client_options(&self) -> Result<ClientOptions, RadrootsNostrError> { + let mut opts = ClientOptions::new(); + if let Some(enabled) = self.automatic_authentication { + opts = opts.automatic_authentication(enabled); + } + if let Some(max_ms) = self.max_avg_latency_ms { + opts = opts.max_avg_latency(Duration::from_millis(max_ms)); + } + if let Some(enabled) = self.verify_subscriptions { + opts = opts.verify_subscriptions(enabled); + } + if let Some(enabled) = self.ban_relay_on_mismatch { + opts = opts.ban_relay_on_mismatch(enabled); + } + #[cfg(not(target_arch = "wasm32"))] + if let Some(proxy) = self.proxy { + let connection = nostr_sdk::client::options::Connection::new().proxy(proxy); + opts = opts.connection(connection); + } + Ok(opts) + } +} + impl RadrootsNostrClient { pub fn new(keys: RadrootsNostrKeys) -> Self { Self { @@ -35,6 +110,15 @@ impl RadrootsNostrClient { } } + pub fn from_keys_with_options( + keys: RadrootsNostrKeys, + options: RadrootsNostrClientOptions, + ) -> Result<Self, RadrootsNostrError> { + let opts = options.to_client_options()?; + let inner = ClientBuilder::new().signer(keys).opts(opts).build(); + Ok(Self { inner }) + } + pub fn new_with_monitor(keys: RadrootsNostrKeys, monitor: RadrootsNostrMonitor) -> Self { let inner = Client::builder().signer(keys).monitor(monitor).build(); Self { inner } diff --git a/nostr/src/error.rs b/nostr/src/error.rs @@ -10,6 +10,10 @@ pub enum RadrootsNostrError { #[error("Database error: {0}")] DatabaseError(#[from] nostr_sdk::prelude::DatabaseError), + #[cfg(feature = "client")] + #[error("Client configuration error: {0}")] + ClientConfigError(String), + #[error("Event error: {0}")] EventError(#[from] nostr::event::Error), @@ -22,6 +26,9 @@ pub enum RadrootsNostrError { #[error("Key error: {0}")] KeyError(#[from] nostr::key::Error), + #[error("Filter tag error: {0}")] + FilterTagError(String), + #[cfg(feature = "codec")] #[error("Profile encode error: {0}")] ProfileEncodeError(#[from] radroots_events_codec::profile::error::ProfileEncodeError), diff --git a/nostr/src/filter.rs b/nostr/src/filter.rs @@ -1,5 +1,25 @@ +use crate::error::RadrootsNostrError; use crate::types::{RadrootsNostrFilter, RadrootsNostrKind, RadrootsNostrTimestamp}; +pub fn radroots_nostr_filter_tag( + filter: RadrootsNostrFilter, + tag: &str, + values: Vec<String>, +) -> Result<RadrootsNostrFilter, RadrootsNostrError> { + let mut chars = tag.chars(); + let tag_char = chars + .next() + .ok_or_else(|| RadrootsNostrError::FilterTagError("tag is empty".to_string()))?; + if chars.next().is_some() { + return Err(RadrootsNostrError::FilterTagError( + "tag must be a single letter".to_string(), + )); + } + let tag_key = nostr::filter::SingleLetterTag::from_char(tag_char) + .map_err(|err| RadrootsNostrError::FilterTagError(err.to_string()))?; + Ok(filter.custom_tags(tag_key, values)) +} + pub fn radroots_nostr_kind(kind: u16) -> RadrootsNostrKind { RadrootsNostrKind::Custom(kind) } diff --git a/nostr/src/lib.rs b/nostr/src/lib.rs @@ -43,6 +43,7 @@ pub mod prelude { pub use crate::client::{ radroots_nostr_fetch_event_by_id, radroots_nostr_send_event, + RadrootsNostrClientOptions, RadrootsNostrClient, }; @@ -50,6 +51,7 @@ pub mod prelude { pub use crate::filter::{ radroots_nostr_filter_kind, radroots_nostr_filter_new_events, + radroots_nostr_filter_tag, radroots_nostr_kind, };