From 1db1d3a21d76880cc9064ae93c7bfe0e26f386c3 Mon Sep 17 00:00:00 2001 From: nazeh Date: Mon, 5 Aug 2024 16:34:26 +0300 Subject: [PATCH] feat(js): add list() method --- pubky/Cargo.toml | 4 +- pubky/pkg/test/public.js | 132 +++++++++++++++++++++++++++++++ pubky/src/native.rs | 3 + pubky/src/shared/list_builder.rs | 11 +++ pubky/src/shared/public.rs | 9 +-- pubky/src/wasm.rs | 62 +++++++++++++-- 6 files changed, 207 insertions(+), 14 deletions(-) diff --git a/pubky/Cargo.toml b/pubky/Cargo.toml index 149b6be..4c82cfb 100644 --- a/pubky/Cargo.toml +++ b/pubky/Cargo.toml @@ -40,5 +40,5 @@ tokio = "1.37.0" [package.metadata.docs.rs] all-features = true -# [package.metadata.wasm-pack.profile.release] -# wasm-opt = ['-g', '-O'] +[package.metadata.wasm-pack.profile.release] +wasm-opt = ['-g', '-O'] diff --git a/pubky/pkg/test/public.js b/pubky/pkg/test/public.js index 2e6c988..9ceaa5b 100644 --- a/pubky/pkg/test/public.js +++ b/pubky/pkg/test/public.js @@ -111,3 +111,135 @@ test("forbidden", async (t) => { `HTTP status client error (403 Forbidden) for url (http://localhost:15411/${publicKey.z32()}/priv/example.com/arbitrary)` ) }) + +test("list", async (t) => { + const client = PubkyClient.testnet(); + + const keypair = Keypair.random() + const publicKey = keypair.publicKey() + const pubky = publicKey.z32() + + const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo') + await client.signup(keypair, homeserver) + + + + let urls = [ + `pubky://${pubky}/pub/a.wrong/a.txt`, + `pubky://${pubky}/pub/example.com/a.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.wrong/a.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/d.txt`, + `pubky://${pubky}/pub/z.wrong/a.txt`, + ] + + for (let url of urls) { + await client.put(url, Buffer.from("")); + } + + let url = `pubky://${pubky}/pub/example.com/`; + + { + let list = await client.list(url); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/a.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/d.txt`, + + ], + "normal list with no limit or cursor" + ); + } + + { + let list = await client.list(url, null, null, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/a.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + + ], + "normal list with limit but no cursor" + ); + } + + { + let list = await client.list(url, "a.txt", null, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + + ], + "normal list with limit and a suffix cursor" + ); + } + + { + let list = await client.list(url, `pubky://${pubky}/pub/example.com/a.txt`, null, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + + ], + "normal list with limit and a full url cursor" + ); + } + + + { + let list = await client.list(url, null, true); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/d.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + `pubky://${pubky}/pub/example.com/a.txt`, + + ], + "reverse list with no limit or cursor" + ); + } + + { + let list = await client.list(url, null, true, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/d.txt`, + `pubky://${pubky}/pub/example.com/c.txt`, + + ], + "reverse list with limit but no cursor" + ); + } + + { + let list = await client.list(url, "d.txt", true, 2); + + t.deepEqual( + list, + [ + `pubky://${pubky}/pub/example.com/c.txt`, + `pubky://${pubky}/pub/example.com/b.txt`, + + ], + "reverse list with limit and a suffix cursor" + ); + } +}) diff --git a/pubky/src/native.rs b/pubky/src/native.rs index 8fb6ee0..6f9f676 100644 --- a/pubky/src/native.rs +++ b/pubky/src/native.rs @@ -107,6 +107,9 @@ impl PubkyClient { self.inner_delete(url).await } + /// Returns a [ListBuilder] to help pass options before calling [ListBuilder::send]. + /// + /// `url` sets the path you want to lest within. pub fn list>(&self, url: T) -> Result { self.inner_list(url) } diff --git a/pubky/src/shared/list_builder.rs b/pubky/src/shared/list_builder.rs index a1a1330..5d0aa32 100644 --- a/pubky/src/shared/list_builder.rs +++ b/pubky/src/shared/list_builder.rs @@ -13,6 +13,7 @@ pub struct ListBuilder<'a> { } impl<'a> ListBuilder<'a> { + /// Create a new List request builder pub fn new(client: &'a PubkyClient, url: Url) -> Self { Self { client, @@ -23,21 +24,31 @@ impl<'a> ListBuilder<'a> { } } + /// Set the `reverse` option. pub fn reverse(mut self, reverse: bool) -> Self { self.reverse = reverse; self } + /// Set the `limit` value. pub fn limit(mut self, limit: u16) -> Self { self.limit = limit.into(); self } + /// Set the `cursor` value. + /// + /// usually the last url from previous responses. pub fn cursor(mut self, cursor: &'a str) -> Self { self.cursor = cursor.into(); self } + /// Send the list request. + /// + /// Returns a list of Pubky URLs of the files in the path of the `url` + /// respecting [ListBuilder::reverse], [ListBuilder::limit] and [ListBuilder::cursor] + /// options. pub async fn send(self) -> Result> { let mut url = self.client.pubky_to_http(self.url).await?; diff --git a/pubky/src/shared/public.rs b/pubky/src/shared/public.rs index e1eaed8..f1b5829 100644 --- a/pubky/src/shared/public.rs +++ b/pubky/src/shared/public.rs @@ -236,8 +236,9 @@ mod tests { client.put(url.as_str(), &[0]).await.unwrap(); } + let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); + { - let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client.list(url.as_str()).unwrap().send().await.unwrap(); assert_eq!( @@ -253,7 +254,6 @@ mod tests { } { - let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client .list(url.as_str()) .unwrap() @@ -273,7 +273,6 @@ mod tests { } { - let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client .list(url.as_str()) .unwrap() @@ -294,7 +293,6 @@ mod tests { } { - let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client .list(url.as_str()) .unwrap() @@ -318,7 +316,6 @@ mod tests { } { - let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client .list(url.as_str()) .unwrap() @@ -340,7 +337,6 @@ mod tests { } { - let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client .list(url.as_str()) .unwrap() @@ -361,7 +357,6 @@ mod tests { } { - let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); let list = client .list(url.as_str()) .unwrap() diff --git a/pubky/src/wasm.rs b/pubky/src/wasm.rs index 7f44e44..2e54332 100644 --- a/pubky/src/wasm.rs +++ b/pubky/src/wasm.rs @@ -3,7 +3,8 @@ use std::{ sync::{Arc, RwLock}, }; -use wasm_bindgen::prelude::*; +use js_sys::{Array, Uint8Array}; +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; use reqwest::{IntoUrl, Method, RequestBuilder, Response}; use url::Url; @@ -58,7 +59,7 @@ impl PubkyClient { pub fn create_recovery_file( keypair: &Keypair, passphrase: &str, - ) -> Result { + ) -> Result { create_recovery_file(keypair.as_inner(), passphrase) .map(|b| b.as_slice().into()) .map_err(|e| e.into()) @@ -138,18 +139,69 @@ impl PubkyClient { 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, url: &str) -> Result, JsValue> { + #[wasm_bindgen] + pub async fn get(&self, url: &str) -> Result, JsValue> { self.inner_get(url) .await .map(|b| b.map(|b| (&*b).into())) .map_err(|e| e.into()) } - #[wasm_bindgen] /// Delete a file at a path relative to a pubky author. + #[wasm_bindgen] pub async fn delete(&self, url: &str) -> Result<(), JsValue> { self.inner_delete(url).await.map_err(|e| e.into()) } + + /// Returns a list of Pubky URLs of the files within the `url` path, + /// respecting the `cursor`, `reverse` and `limit` options. + /// + /// `cursor` is usually the last url from previous responses. + #[wasm_bindgen] + pub async fn list( + &self, + url: &str, + cursor: Option, + reverse: Option, + limit: Option, + ) -> Result { + // TODO: try later to return Vec from async function. + + if let Some(cursor) = cursor { + return self + .inner_list(url)? + .reverse(reverse.unwrap_or(false)) + .limit(limit.unwrap_or(u16::MAX)) + .cursor(&cursor) + .send() + .await + .map(|urls| { + let js_array = Array::new(); + + for url in urls { + js_array.push(&JsValue::from_str(&url)); + } + + js_array + }) + .map_err(|e| e.into()); + } + + self.inner_list(url)? + .reverse(reverse.unwrap_or(false)) + .limit(limit.unwrap_or(u16::MAX)) + .send() + .await + .map(|urls| { + let js_array = Array::new(); + + for url in urls { + js_array.push(&JsValue::from_str(&url)); + } + + js_array + }) + .map_err(|e| e.into()) + } }