mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-19 22:14:32 +01:00
feat(pubky): add put/get methods for js
This commit is contained in:
@@ -14,30 +14,21 @@ crate-type = ["cdylib", "rlib"]
|
||||
thiserror = "1.0.62"
|
||||
wasm-bindgen = "0.2.92"
|
||||
url = "2.5.2"
|
||||
reqwest = { version = "0.12.5", features = ["cookies"] }
|
||||
bytes = "1.6.1"
|
||||
|
||||
pubky-common = { version = "0.1.0", path = "../pubky-common" }
|
||||
flume = "0.11.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
pkarr = { version="2.1.0", features = ["async"] }
|
||||
reqwest = { version = "0.12.5", features = ["cookies"], default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
pkarr = { version = "2.1.0", default-features = false }
|
||||
reqwest = { version = "0.12.5", default-features = false }
|
||||
|
||||
futures = "0.3.29"
|
||||
js-sys = "0.3.69"
|
||||
wasm-bindgen = "0.2.92"
|
||||
wasm-bindgen-futures = "0.4.42"
|
||||
web-sys = { version = "0.3.69", features = [
|
||||
"console",
|
||||
"Request",
|
||||
"RequestInit",
|
||||
"RequestMode",
|
||||
"Response",
|
||||
"Window",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
pubky_homeserver = { path = "../pubky-homeserver" }
|
||||
|
||||
@@ -8,7 +8,7 @@ test('seed auth', async (t) => {
|
||||
const keypair = Keypair.random()
|
||||
const publicKey = keypair.public_key()
|
||||
|
||||
const homeserver = PublicKey.try_from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
|
||||
const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
|
||||
await client.signup(keypair, homeserver)
|
||||
|
||||
const session = await client.session(publicKey)
|
||||
|
||||
46
pubky/pkg/test/public.js
Normal file
46
pubky/pkg/test/public.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import test from 'tape'
|
||||
|
||||
import { PubkyClient, Keypair, PublicKey } from '../index.js'
|
||||
|
||||
test('public: put/get', async (t) => {
|
||||
const client = new PubkyClient();
|
||||
|
||||
const keypair = Keypair.random();
|
||||
|
||||
const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo');
|
||||
await client.signup(keypair, homeserver);
|
||||
|
||||
const publicKey = keypair.public_key();
|
||||
|
||||
const body = Buffer.from(JSON.stringify({ foo: 'bar' }))
|
||||
|
||||
// PUT public data, by authorized client
|
||||
await client.put(publicKey, "/pub/example.com/arbitrary", body);
|
||||
|
||||
|
||||
// GET public data without signup or signin
|
||||
{
|
||||
const client = new PubkyClient();
|
||||
|
||||
let response = await client.get(publicKey, "/pub/example.com/arbitrary");
|
||||
|
||||
t.ok(Buffer.from(response).equals(body))
|
||||
}
|
||||
|
||||
// // DELETE public data, by authorized client
|
||||
// await client.delete(publicKey, "/pub/example.com/arbitrary");
|
||||
//
|
||||
//
|
||||
// // GET public data without signup or signin
|
||||
// {
|
||||
// const client = new PubkyClient();
|
||||
//
|
||||
// let response = await client.get(publicKey, "/pub/example.com/arbitrary");
|
||||
//
|
||||
// t.notOk(response)
|
||||
// }
|
||||
})
|
||||
|
||||
test.skip("not found")
|
||||
|
||||
test.skip("unauthorized")
|
||||
@@ -1,11 +1,10 @@
|
||||
pub mod public;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use ::pkarr::{
|
||||
mainline::dht::{DhtSettings, Testnet},
|
||||
PkarrClient, PublicKey, Settings, SignedPacket,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use pkarr::Keypair;
|
||||
use pubky_common::session::Session;
|
||||
use reqwest::{Method, RequestBuilder, Response};
|
||||
@@ -21,6 +20,8 @@ impl Default for PubkyClient {
|
||||
}
|
||||
}
|
||||
|
||||
// === Public API ===
|
||||
|
||||
impl PubkyClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -54,6 +55,8 @@ impl PubkyClient {
|
||||
}
|
||||
}
|
||||
|
||||
// === Auth ===
|
||||
|
||||
/// Signup to a homeserver and update Pkarr accordingly.
|
||||
///
|
||||
/// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key
|
||||
@@ -80,6 +83,24 @@ impl PubkyClient {
|
||||
self.inner_signin(keypair).await
|
||||
}
|
||||
|
||||
// === 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
|
||||
}
|
||||
|
||||
/// Download a small payload from a given path relative to a pubky author.
|
||||
pub async fn get(&self, pubky: &PublicKey, path: &str) -> Result<Bytes> {
|
||||
self.inner_get(pubky, path).await
|
||||
}
|
||||
}
|
||||
|
||||
// === Internals ===
|
||||
|
||||
impl PubkyClient {
|
||||
// === Pkarr ===
|
||||
|
||||
pub(crate) async fn pkarr_resolve(
|
||||
&self,
|
||||
public_key: &PublicKey,
|
||||
@@ -91,6 +112,8 @@ impl PubkyClient {
|
||||
Ok(self.pkarr.publish(signed_packet).await?)
|
||||
}
|
||||
|
||||
// === HTTP ===
|
||||
|
||||
pub(crate) fn request(&self, method: reqwest::Method, url: Url) -> RequestBuilder {
|
||||
self.http.request(method, url)
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod auth;
|
||||
pub mod pkarr;
|
||||
pub mod public;
|
||||
|
||||
86
pubky/src/shared/public.rs
Normal file
86
pubky/src/shared/public.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use bytes::Bytes;
|
||||
|
||||
use pkarr::PublicKey;
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::{error::Result, PubkyClient};
|
||||
|
||||
impl PubkyClient {
|
||||
pub async fn inner_put(&self, pubky: &PublicKey, path: &str, content: &[u8]) -> Result<()> {
|
||||
let path = normalize_path(path);
|
||||
|
||||
let (_, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
|
||||
url.set_path(&format!("/{pubky}/{path}"));
|
||||
|
||||
self.request(Method::PUT, url)
|
||||
.body(content.to_owned())
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn inner_get(&self, pubky: &PublicKey, path: &str) -> Result<Bytes> {
|
||||
let path = normalize_path(path);
|
||||
|
||||
let (_, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
|
||||
url.set_path(&format!("/{pubky}/{path}"));
|
||||
|
||||
let response = self.request(Method::GET, url).send().await?;
|
||||
|
||||
// TODO: bail on too large files.
|
||||
let bytes = response.bytes().await?;
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_path(path: &str) -> String {
|
||||
let mut path = path.to_string();
|
||||
|
||||
if path.starts_with('/') {
|
||||
path = path[1..].to_string()
|
||||
}
|
||||
|
||||
// TODO: should we return error instead?
|
||||
if path.ends_with('/') {
|
||||
path = path[..path.len()].to_string()
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::*;
|
||||
|
||||
use pkarr::{mainline::Testnet, Keypair};
|
||||
use pubky_homeserver::Homeserver;
|
||||
|
||||
#[tokio::test]
|
||||
async fn put_get() {
|
||||
let testnet = Testnet::new(3);
|
||||
let server = Homeserver::start_test(&testnet).await.unwrap();
|
||||
|
||||
let client = PubkyClient::test(&testnet);
|
||||
|
||||
let keypair = Keypair::random();
|
||||
|
||||
client.signup(&keypair, &server.public_key()).await.unwrap();
|
||||
|
||||
client
|
||||
.put(&keypair.public_key(), "/pub/foo.txt", &[0, 1, 2, 3, 4])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = client
|
||||
.get(&keypair.public_key(), "/pub/foo.txt")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response, bytes::Bytes::from(vec![0, 1, 2, 3, 4]))
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use url::Url;
|
||||
|
||||
use crate::PubkyClient;
|
||||
|
||||
mod http;
|
||||
mod keys;
|
||||
mod pkarr;
|
||||
mod session;
|
||||
@@ -66,37 +67,22 @@ impl PubkyClient {
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub(crate) fn request(&self, method: reqwest::Method, url: Url) -> RequestBuilder {
|
||||
let request = self.http.request(method, url).fetch_credentials_include();
|
||||
// === Public data ===
|
||||
|
||||
for cookie in self.session_cookies.read().unwrap().iter() {
|
||||
return request.header("Cookie", cookie);
|
||||
}
|
||||
|
||||
request
|
||||
#[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())
|
||||
}
|
||||
|
||||
// Support cookies for nodejs
|
||||
|
||||
pub(crate) fn store_session(&self, response: Response) {
|
||||
if let Some(cookie) = response
|
||||
.headers()
|
||||
.get("set-cookie")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.and_then(|s| s.split(';').next())
|
||||
{
|
||||
self.session_cookies
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(cookie.to_string());
|
||||
}
|
||||
}
|
||||
pub(crate) fn remove_session(&self, pubky: &pkarr::PublicKey) {
|
||||
let key = pubky.to_string();
|
||||
|
||||
self.session_cookies
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|cookie| !cookie.starts_with(&key));
|
||||
#[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<js_sys::Uint8Array, JsValue> {
|
||||
self.inner_get(pubky.as_inner(), path)
|
||||
.await
|
||||
.map(|b| (*b).into())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
42
pubky/src/wasm/http.rs
Normal file
42
pubky/src/wasm/http.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use crate::PubkyClient;
|
||||
|
||||
use reqwest::{Method, RequestBuilder, Response};
|
||||
use url::Url;
|
||||
|
||||
use ::pkarr::PublicKey;
|
||||
|
||||
impl PubkyClient {
|
||||
pub(crate) fn request(&self, method: Method, url: Url) -> RequestBuilder {
|
||||
let request = self.http.request(method, url).fetch_credentials_include();
|
||||
|
||||
for cookie in self.session_cookies.read().unwrap().iter() {
|
||||
return request.header("Cookie", cookie);
|
||||
}
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
// Support cookies for nodejs
|
||||
|
||||
pub(crate) fn store_session(&self, response: Response) {
|
||||
if let Some(cookie) = response
|
||||
.headers()
|
||||
.get("set-cookie")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.and_then(|s| s.split(';').next())
|
||||
{
|
||||
self.session_cookies
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(cookie.to_string());
|
||||
}
|
||||
}
|
||||
pub(crate) fn remove_session(&self, pubky: &pkarr::PublicKey) {
|
||||
let key = pubky.to_string();
|
||||
|
||||
self.session_cookies
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|cookie| !cookie.starts_with(&key));
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,8 @@ impl PublicKey {
|
||||
self.0.to_string()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[wasm_bindgen(js_name = "from")]
|
||||
/// @throws
|
||||
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(),
|
||||
|
||||
Reference in New Issue
Block a user