client.rs (5871B)
1 use crate::error::RadrootsNostrConnectError; 2 use crate::message::{ 3 RADROOTS_NOSTR_CONNECT_RPC_KIND, RadrootsNostrConnectRequest, 4 RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse, 5 RadrootsNostrConnectResponseEnvelope, 6 }; 7 use crate::method::RadrootsNostrConnectMethod; 8 use nostr::nips::nip44::{self, Version}; 9 use nostr::{Event, EventBuilder, Keys, Kind, PublicKey, RelayUrl, Tag}; 10 use std::future::Future; 11 use std::pin::Pin; 12 13 pub type RadrootsNostrConnectClientTransportFuture<'a, T> = 14 Pin<Box<dyn Future<Output = Result<T, RadrootsNostrConnectError>> + Send + 'a>>; 15 16 #[derive(Debug, Clone, PartialEq, Eq)] 17 pub struct RadrootsNostrConnectClientTarget { 18 pub remote_signer_public_key: PublicKey, 19 pub relays: Vec<RelayUrl>, 20 } 21 22 impl RadrootsNostrConnectClientTarget { 23 pub fn new(remote_signer_public_key: PublicKey, relays: Vec<RelayUrl>) -> Self { 24 Self { 25 remote_signer_public_key, 26 relays, 27 } 28 } 29 } 30 31 #[derive(Debug, Clone, PartialEq, Eq)] 32 pub struct RadrootsNostrConnectClientRequest { 33 pub request_id: String, 34 pub request: RadrootsNostrConnectRequest, 35 } 36 37 impl RadrootsNostrConnectClientRequest { 38 pub fn new(request_id: impl Into<String>, request: RadrootsNostrConnectRequest) -> Self { 39 Self { 40 request_id: request_id.into(), 41 request, 42 } 43 } 44 45 pub fn method(&self) -> RadrootsNostrConnectMethod { 46 self.request.method() 47 } 48 49 pub fn into_message(self) -> RadrootsNostrConnectRequestMessage { 50 RadrootsNostrConnectRequestMessage::new(self.request_id, self.request) 51 } 52 } 53 54 #[derive(Debug, Clone, PartialEq, Eq)] 55 pub enum RadrootsNostrConnectClientProgress { 56 AuthChallenge { url: String }, 57 } 58 59 #[derive(Debug, Clone, PartialEq, Eq)] 60 pub enum RadrootsNostrConnectClientEventOutcome { 61 Ignore, 62 Progress(RadrootsNostrConnectClientProgress), 63 Response(RadrootsNostrConnectResponse), 64 } 65 66 pub trait RadrootsNostrConnectClientTransport { 67 fn publish_request_event<'a>( 68 &'a mut self, 69 event: Event, 70 ) -> RadrootsNostrConnectClientTransportFuture<'a, ()>; 71 72 fn next_response_event<'a>( 73 &'a mut self, 74 ) -> RadrootsNostrConnectClientTransportFuture<'a, Event>; 75 } 76 77 pub fn build_request_event( 78 client_keys: &Keys, 79 target: &RadrootsNostrConnectClientTarget, 80 message: RadrootsNostrConnectRequestMessage, 81 ) -> Result<Event, RadrootsNostrConnectError> { 82 let payload = serde_json::to_string(&message).map_err(RadrootsNostrConnectError::from)?; 83 let ciphertext = nip44::encrypt( 84 client_keys.secret_key(), 85 &target.remote_signer_public_key, 86 payload, 87 Version::V2, 88 ) 89 .map_err(|error| RadrootsNostrConnectError::Encrypt { 90 reason: error.to_string(), 91 })?; 92 93 EventBuilder::new(Kind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), ciphertext) 94 .tag(Tag::public_key(target.remote_signer_public_key)) 95 .sign_with_keys(client_keys) 96 .map_err(|error| RadrootsNostrConnectError::Sign { 97 reason: error.to_string(), 98 }) 99 } 100 101 pub fn parse_response_event( 102 client_keys: &Keys, 103 target: &RadrootsNostrConnectClientTarget, 104 request_id: &str, 105 method: &RadrootsNostrConnectMethod, 106 event: &Event, 107 ) -> Result<RadrootsNostrConnectClientEventOutcome, RadrootsNostrConnectError> { 108 if event.kind != Kind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND) { 109 return Ok(RadrootsNostrConnectClientEventOutcome::Ignore); 110 } 111 112 if event.pubkey != target.remote_signer_public_key { 113 return Ok(RadrootsNostrConnectClientEventOutcome::Ignore); 114 } 115 116 let client_public_key = client_keys.public_key(); 117 if !event 118 .tags 119 .public_keys() 120 .any(|public_key| *public_key == client_public_key) 121 { 122 return Ok(RadrootsNostrConnectClientEventOutcome::Ignore); 123 } 124 125 let decrypted = nip44::decrypt( 126 client_keys.secret_key(), 127 &target.remote_signer_public_key, 128 &event.content, 129 ) 130 .map_err(|error| RadrootsNostrConnectError::Decrypt { 131 reason: error.to_string(), 132 })?; 133 134 let envelope: RadrootsNostrConnectResponseEnvelope = 135 serde_json::from_str(&decrypted).map_err(RadrootsNostrConnectError::from)?; 136 if envelope.id != request_id { 137 return Ok(RadrootsNostrConnectClientEventOutcome::Ignore); 138 } 139 140 let response = RadrootsNostrConnectResponse::from_envelope(method, envelope)?; 141 Ok(match response { 142 RadrootsNostrConnectResponse::AuthUrl(url) => { 143 RadrootsNostrConnectClientEventOutcome::Progress( 144 RadrootsNostrConnectClientProgress::AuthChallenge { url }, 145 ) 146 } 147 response => RadrootsNostrConnectClientEventOutcome::Response(response), 148 }) 149 } 150 151 pub async fn execute_request_with_transport<T, F>( 152 client_keys: &Keys, 153 target: &RadrootsNostrConnectClientTarget, 154 request: RadrootsNostrConnectClientRequest, 155 transport: &mut T, 156 mut on_progress: F, 157 ) -> Result<RadrootsNostrConnectResponse, RadrootsNostrConnectError> 158 where 159 T: RadrootsNostrConnectClientTransport, 160 F: FnMut(RadrootsNostrConnectClientProgress) -> Result<(), RadrootsNostrConnectError>, 161 { 162 let method = request.method(); 163 let request_id = request.request_id.clone(); 164 let event = build_request_event(client_keys, target, request.into_message())?; 165 transport.publish_request_event(event).await?; 166 167 loop { 168 let event = transport.next_response_event().await?; 169 match parse_response_event(client_keys, target, &request_id, &method, &event)? { 170 RadrootsNostrConnectClientEventOutcome::Ignore => {} 171 RadrootsNostrConnectClientEventOutcome::Progress(progress) => on_progress(progress)?, 172 RadrootsNostrConnectClientEventOutcome::Response(response) => return Ok(response), 173 } 174 } 175 }