uri.rs (7589B)
1 use crate::error::RadrootsNostrConnectError; 2 use crate::permission::RadrootsNostrConnectPermissions; 3 use nostr::{PublicKey, RelayUrl}; 4 use serde::{Deserialize, Serialize}; 5 use std::fmt; 6 use std::str::FromStr; 7 use url::Url; 8 9 pub const RADROOTS_NOSTR_CONNECT_URI_SCHEME: &str = "nostrconnect"; 10 pub const RADROOTS_NOSTR_CONNECT_BUNKER_URI_SCHEME: &str = "bunker"; 11 12 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 13 pub struct RadrootsNostrConnectBunkerUri { 14 pub remote_signer_public_key: PublicKey, 15 pub relays: Vec<RelayUrl>, 16 #[serde(skip_serializing_if = "Option::is_none")] 17 pub secret: Option<String>, 18 } 19 20 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] 21 pub struct RadrootsNostrConnectClientMetadata { 22 #[serde( 23 default, 24 skip_serializing_if = "RadrootsNostrConnectPermissions::is_empty" 25 )] 26 pub requested_permissions: RadrootsNostrConnectPermissions, 27 #[serde(skip_serializing_if = "Option::is_none")] 28 pub name: Option<String>, 29 #[serde(skip_serializing_if = "Option::is_none")] 30 pub url: Option<String>, 31 #[serde(skip_serializing_if = "Option::is_none")] 32 pub image: Option<String>, 33 } 34 35 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 36 pub struct RadrootsNostrConnectClientUri { 37 pub client_public_key: PublicKey, 38 pub relays: Vec<RelayUrl>, 39 pub secret: String, 40 #[serde(default)] 41 pub metadata: RadrootsNostrConnectClientMetadata, 42 } 43 44 #[derive(Debug, Clone, PartialEq, Eq)] 45 pub enum RadrootsNostrConnectUri { 46 Bunker(RadrootsNostrConnectBunkerUri), 47 Client(RadrootsNostrConnectClientUri), 48 } 49 50 impl RadrootsNostrConnectUri { 51 pub fn parse(value: &str) -> Result<Self, RadrootsNostrConnectError> { 52 let url = Url::parse(value).map_err(|error| RadrootsNostrConnectError::InvalidUrl { 53 value: value.to_owned(), 54 reason: error.to_string(), 55 })?; 56 let host = url 57 .host_str() 58 .ok_or(RadrootsNostrConnectError::MissingPublicKey)?; 59 60 match url.scheme() { 61 RADROOTS_NOSTR_CONNECT_BUNKER_URI_SCHEME => { 62 let remote_signer_public_key = parse_public_key(host)?; 63 let mut relays = Vec::new(); 64 let mut secret = None; 65 66 for (key, value) in url.query_pairs() { 67 match key.as_ref() { 68 "relay" => relays.push(parse_relay_url(value.as_ref())?), 69 "secret" => secret = Some(value.into_owned()), 70 _ => {} 71 } 72 } 73 74 if relays.is_empty() { 75 return Err(RadrootsNostrConnectError::MissingRelay); 76 } 77 78 Ok(Self::Bunker(RadrootsNostrConnectBunkerUri { 79 remote_signer_public_key, 80 relays, 81 secret, 82 })) 83 } 84 RADROOTS_NOSTR_CONNECT_URI_SCHEME => { 85 let client_public_key = parse_public_key(host)?; 86 let mut relays = Vec::new(); 87 let mut secret = None; 88 let mut metadata = RadrootsNostrConnectClientMetadata::default(); 89 90 for (key, value) in url.query_pairs() { 91 match key.as_ref() { 92 "relay" => relays.push(parse_relay_url(value.as_ref())?), 93 "secret" => secret = Some(value.into_owned()), 94 "perms" => { 95 metadata.requested_permissions = 96 RadrootsNostrConnectPermissions::from_str(value.as_ref())?; 97 } 98 "name" => metadata.name = Some(value.into_owned()), 99 "url" => metadata.url = Some(validate_url(value.as_ref())?), 100 "image" => metadata.image = Some(validate_url(value.as_ref())?), 101 _ => {} 102 } 103 } 104 105 if relays.is_empty() { 106 return Err(RadrootsNostrConnectError::MissingRelay); 107 } 108 109 let secret = secret.ok_or(RadrootsNostrConnectError::MissingSecret)?; 110 111 Ok(Self::Client(RadrootsNostrConnectClientUri { 112 client_public_key, 113 relays, 114 secret, 115 metadata, 116 })) 117 } 118 scheme => Err(RadrootsNostrConnectError::InvalidUriScheme( 119 scheme.to_owned(), 120 )), 121 } 122 } 123 } 124 125 impl FromStr for RadrootsNostrConnectUri { 126 type Err = RadrootsNostrConnectError; 127 128 fn from_str(value: &str) -> Result<Self, Self::Err> { 129 Self::parse(value) 130 } 131 } 132 133 impl fmt::Display for RadrootsNostrConnectUri { 134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 135 match self { 136 Self::Bunker(uri) => { 137 let mut serializer = url::form_urlencoded::Serializer::new(String::new()); 138 for relay in &uri.relays { 139 serializer.append_pair("relay", &relay.to_string()); 140 } 141 if let Some(secret) = &uri.secret { 142 serializer.append_pair("secret", secret); 143 } 144 let query = serializer.finish(); 145 write!( 146 f, 147 "{RADROOTS_NOSTR_CONNECT_BUNKER_URI_SCHEME}://{}?{query}", 148 uri.remote_signer_public_key 149 ) 150 } 151 Self::Client(uri) => { 152 let mut serializer = url::form_urlencoded::Serializer::new(String::new()); 153 for relay in &uri.relays { 154 serializer.append_pair("relay", &relay.to_string()); 155 } 156 serializer.append_pair("secret", &uri.secret); 157 if !uri.metadata.requested_permissions.is_empty() { 158 serializer 159 .append_pair("perms", &uri.metadata.requested_permissions.to_string()); 160 } 161 if let Some(name) = &uri.metadata.name { 162 serializer.append_pair("name", name); 163 } 164 if let Some(url) = &uri.metadata.url { 165 serializer.append_pair("url", url); 166 } 167 if let Some(image) = &uri.metadata.image { 168 serializer.append_pair("image", image); 169 } 170 let query = serializer.finish(); 171 write!( 172 f, 173 "{RADROOTS_NOSTR_CONNECT_URI_SCHEME}://{}?{query}", 174 uri.client_public_key 175 ) 176 } 177 } 178 } 179 } 180 181 fn parse_public_key(value: &str) -> Result<PublicKey, RadrootsNostrConnectError> { 182 PublicKey::parse(value) 183 .or_else(|_| PublicKey::from_hex(value)) 184 .map_err(|error| RadrootsNostrConnectError::InvalidPublicKey { 185 value: value.to_owned(), 186 reason: error.to_string(), 187 }) 188 } 189 190 fn parse_relay_url(value: &str) -> Result<RelayUrl, RadrootsNostrConnectError> { 191 RelayUrl::parse(value).map_err(|error| RadrootsNostrConnectError::InvalidRelayUrl { 192 value: value.to_owned(), 193 reason: error.to_string(), 194 }) 195 } 196 197 fn validate_url(value: &str) -> Result<String, RadrootsNostrConnectError> { 198 Url::parse(value) 199 .map(|url| url.to_string()) 200 .map_err(|error| RadrootsNostrConnectError::InvalidUrl { 201 value: value.to_owned(), 202 reason: error.to_string(), 203 }) 204 }