client.rs (5665B)
1 #![forbid(unsafe_code)] 2 3 use std::time::Duration; 4 5 use crate::core::nip46::session::Nip46Session; 6 use crate::transport::jsonrpc::{RpcError, params::DEFAULT_TIMEOUT_SECS}; 7 use nostr::JsonUtil; 8 use nostr::UnsignedEvent; 9 use nostr::nips::{ 10 nip44, 11 nip46::{NostrConnectMessage, NostrConnectMethod, NostrConnectRequest, ResponseResult}, 12 }; 13 use radroots_nostr::prelude::{ 14 RadrootsNostrEventBuilder, RadrootsNostrFilter, RadrootsNostrKind, 15 RadrootsNostrRelayPoolNotification, RadrootsNostrSubscriptionId, RadrootsNostrTimestamp, 16 radroots_nostr_filter_tag, 17 }; 18 use tokio::sync::broadcast; 19 use tokio::time::sleep; 20 21 pub async fn sign_event( 22 session: &Nip46Session, 23 unsigned: UnsignedEvent, 24 label: &str, 25 ) -> Result<nostr::Event, RpcError> { 26 let req = NostrConnectRequest::SignEvent(unsigned); 27 let response = request(session, req, label).await?; 28 let response = response 29 .to_response(NostrConnectMethod::SignEvent) 30 .map_err(|e| RpcError::Other(format!("nip46 {label} failed: {e}")))?; 31 32 if let Some(error) = response.error { 33 return Err(RpcError::Other(format!("nip46 {label} error: {error}"))); 34 } 35 36 let event = match response.result { 37 Some(ResponseResult::SignEvent(event)) => *event, 38 Some(_) => { 39 return Err(RpcError::Other(format!( 40 "nip46 {label} unexpected response" 41 ))); 42 } 43 None => return Err(RpcError::Other(format!("nip46 {label} missing response"))), 44 }; 45 46 event 47 .verify() 48 .map_err(|e| RpcError::Other(format!("nip46 {label} invalid event: {e}")))?; 49 50 Ok(event) 51 } 52 53 pub async fn request( 54 session: &Nip46Session, 55 request: NostrConnectRequest, 56 label: &str, 57 ) -> Result<NostrConnectMessage, RpcError> { 58 session.client.connect().await; 59 session 60 .client 61 .wait_for_connection(Duration::from_secs(DEFAULT_TIMEOUT_SECS)) 62 .await; 63 64 let message = NostrConnectMessage::request(&request); 65 let request_id = message.id().to_string(); 66 let filter = response_filter(session, RadrootsNostrTimestamp::now(), label)?; 67 let notifications = session.client.notifications(); 68 let subscription = session 69 .client 70 .subscribe(filter, None) 71 .await 72 .map_err(|e| RpcError::Other(format!("nip46 {label} failed: {e}")))?; 73 let event = RadrootsNostrEventBuilder::nostr_connect( 74 &session.client_keys, 75 session.remote_signer_pubkey.clone(), 76 message, 77 ) 78 .map_err(|e| RpcError::Other(format!("nip46 {label} failed: {e}")))?; 79 80 if let Err(error) = session 81 .client 82 .send_event_builder(event) 83 .await 84 .map_err(|e| RpcError::Other(format!("nip46 {label} failed: {e}"))) 85 { 86 session.client.unsubscribe(&subscription.val).await; 87 return Err(error); 88 } 89 90 wait_for_response( 91 session, 92 &request_id, 93 label, 94 notifications, 95 &subscription.val, 96 ) 97 .await 98 } 99 100 fn response_filter( 101 session: &Nip46Session, 102 since: RadrootsNostrTimestamp, 103 label: &str, 104 ) -> Result<RadrootsNostrFilter, RpcError> { 105 let filter = RadrootsNostrFilter::new() 106 .kind(RadrootsNostrKind::NostrConnect) 107 .author(session.remote_signer_pubkey.clone()) 108 .since(since); 109 radroots_nostr_filter_tag(filter, "p", vec![session.client_pubkey.to_hex()]) 110 .map_err(|e| RpcError::Other(format!("nip46 {label} failed: {e}"))) 111 } 112 113 async fn wait_for_response( 114 session: &Nip46Session, 115 request_id: &str, 116 label: &str, 117 mut notifications: broadcast::Receiver<RadrootsNostrRelayPoolNotification>, 118 subscription_id: &RadrootsNostrSubscriptionId, 119 ) -> Result<NostrConnectMessage, RpcError> { 120 let timeout = sleep(Duration::from_secs(DEFAULT_TIMEOUT_SECS)); 121 tokio::pin!(timeout); 122 123 loop { 124 tokio::select! { 125 _ = &mut timeout => { 126 session.client.unsubscribe(subscription_id).await; 127 return Err(RpcError::Other(format!("nip46 {label} response not found"))); 128 } 129 msg = notifications.recv() => { 130 let notification = match msg { 131 Ok(notification) => notification, 132 Err(broadcast::error::RecvError::Lagged(_)) => continue, 133 Err(broadcast::error::RecvError::Closed) => { 134 session.client.unsubscribe(subscription_id).await; 135 return Err(RpcError::Other(format!("nip46 {label} notification closed"))); 136 } 137 }; 138 let RadrootsNostrRelayPoolNotification::Event { event, .. } = notification else { 139 continue; 140 }; 141 let event = (*event).clone(); 142 if event.kind != RadrootsNostrKind::NostrConnect 143 || event.pubkey != session.remote_signer_pubkey 144 { 145 continue; 146 } 147 let decrypted = nip44::decrypt( 148 session.client_keys.secret_key(), 149 &session.remote_signer_pubkey, 150 &event.content, 151 ) 152 .map_err(|e| RpcError::Other(format!("nip46 {label} failed: {e}")))?; 153 let message = NostrConnectMessage::from_json(&decrypted) 154 .map_err(|e| RpcError::Other(format!("nip46 {label} failed: {e}")))?; 155 if message.is_response() && message.id() == request_id { 156 session.client.unsubscribe(subscription_id).await; 157 return Ok(message); 158 } 159 } 160 } 161 } 162 }