lib

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

commit 0d6a53f85eca65d7ff9984602b8a9588de12745b
parent 1f7200ab0b41c0dd14d30ac76d702ab819c1c55c
Author: triesap <tyson@radroots.org>
Date:   Mon, 13 Apr 2026 08:11:49 +0000

sdk: rewire listing publish around session handles

Diffstat:
Mcrates/sdk/src/client.rs | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mcrates/sdk/tests/radrootsd.rs | 148++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
2 files changed, 180 insertions(+), 56 deletions(-)

diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs @@ -410,10 +410,6 @@ impl SdkRadrootsdSignerSessionHandle { pub fn relays(&self) -> &[String] { self.relays.as_slice() } - - pub(crate) fn session_id(&self) -> &str { - self.session.session_id() - } } #[cfg(feature = "radrootsd-client")] @@ -434,21 +430,52 @@ impl From<radrootsd::SdkRadrootsdSignerSessionConnectResponse> for SdkRadrootsdS #[cfg(feature = "radrootsd-client")] #[derive(Clone, PartialEq, Eq)] pub struct SdkRadrootsdListingPublishOptions { - pub signer_session_id: String, - pub idempotency_key: Option<String>, + session: SdkRadrootsdSignerSessionRef, + idempotency_key: Option<String>, + signer_authority: Option<radrootsd::SdkRadrootsdSignerAuthority>, } #[cfg(feature = "radrootsd-client")] impl SdkRadrootsdListingPublishOptions { - pub fn new(signer_session_id: impl Into<String>) -> Self { + pub fn from_signer_session(session: &SdkRadrootsdSignerSessionHandle) -> Self { Self { - signer_session_id: signer_session_id.into(), + session: session.session().clone(), idempotency_key: None, + signer_authority: None, } } - pub fn from_signer_session(session: &SdkRadrootsdSignerSessionHandle) -> Self { - Self::new(session.session_id()) + pub fn from_signer_session_ref(session: &SdkRadrootsdSignerSessionRef) -> Self { + Self { + session: session.clone(), + idempotency_key: None, + signer_authority: None, + } + } + + pub fn with_idempotency_key(mut self, idempotency_key: impl Into<String>) -> Self { + self.idempotency_key = Some(idempotency_key.into()); + self + } + + pub fn with_signer_authority( + mut self, + signer_authority: radrootsd::SdkRadrootsdSignerAuthority, + ) -> Self { + self.signer_authority = Some(signer_authority); + self + } + + pub fn session(&self) -> &SdkRadrootsdSignerSessionRef { + &self.session + } + + pub fn idempotency_key(&self) -> Option<&str> { + self.idempotency_key.as_deref() + } + + pub fn signer_authority(&self) -> Option<&radrootsd::SdkRadrootsdSignerAuthority> { + self.signer_authority.as_ref() } } @@ -456,8 +483,9 @@ impl SdkRadrootsdListingPublishOptions { impl fmt::Debug for SdkRadrootsdListingPublishOptions { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut debug = f.debug_struct("SdkRadrootsdListingPublishOptions"); - debug.field("signer_session_id", &"<redacted>"); + debug.field("session", &self.session); debug.field("idempotency_key", &self.idempotency_key); + debug.field("signer_authority", &self.signer_authority); debug.finish() } } @@ -1036,14 +1064,27 @@ impl<'a> ListingClient<'a> { pub async fn publish_listing_via_radrootsd( &self, listing_value: &listing::RadrootsListing, + session: &SdkRadrootsdSignerSessionHandle, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + self.publish_listing_via_radrootsd_with_options( + listing_value, + &SdkRadrootsdListingPublishOptions::from_signer_session(session), + ) + .await + } + + #[cfg(feature = "radrootsd-client")] + pub async fn publish_listing_via_radrootsd_with_options( + &self, + listing_value: &listing::RadrootsListing, options: &SdkRadrootsdListingPublishOptions, ) -> Result<SdkPublishReceipt, SdkPublishError> { let request = radrootsd::SdkRadrootsdListingPublishRequest { listing: listing_value.clone(), kind: Some(KIND_LISTING), - signer_session_id: options.signer_session_id.clone(), - signer_authority: None, - idempotency_key: options.idempotency_key.clone(), + signer_session_id: options.session().session_id().to_owned(), + signer_authority: options.signer_authority().cloned(), + idempotency_key: options.idempotency_key().map(str::to_owned), }; self.client.publish_listing_via_radrootsd(&request).await } @@ -1052,6 +1093,19 @@ impl<'a> ListingClient<'a> { pub async fn publish_draft_via_radrootsd( &self, draft: listing::RadrootsListingDraft, + session: &SdkRadrootsdSignerSessionHandle, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + self.publish_draft_via_radrootsd_with_options( + draft, + &SdkRadrootsdListingPublishOptions::from_signer_session(session), + ) + .await + } + + #[cfg(feature = "radrootsd-client")] + pub async fn publish_draft_via_radrootsd_with_options( + &self, + draft: listing::RadrootsListingDraft, options: &SdkRadrootsdListingPublishOptions, ) -> Result<SdkPublishReceipt, SdkPublishError> { let parts = draft.into_wire_parts(); @@ -1066,9 +1120,9 @@ impl<'a> ListingClient<'a> { }; let request = radrootsd::SdkRadrootsdListingPublishRequest::from_event( &event, - options.signer_session_id.clone(), - None, - options.idempotency_key.clone(), + options.session().session_id().to_owned(), + options.signer_authority().cloned(), + options.idempotency_key().map(str::to_owned), ) .map_err(|err| SdkPublishError::Encode(err.to_string()))?; self.client.publish_listing_via_radrootsd(&request).await diff --git a/crates/sdk/tests/radrootsd.rs b/crates/sdk/tests/radrootsd.rs @@ -298,6 +298,35 @@ fn sample_session_view_json(session_id: &str) -> Value { }) } +async fn connected_bunker_session_handle( + session_id: &str, +) -> TestResult<SdkRadrootsdSignerSessionHandle> { + let (server, _) = JsonRpcServer::spawn( + Some("Bearer sdk-secret"), + json!({ + "jsonrpc": "2.0", + "id": "radroots-sdk-nip46-connect", + "result": { + "session_id": session_id, + "mode": "Bunker", + "remote_signer_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "client_pubkey": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "relays": ["wss://radroots.org"] + } + }), + ) + .await?; + let client = radrootsd_test_client(server.endpoint())?; + client + .radrootsd() + .signer_sessions() + .connect_bunker( + "bunker://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?relay=wss%3A%2F%2Fradroots.org&secret=shared-secret", + ) + .await + .map_err(Into::into) +} + #[test] fn radrootsd_debug_redacts_signer_session_values() { let signer_authority = SdkRadrootsdSignerAuthority { @@ -356,14 +385,6 @@ fn radrootsd_debug_redacts_signer_session_values() { assert!(!receipt_debug.contains("session-123")); assert!(receipt_debug.contains("<redacted>")); - let options = SdkRadrootsdListingPublishOptions { - signer_session_id: "session-123".to_owned(), - idempotency_key: Some("idem-1".to_owned()), - }; - let options_debug = format!("{options:?}"); - assert!(!options_debug.contains("session-123")); - assert!(options_debug.contains("<redacted>")); - let connect_request = SdkRadrootsdSignerSessionConnectRequest::nostrconnect( "nostrconnect://bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb?relay=wss%3A%2F%2Fradroots.org&secret=shared-secret", "client-secret-key", @@ -827,23 +848,15 @@ async fn radrootsd_listing_publish_accepts_sdk_built_draft() -> TestResult<()> { ) .await?; - let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Production); - config.transport = SdkTransportMode::Radrootsd; - config.signer = SignerConfig::Nip46; - config.radrootsd = RadrootsdConfig { - endpoint: Some(server.endpoint().to_owned()), - auth: RadrootsdAuth::BearerToken("sdk-secret".to_owned()), - }; - let client = RadrootsSdkClient::from_config(config)?; + let handle = connected_bunker_session_handle("session-123").await?; + let client = radrootsd_test_client(server.endpoint())?; let draft = client.listing().build_draft(&sample_listing())?; - let options = SdkRadrootsdListingPublishOptions { - signer_session_id: "session-123".to_owned(), - idempotency_key: Some("idem-1".to_owned()), - }; + let options = SdkRadrootsdListingPublishOptions::from_signer_session(&handle) + .with_idempotency_key("idem-1"); let receipt = client .listing() - .publish_draft_via_radrootsd(draft, &options) + .publish_draft_via_radrootsd_with_options(draft, &options) .await?; let request_json = request_rx.await?; @@ -910,26 +923,18 @@ async fn radrootsd_listing_publish_accepts_typed_listing_value() -> TestResult<( ) .await?; - let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Production); - config.transport = SdkTransportMode::Radrootsd; - config.signer = SignerConfig::Nip46; - config.radrootsd = RadrootsdConfig { - endpoint: Some(server.endpoint().to_owned()), - auth: RadrootsdAuth::BearerToken("sdk-secret".to_owned()), - }; - let client = RadrootsSdkClient::from_config(config)?; - let mut options = SdkRadrootsdListingPublishOptions::new("session-456"); - options.idempotency_key = Some("idem-2".to_owned()); + let handle = connected_bunker_session_handle("session-456").await?; + let client = radrootsd_test_client(server.endpoint())?; let receipt = client .listing() - .publish_listing_via_radrootsd(&sample_listing(), &options) + .publish_listing_via_radrootsd(&sample_listing(), &handle) .await?; let request_json = request_rx.await?; assert_eq!(request_json["method"], "bridge.listing.publish"); assert_eq!(request_json["params"]["signer_session_id"], "session-456"); - assert_eq!(request_json["params"]["idempotency_key"], "idem-2"); + assert!(request_json["params"]["idempotency_key"].is_null()); assert_eq!(request_json["params"]["kind"], 30402); assert_eq!( request_json["params"]["listing"]["d_tag"], @@ -944,16 +949,81 @@ async fn radrootsd_listing_publish_accepts_typed_listing_value() -> TestResult<( } #[tokio::test] +async fn radrootsd_listing_publish_with_options_forwards_typed_continuity_metadata() +-> TestResult<()> { + let (server, request_rx) = JsonRpcServer::spawn( + Some("Bearer sdk-secret"), + json!({ + "jsonrpc": "2.0", + "id": "radroots-sdk-listing-publish", + "result": { + "deduplicated": false, + "job": { + "job_id": "job-3", + "command": "bridge.listing.publish", + "status": "published", + "terminal": true, + "recovered_after_restart": false, + "signer_mode": "nip46_session:session-789", + "signer_session_id": "session-789", + "event_kind": 30402, + "event_id": "event-3", + "event_addr": "30402:seller:listing-3", + "relay_count": 1, + "acknowledged_relay_count": 1 + } + } + }), + ) + .await?; + + let handle = connected_bunker_session_handle("session-789").await?; + let client = radrootsd_test_client(server.endpoint())?; + let options = SdkRadrootsdListingPublishOptions::from_signer_session(&handle) + .with_idempotency_key("idem-3") + .with_signer_authority(SdkRadrootsdSignerAuthority { + provider_runtime_id: "runtime-1".to_owned(), + account_identity_id: "identity-1".to_owned(), + provider_signer_session_id: Some("provider-session-123".to_owned()), + }); + + let receipt = client + .listing() + .publish_listing_via_radrootsd_with_options(&sample_listing(), &options) + .await?; + let request_json = request_rx.await?; + + assert_eq!(request_json["method"], "bridge.listing.publish"); + assert_eq!(request_json["params"]["signer_session_id"], "session-789"); + assert_eq!(request_json["params"]["idempotency_key"], "idem-3"); + assert_eq!( + request_json["params"]["signer_authority"]["provider_runtime_id"], + "runtime-1" + ); + assert_eq!( + request_json["params"]["signer_authority"]["account_identity_id"], + "identity-1" + ); + assert_eq!( + request_json["params"]["signer_authority"]["provider_signer_session_id"], + "provider-session-123" + ); + assert_eq!(receipt.event_id, Some("event-3".to_owned())); + + Ok(()) +} + +#[tokio::test] async fn radrootsd_listing_publish_rejects_draft_only_signer_mode() -> TestResult<()> { let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Production); config.transport = SdkTransportMode::Radrootsd; config.signer = SignerConfig::DraftOnly; let client = RadrootsSdkClient::from_config(config)?; - let options = SdkRadrootsdListingPublishOptions::new("session-123"); + let handle = connected_bunker_session_handle("session-123").await?; let error = client .listing() - .publish_listing_via_radrootsd(&sample_listing(), &options) + .publish_listing_via_radrootsd(&sample_listing(), &handle) .await .expect_err("unsupported signer mode"); @@ -976,11 +1046,11 @@ async fn radrootsd_listing_publish_rejects_local_identity_signer_mode() -> TestR config.transport = SdkTransportMode::Radrootsd; config.signer = SignerConfig::LocalIdentity; let client = RadrootsSdkClient::from_config(config)?; - let options = SdkRadrootsdListingPublishOptions::new("session-123"); + let handle = connected_bunker_session_handle("session-123").await?; let error = client .listing() - .publish_listing_via_radrootsd(&sample_listing(), &options) + .publish_listing_via_radrootsd(&sample_listing(), &handle) .await .expect_err("unsupported signer mode"); @@ -1000,11 +1070,11 @@ async fn radrootsd_listing_publish_rejects_local_identity_signer_mode() -> TestR #[tokio::test] async fn radrootsd_listing_publish_rejects_relay_transport_mode() -> TestResult<()> { let client = RadrootsSdkClient::from_config(RadrootsSdkConfig::production())?; - let options = SdkRadrootsdListingPublishOptions::new("session-123"); + let handle = connected_bunker_session_handle("session-123").await?; let error = client .listing() - .publish_listing_via_radrootsd(&sample_listing(), &options) + .publish_listing_via_radrootsd(&sample_listing(), &handle) .await .expect_err("unsupported transport");