feat(pubky): start testing js package against local homeserver

This commit is contained in:
nazeh
2024-07-28 18:38:21 +03:00
parent dac2284065
commit d35c586a12
23 changed files with 309 additions and 195 deletions

52
Cargo.lock generated
View File

@@ -413,7 +413,6 @@ checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa"
dependencies = [
"cookie",
"idna 0.5.0",
"indexmap",
"log",
"publicsuffix",
"serde",
@@ -463,15 +462,6 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "critical-section"
version = "1.1.2"
@@ -672,16 +662,6 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "flate2"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "flume"
version = "0.11.0"
@@ -1600,7 +1580,6 @@ dependencies = [
"reqwest",
"thiserror",
"tokio",
"ureq",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -1615,6 +1594,7 @@ dependencies = [
"base32",
"blake3",
"ed25519-dalek",
"js-sys",
"once_cell",
"pkarr",
"postcard",
@@ -1637,7 +1617,6 @@ dependencies = [
"flume",
"futures-util",
"heed",
"once_cell",
"pkarr",
"postcard",
"pubky-common",
@@ -1856,9 +1835,7 @@ version = "0.23.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
@@ -2532,24 +2509,6 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea"
dependencies = [
"base64 0.22.1",
"cookie",
"cookie_store",
"flate2",
"log",
"once_cell",
"rustls",
"rustls-pki-types",
"url",
"webpki-roots",
]
[[package]]
name = "url"
version = "2.5.2"
@@ -2701,15 +2660,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

@@ -15,3 +15,6 @@ rand = "0.8.5"
thiserror = "1.0.60"
postcard = { version = "1.0.8", features = ["alloc"] }
serde = { version = "1.0.204", features = ["derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3.69"

View File

@@ -1,7 +1,6 @@
//! Monotonic unix timestamp in microseconds
use std::fmt::Display;
use std::time::SystemTime;
use std::{
ops::{Add, Sub},
sync::Mutex,
@@ -10,6 +9,9 @@ use std::{
use once_cell::sync::Lazy;
use rand::Rng;
#[cfg(not(target_arch = "wasm32"))]
use std::time::SystemTime;
/// ~4% chance of none of 10 clocks have matching id.
const CLOCK_MASK: u64 = (1 << 8) - 1;
const TIME_MASK: u64 = !0 >> 8;
@@ -162,6 +164,15 @@ fn system_time() -> u64 {
.as_micros() as u64
}
#[cfg(target_arch = "wasm32")]
/// Return the number of microseconds since [SystemTime::UNIX_EPOCH]
pub fn system_time() -> u64 {
// Won't be an issue for more than 5000 years!
(js_sys::Date::now() as u64 )
// Turn miliseconds to microseconds
* 1000
}
#[derive(thiserror::Error, Debug)]
pub enum TimestampError {
#[error("Invalid bytes length, Timestamp should be encoded as 8 bytes, got {0}")]

View File

@@ -14,7 +14,6 @@ dirs-next = "2.0.0"
flume = "0.11.0"
futures-util = "0.3.30"
heed = "0.20.3"
once_cell = "1.19.0"
pkarr = { version = "2.1.0", features = ["async"] }
postcard = { version = "1.0.8", features = ["alloc"] }
pubky-common = { version = "0.1.0", path = "../pubky-common" }

View File

@@ -1,9 +1,9 @@
//! Configuration for the server
use anyhow::{anyhow, Result};
use pkarr::Keypair;
use pkarr::{mainline::dht::DhtSettings, Keypair};
// use serde::{Deserialize, Serialize};
use std::{fmt::Debug, path::PathBuf};
use std::{fmt::Debug, path::PathBuf, time::Duration};
use pubky_common::timestamp::Timestamp;
@@ -18,14 +18,15 @@ const DEFAULT_STORAGE_DIR: &str = "pubky";
Clone,
)]
pub struct Config {
port: Option<u16>,
bootstrap: Option<Vec<String>>,
domain: String,
pub port: Option<u16>,
pub bootstrap: Option<Vec<String>>,
pub domain: String,
/// Path to the storage directory
///
/// Defaults to a directory in the OS data directory
storage: Option<PathBuf>,
keypair: Keypair,
pub storage: Option<PathBuf>,
pub keypair: Keypair,
pub request_timeout: Option<Duration>,
}
impl Config {
@@ -50,6 +51,7 @@ impl Config {
Self {
bootstrap,
storage,
request_timeout: Some(Duration::from_millis(10)),
..Default::default()
}
}
@@ -93,6 +95,7 @@ impl Default for Config {
domain: "localhost".to_string(),
storage: None,
keypair: Keypair::random(),
request_timeout: None,
}
}
}

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use pkarr::mainline::Testnet;
use pubky_homeserver::Homeserver;
use pkarr::{mainline::Testnet, Keypair};
use pubky_homeserver::{config::Config, Homeserver};
use clap::Parser;
@@ -27,7 +27,12 @@ async fn main() -> Result<()> {
let server = if args.testnet {
let testnet = Testnet::new(3);
Homeserver::start_test(&testnet).await?
Homeserver::start(Config {
port: Some(15411),
keypair: Keypair::from_secret_key(&[0_u8; 32]),
..Config::test(&testnet)
})
.await?
} else {
Homeserver::start(Default::default()).await?
};

View File

@@ -1,10 +1,16 @@
use std::sync::Arc;
use axum::{
extract::DefaultBodyLimit,
http::Method,
routing::{delete, get, post, put},
Router,
};
use tower_cookies::CookieManagerLayer;
use tower_http::trace::TraceLayer;
use tower_http::{
cors::{self, CorsLayer},
trace::TraceLayer,
};
use crate::server::AppState;
@@ -24,7 +30,6 @@ fn base(state: AppState) -> Router {
.route("/:pubky/session", delete(auth::signout))
.route("/:pubky/*path", put(public::put))
.route("/:pubky/*path", get(public::get))
.layer(TraceLayer::new_for_http())
.layer(CookieManagerLayer::new())
// TODO: revisit if we enable streaming big payloads
// TODO: maybe add to a separate router (drive router?).
@@ -33,5 +38,13 @@ fn base(state: AppState) -> Router {
}
pub fn create_app(state: AppState) -> Router {
base(state).merge(pkarr_router())
base(state.clone())
// TODO: Only enable this for test environments?
.nest("/pkarr", pkarr_router(state))
.layer(
CorsLayer::new()
.allow_methods([Method::GET, Method::PUT, Method::POST, Method::DELETE])
.allow_origin(cors::Any),
)
.layer(TraceLayer::new_for_http())
}

View File

@@ -2,35 +2,39 @@ use std::{collections::HashMap, sync::RwLock};
use axum::{
body::{Body, Bytes},
extract::State,
http::StatusCode,
response::IntoResponse,
routing::{get, put},
Router,
};
use futures_util::stream::StreamExt;
use once_cell::sync::OnceCell;
use pkarr::{PublicKey, SignedPacket};
use tracing::debug;
use crate::{
error::{Error, Result},
extractors::Pubky,
server::AppState,
};
// TODO: maybe replace after we have local storage of users packets?
static IN_MEMORY: OnceCell<RwLock<HashMap<PublicKey, SignedPacket>>> = OnceCell::new();
/// Pkarr relay, helpful for testing.
///
/// For real productioin, you should use a [production ready
/// relay](https://github.com/pubky/pkarr/server).
pub fn pkarr_router() -> Router {
pub fn pkarr_router(state: AppState) -> Router {
Router::new()
.route("/pkarr/:pubky", put(pkarr_put))
.route("/pkarr/:pubky", get(pkarr_get))
.route("/:pubky", put(pkarr_put))
.route("/:pubky", get(pkarr_get))
.with_state(state)
}
pub async fn pkarr_put(pubky: Pubky, body: Body) -> Result<impl IntoResponse> {
pub async fn pkarr_put(
State(mut state): State<AppState>,
pubky: Pubky,
body: Body,
) -> Result<impl IntoResponse> {
let mut bytes = Vec::with_capacity(1104);
let mut stream = body.into_data_stream();
@@ -43,25 +47,13 @@ pub async fn pkarr_put(pubky: Pubky, body: Body) -> Result<impl IntoResponse> {
let signed_packet = SignedPacket::from_relay_payload(&public_key, &Bytes::from(bytes))?;
let mut store = IN_MEMORY
.get()
.expect("In memory pkarr store is not initialized")
.write()
.unwrap();
store.insert(public_key, signed_packet);
state.pkarr_client.publish(&signed_packet).await?;
Ok(())
}
pub async fn pkarr_get(pubky: Pubky) -> Result<impl IntoResponse> {
let store = IN_MEMORY
.get()
.expect("In memory pkarr store is not initialized")
.read()
.unwrap();
if let Some(signed_packet) = store.get(pubky.public_key()) {
pub async fn pkarr_get(State(state): State<AppState>, pubky: Pubky) -> Result<impl IntoResponse> {
if let Some(signed_packet) = state.pkarr_client.resolve(pubky.public_key()).await? {
return Ok(signed_packet.to_relay_payload());
}

View File

@@ -1,13 +1,16 @@
use std::{future::IntoFuture, net::SocketAddr};
use std::{
collections::HashMap, future::IntoFuture, net::SocketAddr, num::NonZeroUsize, sync::Arc,
};
use anyhow::{Error, Result};
use lru::LruCache;
use pubky_common::auth::AuthnVerifier;
use tokio::{net::TcpListener, signal, task::JoinSet};
use tokio::{net::TcpListener, signal, sync::Mutex, task::JoinSet};
use tracing::{debug, info, warn};
use pkarr::{
mainline::dht::{DhtSettings, Testnet},
PkarrClient, PublicKey, Settings,
PkarrClient, PkarrClientAsync, PublicKey, Settings, SignedPacket,
};
use crate::{config::Config, database::DB, pkarr::publish_server_packet};
@@ -23,6 +26,7 @@ pub struct Homeserver {
pub(crate) struct AppState {
pub verifier: AuthnVerifier,
pub db: DB,
pub pkarr_client: PkarrClientAsync,
}
impl Homeserver {
@@ -33,9 +37,20 @@ impl Homeserver {
let db = DB::open(&config.storage()?)?;
let pkarr_client = PkarrClient::new(Settings {
dht: DhtSettings {
bootstrap: config.bootstsrap(),
request_timeout: config.request_timeout,
..Default::default()
},
..Default::default()
})?
.as_async();
let state = AppState {
verifier: AuthnVerifier::new(public_key.clone()),
db,
pkarr_client: pkarr_client.clone(),
};
let app = crate::routes::create_app(state);
@@ -60,15 +75,6 @@ impl Homeserver {
info!("Homeserver listening on http://localhost:{port}");
let pkarr_client = PkarrClient::new(Settings {
dht: DhtSettings {
bootstrap: config.bootstsrap(),
..Default::default()
},
..Default::default()
})?
.as_async();
publish_server_packet(pkarr_client, config.keypair(), config.domain(), port).await?;
info!("Homeserver listening on pubky://{public_key}");

View File

@@ -11,20 +11,22 @@ keywords = ["web", "dht", "dns", "decentralized", "identity"]
crate-type = ["cdylib", "rlib"]
[dependencies]
pkarr = "2.1.0"
thiserror = "1.0.62"
wasm-bindgen = "0.2.92"
url = "2.5.2"
reqwest = { version = "0.12.5", features = ["cookies"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
pubky-common = { version = "0.1.0", path = "../pubky-common" }
ureq = { version = "2.10.0", features = ["cookies"] }
flume = { version = "0.11.0", features = ["select", "eventual-fairness"], default-features = false }
bytes = "1.6.1"
pubky-common = { version = "0.1.0", path = "../pubky-common" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
pkarr = { version="2.1.0", features = ["async"] }
flume = { version = "0.11.0", features = ["select", "eventual-fairness"], default-features = false }
[target.'cfg(target_arch = "wasm32")'.dependencies]
pkarr = { version = "2.1.0", default-features = false }
futures = "0.3.29"
js-sys = "0.3.69"
wasm-bindgen = "0.2.92"

View File

@@ -1,12 +1,15 @@
import test from 'brittle'
import z32 from 'z32'
import App from '@pubky/homeserver/test/helper/app.js'
import Client from '../src/index.js'
import { PubkyClient, Keypair, PublicKey } from '../index.js'
test('seed auth', async (t) => {
// const homeserver = await App(t)
let client = new PubkyClient();
let keypair = Keypair.random();
let homeserver = PublicKey.try_from("8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo");
await client.signup(keypair, homeserver);
// const client = new Client(
// homeserver.homeserver.pkarr.serverPkarr.publicKey(),

View File

@@ -5,7 +5,7 @@ use std::process::{Command, ExitStatus};
// If the process hangs, try `cargo clean` to remove all locks.
fn main() {
println!("cargo:rerun-if-changed=client/");
println!("Building wasm for pubky...");
build_wasm("nodejs").unwrap();
patch().unwrap();

View File

@@ -35,4 +35,18 @@ pub enum Error {
#[error(transparent)]
#[cfg(not(target_arch = "wasm32"))]
Session(#[from] pubky_common::session::Error),
#[error("Could not resolve endpoint for {0}")]
ResolveEndpoint(String),
}
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[cfg(target_arch = "wasm32")]
impl From<Error> for JsValue {
fn from(error: Error) -> JsValue {
let error_message = error.to_string();
js_sys::Error::new(&error_message).into()
}
}

View File

@@ -1,33 +1,24 @@
#![allow(unused)]
macro_rules! if_not_wasm {
($($item:item)*) => {$(
#[cfg(not(target_arch = "wasm32"))]
$item
)*}
}
macro_rules! if_wasm {
($($item:item)*) => {$(
#[cfg(target_arch = "wasm32")]
$item
)*}
}
if_not_wasm! {
mod client;
use client::PubkyClient;
}
if_wasm! {
mod wasm;
pub use wasm::keys::Keypair;
pub use wasm::PubkyClient;
}
mod error;
mod shared;
#[cfg(not(target_arch = "wasm32"))]
mod native;
#[cfg(target_arch = "wasm32")]
mod wasm;
use wasm_bindgen::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
use ::pkarr::PkarrClientAsync;
pub use error::Error;
#[derive(Debug, Clone)]
#[wasm_bindgen]
pub struct PubkyClient {
http: reqwest::Client,
#[cfg(not(target_arch = "wasm32"))]
pkarr: PkarrClientAsync,
}

View File

@@ -1,24 +1,18 @@
mod auth;
mod pkarr;
mod public;
pub mod auth;
pub mod pkarr;
pub mod public;
use std::{collections::HashMap, fmt::format, time::Duration};
use std::time::Duration;
use ::pkarr::PkarrClientAsync;
use url::Url;
use ::pkarr::{
mainline::dht::{DhtSettings, Testnet},
PkarrClient, PkarrClientAsync, Settings,
};
use pkarr::{DhtSettings, PkarrClient, Settings, Testnet};
use crate::error::{Error, Result};
use crate::PubkyClient;
static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
#[derive(Debug, Clone)]
pub struct PubkyClient {
http: reqwest::Client,
pkarr: PkarrClientAsync,
}
impl PubkyClient {
pub fn new() -> Self {
Self {
@@ -26,10 +20,12 @@ impl PubkyClient {
.user_agent(DEFAULT_USER_AGENT)
.build()
.unwrap(),
#[cfg(not(target_arch = "wasm32"))]
pkarr: PkarrClient::new(Default::default()).unwrap().as_async(),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn test(testnet: &Testnet) -> Self {
Self {
http: reqwest::Client::builder()

View File

@@ -3,15 +3,20 @@ use reqwest::StatusCode;
use pkarr::{Keypair, PublicKey};
use pubky_common::{auth::AuthnSignature, session::Session};
use super::{Error, PubkyClient, Result};
use crate::{
error::{Error, Result},
PubkyClient,
};
impl PubkyClient {
/// Signup to a homeserver and update Pkarr accordingly.
///
/// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key
/// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy"
pub async fn signup(&self, keypair: &Keypair, homeserver: &str) -> Result<()> {
let (audience, mut url) = self.resolve_endpoint(homeserver).await?;
pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result<()> {
let homeserver = homeserver.to_string();
let (audience, mut url) = self.resolve_endpoint(&homeserver).await?;
url.set_path(&format!("/{}", keypair.public_key()));
@@ -21,7 +26,7 @@ impl PubkyClient {
self.http.put(url).body(body).send().await?;
self.publish_pubky_homeserver(keypair, homeserver).await?;
self.publish_pubky_homeserver(keypair, &homeserver).await?;
Ok(())
}
@@ -96,10 +101,7 @@ mod tests {
let keypair = Keypair::random();
client
.signup(&keypair, &server.public_key().to_string())
.await
.unwrap();
client.signup(&keypair, &server.public_key()).await.unwrap();
let session = client.session(&keypair.public_key()).await.unwrap();

View File

@@ -1,12 +1,16 @@
pub use pkarr::{
use url::Url;
use pkarr::{
dns::{rdata::SVCB, Packet},
mainline::{dht::DhtSettings, Testnet},
Keypair, PkarrClient, PublicKey, Settings, SignedPacket,
Keypair, PublicKey, SignedPacket,
};
use crate::shared::pkarr::{format_url, parse_pubky_svcb, prepare_packet_for_signup};
use super::{Error, PubkyClient, Result, Url};
use crate::{
error::{Error, Result},
PubkyClient,
};
impl PubkyClient {
/// Publish the SVCB record for `_pubky.<public_key>`.
@@ -38,6 +42,7 @@ impl PubkyClient {
/// Resolve a service's public_key and clearnet url from a Pubky domain
pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> {
let original_target = target;
// TODO: cache the result of this function?
let mut target = target.to_string();
@@ -48,7 +53,11 @@ impl PubkyClient {
// PublicKey is very good at extracting the Pkarr TLD from a string.
while let Ok(public_key) = PublicKey::try_from(target.clone()) {
let response = self.pkarr.resolve(&public_key).await?;
let response = self
.pkarr
.resolve(&public_key)
.await
.map_err(|e| Error::ResolveEndpoint(original_target.into()))?;
let done = parse_pubky_svcb(
response,
@@ -64,7 +73,7 @@ impl PubkyClient {
}
}
format_url(homeserver_public_key, host)
format_url(original_target, homeserver_public_key, host)
}
}
@@ -79,6 +88,15 @@ mod tests {
};
use pubky_homeserver::Homeserver;
#[tokio::test]
async fn resolve_endpoint() {
let target = "oc9tdmh8c4pmy3tk946oqqfkhic18xdiytadspiy55qhbnja5w9o";
let client = PubkyClient::new();
client.resolve_endpoint(target).await.unwrap();
}
#[tokio::test]
async fn resolve_homeserver() {
let testnet = Testnet::new(3);

View File

@@ -2,7 +2,7 @@ use bytes::Bytes;
use pkarr::PublicKey;
use super::{PubkyClient, Result};
use crate::{error::Result, PubkyClient};
impl PubkyClient {
pub async fn put(&self, pubky: &PublicKey, path: &str, content: &[u8]) -> Result<()> {
@@ -67,10 +67,7 @@ mod tests {
let keypair = Keypair::random();
client
.signup(&keypair, &server.public_key().to_string())
.await
.unwrap();
client.signup(&keypair, &server.public_key()).await.unwrap();
let response = client
.put(&keypair.public_key(), "/pub/foo.txt", &[0, 1, 2, 3, 4])

View File

@@ -87,6 +87,7 @@ pub fn parse_pubky_svcb(
}
pub fn format_url(
original_target: &str,
homeserver_public_key: Option<PublicKey>,
host: String,
) -> Result<(PublicKey, Url)> {
@@ -100,5 +101,5 @@ pub fn format_url(
return Ok((homeserver, Url::parse(&url)?));
}
Err(Error::Generic("Could not resolve endpoint".to_string()))
Err(Error::ResolveEndpoint(original_target.into()))
}

View File

@@ -4,11 +4,7 @@ pub mod auth;
pub mod keys;
pub mod pkarr;
#[wasm_bindgen]
pub struct PubkyClient {
pub(crate) http: reqwest::Client,
pub(crate) pkarr: pkarr::PkarrRelayClient,
}
use crate::PubkyClient;
#[wasm_bindgen]
impl PubkyClient {
@@ -16,7 +12,7 @@ impl PubkyClient {
pub fn new() -> Self {
Self {
http: reqwest::Client::new(),
pkarr: pkarr::PkarrRelayClient::default(),
// pkarr: pkarr::PkarrRelayClient::default(),
}
}
}

View File

@@ -1,10 +1,14 @@
use pubky_common::auth::AuthnSignature;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::RequestMode;
use pkarr::PkarrRelayClient;
use super::{keys::Keypair, PubkyClient};
use super::{
keys::{Keypair, PublicKey},
PubkyClient,
};
#[wasm_bindgen]
impl PubkyClient {
@@ -13,16 +17,21 @@ impl PubkyClient {
/// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key
/// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy"
#[wasm_bindgen]
pub async fn signup(&self, keypair: &Keypair, homeserver: &str) -> Result<String, JsValue> {
let (audience, mut url) = self.resolve_endpoint(homeserver)?;
pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result<(), JsValue> {
let keypair = keypair.as_inner();
let homeserver = homeserver.as_inner().to_string();
let (audience, mut url) = self.resolve_endpoint(&homeserver).await?;
url.set_path(&format!("/{}", keypair.public_key()));
self.http
.put(&url)
.send_bytes(AuthnSignature::generate(keypair, &audience).as_bytes())?;
let body = AuthnSignature::generate(keypair, &audience)
.as_bytes()
.to_owned();
self.publish_pubky_homeserver(keypair, homeserver).await;
self.http.put(url).body(body).send().await?;
self.publish_pubky_homeserver(keypair, &homeserver).await?;
Ok(())
}

View File

@@ -1,5 +1,7 @@
use wasm_bindgen::prelude::*;
use crate::Error;
#[wasm_bindgen]
pub struct Keypair(pkarr::Keypair);
@@ -48,6 +50,17 @@ impl PublicKey {
pub fn to_string(&self) -> String {
self.0.to_string()
}
#[wasm_bindgen]
pub fn try_from(value: JsValue) -> Result<PublicKey, JsValue> {
let string = value.as_string().ok_or(Error::Generic(
"Couldn't create a PublicKey from this type of value".to_string(),
))?;
Ok(PublicKey(
pkarr::PublicKey::try_from(string).map_err(|e| Error::Pkarr(e))?,
))
}
}
impl PublicKey {

View File

@@ -1,14 +1,24 @@
use reqwest::StatusCode;
use url::Url;
use wasm_bindgen::prelude::*;
pub use pkarr::{
dns::{rdata::SVCB, Packet},
PkarrRelayClient, PublicKey, SignedPacket,
Keypair, PublicKey, SignedPacket,
};
use crate::error::Result;
use crate::error::{Error, Result};
use crate::shared::pkarr::{format_url, parse_pubky_svcb, prepare_packet_for_signup};
use crate::PubkyClient;
use super::{keys::Keypair, PubkyClient};
const TEST_RELAY: &str = "http://localhost:15411/pkarr";
#[macro_export]
macro_rules! log {
($($arg:expr),*) => {
web_sys::console::debug_1(&format!($($arg),*).into());
};
}
impl PubkyClient {
/// Publish the SVCB record for `_pubky.<public_key>`.
@@ -17,11 +27,91 @@ impl PubkyClient {
keypair: &Keypair,
host: &str,
) -> Result<()> {
let existing = self.pkarr.resolve(&keypair.public_key().as_inner()).await?;
// let existing = self.pkarr.resolve(&keypair.public_key()).await?;
let existing = self.pkarr_resolve(&keypair.public_key()).await?;
let signed_packet = prepare_packet_for_signup(keypair.as_inner(), host, existing)?;
let signed_packet = prepare_packet_for_signup(keypair, host, existing)?;
self.pkarr.publish(&signed_packet).await?;
// self.pkarr.publish(&signed_packet).await?;
self.pkarr_publish(&signed_packet).await?;
Ok(())
}
/// Resolve the homeserver for a pubky.
pub(crate) async fn resolve_pubky_homeserver(
&self,
pubky: &PublicKey,
) -> Result<(PublicKey, Url)> {
let target = format!("_pubky.{}", pubky);
self.resolve_endpoint(&target)
.await
.map_err(|_| Error::Generic("Could not resolve homeserver".to_string()))
}
/// Resolve a service's public_key and clearnet url from a Pubky domain
pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> {
let original_target = target;
// TODO: cache the result of this function?
let mut target = target.to_string();
let mut homeserver_public_key = None;
let mut host = target.clone();
let mut step = 0;
// PublicKey is very good at extracting the Pkarr TLD from a string.
while let Ok(public_key) = PublicKey::try_from(target.clone()) {
let response = self
.pkarr_resolve(&public_key)
.await
.map_err(|e| Error::ResolveEndpoint(original_target.into()))?;
let done = parse_pubky_svcb(
response,
&public_key,
&mut target,
&mut homeserver_public_key,
&mut host,
&mut step,
);
if done {
break;
}
}
format_url(original_target, homeserver_public_key, host)
}
//TODO: Allow multiple relays in parallel
//TODO: migrate to pkarr::PkarrRelayClient
async fn pkarr_resolve(&self, public_key: &PublicKey) -> Result<Option<SignedPacket>> {
let res = self
.http
.get(format!("{TEST_RELAY}/{}", public_key))
.send()
.await?;
if res.status() == StatusCode::NOT_FOUND {
return Ok(None);
};
// TODO: guard against too large responses.
let bytes = res.bytes().await?;
let existing = SignedPacket::from_relay_payload(public_key, &bytes)?;
Ok(Some(existing))
}
async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> {
self.http
.put(format!("{TEST_RELAY}/{}", signed_packet.public_key()))
.body(signed_packet.to_relay_payload())
.send()
.await?;
Ok(())
}