#![doc = include_str!("../README.md")]
#[cfg(feature = "dht")]
use mainline::{Dht, DhtSettings, GetMutableResponse, MutableItem, Response, StoreQueryMetdata};
#[cfg(feature = "relay")]
use url::Url;
pub use bytes;
pub use simple_dns as dns;
pub use url;
mod error;
mod keys;
mod signed_packet;
pub use crate::error::Error;
pub use crate::keys::{Keypair, PublicKey};
pub use crate::signed_packet::SignedPacket;
pub type Result<T, E = Error> = core::result::Result<T, E>;
pub const DEFAULT_PKARR_RELAY: &str = "https://relay.pkarr.org";
pub struct PkarrClientBuilder {
#[cfg(feature = "dht")]
settings: DhtSettings,
}
impl PkarrClientBuilder {
pub fn new() -> Self {
Self {
#[cfg(feature = "dht")]
settings: DhtSettings::default(),
}
}
#[cfg(feature = "dht")]
pub fn bootstrap(mut self, bootstrap: &[String]) -> Self {
self.settings.bootstrap = Some(bootstrap.to_owned());
self
}
pub fn build(self) -> PkarrClient {
PkarrClient {
#[cfg(all(feature = "relay", not(feature = "async")))]
http_client: reqwest::blocking::Client::new(),
#[cfg(all(feature = "relay", feature = "async"))]
http_client: reqwest::Client::new(),
#[cfg(feature = "dht")]
dht: Dht::new(self.settings),
}
}
}
impl Default for PkarrClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PkarrClient {
#[cfg(all(feature = "relay", not(feature = "async")))]
http_client: reqwest::blocking::Client,
#[cfg(all(feature = "relay", feature = "async"))]
http_client: reqwest::Client,
#[cfg(feature = "dht")]
dht: Dht,
}
impl PkarrClient {
pub fn new() -> Self {
Self {
#[cfg(all(feature = "relay", not(feature = "async")))]
http_client: reqwest::blocking::Client::new(),
#[cfg(all(feature = "relay", feature = "async"))]
http_client: reqwest::Client::new(),
#[cfg(feature = "dht")]
dht: Dht::default(),
}
}
pub fn builder() -> PkarrClientBuilder {
PkarrClientBuilder::default()
}
#[cfg(all(feature = "relay", not(feature = "async")))]
pub fn with_http_client(mut self, client: reqwest::blocking::Client) -> Self {
self.http_client = client;
self
}
#[cfg(all(feature = "relay", feature = "async"))]
pub fn with_http_client(mut self, client: reqwest::Client) -> Self {
self.http_client = client;
self
}
#[cfg(all(feature = "relay", not(feature = "async")))]
pub fn relay_get(&self, url: &Url, public_key: PublicKey) -> Result<Option<SignedPacket>> {
let url = format_relay_url(url, &public_key)?;
let response = self.http_client.get(url).send()?;
if response.status().is_success() {
let bytes = response.bytes()?;
return Ok(Some(SignedPacket::from_relay_response(public_key, bytes)?));
} else if response.status() == reqwest::StatusCode::NOT_FOUND {
return Ok(None);
} else {
return Err(Error::RelayResponse(
response.url().clone(),
response.status(),
response.text()?,
));
}
}
#[cfg(all(feature = "relay", feature = "async"))]
pub async fn relay_get(
&self,
url: &Url,
public_key: PublicKey,
) -> Result<Option<SignedPacket>> {
let url = format_relay_url(url, &public_key)?;
let response = self.http_client.get(url).send().await?;
if response.status().is_success() {
let bytes = response.bytes().await?;
return Ok(Some(SignedPacket::from_relay_response(public_key, bytes)?));
} else if response.status() == reqwest::StatusCode::NOT_FOUND {
return Ok(None);
} else {
return Err(Error::RelayResponse(
response.url().clone(),
response.status(),
response.text().await?,
));
}
}
#[cfg(all(feature = "relay", not(feature = "async")))]
pub fn relay_put(&self, url: &Url, signed_packet: &SignedPacket) -> Result<()> {
let url = format_relay_url(url, signed_packet.public_key())?;
let response = self
.http_client
.put(url)
.body(signed_packet.as_relay_request())
.send()?;
if !response.status().is_success() {
return Err(Error::RelayResponse(
response.url().clone(),
response.status(),
response.text()?,
));
}
Ok(())
}
#[cfg(all(feature = "relay", feature = "async"))]
pub async fn relay_put(&self, url: &Url, signed_packet: &SignedPacket) -> Result<()> {
let url = format_relay_url(url, signed_packet.public_key())?;
let response = self
.http_client
.put(url)
.body(signed_packet.as_relay_request())
.send()
.await?;
if !response.status().is_success() {
return Err(Error::RelayResponse(
response.url().clone(),
response.status(),
response.text().await?,
));
}
Ok(())
}
#[cfg(all(feature = "dht", not(feature = "async")))]
pub fn publish(&self, signed_packet: &SignedPacket) -> Result<StoreQueryMetdata> {
let item: MutableItem = signed_packet.into();
self.dht.put_mutable(item).map_err(Error::MainlineError)
}
#[cfg(all(feature = "dht", feature = "async"))]
pub async fn publish(&self, signed_packet: &SignedPacket) -> Result<StoreQueryMetdata> {
let item: MutableItem = signed_packet.into();
self.dht.put_mutable(item).map_err(Error::MainlineError)
}
#[cfg(all(feature = "dht", not(feature = "async")))]
pub fn resolve(&self, public_key: PublicKey) -> Option<SignedPacket> {
let mut response = self.dht.get_mutable(public_key.as_bytes(), None);
for res in &mut response {
let signed_packet: Result<SignedPacket> = res.item.try_into();
if let Ok(signed_packet) = signed_packet {
return Some(signed_packet);
};
}
None
}
#[cfg(all(feature = "dht", feature = "async"))]
pub async fn resolve(&self, public_key: PublicKey) -> Option<SignedPacket> {
let mut response = self.dht.get_mutable(public_key.as_bytes(), None);
for res in &mut response {
let signed_packet: Result<SignedPacket> = res.item.try_into();
if let Ok(signed_packet) = signed_packet {
return Some(signed_packet);
};
}
None
}
#[cfg(all(feature = "dht", not(feature = "async")))]
pub fn resolve_most_recent(&self, public_key: PublicKey) -> Option<SignedPacket> {
let mut response = self.dht.get_mutable(public_key.as_bytes(), None);
let mut most_recent: Option<SignedPacket> = None;
for next in &mut response {
let next_packet: Result<SignedPacket> = next.item.try_into();
if let Ok(next_packet) = next_packet {
if let Some(most_recent) = &most_recent {
if most_recent.more_recent_than(&next_packet) {
continue;
}
}
most_recent = Some(next_packet)
};
}
most_recent
}
#[cfg(all(feature = "dht", feature = "async"))]
pub async fn resolve_most_recent(&self, public_key: PublicKey) -> Option<SignedPacket> {
let mut response = self.dht.get_mutable(public_key.as_bytes(), None);
let mut most_recent: Option<SignedPacket> = None;
for next in &mut response {
let next_packet: Result<SignedPacket> = next.item.try_into();
if let Ok(next_packet) = next_packet {
if let Some(most_recent) = &most_recent {
if most_recent.more_recent_than(&next_packet) {
continue;
}
}
most_recent = Some(next_packet)
};
}
most_recent
}
#[cfg(all(feature = "dht", not(feature = "async")))]
pub fn resolve_raw(&self, public_key: PublicKey) -> Response<GetMutableResponse> {
self.dht.get_mutable(public_key.as_bytes(), None)
}
#[cfg(all(feature = "dht", feature = "async"))]
pub async fn resolve_raw(&self, public_key: PublicKey) -> Response<GetMutableResponse> {
self.dht.get_mutable(public_key.as_bytes(), None)
}
}
impl Default for PkarrClient {
fn default() -> Self {
Self::builder().build()
}
}
#[cfg(feature = "relay")]
fn format_relay_url(url: &Url, public_key: &PublicKey) -> Result<Url> {
let mut url = url.to_owned();
url.path_segments_mut()
.map_err(|_| Error::Static("invalid url"))?
.push(&public_key.to_z32());
Ok(url)
}