lib

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

commit d4cf0b41c681051489c3fc3c8501ba7ecbacf690
parent 34dc13cf36e883a143adb3f2be164b68a49cc50c
Author: triesap <tyson@radroots.org>
Date:   Fri, 17 Jul 2026 21:46:51 +0000

nostr: expose bounded relay subscription controls

- add relay-targeted subscribe and publish helpers
- expose explicit subscription cancellation through Radroots types
- preserve caller ownership without detached listener tasks
- qualify native behavior and WASM compilation

Diffstat:
Mcrates/nostr/Cargo.toml | 4+++-
Mcrates/nostr/src/client.rs | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml @@ -34,7 +34,9 @@ nip17 = ["std", "codec", "nostr/nip44", "nostr/nip59"] [dependencies] radroots_event = { workspace = true, optional = true, default-features = false } radroots_event_codec = { workspace = true, optional = true, default-features = false } -radroots_identity = { workspace = true, optional = true, default-features = true } +radroots_identity = { workspace = true, optional = true, default-features = false, features = [ + "std", +] } nostr = { workspace = true, features = ["nip04"] } nostr-sdk = { workspace = true, optional = true } reqwest = { workspace = true, optional = true, default-features = false, features = [ diff --git a/crates/nostr/src/client.rs b/crates/nostr/src/client.rs @@ -184,6 +184,22 @@ impl RadrootsNostrClient { Ok(self.inner.subscribe(filter, opts).await?) } + pub async fn subscribe_to_relays( + &self, + relays: &[RadrootsNostrRelayUrl], + filter: RadrootsNostrFilter, + opts: Option<RadrootsNostrSubscribeAutoCloseOptions>, + ) -> Result<RadrootsNostrOutput<RadrootsNostrSubscriptionId>, RadrootsNostrError> { + Ok(self + .inner + .subscribe_to(relays.iter().cloned(), filter, opts) + .await?) + } + + pub async fn unsubscribe(&self, subscription_id: &RadrootsNostrSubscriptionId) { + self.inner.unsubscribe(subscription_id).await; + } + pub async fn send_event_builder( &self, event: RadrootsNostrEventBuilder, @@ -198,6 +214,17 @@ impl RadrootsNostrClient { Ok(self.inner.send_event(event).await?) } + pub async fn send_event_to_relays( + &self, + relays: &[RadrootsNostrRelayUrl], + event: &RadrootsNostrEvent, + ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { + Ok(self + .inner + .send_event_to(relays.iter().cloned(), event) + .await?) + } + pub async fn set_metadata( &self, md: &RadrootsNostrMetadata, @@ -237,6 +264,10 @@ pub async fn radroots_nostr_fetch_event_by_id( #[cfg(test)] mod tests { use super::{RadrootsNostrClient, RadrootsNostrClientOptions}; + use crate::types::{ + RadrootsNostrEventBuilder, RadrootsNostrFilter, RadrootsNostrKeys, RadrootsNostrKind, + RadrootsNostrSecretKey, RadrootsNostrSubscriptionId, + }; #[tokio::test] async fn signerless_client_has_no_signer() { @@ -256,4 +287,26 @@ mod tests { assert!(!client.has_signer().await); } + + #[tokio::test] + async fn targeted_operations_require_relays_and_cleanup_is_explicit() { + let keys = RadrootsNostrKeys::new( + RadrootsNostrSecretKey::from_slice(&[1_u8; 32]).expect("test secret key"), + ); + let client = RadrootsNostrClient::new(keys.clone()); + let subscription = client + .subscribe_to_relays(&[], RadrootsNostrFilter::new(), None) + .await; + assert!(subscription.is_err()); + + let event = RadrootsNostrEventBuilder::new(RadrootsNostrKind::TextNote, "test") + .sign_with_keys(&keys) + .expect("test event"); + let published = client.send_event_to_relays(&[], &event).await; + assert!(published.is_err()); + + client + .unsubscribe(&RadrootsNostrSubscriptionId::new("missing")) + .await; + } }