mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-10 09:44:20 +01:00
feat(pubky): accept url instead of public key and path
This commit is contained in:
@@ -26,19 +26,22 @@ await client.signup(keypair, homeserver)
|
||||
|
||||
const publicKey = keypair.public_key();
|
||||
|
||||
// Pubky URL
|
||||
let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`;
|
||||
|
||||
// Verify that you are signed in.
|
||||
const session = await client.session(publicKey)
|
||||
|
||||
const body = Buffer.from(JSON.stringify({ foo: 'bar' }))
|
||||
|
||||
// PUT public data, by authorized client
|
||||
await client.put(publicKey, "/pub/example.com/arbitrary", body);
|
||||
await client.put(url, body);
|
||||
|
||||
// GET public data without signup or signin
|
||||
{
|
||||
const client = new PubkyClient();
|
||||
|
||||
let response = await client.get(publicKey, "/pub/example.com/arbitrary");
|
||||
let response = await client.get(url);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -12,17 +12,19 @@ test('public: put/get', async (t) => {
|
||||
|
||||
const publicKey = keypair.public_key();
|
||||
|
||||
let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`;
|
||||
|
||||
const body = Buffer.from(JSON.stringify({ foo: 'bar' }))
|
||||
|
||||
// PUT public data, by authorized client
|
||||
await client.put(publicKey, "/pub/example.com/arbitrary", body);
|
||||
await client.put(url, body);
|
||||
|
||||
|
||||
// GET public data without signup or signin
|
||||
{
|
||||
const client = PubkyClient.testnet();
|
||||
|
||||
let response = await client.get(publicKey, "/pub/example.com/arbitrary");
|
||||
let response = await client.get(url);
|
||||
|
||||
t.ok(Buffer.from(response).equals(body))
|
||||
}
|
||||
|
||||
@@ -86,13 +86,13 @@ impl PubkyClient {
|
||||
// === Public data ===
|
||||
|
||||
/// Upload a small payload to a given path.
|
||||
pub async fn put(&self, pubky: &PublicKey, path: &str, content: &[u8]) -> Result<()> {
|
||||
self.inner_put(pubky, path, content).await
|
||||
pub async fn put<T: TryInto<Url>>(&self, url: T, content: &[u8]) -> Result<()> {
|
||||
self.inner_put(url, content).await
|
||||
}
|
||||
|
||||
/// Download a small payload from a given path relative to a pubky author.
|
||||
pub async fn get(&self, pubky: &PublicKey, path: &str) -> Result<Option<Bytes>> {
|
||||
self.inner_get(pubky, path).await
|
||||
pub async fn get<T: TryInto<Url>>(&self, url: T) -> Result<Option<Bytes>> {
|
||||
self.inner_get(url).await
|
||||
}
|
||||
|
||||
// /// Delete a file at a path relative to a pubky author.
|
||||
|
||||
@@ -2,12 +2,15 @@ use reqwest::{Method, StatusCode};
|
||||
|
||||
use pkarr::{Keypair, PublicKey};
|
||||
use pubky_common::{auth::AuthnSignature, session::Session};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
PubkyClient,
|
||||
};
|
||||
|
||||
use super::pkarr::Endpoint;
|
||||
|
||||
impl PubkyClient {
|
||||
/// Signup to a homeserver and update Pkarr accordingly.
|
||||
///
|
||||
@@ -22,7 +25,10 @@ impl PubkyClient {
|
||||
|
||||
let public_key = &keypair.public_key();
|
||||
|
||||
let (audience, mut url) = self.resolve_endpoint(&homeserver).await?;
|
||||
let Endpoint {
|
||||
public_key: audience,
|
||||
mut url,
|
||||
} = self.resolve_endpoint(&homeserver).await?;
|
||||
|
||||
url.set_path(&format!("/{}", public_key));
|
||||
|
||||
@@ -30,7 +36,11 @@ impl PubkyClient {
|
||||
.as_bytes()
|
||||
.to_owned();
|
||||
|
||||
let response = self.request(Method::PUT, url).body(body).send().await?;
|
||||
let response = self
|
||||
.request(Method::PUT, url.clone())
|
||||
.body(body)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
self.store_session(response);
|
||||
|
||||
@@ -44,7 +54,7 @@ impl PubkyClient {
|
||||
/// Returns None if not signed in, or [reqwest::Error]
|
||||
/// if the response has any other `>=404` status code.
|
||||
pub(crate) async fn inner_session(&self, pubky: &PublicKey) -> Result<Option<Session>> {
|
||||
let (_, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
let Endpoint { mut url, .. } = self.resolve_pubky_homeserver(pubky).await?;
|
||||
|
||||
url.set_path(&format!("/{}/session", pubky));
|
||||
|
||||
@@ -65,7 +75,10 @@ impl PubkyClient {
|
||||
|
||||
/// Signout from a homeserver.
|
||||
pub async fn inner_signout(&self, pubky: &PublicKey) -> Result<()> {
|
||||
let (_, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
let Endpoint {
|
||||
public_key,
|
||||
mut url,
|
||||
} = self.resolve_pubky_homeserver(pubky).await?;
|
||||
|
||||
url.set_path(&format!("/{}/session", pubky));
|
||||
|
||||
@@ -80,7 +93,10 @@ impl PubkyClient {
|
||||
pub async fn inner_signin(&self, keypair: &Keypair) -> Result<()> {
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
let (audience, mut url) = self.resolve_pubky_homeserver(&pubky).await?;
|
||||
let Endpoint {
|
||||
public_key: audience,
|
||||
mut url,
|
||||
} = self.resolve_pubky_homeserver(&pubky).await?;
|
||||
|
||||
url.set_path(&format!("/{}/session", &pubky));
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use url::Url;
|
||||
use url::{Origin, Url};
|
||||
|
||||
use pkarr::{
|
||||
dns::{rdata::SVCB, Packet},
|
||||
@@ -48,11 +48,8 @@ impl PubkyClient {
|
||||
}
|
||||
|
||||
/// Resolve the homeserver for a pubky.
|
||||
pub(crate) async fn resolve_pubky_homeserver(
|
||||
&self,
|
||||
pubky: &PublicKey,
|
||||
) -> Result<(PublicKey, Url)> {
|
||||
let target = format!("_pubky.{}", pubky);
|
||||
pub(crate) async fn resolve_pubky_homeserver(&self, pubky: &PublicKey) -> Result<Endpoint> {
|
||||
let target = format!("_pubky.{pubky}");
|
||||
|
||||
self.resolve_endpoint(&target)
|
||||
.await
|
||||
@@ -60,14 +57,14 @@ 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)> {
|
||||
pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<Endpoint> {
|
||||
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 origin = target.clone();
|
||||
|
||||
let mut step = 0;
|
||||
|
||||
@@ -118,9 +115,9 @@ impl PubkyClient {
|
||||
}
|
||||
let port = u16::from_be_bytes([port[0], port[1]]);
|
||||
|
||||
host = format!("{target}:{port}");
|
||||
origin = format!("{target}:{port}");
|
||||
} else {
|
||||
host.clone_from(&target);
|
||||
origin.clone_from(&target);
|
||||
};
|
||||
|
||||
if step >= MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION {
|
||||
@@ -130,20 +127,29 @@ impl PubkyClient {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(homeserver) = homeserver_public_key {
|
||||
let url = if host.starts_with("localhost") {
|
||||
format!("http://{host}")
|
||||
} else {
|
||||
format!("https://{host}")
|
||||
};
|
||||
if let Some(public_key) = homeserver_public_key {
|
||||
let mut url = Url::parse(&format!(
|
||||
"{}://{}",
|
||||
if origin.starts_with("localhost") {
|
||||
"http"
|
||||
} else {
|
||||
"https"
|
||||
},
|
||||
origin
|
||||
))?;
|
||||
|
||||
return Ok((homeserver, Url::parse(&url)?));
|
||||
return Ok(Endpoint { public_key, url });
|
||||
}
|
||||
|
||||
Err(Error::ResolveEndpoint(original_target.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Endpoint {
|
||||
pub public_key: PublicKey,
|
||||
pub url: Url,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -200,7 +206,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (public_key, url) = client
|
||||
let Endpoint { public_key, url } = client
|
||||
.resolve_pubky_homeserver(&pubky.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -4,11 +4,16 @@ use pkarr::PublicKey;
|
||||
use reqwest::{Method, Response, StatusCode};
|
||||
use url::Url;
|
||||
|
||||
use crate::{error::Result, PubkyClient};
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
PubkyClient,
|
||||
};
|
||||
|
||||
use super::pkarr::Endpoint;
|
||||
|
||||
impl PubkyClient {
|
||||
pub async fn inner_put(&self, pubky: &PublicKey, path: &str, content: &[u8]) -> Result<()> {
|
||||
let url = self.url(pubky, path).await?;
|
||||
pub async fn inner_put<T: TryInto<Url>>(&self, url: T, content: &[u8]) -> Result<()> {
|
||||
let url = self.pubky_to_http(url).await?;
|
||||
|
||||
let response = self
|
||||
.request(Method::PUT, url)
|
||||
@@ -21,8 +26,8 @@ impl PubkyClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn inner_get(&self, pubky: &PublicKey, path: &str) -> Result<Option<Bytes>> {
|
||||
let url = self.url(pubky, path).await?;
|
||||
pub async fn inner_get<T: TryInto<Url>>(&self, url: T) -> Result<Option<Bytes>> {
|
||||
let url = self.pubky_to_http(url).await?;
|
||||
|
||||
let response = self.request(Method::GET, url).send().await?;
|
||||
|
||||
@@ -38,8 +43,8 @@ impl PubkyClient {
|
||||
Ok(Some(bytes))
|
||||
}
|
||||
|
||||
pub async fn inner_delete(&self, pubky: &PublicKey, path: &str) -> Result<()> {
|
||||
let url = self.url(pubky, path).await?;
|
||||
pub async fn inner_delete<T: TryInto<Url>>(&self, url: T) -> Result<()> {
|
||||
let url = self.pubky_to_http(url).await?;
|
||||
|
||||
let response = self.request(Method::DELETE, url).send().await?;
|
||||
|
||||
@@ -48,12 +53,35 @@ impl PubkyClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn url(&self, pubky: &PublicKey, path: &str) -> Result<Url> {
|
||||
let path = normalize_path(path)?;
|
||||
async fn pubky_to_http<T: TryInto<Url>>(&self, url: T) -> Result<Url> {
|
||||
let mut original_url: Url = url
|
||||
.try_into()
|
||||
.map_err(|e| Error::Generic("Invalid Url".to_string()))?;
|
||||
|
||||
let (_, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
if original_url.scheme() != "pubky" {
|
||||
return Ok(original_url);
|
||||
}
|
||||
|
||||
url.set_path(&format!("/{pubky}/{path}"));
|
||||
let pubky = original_url
|
||||
.host_str()
|
||||
.ok_or(Error::Generic("Missing Pubky Url host".to_string()))?
|
||||
.to_string();
|
||||
|
||||
let Endpoint { mut url, .. } = self
|
||||
.resolve_pubky_homeserver(&PublicKey::try_from(pubky.clone())?)
|
||||
.await?;
|
||||
|
||||
let path = original_url.path_segments();
|
||||
|
||||
// TODO: replace if we move to subdomains instead of paths.
|
||||
let mut split = url.path_segments_mut().unwrap();
|
||||
split.push(&pubky);
|
||||
if let Some(segments) = path {
|
||||
for segment in segments {
|
||||
split.push(segment);
|
||||
}
|
||||
}
|
||||
drop(split);
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
@@ -96,16 +124,11 @@ mod tests {
|
||||
|
||||
client.signup(&keypair, &server.public_key()).await.unwrap();
|
||||
|
||||
client
|
||||
.put(&keypair.public_key(), "/pub/foo.txt", &[0, 1, 2, 3, 4])
|
||||
.await
|
||||
.unwrap();
|
||||
let url = format!("pubky://{}/pub/foo.txt", keypair.public_key());
|
||||
|
||||
let response = client
|
||||
.get(&keypair.public_key(), "/pub/foo.txt")
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
client.put(url.as_str(), &[0, 1, 2, 3, 4]).await.unwrap();
|
||||
|
||||
let response = client.get(url.as_str()).await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(response, bytes::Bytes::from(vec![0, 1, 2, 3, 4]));
|
||||
|
||||
@@ -135,18 +158,19 @@ mod tests {
|
||||
|
||||
let public_key = keypair.public_key();
|
||||
|
||||
let url = format!("pubky://{public_key}/pub/foo.txt");
|
||||
|
||||
let other_client = PubkyClient::test(&testnet);
|
||||
{
|
||||
let other = Keypair::random();
|
||||
|
||||
// TODO: remove extra client after switching to subdomains.
|
||||
other_client
|
||||
.signup(&other, &server.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = other_client
|
||||
.put(&public_key, "/pub/foo.txt", &[0, 1, 2, 3, 4])
|
||||
.await;
|
||||
let response = other_client.put(url.as_str(), &[0, 1, 2, 3, 4]).await;
|
||||
|
||||
match response {
|
||||
Err(Error::Reqwest(error)) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use reqwest::{Method, RequestBuilder, Response};
|
||||
use reqwest::{IntoUrl, Method, RequestBuilder, Response};
|
||||
use url::Url;
|
||||
|
||||
use crate::PubkyClient;
|
||||
@@ -116,20 +116,14 @@ impl PubkyClient {
|
||||
|
||||
#[wasm_bindgen]
|
||||
/// Upload a small payload to a given path.
|
||||
pub async fn put(&self, pubky: &PublicKey, path: &str, content: &[u8]) -> Result<(), JsValue> {
|
||||
self.inner_put(pubky.as_inner(), path, content)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
pub async fn put(&self, url: &str, content: &[u8]) -> Result<(), JsValue> {
|
||||
self.inner_put(url, content).await.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
/// Download a small payload from a given path relative to a pubky author.
|
||||
pub async fn get(
|
||||
&self,
|
||||
pubky: &PublicKey,
|
||||
path: &str,
|
||||
) -> Result<Option<js_sys::Uint8Array>, JsValue> {
|
||||
self.inner_get(pubky.as_inner(), path)
|
||||
pub async fn get(&self, url: &str) -> Result<Option<js_sys::Uint8Array>, JsValue> {
|
||||
self.inner_get(url)
|
||||
.await
|
||||
.map(|b| b.map(|b| (&*b).into()))
|
||||
.map_err(|e| e.into())
|
||||
|
||||
Reference in New Issue
Block a user