mirror of
https://github.com/aljazceru/pubky-core.git
synced 2025-12-31 12:54:35 +01:00
feat(pubky): start testing js package against local homeserver
This commit is contained in:
52
Cargo.lock
generated
52
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}")]
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
};
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
@@ -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])
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user