mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-18 21:44:21 +01:00
refactor(pubky): share helper functions between rust and wasm
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -29,4 +29,6 @@ if_wasm! {
|
||||
}
|
||||
|
||||
mod error;
|
||||
mod shared;
|
||||
|
||||
pub use error::Error;
|
||||
|
||||
1
pubky/src/shared.rs
Normal file
1
pubky/src/shared.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod pkarr;
|
||||
104
pubky/src/shared/pkarr.rs
Normal file
104
pubky/src/shared/pkarr.rs
Normal 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()))
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user