diff --git a/.gitignore b/.gitignore index ea4024f..c0b157c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ cert.csr cert.der /firedbg -.vscode/ltex.* \ No newline at end of file +.vscode/ltex.* +pkg/ +wasm-pack.log \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 8214182..4ae4360 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,14 @@ { "markiscodecoverage.searchCriteria": ".coverage/lcov*.info", - "rust-analyzer.cargo.features": ["types", "reqwest", "gateway", "serde"] + // "rust-analyzer.cargo.features": ["types", "reqwest", "gateway", "serde"] + "rust-analyzer.cargo.features": [ + "_wasm_bindgen", + "wasm", + "types", + "reqwest", + "gateway", + "serde" + ], + "rust-analyzer.cargo.noDefaultFeatures": true, + "rust-analyzer.cargo.target": "wasm32-unknown-unknown" } diff --git a/Cargo.toml b/Cargo.toml index 5cceb9c..8116cd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/polyphony-chat/polyproto" rust-version = "1.85.0" [lib] -crate-type = ["rlib", "cdylib", "staticlib"] +crate-type = ["cdylib", "rlib"] [features] default = ["types", "serde", "gateway", "tokio/net"] @@ -20,6 +20,8 @@ serde = ["dep:serde", "serde_json", "serde_with", "url/serde"] serde_with = ["dep:serde_with"] serde_json = ["dep:serde_json"] gateway = ["serde", "types"] +_wasm_bindgen = ["wasm", "dep:wasm-bindgen", "dep:js-sys", "dep:wee_alloc"] +__no_wee_alloc = [] [dependencies] der = { version = "0.7.9", features = ["pem"] } @@ -50,6 +52,11 @@ futures-util = "0.3.31" urlencoding = "2.1.3" ws_stream_wasm = { version = "*", optional = true } +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { version = "0.2.100", optional = true } +js-sys = { version = "0.3.77", optional = true } +wee_alloc = { version = "0.4.5", optional = true } + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = "0.23.25" tokio-tungstenite = { version = "0.26.2", features = [ @@ -73,7 +80,18 @@ httptest = "0.16.3" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.50" -wasm-bindgen = "0.2.100" +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7" } + +[target.'cfg(target_arch = "wasm32")'.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" +lto = true +codegen-units = 1 +panic = "abort" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } diff --git a/headaches.md b/headaches.md new file mode 100644 index 0000000..4786a1c --- /dev/null +++ b/headaches.md @@ -0,0 +1,8 @@ +# Headaches involving `wasm-bindgen` and overall Rust ⇾ TS/JS bindgen + +- `wasm-bindgen` does not support traits, or struct generics with trait bounds like `struct X` +- Crates like `ts_rs` might help but would require a more complex build process to assemble a finished TS/JS project +- Worst case: Manually write JS/TS code to bridge the things unsupported by other bindgen libs + - Would be immensely painful +- Other idea: LOADS of handwritten wrappers for Rust functions, also written in Rust but `wasm-bindgen` compatible. +- For traits like signature, we need to make an extremely generic impl that can be somehow instantiated from js/ts \ No newline at end of file diff --git a/scripts/wasm-pack-helper b/scripts/wasm-pack-helper new file mode 100755 index 0000000..048e29f --- /dev/null +++ b/scripts/wasm-pack-helper @@ -0,0 +1,40 @@ +#!/bin/bash +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +# check number of arguments +if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then + echo "Error: Invalid number of arguments. Usage: $0 [release|debug]" + exit 1 +fi + +command=$1 +mode=${2:-"debug"} + +# validate if arg #1 is valid +if [[ "$command" != "build" && "$command" != "pack" && "$command" != "publish" ]]; then + echo "Error: First argument must be one of 'build', 'pack', or 'publish'." + exit 1 +fi + +# same for arg #2 +if [[ "$mode" != "release" && "$mode" != "debug" ]]; then + echo "Error: Second argument must be either 'release' or 'debug'. Defaulting to 'debug'." + mode="debug" +fi + +# wasm-pack check +if ! command -v wasm-pack &> /dev/null; then + # prompt user to install wasm-pack + read -p "wasm-pack could not be found. Do you want to install it? (y/n): " install_wasm_pack + if [[ "$install_wasm_pack" == [Yy]* ]]; then + cargo install wasm-pack --force + else + echo "Error: wasm-pack is required for this script." + exit 1 + fi +fi + +# Execute the wasm-pack command +wasm-pack $command --$mode --no-default-features --features=wasm,reqwest,serde,types,_wasm_bindgen \ No newline at end of file diff --git a/src/key.rs b/src/key.rs index 26a5d2f..ebcc226 100644 --- a/src/key.rs +++ b/src/key.rs @@ -36,5 +36,7 @@ pub trait PublicKey: PartialEq + Eq + Clone { S::algorithm_identifier() } /// Creates a new [Self] from a [PublicKeyInfo]. - fn try_from_public_key_info(public_key_info: PublicKeyInfo) -> Result; + fn try_from_public_key_info( + public_key_info: PublicKeyInfo, + ) -> Result; } diff --git a/src/lib.rs b/src/lib.rs index cbb1139..8f6d63a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,6 +139,14 @@ of this project. clippy::todo )] +#[cfg(all( + target_arch = "wasm32", + feature = "_wasm_bindgen", + not(feature = "__no_wee_alloc") +))] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + /// The OID for the `domainComponent` RDN pub const OID_RDN_DOMAIN_COMPONENT: &str = "0.9.2342.19200300.100.1.25"; /// The OID for the `commonName` RDN @@ -168,6 +176,10 @@ pub mod signature; /// Types used in polyproto and the polyproto HTTP/REST APIs pub mod types; +#[cfg(all(target_arch = "wasm32", feature = "_wasm_bindgen"))] +/// 🚧 Under construction! 👷 Module for exporting polyproto to JS/TS. +pub mod wasm_bindgen; + mod constraints; pub use der; diff --git a/src/wasm_bindgen/errors.rs b/src/wasm_bindgen/errors.rs new file mode 100644 index 0000000..e481daf --- /dev/null +++ b/src/wasm_bindgen/errors.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use wasm_bindgen::prelude::*; + +#[derive(Debug, Clone, Copy)] +#[wasm_bindgen(js_name = "PolyprotoError")] +pub enum JsConstraintError { + InvalidInput, +} diff --git a/src/wasm_bindgen/mod.rs b/src/wasm_bindgen/mod.rs new file mode 100644 index 0000000..b6aad96 --- /dev/null +++ b/src/wasm_bindgen/mod.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +pub mod errors; +pub mod types; + +mod utils; diff --git a/src/wasm_bindgen/types.rs b/src/wasm_bindgen/types.rs new file mode 100644 index 0000000..801148c --- /dev/null +++ b/src/wasm_bindgen/types.rs @@ -0,0 +1,40 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::types::DomainName as RDomainName; +use crate::types::FederationId as RFederationId; +use wasm_bindgen::prelude::*; + +use super::errors::JsConstraintError; + +#[derive(Clone, Debug)] +#[wasm_bindgen(inspectable)] +/// A `FederationId` is a globally unique identifier for an actor in the context of polyproto. +pub struct FederationId { + #[wasm_bindgen(skip)] + _inner: RFederationId, +} + +#[wasm_bindgen] +impl FederationId { + #[wasm_bindgen(constructor)] + /// Validates input, then creates a new `FederationId`. Throws an error if input validation fails. + pub fn new(id: &str) -> Result { + Ok(FederationId { + _inner: RFederationId::new(id).map_err(|_| JsConstraintError::InvalidInput)?, + }) + } + + #[wasm_bindgen(js_name = "toJSON")] + pub fn js_to_json(&self) -> String { + self._inner.to_string() + } +} + +#[derive(Debug, Clone)] +#[wasm_bindgen(inspectable)] +pub struct DomainName { + #[wasm_bindgen(skip)] + _inner: RDomainName, +} diff --git a/src/wasm_bindgen/utils.rs b/src/wasm_bindgen/utils.rs new file mode 100644 index 0000000..5353c68 --- /dev/null +++ b/src/wasm_bindgen/utils.rs @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +}