diff --git a/Cargo.lock b/Cargo.lock index 71da5f4..0a0eebb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 03ee202..017397d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/pubky/pkg/test/http.js b/pubky/pkg/test/http.js index 5348d27..a11a63d 100644 --- a/pubky/pkg/test/http.js +++ b/pubky/pkg/test/http.js @@ -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); }) diff --git a/pubky/src/native.rs b/pubky/src/native.rs index 2aa8b9e..83406f6 100644 --- a/pubky/src/native.rs +++ b/pubky/src/native.rs @@ -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 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() diff --git a/pubky/src/native/api/http.rs b/pubky/src/native/api/http.rs index 4d762b3..ada724b 100644 --- a/pubky/src/native/api/http.rs +++ b/pubky/src/native/api/http.rs @@ -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); + } } diff --git a/pubky/src/native/api.rs b/pubky/src/native/api/mod.rs similarity index 83% rename from pubky/src/native/api.rs rename to pubky/src/native/api/mod.rs index a506a10..a502667 100644 --- a/pubky/src/native/api.rs +++ b/pubky/src/native/api/mod.rs @@ -1,5 +1,3 @@ -//! Public API modules - pub mod http; pub mod recovery_file; diff --git a/pubky/src/native/internals.rs b/pubky/src/native/internals.rs index 8674b68..4e9f593 100644 --- a/pubky/src/native/internals.rs +++ b/pubky/src/native/internals.rs @@ -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) } } diff --git a/pubky/src/native/internals/endpoints.rs b/pubky/src/native/internals/endpoints.rs deleted file mode 100644 index 8d30d30..0000000 --- a/pubky/src/native/internals/endpoints.rs +++ /dev/null @@ -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, -} - -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 { - 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 = 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> { - if self.target == "." { - let port = self.port; - return Ok(self - .addrs - .iter() - .map(|addr| SocketAddr::from((*addr, port))) - .collect::>() - .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!["209.151.148.15:6881", "[2a05:d014:275:6201::64]:6881"] - ) - } -} diff --git a/pubky/src/native/internals/resolver.rs b/pubky/src/native/internals/resolver.rs deleted file mode 100644 index b04ac09..0000000 --- a/pubky/src/native/internals/resolver.rs +++ /dev/null @@ -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 { - let target = qname; - // TODO: cache the result of this function? - - let is_svcb = target.starts_with('_'); - - let mut step = 0; - let mut svcb: Option = 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, ¤t, 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 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, - ) -> Pin>> { - 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, - ) -> Pin>> { - 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!["0.0.0.10:3000"] - ); - dbg!(&endpoint); - } -} diff --git a/pubky/src/shared/auth.rs b/pubky/src/shared/auth.rs index 7c3d9e1..5be14d9 100644 --- a/pubky/src/shared/auth.rs +++ b/pubky/src/shared/auth.rs @@ -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?; diff --git a/pubky/src/shared/list_builder.rs b/pubky/src/shared/list_builder.rs index 2087274..b76fee4 100644 --- a/pubky/src/shared/list_builder.rs +++ b/pubky/src/shared/list_builder.rs @@ -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()?; diff --git a/pubky/src/shared/public.rs b/pubky/src/shared/public.rs index 62f8144..ada987d 100644 --- a/pubky/src/shared/public.rs +++ b/pubky/src/shared/public.rs @@ -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>(&self, url: T) -> Result> { 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>(&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(); diff --git a/pubky/src/wasm/api/http.rs b/pubky/src/wasm/api/http.rs index 562bf66..46c5733 100644 --- a/pubky/src/wasm/api/http.rs +++ b/pubky/src/wasm/api/http.rs @@ -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| { diff --git a/pubky/src/wasm/api.rs b/pubky/src/wasm/api/mod.rs similarity index 55% rename from pubky/src/wasm/api.rs rename to pubky/src/wasm/api/mod.rs index e339f11..a502667 100644 --- a/pubky/src/wasm/api.rs +++ b/pubky/src/wasm/api/mod.rs @@ -1,6 +1,6 @@ -//! Public API modules +pub mod http; +pub mod recovery_file; +// TODO: put the Homeserver API behind a feature flag pub mod auth; -pub mod http; pub mod public; -pub mod recovery_file; diff --git a/pubky/src/wasm/internals.rs b/pubky/src/wasm/internals.rs index 6973130..95b625d 100644 --- a/pubky/src/wasm/internals.rs +++ b/pubky/src/wasm/internals.rs @@ -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() } }