refactor(pubky): share helper functions between rust and wasm

This commit is contained in:
nazeh
2024-07-27 10:13:42 +03:00
parent e05a49cd04
commit c466ca5546
10 changed files with 160 additions and 107 deletions

View File

@@ -13,12 +13,13 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
pkarr = "2.1.0"
thiserror = "1.0.62"
wasm-bindgen = "0.2.92"
url = "2.5.2"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
pubky-common = { version = "0.1.0", path = "../pubky-common" }
ureq = { version = "2.10.0", features = ["cookies"] }
url = "2.5.2"
flume = { version = "0.11.0", features = ["select", "eventual-fairness"], default-features = false }
bytes = "1.6.1"

View File

@@ -4,33 +4,16 @@ pub use pkarr::{
Keypair, PkarrClient, PublicKey, Settings, SignedPacket,
};
use super::{Error, PubkyClient, Result, Url};
use crate::shared::pkarr::{format_url, parse_pubky_svcb, prepare_packet_for_signup};
const MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION: u8 = 3;
use super::{Error, PubkyClient, Result, Url};
impl PubkyClient {
/// Publish the SVCB record for `_pubky.<public_key>`.
pub(crate) fn publish_pubky_homeserver(&self, keypair: &Keypair, host: &str) -> Result<()> {
let mut packet = Packet::new_reply(0);
let existing = self.pkarr.resolve(&keypair.public_key())?;
if let Some(existing) = self.pkarr.resolve(&keypair.public_key())? {
for answer in existing.packet().answers.iter().cloned() {
if !answer.name.to_string().starts_with("_pubky") {
packet.answers.push(answer.into_owned())
}
}
}
let svcb = SVCB::new(0, host.try_into()?);
packet.answers.push(pkarr::dns::ResourceRecord::new(
"_pubky".try_into().unwrap(),
pkarr::dns::CLASS::IN,
60 * 60,
pkarr::dns::rdata::RData::SVCB(svcb),
));
let signed_packet = SignedPacket::from_packet(keypair, &packet)?;
let signed_packet = prepare_packet_for_signup(keypair, host, existing)?;
self.pkarr.publish(&signed_packet)?;
@@ -48,66 +31,32 @@ impl PubkyClient {
/// Resolve a service's public_key and clearnet url from a Pubky domain
pub(crate) fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> {
// TODO: cache the result of this function?
// TODO: use MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION
// TODO: move to common?
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()) {
if let Some(signed_packet) = self.pkarr.resolve(&public_key)? {
let mut prior = None;
let response = self.pkarr.resolve(&public_key)?;
for answer in signed_packet.resource_records(&target) {
if let pkarr::dns::rdata::RData::SVCB(svcb) = &answer.rdata {
if svcb.priority == 0 {
prior = Some(svcb)
} else if let Some(sofar) = prior {
if svcb.priority >= sofar.priority {
prior = Some(svcb)
}
// TODO return random if priority is the same
} else {
prior = Some(svcb)
}
}
}
let done = parse_pubky_svcb(
response,
&public_key,
&mut target,
&mut homeserver_public_key,
&mut host,
&mut step,
);
if let Some(svcb) = prior {
homeserver_public_key = Some(public_key);
target = svcb.target.to_string();
if let Some(port) = svcb.get_param(pkarr::dns::rdata::SVCB::PORT) {
if port.len() < 2 {
// TODO: debug! Error encoding port!
}
let port = u16::from_be_bytes([port[0], port[1]]);
host = format!("{target}:{port}");
} else {
host.clone_from(&target);
};
continue;
}
};
break;
if done {
break;
}
}
if let Some(homeserver) = homeserver_public_key {
let url = if host.starts_with("localhost") {
format!("http://{host}")
} else {
format!("https://{host}")
};
return Ok((homeserver, Url::parse(&url)?));
}
Err(Error::Generic("Could not resolve endpoint".to_string()))
format_url(homeserver_public_key, host)
}
}

View File

@@ -22,6 +22,9 @@ pub enum Error {
#[error(transparent)]
Pkarr(#[from] pkarr::Error),
#[error(transparent)]
Url(#[from] url::ParseError),
#[error(transparent)]
#[cfg(not(target_arch = "wasm32"))]
Flume(#[from] flume::RecvError),
@@ -30,10 +33,6 @@ pub enum Error {
#[cfg(not(target_arch = "wasm32"))]
Ureq(#[from] Box<ureq::Error>),
#[error(transparent)]
#[cfg(not(target_arch = "wasm32"))]
Url(#[from] url::ParseError),
#[error(transparent)]
#[cfg(not(target_arch = "wasm32"))]
Session(#[from] pubky_common::session::Error),

View File

@@ -29,4 +29,6 @@ if_wasm! {
}
mod error;
mod shared;
pub use error::Error;

1
pubky/src/shared.rs Normal file
View File

@@ -0,0 +1 @@
pub mod pkarr;

104
pubky/src/shared/pkarr.rs Normal file
View File

@@ -0,0 +1,104 @@
use url::Url;
use pkarr::{
dns::{rdata::SVCB, Packet},
Keypair, PublicKey, SignedPacket,
};
use crate::error::{Error, Result};
const MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION: u8 = 3;
pub fn prepare_packet_for_signup(
keypair: &Keypair,
host: &str,
existing: Option<SignedPacket>,
) -> Result<SignedPacket> {
let mut packet = Packet::new_reply(0);
if let Some(existing) = existing {
for answer in existing.packet().answers.iter().cloned() {
if !answer.name.to_string().starts_with("_pubky") {
packet.answers.push(answer.into_owned())
}
}
}
let svcb = SVCB::new(0, host.try_into()?);
packet.answers.push(pkarr::dns::ResourceRecord::new(
"_pubky".try_into().unwrap(),
pkarr::dns::CLASS::IN,
60 * 60,
pkarr::dns::rdata::RData::SVCB(svcb),
));
Ok(SignedPacket::from_packet(keypair, &packet)?)
}
pub fn parse_pubky_svcb(
signed_packet: Option<SignedPacket>,
public_key: &PublicKey,
target: &mut String,
homeserver_public_key: &mut Option<PublicKey>,
host: &mut String,
step: &mut u8,
) -> bool {
*step += 1;
let mut prior = None;
if let Some(signed_packet) = signed_packet {
for answer in signed_packet.resource_records(target) {
if let pkarr::dns::rdata::RData::SVCB(svcb) = &answer.rdata {
if svcb.priority == 0 {
prior = Some(svcb)
} else if let Some(sofar) = prior {
if svcb.priority >= sofar.priority {
prior = Some(svcb)
}
// TODO return random if priority is the same
} else {
prior = Some(svcb)
}
}
}
if let Some(svcb) = prior {
*homeserver_public_key = Some(public_key.clone());
*target = svcb.target.to_string();
if let Some(port) = svcb.get_param(pkarr::dns::rdata::SVCB::PORT) {
if port.len() < 2 {
// TODO: debug! Error encoding port!
}
let port = u16::from_be_bytes([port[0], port[1]]);
*host = format!("{target}:{port}");
} else {
host.clone_from(target);
};
return *step >= MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION;
}
}
true
}
pub fn format_url(
homeserver_public_key: Option<PublicKey>,
host: String,
) -> Result<(PublicKey, Url)> {
if let Some(homeserver) = homeserver_public_key {
let url = if host.starts_with("localhost") {
format!("http://{host}")
} else {
format!("https://{host}")
};
return Ok((homeserver, Url::parse(&url)?));
}
Err(Error::Generic("Could not resolve endpoint".to_string()))
}

View File

@@ -8,3 +8,13 @@ pub mod pkarr;
pub struct PubkyClient {
pub(crate) pkarr: pkarr::PkarrRelayClient,
}
#[wasm_bindgen]
impl PubkyClient {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
pkarr: pkarr::PkarrRelayClient::default(),
}
}
}

View File

@@ -8,19 +8,12 @@ use super::{keys::Keypair, PubkyClient};
#[wasm_bindgen]
impl PubkyClient {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
pkarr: PkarrRelayClient::default(),
}
}
/// 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"
#[wasm_bindgen]
pub fn signup(&self, secret_key: Keypair, homeserver: &str) -> Result<(), JsValue> {
pub async fn signup(&self, keypair: &Keypair, homeserver: &str) -> Result<(), JsError> {
// let (audience, mut url) = self.resolve_endpoint(homeserver)?;
// url.set_path(&format!("/{}", keypair.public_key()));
@@ -29,7 +22,7 @@ impl PubkyClient {
// fetch_base(url.to_string(), "PUT", body).await?;
// self.publish_pubky_homeserver(keypair, homeserver);
self.publish_pubky_homeserver(keypair, homeserver).await?;
Ok(())
}

View File

@@ -27,6 +27,12 @@ impl Keypair {
}
}
impl Keypair {
pub fn as_inner(&self) -> &pkarr::Keypair {
&self.0
}
}
#[wasm_bindgen]
pub struct PublicKey(pkarr::PublicKey);
@@ -43,3 +49,9 @@ impl PublicKey {
self.0.to_string()
}
}
impl PublicKey {
pub fn as_inner(&self) -> &pkarr::PublicKey {
&self.0
}
}

View File

@@ -2,14 +2,13 @@ use wasm_bindgen::prelude::*;
pub use pkarr::{
dns::{rdata::SVCB, Packet},
Keypair, PkarrRelayClient, PublicKey, SignedPacket,
PkarrRelayClient, PublicKey, SignedPacket,
};
use crate::error::Result;
use crate::shared::pkarr::{format_url, parse_pubky_svcb, prepare_packet_for_signup};
use super::PubkyClient;
// TODO: Share more code with the non-wasm client.
use super::{keys::Keypair, PubkyClient};
impl PubkyClient {
/// Publish the SVCB record for `_pubky.<public_key>`.
@@ -18,26 +17,9 @@ impl PubkyClient {
keypair: &Keypair,
host: &str,
) -> Result<()> {
let mut packet = Packet::new_reply(0);
let existing = self.pkarr.resolve(&keypair.public_key().as_inner()).await?;
if let Some(existing) = self.pkarr.resolve(&keypair.public_key()).await? {
for answer in existing.packet().answers.iter().cloned() {
if !answer.name.to_string().starts_with("_pubky") {
packet.answers.push(answer.into_owned())
}
}
}
let svcb = SVCB::new(0, host.try_into()?);
packet.answers.push(pkarr::dns::ResourceRecord::new(
"_pubky".try_into().unwrap(),
pkarr::dns::CLASS::IN,
60 * 60,
pkarr::dns::rdata::RData::SVCB(svcb),
));
let signed_packet = SignedPacket::from_packet(keypair, &packet)?;
let signed_packet = prepare_packet_for_signup(keypair.as_inner(), host, existing)?;
self.pkarr.publish(&signed_packet).await?;