feat(homeserver,js): connect to testnet from js

This commit is contained in:
nazeh
2024-12-15 15:14:12 +03:00
parent c97e63ac88
commit 6262bb75cd
8 changed files with 81 additions and 31 deletions

5
Cargo.lock generated
View File

@@ -1921,7 +1921,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkarr"
version = "3.0.0"
source = "git+https://github.com/Pubky/pkarr?branch=v3#95f106e5a8a1d6275738aabf25c32c7cc7932a50"
source = "git+https://github.com/Pubky/pkarr?branch=v3#aac62e74f3ab5d94b1a52cd00ff355d8d50e29e1"
dependencies = [
"base32",
"byteorder",
@@ -1952,13 +1952,14 @@ dependencies = [
"thiserror 2.0.6",
"tokio",
"tracing",
"url",
"wasm-bindgen-futures",
]
[[package]]
name = "pkarr-relay"
version = "0.1.0"
source = "git+https://github.com/Pubky/pkarr?branch=v3#95f106e5a8a1d6275738aabf25c32c7cc7932a50"
source = "git+https://github.com/Pubky/pkarr?branch=v3#aac62e74f3ab5d94b1a52cd00ff355d8d50e29e1"
dependencies = [
"anyhow",
"axum",

View File

@@ -33,6 +33,8 @@ struct ConfigToml {
/// Server configuration
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config {
/// Run in [testnet](crate::Homeserver::start_testnet) mode.
pub testnet: bool,
/// The configured port for this server.
pub port: u16,
/// Bootstrapping DHT nodes.
@@ -99,6 +101,7 @@ impl Config {
impl Default for Config {
fn default() -> Self {
Self {
testnet: false,
port: 0,
bootstrap: None,
domain: None,
@@ -138,6 +141,7 @@ impl TryFrom<ConfigToml> for Config {
};
Ok(Config {
testnet: false,
port: value.port.unwrap_or(0),
bootstrap: value.bootstrap,
domain: value.domain,

View File

@@ -1,3 +1,5 @@
use std::path::PathBuf;
use ::pkarr::{Keypair, PublicKey};
use anyhow::Result;
use http::HttpServers;
@@ -13,6 +15,12 @@ mod pkarr;
pub struct HomeserverBuilder(Config);
impl HomeserverBuilder {
pub fn testnet(mut self) -> Self {
self.0.testnet = true;
self
}
/// Configure the Homeserver's keypair
pub fn keypair(mut self, keypair: Keypair) -> Self {
self.0.keypair = keypair;
@@ -27,6 +35,13 @@ impl HomeserverBuilder {
self
}
/// Configure the storage path of the Homeserver
pub fn storage(mut self, storage: PathBuf) -> Self {
self.0.storage = storage;
self
}
/// Start running a Homeserver
///
/// # Safety
@@ -68,7 +83,14 @@ impl Homeserver {
info!("Publishing Pkarr packet..");
let pkarr_server = PkarrServer::new(config, http_servers.https_address().await?.port())?;
let pkarr_server = PkarrServer::new(
&config,
if config.testnet {
http_servers.http_address().await?.port()
} else {
http_servers.https_address().await?.port()
},
)?;
pkarr_server.publish_server_packet().await?;
info!("Homeserver listening on https://{}", core.public_key());
@@ -78,20 +100,27 @@ impl Homeserver {
/// Start a homeserver in a Testnet mode.
///
/// - Homeserver address is hardcoded to ``
/// - Homeserver address is hardcoded to `8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo`
/// - Run a pkarr Relay on port `15411`
/// - Use a temporary storage for the both homeserver and relay
/// - Only publish http port (ignore https port or domain configurations)
///
/// # Safety
/// See [Self::start]
pub async unsafe fn start_testnet() -> Result<Self> {
let testnet = ::pkarr::mainline::Testnet::new(10)?;
let storage =
std::env::temp_dir().join(pubky_common::timestamp::Timestamp::now().to_string());
let relay = unsafe {
let mut config = pkarr_relay::Config {
http_port: 15411,
cache_path: Some(storage.join("pkarr-relay")),
..Default::default()
};
config.pkarr_config.dht_config.bootstrap = testnet.bootstrap.clone();
config.pkarr_config.resolvers = Some(vec![]);
pkarr_relay::Relay::start(config).await?
};
@@ -100,14 +129,16 @@ impl Homeserver {
unsafe {
Homeserver::builder()
.testnet()
.keypair(Keypair::from_secret_key(&[0; 32]))
.bootstrap(testnet.bootstrap)
.storage(storage.join("pubky-homeserver"))
.build()
.await
}
}
/// Test version of [Homeserver::start], using mainline Testnet, and a temporary storage.
/// Unit tests version of [Homeserver::start], using mainline Testnet, and a temporary storage.
pub async fn start_test(testnet: &::pkarr::mainline::Testnet) -> Result<Self> {
unsafe { Homeserver::start(Config::test(testnet)).await }
}

View File

@@ -11,7 +11,7 @@ pub struct PkarrServer {
}
impl PkarrServer {
pub fn new(config: Config, port: u16) -> Result<Self> {
pub fn new(config: &Config, port: u16) -> Result<Self> {
let mut dht_config = pkarr::mainline::Config::default();
if let Some(bootstrap) = config.bootstrap.clone() {
@@ -41,7 +41,7 @@ impl PkarrServer {
}
}
pub fn create_signed_packet(config: Config, port: u16) -> Result<SignedPacket> {
pub fn create_signed_packet(config: &Config, port: u16) -> Result<SignedPacket> {
// TODO: Try to resolve first before publishing.
let default = ".".to_string();

View File

@@ -11,7 +11,6 @@ test.only("basic fetch", async (t) => {
// Normal TLD
{
let response = await client.fetch(`http://relay.pkarr.org/`);
t.equal(response.status, 200);

View File

@@ -13,7 +13,7 @@ impl Default for Client {
}
}
static TESTNET_RELAYS: [&str; 1] = ["http://localhost:15411/pkarr"];
static TESTNET_RELAYS: [&str; 1] = ["http://localhost:15411/"];
#[wasm_bindgen]
impl Client {
@@ -27,13 +27,18 @@ impl Client {
}
/// Create a client with with configurations appropriate for local testing:
/// - set Pkarr relays to `["http://localhost:15411/pkarr"]` instead of default relay.
/// - set Pkarr relays to `["http://localhost:15411"]` instead of default relay.
#[wasm_bindgen]
pub fn testnet() -> Self {
Self {
http: reqwest::Client::builder().build().unwrap(),
pkarr: pkarr::Client::builder()
.relays(TESTNET_RELAYS.into_iter().map(|s| s.to_string()).collect())
.relays(
TESTNET_RELAYS
.into_iter()
.map(|s| url::Url::parse(s).expect("TESTNET_RELAYS should be valid urls"))
.collect(),
)
.build()
.unwrap(),
}

View File

@@ -19,6 +19,8 @@ impl Client {
JsValue::from_str(&format!("pubky::Client::fetch(): Invalid `url`; {:?}", err))
})?;
self.transform_url(&mut url).await;
let js_req =
web_sys::Request::new_with_str_and_init(url.as_str(), init).map_err(|err| {
JsValue::from_str(&format!(

View File

@@ -4,12 +4,12 @@
use reqwest::{IntoUrl, Method, RequestBuilder};
use url::Url;
use futures_lite::{pin, Stream, StreamExt};
use futures_lite::StreamExt;
use pkarr::extra::endpoints::EndpointsResolver;
use pkarr::extra::endpoints::{Endpoint, EndpointsResolver};
use pkarr::PublicKey;
use crate::{error::Result, Client};
use crate::Client;
impl Client {
/// A wrapper around [reqwest::Client::request], with the same signature between native and wasm.
@@ -17,37 +17,45 @@ impl Client {
let original_url = url.as_str();
let mut url = Url::parse(original_url).expect("Invalid url in inner_request");
self.transform_url(&mut url).await;
self.http.request(method, url).fetch_credentials_include()
}
pub(super) async fn transform_url(&self, url: &mut Url) {
if url.scheme() == "pubky" {
url.set_scheme("https");
// TODO: use https for anything other than testnet
url.set_scheme("http")
.expect("couldn't replace pubky:// with http://");
url.set_host(Some(&format!("_pubky.{}", url.host_str().unwrap_or(""))))
.expect("couldn't map pubk://<pubky> to https://_pubky.<pubky>");
}
if PublicKey::try_from(original_url).is_ok() {
let stream = self
.pkarr
.resolve_https_endpoints(url.host_str().unwrap_or(""));
let qname = url.host_str().unwrap_or("").to_string();
let mut so_far = None;
if PublicKey::try_from(qname.to_string()).is_ok() {
let mut stream = self.pkarr.resolve_https_endpoints(&qname);
while let Some(endpoint) = stream.next().await {
if let Some(e) = so_far {
if e.domain() == "." && endpoint.domain() != "." {
let mut so_far: Option<Endpoint> = None;
// TODO: currently we return the first thing we can see,
// in the future we might want to failover to other endpoints
while so_far.is_none() {
while let Some(endpoint) = stream.next().await {
if endpoint.domain() != "." {
so_far = Some(endpoint);
}
} else {
so_far = Some(endpoint)
}
}
if let Some(e) = so_far {
url.set_host(Some(e.domain()));
url.set_port(Some(e.port()));
return self.http.request(method, url).fetch_credentials_include();
url.set_host(Some(e.domain()))
.expect("coultdn't use the resolved endpoint's domain");
url.set_port(Some(e.port()))
.expect("coultdn't use the resolved endpoint's port");
} else {
// TODO: didn't find any domain, what to do?
}
}
self.http.request(method, url).fetch_credentials_include()
}
}