From ae4a34c511b04a9d266ab344cbef2fa658464d1a Mon Sep 17 00:00:00 2001 From: nazeh Date: Wed, 24 Jul 2024 22:26:32 +0300 Subject: [PATCH] feat(pubky): add wasm mod --- Cargo.lock | 5 ++ js/pubky/README.md | 3 + js/pubky/examples/basic.js | 9 +++ js/pubky/src/index.js | 5 ++ js/pubky/src/lib/client.js | 102 ++++++++++++++++++++++++++++++ js/pubky/src/lib/error.js | 1 + js/pubky/src/lib/fetch-browser.js | 11 ++++ js/pubky/src/lib/fetch.js | 3 + pubky/Cargo.toml | 21 +++++- pubky/src/lib.rs | 32 ++++++++-- pubky/src/wasm.rs | 6 ++ pubky/src/wasm/client.rs | 73 +++++++++++++++++++++ pubky/src/wasm/keys.rs | 15 +++++ 13 files changed, 280 insertions(+), 6 deletions(-) create mode 100644 js/pubky/README.md create mode 100644 js/pubky/examples/basic.js create mode 100644 js/pubky/src/index.js create mode 100644 js/pubky/src/lib/client.js create mode 100644 js/pubky/src/lib/error.js create mode 100644 js/pubky/src/lib/fetch-browser.js create mode 100644 js/pubky/src/lib/fetch.js create mode 100644 pubky/src/wasm.rs create mode 100644 pubky/src/wasm/client.rs create mode 100644 pubky/src/wasm/keys.rs diff --git a/Cargo.lock b/Cargo.lock index 613c9c4..910eaa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1252,6 +1252,8 @@ version = "0.1.0" dependencies = [ "bytes", "flume", + "futures", + "js-sys", "pkarr", "pubky-common", "pubky_homeserver", @@ -1259,6 +1261,9 @@ dependencies = [ "tokio", "ureq", "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] diff --git a/js/pubky/README.md b/js/pubky/README.md new file mode 100644 index 0000000..c57a08a --- /dev/null +++ b/js/pubky/README.md @@ -0,0 +1,3 @@ +# Pubky + +This is a JavaScript implementation of a Pubky protocol client-side tools. diff --git a/js/pubky/examples/basic.js b/js/pubky/examples/basic.js new file mode 100644 index 0000000..6b40582 --- /dev/null +++ b/js/pubky/examples/basic.js @@ -0,0 +1,9 @@ +import { PubkyClient } from '../src/index.js' + +main() + +async function main() { + let client = new PubkyClient() + + console.log(client) +} diff --git a/js/pubky/src/index.js b/js/pubky/src/index.js new file mode 100644 index 0000000..e3e7548 --- /dev/null +++ b/js/pubky/src/index.js @@ -0,0 +1,5 @@ +// import { PubkyClient as _PubkyClient } from './lib/client.js' +// import { PubkyError as _PubkyError } from './lib/error.js' +// +// export const PubkyClient = _PubkyClient; +// export const PubkyError = _PubkyError; diff --git a/js/pubky/src/lib/client.js b/js/pubky/src/lib/client.js new file mode 100644 index 0000000..742262d --- /dev/null +++ b/js/pubky/src/lib/client.js @@ -0,0 +1,102 @@ +import Pkarr, { SignedPacket } from 'pkarr' +import { URL } from 'url' + +// import { AuthnSignature, crypto } from '@pubky/common' + +import * as crypto from '../common/crypto.js' + +// import { Pubky } from './pubky.js' +import fetch from './fetch.js' + + +const DEFAULT_PKARR_RELAY = new URL('https://relay.pkarr.org') + +export class PubkyClient { + // TODO: use DHT in nodejs + #pkarrRelay + + crypto = crypto + static crypto = crypto + + /** + * @param {object} [options={}] + * @param {URL} [options.pkarrRelay] + * + * @param {{relay: string, bootstrap: Array<{host: string, port: number}>}} [options.testnet] + */ + constructor(options = {}) { + this.#pkarrRelay = options.pkarrRelay || DEFAULT_PKARR_RELAY + } + + /** + * Publish the SVCB record for `_pubky.`. + * @param {crypto.KeyPair} keypair + * @param {String} host + */ + async publishPubkyHomeserver(keypair, host) { + + let existing = await (async () => { + try { + return (await Pkarr.relayGet(this.#pkarrRelay.toString(), keypair.publicKey().bytes)).packet() + } catch (error) { + return { + id: 0, + type: 'response', + flags: 0, + answers: [] + } + } + })(); + + let answers = [ + ]; + + for (let answer of existing.answers) { + if (!answer.name.startsWith("_pubky")) { + answers.push(answer) + } + } + + let signedPacket = SignedPacket.fromPacket(keypair, { + id: 0, + type: 'response', + flags: 0, + answers: [ + ...answers, + { + name: '_pubky.', type: 'SVCB', ttl: 7200, data: + + Buffer.from( + + ) + + } + ] + }) + + // let mut packet = Packet:: new_reply(0); + // + // 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) ?; + // + // self.pkarr.publish(& signed_packet) ?; + } +} + +export default PubkyClient diff --git a/js/pubky/src/lib/error.js b/js/pubky/src/lib/error.js new file mode 100644 index 0000000..29d4146 --- /dev/null +++ b/js/pubky/src/lib/error.js @@ -0,0 +1 @@ +export class PubkyError extends Error { } diff --git a/js/pubky/src/lib/fetch-browser.js b/js/pubky/src/lib/fetch-browser.js new file mode 100644 index 0000000..30b547c --- /dev/null +++ b/js/pubky/src/lib/fetch-browser.js @@ -0,0 +1,11 @@ +/* eslint-disable no-prototype-builtins */ +const g = + (typeof globalThis !== 'undefined' && globalThis) || + // eslint-disable-next-line no-undef + (typeof self !== 'undefined' && self) || + // eslint-disable-next-line no-undef + (typeof global !== 'undefined' && global) || + {} + +// @ts-ignore +export default g.fetch diff --git a/js/pubky/src/lib/fetch.js b/js/pubky/src/lib/fetch.js new file mode 100644 index 0000000..7c24a8b --- /dev/null +++ b/js/pubky/src/lib/fetch.js @@ -0,0 +1,3 @@ +import fetch from 'node-fetch-cache' + +export default fetch diff --git a/pubky/Cargo.toml b/pubky/Cargo.toml index c40cd9c..57a8b5c 100644 --- a/pubky/Cargo.toml +++ b/pubky/Cargo.toml @@ -3,16 +3,35 @@ name = "pubky" version = "0.1.0" edition = "2021" +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] +pkarr = "2.1.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] pubky-common = { version = "0.1.0", path = "../pubky-common" } -pkarr = "2.1.0" ureq = { version = "2.10.0", features = ["cookies"] } thiserror = "1.0.62" url = "2.5.2" flume = { version = "0.11.0", features = ["select", "eventual-fairness"], default-features = false } bytes = "1.6.1" +[target.'cfg(target_arch = "wasm32")'.dependencies] +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" } tokio = "1.37.0" diff --git a/pubky/src/lib.rs b/pubky/src/lib.rs index 7125ca1..4ed3714 100644 --- a/pubky/src/lib.rs +++ b/pubky/src/lib.rs @@ -1,8 +1,30 @@ #![allow(unused)] -mod client; -mod client_async; -mod error; +macro_rules! if_not_wasm { + ($($item:item)*) => {$( + #[cfg(not(target_arch = "wasm32"))] + $item + )*} +} -pub use client::PubkyClient; -pub use error::Error; +macro_rules! if_wasm { + ($($item:item)*) => {$( + #[cfg(target_arch = "wasm32")] + $item + )*} +} + +if_not_wasm! { + mod client; + mod client_async; + mod error; + + pub use client::PubkyClient; + pub use error::Error; +} + +if_wasm! { +mod wasm; + +pub use wasm::{PubkyClient, Keypair}; +} diff --git a/pubky/src/wasm.rs b/pubky/src/wasm.rs new file mode 100644 index 0000000..faddf5b --- /dev/null +++ b/pubky/src/wasm.rs @@ -0,0 +1,6 @@ +mod keys; + +mod client; + +pub use client::PubkyClient; +pub use keys::Keypair; diff --git a/pubky/src/wasm/client.rs b/pubky/src/wasm/client.rs new file mode 100644 index 0000000..7504789 --- /dev/null +++ b/pubky/src/wasm/client.rs @@ -0,0 +1,73 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::JsFuture; +use web_sys::RequestMode; + +use pkarr::PkarrRelayClient; + +use super::Keypair; + +#[wasm_bindgen] +pub struct Error {} + +#[wasm_bindgen] +pub struct PubkyClient { + pkarr: PkarrRelayClient, +} + +#[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> { + // let (audience, mut url) = self.resolve_endpoint(homeserver)?; + + // url.set_path(&format!("/{}", keypair.public_key())); + + // let body = AuthnSignature::generate(keypair, &audience).as_bytes(); + + // fetch_base(url.to_string(), "PUT", body).await?; + + // self.publish_pubky_homeserver(keypair, homeserver); + + Ok(()) + } +} + +async fn fetch_base( + url: &String, + method: &str, + body: Option>, +) -> Result { + let mut opts = web_sys::RequestInit::new(); + opts.method(method); + opts.mode(RequestMode::Cors); + + if let Some(body) = body { + let body_bytes: &[u8] = &body; + let body_array: js_sys::Uint8Array = body_bytes.into(); + let js_value: &JsValue = body_array.as_ref(); + opts.body(Some(js_value)); + } + + let js_request = web_sys::Request::new_with_str_and_init(url, &opts)?; + // .map_err(|error| Error::JsError(error))?; + + let window = web_sys::window().unwrap(); + let response = JsFuture::from(window.fetch_with_request(&js_request)).await?; + // .map_err(|error| Error::JsError(error))?; + + let response: web_sys::Response = response.dyn_into()?; + // .map_err(|error| Error::JsError(error))? + + Ok(response) +} diff --git a/pubky/src/wasm/keys.rs b/pubky/src/wasm/keys.rs new file mode 100644 index 0000000..3859511 --- /dev/null +++ b/pubky/src/wasm/keys.rs @@ -0,0 +1,15 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct Keypair(pkarr::Keypair); + +#[wasm_bindgen] +impl Keypair { + #[wasm_bindgen] + pub fn from_secret_key(secret_key: js_sys::Uint8Array) -> Self { + let mut bytes = [0; 32]; + secret_key.copy_to(&mut bytes); + + Self(pkarr::Keypair::from_secret_key(&bytes)) + } +}