mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-02 22:04:31 +01:00
feat(js): pass all unit tests
This commit is contained in:
@@ -42,20 +42,22 @@ 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(url, body);
|
||||
await client.fetch(url, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({foo: "bar"}),
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
// GET public data without signup or signin
|
||||
{
|
||||
const client = new Client();
|
||||
|
||||
let response = await client.get(url);
|
||||
let response = await client.fetch(url);
|
||||
}
|
||||
|
||||
// Delete public data, by authorized client
|
||||
await client.delete(url);
|
||||
await client.fetch(url, { method: "DELETE", credentials: "include "});
|
||||
```
|
||||
|
||||
## API
|
||||
@@ -67,6 +69,13 @@ await client.delete(url);
|
||||
let client = new Client()
|
||||
```
|
||||
|
||||
#### fetch
|
||||
```js
|
||||
let response = await client.fetch(url, opts);
|
||||
```
|
||||
|
||||
Just like normal Fetch API, but it can handle `pubky://` urls and `http(s)://` urls with Pkarr domains.
|
||||
|
||||
#### signup
|
||||
```js
|
||||
await client.signup(keypair, homeserver)
|
||||
@@ -127,27 +136,6 @@ let session = await client.session(publicKey)
|
||||
- publicKey: An instance of [PublicKey](#publickey).
|
||||
- Returns: A [Session](#session) object if signed in, or undefined if not.
|
||||
|
||||
#### put
|
||||
```js
|
||||
let response = await client.put(url, body);
|
||||
```
|
||||
- url: A string representing the Pubky URL.
|
||||
- body: A Buffer containing the data to be stored.
|
||||
|
||||
### get
|
||||
```js
|
||||
let response = await client.get(url)
|
||||
```
|
||||
- url: A string representing the Pubky URL.
|
||||
- Returns: A Uint8Array object containing the requested data, or `undefined` if `NOT_FOUND`.
|
||||
|
||||
### delete
|
||||
|
||||
```js
|
||||
let response = await client.delete(url);
|
||||
```
|
||||
- url: A string representing the Pubky URL.
|
||||
|
||||
### list
|
||||
```js
|
||||
let response = await client.list(url, cursor, reverse, limit)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import test from 'tape'
|
||||
|
||||
import { Client, Keypair, PublicKey } from '../index.cjs'
|
||||
import { Client, Keypair, PublicKey ,setLogLevel} from '../index.cjs'
|
||||
|
||||
const HOMESERVER_PUBLICKEY = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
|
||||
|
||||
test.skip('public: put/get', async (t) => {
|
||||
test('public: put/get', async (t) => {
|
||||
const client = Client.testnet();
|
||||
|
||||
const keypair = Keypair.random();
|
||||
@@ -15,33 +15,43 @@ test.skip('public: put/get', async (t) => {
|
||||
|
||||
let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`;
|
||||
|
||||
const body = Buffer.from(JSON.stringify({ foo: 'bar' }))
|
||||
const json = { foo: 'bar' }
|
||||
|
||||
// PUT public data, by authorized client
|
||||
await client.put(url, body);
|
||||
await client.fetch(url, {
|
||||
method:"PUT",
|
||||
body: JSON.stringify(json),
|
||||
contentType: "json",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
const otherClient = Client.testnet();
|
||||
|
||||
// GET public data without signup or signin
|
||||
{
|
||||
let response = await otherClient.get(url);
|
||||
let response = await otherClient.fetch(url)
|
||||
|
||||
t.ok(Buffer.from(response).equals(body))
|
||||
t.is(response.status, 200);
|
||||
|
||||
t.deepEquals(await response.json(), {foo: "bar"})
|
||||
}
|
||||
|
||||
// DELETE public data, by authorized client
|
||||
await client.delete(url);
|
||||
await client.fetch(url, {
|
||||
method:"DELETE",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
|
||||
// GET public data without signup or signin
|
||||
{
|
||||
let response = await otherClient.get(url);
|
||||
let response = await otherClient.fetch(url);
|
||||
|
||||
t.notOk(response)
|
||||
t.is(response.status, 404)
|
||||
}
|
||||
})
|
||||
|
||||
test.skip("not found", async (t) => {
|
||||
test("not found", async (t) => {
|
||||
const client = Client.testnet();
|
||||
|
||||
|
||||
@@ -53,12 +63,12 @@ test.skip("not found", async (t) => {
|
||||
|
||||
let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`;
|
||||
|
||||
let result = await client.get(url).catch(e => e);
|
||||
let result = await client.fetch(url);
|
||||
|
||||
t.notOk(result);
|
||||
t.is(result.status, 404);
|
||||
})
|
||||
|
||||
test.skip("unauthorized", async (t) => {
|
||||
test("unauthorized", async (t) => {
|
||||
const client = Client.testnet();
|
||||
|
||||
const keypair = Keypair.random()
|
||||
@@ -71,21 +81,20 @@ test.skip("unauthorized", async (t) => {
|
||||
|
||||
await client.signout(publicKey)
|
||||
|
||||
const body = Buffer.from(JSON.stringify({ foo: 'bar' }))
|
||||
|
||||
let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`;
|
||||
|
||||
// PUT public data, by authorized client
|
||||
let result = await client.put(url, body).catch(e => e);
|
||||
let response = await client.fetch(url, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ foo: 'bar' }),
|
||||
contentType: "json",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
t.ok(result instanceof Error);
|
||||
t.is(
|
||||
result.message,
|
||||
`HTTP status client error (401 Unauthorized) for url (http://localhost:15411/${publicKey.z32()}/pub/example.com/arbitrary)`
|
||||
)
|
||||
t.equals(response.status,401);
|
||||
})
|
||||
|
||||
test.skip("forbidden", async (t) => {
|
||||
test("forbidden", async (t) => {
|
||||
const client = Client.testnet();
|
||||
|
||||
const keypair = Keypair.random()
|
||||
@@ -96,21 +105,22 @@ test.skip("forbidden", async (t) => {
|
||||
const session = await client.session(publicKey)
|
||||
t.ok(session, "signup")
|
||||
|
||||
const body = Buffer.from(JSON.stringify({ foo: 'bar' }))
|
||||
const body = (JSON.stringify({ foo: 'bar' }))
|
||||
|
||||
let url = `pubky://${publicKey.z32()}/priv/example.com/arbitrary`;
|
||||
|
||||
// PUT public data, by authorized client
|
||||
let result = await client.put(url, body).catch(e => e);
|
||||
let response = await client.fetch(url, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ foo: 'bar' }),
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
t.ok(result instanceof Error);
|
||||
t.is(
|
||||
result.message,
|
||||
`HTTP status client error (403 Forbidden) for url (http://localhost:15411/${publicKey.z32()}/priv/example.com/arbitrary)`
|
||||
)
|
||||
t.is(response.status, 403)
|
||||
t.is(await response.text(), 'Writing to directories other than \'/pub/\' is forbidden')
|
||||
})
|
||||
|
||||
test.skip("list", async (t) => {
|
||||
test("list", async (t) => {
|
||||
const client = Client.testnet();
|
||||
|
||||
const keypair = Keypair.random()
|
||||
@@ -132,7 +142,11 @@ test.skip("list", async (t) => {
|
||||
]
|
||||
|
||||
for (let url of urls) {
|
||||
await client.put(url, Buffer.from(""));
|
||||
await client.fetch(url, {
|
||||
method: "PUT",
|
||||
body:Buffer.from(""),
|
||||
credentials: "include"
|
||||
});
|
||||
}
|
||||
|
||||
let url = `pubky://${pubky}/pub/example.com/`;
|
||||
@@ -241,7 +255,7 @@ test.skip("list", async (t) => {
|
||||
}
|
||||
})
|
||||
|
||||
test.skip('list shallow', async (t) => {
|
||||
test('list shallow', async (t) => {
|
||||
const client = Client.testnet();
|
||||
|
||||
const keypair = Keypair.random()
|
||||
@@ -264,7 +278,11 @@ test.skip('list shallow', async (t) => {
|
||||
]
|
||||
|
||||
for (let url of urls) {
|
||||
await client.put(url, Buffer.from(""));
|
||||
await client.fetch(url, {
|
||||
method: "PUT",
|
||||
body: Buffer.from(""),
|
||||
credentials: "include"
|
||||
});
|
||||
}
|
||||
|
||||
let url = `pubky://${pubky}/pub/`;
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::Client;
|
||||
mod api;
|
||||
mod cookies;
|
||||
mod http;
|
||||
mod internal;
|
||||
|
||||
pub(crate) use cookies::CookieJar;
|
||||
|
||||
|
||||
@@ -123,6 +123,12 @@ impl Client {
|
||||
pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
|
||||
self.request(Method::HEAD, url)
|
||||
}
|
||||
|
||||
// === Private Methods ===
|
||||
|
||||
pub(crate) async fn inner_request<T: IntoUrl>(&self, method: Method, url: T) -> RequestBuilder {
|
||||
self.request(method, url)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
//! Native specific implementation of methods used in the shared module
|
||||
//!
|
||||
|
||||
use reqwest::{IntoUrl, Method, RequestBuilder};
|
||||
|
||||
use crate::Client;
|
||||
|
||||
impl Client {
|
||||
pub(crate) async fn inner_request<T: IntoUrl>(&self, method: Method, url: T) -> RequestBuilder {
|
||||
self.request(method, url)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ use crate::Client;
|
||||
|
||||
mod api;
|
||||
mod http;
|
||||
mod internals;
|
||||
mod wrappers;
|
||||
|
||||
impl Default for Client {
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Headers, RequestInit};
|
||||
|
||||
use reqwest::Url;
|
||||
use reqwest::{IntoUrl, Method, RequestBuilder, Url};
|
||||
|
||||
use futures_lite::StreamExt;
|
||||
|
||||
@@ -18,70 +19,40 @@ impl Client {
|
||||
pub async fn fetch(
|
||||
&self,
|
||||
url: &str,
|
||||
init: &web_sys::RequestInit,
|
||||
request_init: Option<RequestInit>,
|
||||
) -> Result<js_sys::Promise, JsValue> {
|
||||
let mut url: Url = url.try_into().map_err(|err| {
|
||||
JsValue::from_str(&format!("pubky::Client::fetch(): Invalid `url`; {:?}", err))
|
||||
})?;
|
||||
|
||||
self.transform_url(&mut url).await;
|
||||
let request_init = request_init.unwrap_or_default();
|
||||
|
||||
let js_req =
|
||||
web_sys::Request::new_with_str_and_init(url.as_str(), init).map_err(|err| {
|
||||
if let Some(pkarr_host) = self.prepare_request(&mut url).await {
|
||||
let headers = request_init.get_headers();
|
||||
|
||||
let headers = if headers.is_null() || headers.is_undefined() {
|
||||
Headers::new()?
|
||||
} else {
|
||||
Headers::from(headers)
|
||||
};
|
||||
|
||||
headers.append("pkarr-host", &pkarr_host)?;
|
||||
|
||||
request_init.set_headers(&headers.into());
|
||||
}
|
||||
|
||||
let js_req = web_sys::Request::new_with_str_and_init(url.as_str(), &request_init).map_err(
|
||||
|err| {
|
||||
JsValue::from_str(&format!(
|
||||
"pubky::Client::fetch(): Invalid `init`; {:?}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(js_fetch(&js_req))
|
||||
}
|
||||
|
||||
pub(super) async fn transform_url(&self, url: &mut Url) {
|
||||
if url.scheme() == "pubky" {
|
||||
*url = Url::parse(&format!("https{}", &url.as_str()[5..]))
|
||||
.expect("couldn't replace pubky:// with https://");
|
||||
url.set_host(Some(&format!("_pubky.{}", url.host_str().unwrap_or(""))))
|
||||
.expect("couldn't map pubk://<pubky> to https://_pubky.<pubky>");
|
||||
}
|
||||
|
||||
let qname = url.host_str().unwrap_or("").to_string();
|
||||
|
||||
if PublicKey::try_from(qname.to_string()).is_ok() {
|
||||
let mut stream = self.pkarr.resolve_https_endpoints(&qname);
|
||||
|
||||
let mut so_far: Option<Endpoint> = None;
|
||||
|
||||
// TODO: currently we return the first thing we can see,
|
||||
// in the future we might want to failover to other endpoints
|
||||
while so_far.is_none() {
|
||||
while let Some(endpoint) = stream.next().await {
|
||||
if endpoint.domain() != "." {
|
||||
so_far = Some(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(e) = so_far {
|
||||
// TODO: detect loopback IPs and other equivilants to localhost
|
||||
if self.testnet && e.domain() == "localhost" {
|
||||
url.set_scheme("http")
|
||||
.expect("couldn't replace pubky:// with http://");
|
||||
}
|
||||
|
||||
url.set_host(Some(e.domain()))
|
||||
.expect("coultdn't use the resolved endpoint's domain");
|
||||
url.set_port(Some(e.port()))
|
||||
.expect("coultdn't use the resolved endpoint's port");
|
||||
} else {
|
||||
// TODO: didn't find any domain, what to do?
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Transformed URL to: {}", url.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = fetch)]
|
||||
@@ -102,3 +73,80 @@ fn js_fetch(req: &web_sys::Request) -> Promise {
|
||||
fetch_with_request(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// A wrapper around [reqwest::Client::request], with the same signature between native and wasm.
|
||||
pub(crate) async fn inner_request<T: IntoUrl>(&self, method: Method, url: T) -> RequestBuilder {
|
||||
let original_url = url.as_str();
|
||||
let mut url = Url::parse(original_url).expect("Invalid url in inner_request");
|
||||
|
||||
if let Some(pkarr_host) = self.prepare_request(&mut url).await {
|
||||
self.http
|
||||
.request(method, url.clone())
|
||||
.header::<&str, &str>("pkarr-host", &pkarr_host)
|
||||
.fetch_credentials_include()
|
||||
} else {
|
||||
self.http
|
||||
.request(method, url.clone())
|
||||
.fetch_credentials_include()
|
||||
}
|
||||
}
|
||||
|
||||
/// - Transforms pubky:// url to http(s):// urls
|
||||
/// - Resolves a clearnet host to call with fetch
|
||||
/// - Returns the `pkarr-host` value if available
|
||||
pub(super) async fn prepare_request(&self, url: &mut Url) -> Option<String> {
|
||||
if url.scheme() == "pubky" {
|
||||
*url = Url::parse(&format!("https{}", &url.as_str()[5..]))
|
||||
.expect("couldn't replace pubky:// with https://");
|
||||
url.set_host(Some(&format!("_pubky.{}", url.host_str().unwrap_or(""))))
|
||||
.expect("couldn't map pubk://<pubky> to https://_pubky.<pubky>");
|
||||
}
|
||||
|
||||
// TODO: move at the begining of the method.
|
||||
let host = url.host_str().unwrap_or("").to_string();
|
||||
|
||||
let mut pkarr_host = None;
|
||||
|
||||
if PublicKey::try_from(host.clone()).is_ok() {
|
||||
self.transform_url(url, &host).await;
|
||||
|
||||
pkarr_host = Some(host);
|
||||
|
||||
log::debug!("Transformed URL to: {}", url.as_str());
|
||||
};
|
||||
|
||||
pkarr_host
|
||||
}
|
||||
|
||||
pub async fn transform_url(&self, url: &mut Url, qname: &str) {
|
||||
let mut stream = self.pkarr.resolve_https_endpoints(qname);
|
||||
|
||||
let mut so_far: Option<Endpoint> = None;
|
||||
|
||||
// TODO: currently we return the first thing we can see,
|
||||
// in the future we might want to failover to other endpoints
|
||||
while so_far.is_none() {
|
||||
while let Some(endpoint) = stream.next().await {
|
||||
if endpoint.domain() != "." {
|
||||
so_far = Some(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(e) = so_far {
|
||||
// TODO: detect loopback IPs and other equivilants to localhost
|
||||
if self.testnet && e.domain() == "localhost" {
|
||||
url.set_scheme("http")
|
||||
.expect("couldn't replace pubky:// with http://");
|
||||
}
|
||||
|
||||
url.set_host(Some(e.domain()))
|
||||
.expect("coultdn't use the resolved endpoint's domain");
|
||||
url.set_port(Some(e.port()))
|
||||
.expect("coultdn't use the resolved endpoint's port");
|
||||
} else {
|
||||
// TODO: didn't find any domain, what to do?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
//! Wasm specific implementation of methods used in the shared module
|
||||
//!
|
||||
|
||||
use reqwest::{IntoUrl, Method, RequestBuilder};
|
||||
use url::Url;
|
||||
|
||||
use crate::Client;
|
||||
|
||||
impl Client {
|
||||
/// A wrapper around [reqwest::Client::request], with the same signature between native and wasm.
|
||||
pub(crate) async fn inner_request<T: IntoUrl>(&self, method: Method, url: T) -> RequestBuilder {
|
||||
let original_url = url.as_str();
|
||||
let mut url = Url::parse(original_url).expect("Invalid url in inner_request");
|
||||
|
||||
let original_host = url.host_str().unwrap_or("").to_string();
|
||||
|
||||
self.transform_url(&mut url).await;
|
||||
|
||||
self.http
|
||||
.request(method, url)
|
||||
.header::<&str, &str>("pkarr-host", &original_host)
|
||||
.fetch_credentials_include()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user