feat(pubky): use resolve_endpoint from pkarr

This commit is contained in:
nazeh
2024-09-25 22:50:52 +03:00
parent 9fd8501718
commit 2ae54359bf
15 changed files with 142 additions and 549 deletions

81
Cargo.lock generated
View File

@@ -117,9 +117,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "async-trait"
version = "0.1.82"
version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
@@ -190,7 +190,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper 1.0.1",
"tokio",
"tower 0.5.1",
"tower",
"tower-layer",
"tower-service",
"tracing",
@@ -236,7 +236,7 @@ dependencies = [
"serde",
"tokio",
"tokio-util",
"tower 0.5.1",
"tower",
"tower-layer",
"tower-service",
"tracing",
@@ -1133,8 +1133,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.8"
source = "git+https://github.com/hyperium/hyper-util.git#2639193e9134a235db42cca16c8cff7f21f61661"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
dependencies = [
"bytes",
"futures-channel",
@@ -1145,7 +1146,6 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio",
"tower 0.4.13",
"tower-service",
"tracing",
]
@@ -1224,9 +1224,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.158"
version = "0.2.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "libredox"
@@ -1556,26 +1556,6 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
@@ -1591,7 +1571,6 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkarr"
version = "3.0.0"
source = "git+https://github.com/Pubky/pkarr?branch=v3#445c0db448b9e70f2a63a5b91e6d84f1ef466aeb"
dependencies = [
"base32",
"bytes",
@@ -1604,12 +1583,14 @@ dependencies = [
"js-sys",
"lru",
"mainline",
"once_cell",
"rand",
"reqwest",
"self_cell",
"serde",
"simple-dns",
"thiserror",
"tokio",
"tracing",
]
@@ -1625,9 +1606,9 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "poly1305"
@@ -1689,6 +1670,7 @@ version = "0.1.0"
dependencies = [
"base64 0.22.1",
"bytes",
"hyper-util",
"js-sys",
"pkarr",
"pubky-common",
@@ -1846,9 +1828,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.4"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853"
checksum = "62871f2d65009c0256aed1b9cfeeb8ac272833c404e13d53d400cd0dad7a2ac0"
dependencies = [
"bitflags",
]
@@ -2431,18 +2413,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.63"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.63"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
@@ -2591,9 +2573,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.21"
version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap",
"serde",
@@ -2602,21 +2584,6 @@ dependencies = [
"winnow",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower"
version = "0.5.1"
@@ -3128,9 +3095,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.18"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
checksum = "c52ac009d615e79296318c1bcce2d422aaca15ad08515e344feeda07df67a587"
dependencies = [
"memchr",
]

View File

@@ -16,6 +16,3 @@ serde = { version = "^1.0.209", features = ["derive"] }
[profile.release]
lto = true
opt-level = 'z'
[patch.crates-io]
hyper-util = { git = "https://github.com/hyperium/hyper-util.git" }

View File

@@ -6,10 +6,20 @@ const TLD = '8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo';
// TODO: test HTTPs too somehow.
test.skip("basic fetch", async (t) => {
test("basic fetch", async (t) => {
let client = PubkyClient.testnet();
let response = await client.fetch(`http://${TLD}/`, new Uint8Array([]));
// Normal TLD
{
let response = await client.fetch(`http://relay.pkarr.org/`);
t.equal(response.status, 200);
}
// Pubky
let response = await client.fetch(`http://${TLD}/`);
t.equal(response.status, 200);
})

View File

@@ -8,7 +8,7 @@ use crate::PubkyClient;
mod api;
mod internals;
use internals::resolver::PkarrResolver;
use internals::PkarrResolver;
static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
@@ -55,7 +55,7 @@ impl PubkyClientBuilder {
// TODO: convert to Result<PubkyClient>
let pkarr = pkarr::Client::new(self.pkarr_settings).unwrap();
let dns_resolver: PkarrResolver = pkarr.clone().into();
let dns_resolver: PkarrResolver = (&pkarr).into();
PubkyClient {
http: reqwest::Client::builder()

View File

@@ -28,7 +28,7 @@ mod tests {
use crate::*;
#[tokio::test]
async fn http_get() {
async fn http_get_pubky() {
let testnet = Testnet::new(10);
let homeserver = Homeserver::start_test(&testnet).await.unwrap();
@@ -45,4 +45,21 @@ mod tests {
assert_eq!(response.status(), 200)
}
#[tokio::test]
async fn http_get_icann() {
let testnet = Testnet::new(10);
let client = PubkyClient::builder().testnet(&testnet).build();
let url = format!("http://example.com/");
let response = client
.request(Default::default(), url)
.send()
.await
.unwrap();
assert_eq!(response.status(), 200);
}
}

View File

@@ -1,5 +1,3 @@
//! Public API modules
pub mod http;
pub mod recovery_file;

View File

@@ -3,13 +3,43 @@ use url::Url;
use crate::PubkyClient;
mod endpoints;
pub mod resolver;
use std::net::ToSocketAddrs;
use pkarr::{Client, EndpointResolver, PublicKey};
use reqwest::dns::{Addrs, Resolve};
pub struct PkarrResolver(Client);
impl Resolve for PkarrResolver {
fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving {
let client = self.0.clone();
Box::pin(async move {
let name = name.as_str();
if PublicKey::try_from(name).is_ok() {
let endpoint = client.resolve_endpoint(name).await?;
let addrs: Addrs = Box::new(endpoint.to_socket_addrs().into_iter());
return Ok(addrs);
};
Ok(Box::new(format!("{name}:0").to_socket_addrs().unwrap()))
})
}
}
impl From<&pkarr::Client> for PkarrResolver {
fn from(pkarr: &pkarr::Client) -> Self {
PkarrResolver(pkarr.clone())
}
}
impl PubkyClient {
// === HTTP ===
pub(crate) fn inner_request(&self, method: reqwest::Method, url: Url) -> RequestBuilder {
/// A wrapper around [reqwest::Client::request], with the same signature between native and wasm.
pub(crate) async fn inner_request(&self, method: reqwest::Method, url: Url) -> RequestBuilder {
self.http.request(method, url)
}
}

View File

@@ -1,213 +0,0 @@
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use pkarr::dns::rdata::{RData, SVCB};
use pkarr::dns::ResourceRecord;
use pkarr::SignedPacket;
use pubky_common::timestamp::Timestamp;
#[derive(Debug, Clone)]
pub struct Endpoint {
pub(crate) target: String,
// public_key: PublicKey,
pub(crate) port: u16,
pub(crate) addrs: Vec<IpAddr>,
}
impl Endpoint {
/// 1. Find the SVCB or HTTPS records with the lowest priority
/// 2. Choose a random one of the list of the above
/// 3. If the target is `.`, check A and AAAA records (https://www.rfc-editor.org/rfc/rfc9460#name-special-handling-of-in-targ)
pub(crate) fn find(
signed_packet: &SignedPacket,
target: &str,
is_svcb: bool,
) -> Option<Endpoint> {
let mut lowest_priority = u16::MAX;
let mut lowest_priority_index = 0;
let mut records = vec![];
for record in signed_packet.resource_records(target) {
if let Some(svcb) = get_svcb(record, is_svcb) {
match svcb.priority.cmp(&lowest_priority) {
std::cmp::Ordering::Equal => records.push(svcb),
std::cmp::Ordering::Less => {
lowest_priority_index = records.len();
lowest_priority = svcb.priority;
records.push(svcb)
}
_ => {}
}
}
}
// Good enough random selection
let now = Timestamp::now();
let slice = &records[lowest_priority_index..];
let index = if slice.is_empty() {
0
} else {
(now.into_inner() as usize) % slice.len()
};
slice.get(index).map(|s| {
let target = s.target.to_string();
let mut addrs: Vec<IpAddr> = vec![];
if &target == "." {
for record in signed_packet.resource_records("@") {
match &record.rdata {
RData::A(ip) => addrs.push(IpAddr::V4(ip.address.into())),
RData::AAAA(ip) => addrs.push(IpAddr::V6(ip.address.into())),
_ => {}
}
}
}
Endpoint {
target,
// public_key: signed_packet.public_key(),
port: u16::from_be_bytes(
s.get_param(SVCB::PORT)
.unwrap_or_default()
.try_into()
.unwrap_or([0, 0]),
),
addrs,
}
})
}
pub fn to_socket_addrs(&self) -> std::io::Result<std::vec::IntoIter<SocketAddr>> {
if self.target == "." {
let port = self.port;
return Ok(self
.addrs
.iter()
.map(|addr| SocketAddr::from((*addr, port)))
.collect::<Vec<_>>()
.into_iter());
}
format!("{}:{}", self.target, self.port).to_socket_addrs()
}
}
fn get_svcb<'a>(record: &'a ResourceRecord, is_svcb: bool) -> Option<&'a SVCB<'a>> {
match &record.rdata {
RData::SVCB(svcb) => {
if is_svcb {
Some(svcb)
} else {
None
}
}
RData::HTTPS(curr) => {
if is_svcb {
None
} else {
Some(&curr.0)
}
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use super::*;
use pkarr::{dns, Keypair};
#[tokio::test]
async fn endpoint_target() {
let mut packet = dns::Packet::new_reply(0);
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("foo").unwrap(),
dns::CLASS::IN,
3600,
RData::HTTPS(SVCB::new(0, "https.example.com".try_into().unwrap()).into()),
));
// Make sure HTTPS only follows HTTPs
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("foo").unwrap(),
dns::CLASS::IN,
3600,
RData::SVCB(SVCB::new(0, "protocol.example.com".try_into().unwrap())),
));
// Make sure SVCB only follows SVCB
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("foo").unwrap(),
dns::CLASS::IN,
3600,
RData::HTTPS(SVCB::new(0, "https.example.com".try_into().unwrap()).into()),
));
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("_foo").unwrap(),
dns::CLASS::IN,
3600,
RData::SVCB(SVCB::new(0, "protocol.example.com".try_into().unwrap())),
));
let keypair = Keypair::random();
let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap();
let tld = keypair.public_key();
// Follow foo.tld HTTPS records
let endpoint = Endpoint::find(&signed_packet, &format!("foo.{tld}"), false).unwrap();
assert_eq!(endpoint.target, "https.example.com");
// Follow _foo.tld SVCB records
let endpoint = Endpoint::find(&signed_packet, &format!("_foo.{tld}"), true).unwrap();
assert_eq!(endpoint.target, "protocol.example.com");
}
#[test]
fn endpoint_to_socket_addrs() {
let mut packet = dns::Packet::new_reply(0);
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("@").unwrap(),
dns::CLASS::IN,
3600,
RData::A(Ipv4Addr::from_str("209.151.148.15").unwrap().into()),
));
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("@").unwrap(),
dns::CLASS::IN,
3600,
RData::AAAA(Ipv6Addr::from_str("2a05:d014:275:6201::64").unwrap().into()),
));
let mut svcb = SVCB::new(1, ".".try_into().unwrap());
svcb.set_port(6881);
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("@").unwrap(),
dns::CLASS::IN,
3600,
RData::HTTPS(svcb.into()),
));
let keypair = Keypair::random();
let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap();
// Follow foo.tld HTTPS records
let endpoint = Endpoint::find(
&signed_packet,
&signed_packet.public_key().to_string(),
false,
)
.unwrap();
assert_eq!(endpoint.target, ".");
let addrs = endpoint.to_socket_addrs().unwrap();
assert_eq!(
addrs.map(|s| s.to_string()).collect::<Vec<_>>(),
vec!["209.151.148.15:6881", "[2a05:d014:275:6201::64]:6881"]
)
}
}

View File

@@ -1,234 +0,0 @@
use std::net::ToSocketAddrs;
use pkarr::PublicKey;
use reqwest::dns::{Addrs, Resolve};
use crate::error::{Error, Result};
use super::endpoints::Endpoint;
const DEFAULT_MAX_CHAIN_LENGTH: u8 = 3;
#[derive(Debug, Clone)]
pub struct PkarrResolver {
pkarr: pkarr::Client,
max_chain_length: u8,
}
impl PkarrResolver {
pub fn new(pkarr: pkarr::Client, max_chain_length: u8) -> Self {
PkarrResolver {
pkarr,
max_chain_length,
}
}
/// Resolve a `qname` to an alternative [Endpoint] as defined in [RFC9460](https://www.rfc-editor.org/rfc/rfc9460#name-terminology).
///
/// A `qname` is can be either a regular domain name for HTTPS endpoints,
/// or it could use Attrleaf naming pattern for cusotm protcol. For example:
/// `_foo.example.com` for `foo://example.com`.
async fn resolve_endpoint(&self, qname: &str) -> Result<Endpoint> {
let target = qname;
// TODO: cache the result of this function?
let is_svcb = target.starts_with('_');
let mut step = 0;
let mut svcb: Option<Endpoint> = None;
loop {
let current = svcb.clone().map_or(target.to_string(), |s| s.target);
if let Ok(tld) = PublicKey::try_from(current.clone()) {
if let Ok(Some(signed_packet)) = self.pkarr.resolve(&tld).await {
if step >= self.max_chain_length {
break;
};
step += 1;
// Choose most prior SVCB record
svcb = Endpoint::find(&signed_packet, &current, is_svcb);
// TODO: support wildcard?
} else {
break;
}
} else {
break;
}
}
if let Some(svcb) = svcb {
if PublicKey::try_from(svcb.target.as_str()).is_err() {
return Ok(svcb);
}
}
Err(Error::ResolveEndpoint(target.into()))
}
}
impl Resolve for PkarrResolver {
fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving {
let client = self.clone();
Box::pin(async move {
let name = name.as_str();
if PublicKey::try_from(name).is_ok() {
let endpoint = client.resolve_endpoint(name).await?;
// let addrs = format!("{}:{}", x.target, x.port).to_socket_addrs()?;
let addrs: Addrs = Box::new(endpoint.to_socket_addrs()?);
return Ok(addrs);
};
Ok(Box::new(format!("{name}:0").to_socket_addrs().unwrap()))
})
}
}
impl From<&pkarr::Client> for PkarrResolver {
fn from(pkarr: &pkarr::Client) -> Self {
pkarr.clone().into()
}
}
impl From<pkarr::Client> for PkarrResolver {
fn from(pkarr: pkarr::Client) -> Self {
Self::new(pkarr, DEFAULT_MAX_CHAIN_LENGTH)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pkarr::dns::rdata::{A, SVCB};
use pkarr::dns::{self, rdata::RData};
use pkarr::SignedPacket;
use pkarr::{mainline::Testnet, Keypair};
use std::future::Future;
use std::pin::Pin;
fn generate_subtree(
client: pkarr::Client,
depth: u8,
branching: u8,
domain: Option<String>,
) -> Pin<Box<dyn Future<Output = PublicKey>>> {
Box::pin(async move {
let keypair = Keypair::random();
let mut packet = dns::Packet::new_reply(0);
for _ in 0..branching {
let mut svcb = SVCB::new(0, ".".try_into().unwrap());
if depth == 0 {
svcb.priority = 1;
svcb.set_port((branching) as u16 * 1000);
if let Some(target) = &domain {
let target: &'static str = Box::leak(target.clone().into_boxed_str());
svcb.target = target.try_into().unwrap()
}
} else {
let target =
generate_subtree(client.clone(), depth - 1, branching, domain.clone())
.await
.to_string();
let target: &'static str = Box::leak(target.into_boxed_str());
svcb.target = target.try_into().unwrap();
};
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("@").unwrap(),
dns::CLASS::IN,
3600,
RData::HTTPS(svcb.into()),
));
}
if depth == 0 {
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("@").unwrap(),
dns::CLASS::IN,
3600,
RData::A(A { address: 10 }),
));
}
let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap();
client.publish(&signed_packet).await.unwrap();
keypair.public_key()
})
}
fn generate(
client: pkarr::Client,
depth: u8,
branching: u8,
domain: Option<String>,
) -> Pin<Box<dyn Future<Output = PublicKey>>> {
generate_subtree(client, depth - 1, branching, domain)
}
#[tokio::test]
async fn resolve_endpoints() {
let testnet = Testnet::new(3);
let pkarr = pkarr::Client::builder().testnet(&testnet).build().unwrap();
let resolver: PkarrResolver = (&pkarr).into();
let tld = generate(pkarr, 3, 3, Some("example.com".to_string())).await;
let endpoint = resolver.resolve_endpoint(&tld.to_string()).await.unwrap();
assert_eq!(endpoint.target, "example.com");
}
#[tokio::test]
async fn max_chain_exceeded() {
let testnet = Testnet::new(3);
let pkarr = pkarr::Client::builder().testnet(&testnet).build().unwrap();
let resolver: PkarrResolver = (&pkarr).into();
let tld = generate(pkarr, 4, 3, Some("example.com".to_string())).await;
let endpoint = resolver.resolve_endpoint(&tld.to_string()).await;
assert_eq!(
match endpoint {
Err(error) => error.to_string(),
_ => "".to_string(),
},
crate::Error::ResolveEndpoint(tld.to_string()).to_string()
)
}
#[tokio::test]
async fn resolve_addresses() {
let testnet = Testnet::new(3);
let pkarr = pkarr::Client::builder().testnet(&testnet).build().unwrap();
let resolver: PkarrResolver = (&pkarr).into();
let tld = generate(pkarr, 3, 3, None).await;
let endpoint = resolver.resolve_endpoint(&tld.to_string()).await.unwrap();
assert_eq!(endpoint.target, ".");
assert_eq!(endpoint.port, 3000);
assert_eq!(
endpoint
.to_socket_addrs()
.unwrap()
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>(),
vec!["0.0.0.10:3000"]
);
dbg!(&endpoint);
}
}

View File

@@ -39,6 +39,7 @@ impl PubkyClient {
let response = self
.inner_request(Method::POST, url.clone())
.await
.body(body)
.send()
.await?;
@@ -59,7 +60,7 @@ impl PubkyClient {
url.set_path(&format!("/{}/session", pubky));
let res = self.inner_request(Method::GET, url).send().await?;
let res = self.inner_request(Method::GET, url).await.send().await?;
if res.status() == StatusCode::NOT_FOUND {
return Ok(None);
@@ -80,7 +81,7 @@ impl PubkyClient {
url.set_path(&format!("/{}/session", pubky));
self.inner_request(Method::DELETE, url).send().await?;
self.inner_request(Method::DELETE, url).await.send().await?;
Ok(())
}
@@ -140,6 +141,7 @@ impl PubkyClient {
drop(path_segments);
self.inner_request(Method::POST, callback)
.await
.body(encrypted_token)
.send()
.await?;
@@ -167,6 +169,7 @@ impl PubkyClient {
let response = self
.inner_request(Method::POST, url)
.await
.body(token.serialize())
.send()
.await?;

View File

@@ -90,7 +90,12 @@ impl<'a> ListBuilder<'a> {
drop(query);
let response = self.client.inner_request(Method::GET, url).send().await?;
let response = self
.client
.inner_request(Method::GET, url)
.await
.send()
.await?;
response.error_for_status_ref()?;

View File

@@ -17,6 +17,7 @@ impl PubkyClient {
let response = self
.inner_request(Method::PUT, url)
.await
.body(content.to_owned())
.send()
.await?;
@@ -29,7 +30,7 @@ impl PubkyClient {
pub(crate) async fn inner_get<T: TryInto<Url>>(&self, url: T) -> Result<Option<Bytes>> {
let url = self.pubky_to_http(url).await?;
let response = self.inner_request(Method::GET, url).send().await?;
let response = self.inner_request(Method::GET, url).await.send().await?;
if response.status() == StatusCode::NOT_FOUND {
return Ok(None);
@@ -46,7 +47,7 @@ impl PubkyClient {
pub(crate) async fn inner_delete<T: TryInto<Url>>(&self, url: T) -> Result<()> {
let url = self.pubky_to_http(url).await?;
let response = self.inner_request(Method::DELETE, url).send().await?;
let response = self.inner_request(Method::DELETE, url).await.send().await?;
response.error_for_status_ref()?;
@@ -650,10 +651,7 @@ mod tests {
{
let response = client
.inner_request(
Method::GET,
format!("{feed_url}?limit=10").as_str().try_into().unwrap(),
)
.request(Method::GET, format!("{feed_url}?limit=10"))
.send()
.await
.unwrap();
@@ -683,13 +681,7 @@ mod tests {
{
let response = client
.inner_request(
Method::GET,
format!("{feed_url}?limit=10&cursor={cursor}")
.as_str()
.try_into()
.unwrap(),
)
.request(Method::GET, format!("{feed_url}?limit=10&cursor={cursor}"))
.send()
.await
.unwrap();
@@ -740,10 +732,7 @@ mod tests {
{
let response = client
.inner_request(
Method::GET,
format!("{feed_url}?limit=10").as_str().try_into().unwrap(),
)
.request(Method::GET, format!("{feed_url}?limit=10"))
.send()
.await
.unwrap();
@@ -801,10 +790,7 @@ mod tests {
let feed_url = format!("http://localhost:{}/events/", homeserver.port());
let response = client
.inner_request(
Method::GET,
format!("{feed_url}").as_str().try_into().unwrap(),
)
.request(Method::GET, format!("{feed_url}"))
.send()
.await
.unwrap();

View File

@@ -7,6 +7,8 @@ use reqwest::Url;
use crate::PubkyClient;
use super::super::internals::resolve;
#[wasm_bindgen]
impl PubkyClient {
#[wasm_bindgen]
@@ -19,7 +21,9 @@ impl PubkyClient {
JsValue::from_str(&format!("PubkyClient::fetch(): Invalid `url`; {:?}", err))
})?;
self.resolve_url(&mut url).await.map_err(JsValue::from)?;
resolve(&self.pkarr, &mut url)
.await
.map_err(|err| JsValue::from_str(&format!("PubkyClient::fetch(): {:?}", err)))?;
let js_req =
web_sys::Request::new_with_str_and_init(url.as_str(), init).map_err(|err| {

View File

@@ -1,6 +1,6 @@
//! Public API modules
pub mod auth;
pub mod http;
pub mod public;
pub mod recovery_file;
// TODO: put the Homeserver API behind a feature flag
pub mod auth;
pub mod public;

View File

@@ -1,10 +1,33 @@
use crate::PubkyClient;
use reqwest::{Method, RequestBuilder};
use url::Url;
use pkarr::{EndpointResolver, PublicKey};
use crate::{error::Result, PubkyClient};
// TODO: remove expect
pub async fn resolve(pkarr: &pkarr::Client, url: &mut Url) -> Result<()> {
let qname = url.host_str().expect("URL TO HAVE A HOST!").to_string();
// If http and has a Pubky TLD, switch to socket addresses.
if url.scheme() == "http" && PublicKey::try_from(qname.as_str()).is_ok() {
let endpoint = pkarr.resolve_endpoint(&qname).await?;
if let Some(socket_address) = endpoint.to_socket_addrs().into_iter().next() {
url.set_host(Some(&socket_address.to_string()))?;
let _ = url.set_port(Some(socket_address.port()));
} else if let Some(port) = endpoint.port() {
url.set_host(Some(endpoint.target()))?;
let _ = url.set_port(Some(port));
}
};
Ok(())
}
impl PubkyClient {
pub(crate) fn inner_request(&self, method: Method, url: Url) -> RequestBuilder {
/// A wrapper around [reqwest::Client::request], with the same signature between native and wasm.
pub(crate) async fn inner_request(&self, method: Method, url: Url) -> RequestBuilder {
self.http.request(method, url).fetch_credentials_include()
}
}