From 1b20037053aab159b27d1547fb62ad877a287fd2 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Wed, 16 Aug 2023 10:25:09 -0400 Subject: [PATCH 1/9] Setup for TypeScript type gen for WASM using Tsify - Includes breaking changes to the WASM API surface, since it now accepts objects instead of stringified JSON. --- Cargo.lock | 78 +++++++++- Cargo.toml | 1 + tsify-async/Cargo.toml | 14 ++ tsify-async/src/lib.rs | 30 ++++ veilid-core/Cargo.toml | 3 + veilid-core/src/core_context.rs | 2 +- .../src/crypto/types/byte_array_types.rs | 6 + veilid-core/src/crypto/types/crypto_typed.rs | 2 +- .../src/crypto/types/crypto_typed_group.rs | 4 +- veilid-core/src/crypto/types/mod.rs | 11 ++ veilid-core/src/lib.rs | 3 + veilid-core/src/veilid_api/error.rs | 15 +- .../src/veilid_api/types/aligned_u64.rs | 17 ++- .../src/veilid_api/types/app_message_call.rs | 6 +- veilid-core/src/veilid_api/types/dht/mod.rs | 2 + .../src/veilid_api/types/dht/value_data.rs | 4 +- veilid-core/src/veilid_api/types/fourcc.rs | 13 +- .../src/veilid_api/types/veilid_log.rs | 6 +- .../src/veilid_api/types/veilid_state.rs | 21 +-- veilid-core/src/veilid_config.rs | 51 ++++--- veilid-wasm/Cargo.toml | 2 + veilid-wasm/src/lib.rs | 141 +++++++++--------- 22 files changed, 323 insertions(+), 109 deletions(-) create mode 100644 tsify-async/Cargo.toml create mode 100644 tsify-async/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2585bef8..015ad59a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2136,6 +2136,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-utils" version = "0.2.0" @@ -4250,7 +4263,7 @@ checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals", + "serde_derive_internals 0.26.0", "syn 1.0.109", ] @@ -4358,6 +4371,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_cbor" version = "0.11.2" @@ -4390,6 +4414,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serde_derive_internals" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + [[package]] name = "serde_json" version = "1.0.105" @@ -5347,6 +5382,41 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tsify" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" +dependencies = [ + "gloo-utils 0.1.7", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-async" +version = "0.1.0" +dependencies = [ + "quote", + "serde-wasm-bindgen", + "syn 1.0.109", +] + +[[package]] +name = "tsify-macros" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a94b0f0954b3e59bfc2c246b4c8574390d94a4ad4ad246aaf2fb07d7dfd3b47" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.28.0", + "syn 2.0.31", +] + [[package]] name = "tungstenite" version = "0.11.1" @@ -5614,6 +5684,7 @@ dependencies = [ "send_wrapper 0.6.0", "serde", "serde-big-array", + "serde-wasm-bindgen", "serde_json", "serial_test 2.0.0", "shell-words", @@ -5631,6 +5702,7 @@ dependencies = [ "tracing-subscriber", "tracing-wasm", "trust-dns-resolver", + "tsify", "veilid-bugsalot", "veilid-hashlink", "veilid-igd", @@ -5813,15 +5885,17 @@ dependencies = [ "console_error_panic_hook", "data-encoding", "futures-util", - "gloo-utils", + "gloo-utils 0.2.0", "js-sys", "lazy_static", "send_wrapper 0.6.0", "serde", + "serde-wasm-bindgen", "serde_json", "tracing", "tracing-subscriber", "tracing-wasm", + "tsify", "veilid-core", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/Cargo.toml b/Cargo.toml index 61d53ad7..eaa3bf6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "veilid-cli", "veilid-flutter/rust", "veilid-wasm", + "tsify-async" ] exclude = ["./external"] diff --git a/tsify-async/Cargo.toml b/tsify-async/Cargo.toml new file mode 100644 index 00000000..192733fc --- /dev/null +++ b/tsify-async/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tsify-async" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +syn = "1.0" +quote = "1.0" +serde-wasm-bindgen = "0.5.0" diff --git a/tsify-async/src/lib.rs b/tsify-async/src/lib.rs new file mode 100644 index 00000000..4e4f75b7 --- /dev/null +++ b/tsify-async/src/lib.rs @@ -0,0 +1,30 @@ +// Copied from https://github.com/madonoharu/tsify/issues/24#issuecomment-1670228789 +// TODO: I think this had more to do with: +// the trait `From` is not implemented for `wasm_bindgen::JsValue` +// than it does with tsify itself... Maybe rename to something else? + +use proc_macro::TokenStream; +use quote::quote; +use syn; + +#[proc_macro_derive(TsifyAsync)] +pub fn tsify_async_macro_derive(input: TokenStream) -> TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate + let ast = syn::parse(input).unwrap(); + + // Build the trait implementation + impl_tsify_async_macro(&ast) +} + +fn impl_tsify_async_macro(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + let gen = quote! { + impl From<#name> for JsValue { + fn from(value: #name) -> Self { + serde_wasm_bindgen::to_value(&value).unwrap() + } + } + }; + gen.into() +} diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 941af040..1a1be691 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -140,6 +140,9 @@ lz4_flex = { version = "0.11.1", default-features = false, features = [ "safe-encode", "safe-decode", ] } +tsify = { version = "0.4.5", features = ["js"] } +wasm-bindgen = "^0" +serde-wasm-bindgen = "0.5.0" # Dependencies for native builds only # Linux, Windows, Mac, iOS, Android diff --git a/veilid-core/src/core_context.rs b/veilid-core/src/core_context.rs index 4624b012..61e84f8a 100644 --- a/veilid-core/src/core_context.rs +++ b/veilid-core/src/core_context.rs @@ -231,7 +231,7 @@ impl VeilidCoreContext { update_callback: UpdateCallback, config_json: String, ) -> VeilidAPIResult { - // Set up config from callback + // Set up config from json trace!("setup config with json"); let mut config = VeilidConfig::new(); config.setup_from_json(config_json, update_callback.clone())?; diff --git a/veilid-core/src/crypto/types/byte_array_types.rs b/veilid-core/src/crypto/types/byte_array_types.rs index 4cdf2300..84995829 100644 --- a/veilid-core/src/crypto/types/byte_array_types.rs +++ b/veilid-core/src/crypto/types/byte_array_types.rs @@ -293,11 +293,17 @@ macro_rules! byte_array_type { byte_array_type!(CryptoKey, CRYPTO_KEY_LENGTH, CRYPTO_KEY_LENGTH_ENCODED); +#[declare] pub type PublicKey = CryptoKey; +#[declare] pub type SecretKey = CryptoKey; +#[declare] pub type HashDigest = CryptoKey; +#[declare] pub type SharedSecret = CryptoKey; +#[declare] pub type RouteId = CryptoKey; +#[declare] pub type CryptoKeyDistance = CryptoKey; byte_array_type!(Signature, SIGNATURE_LENGTH, SIGNATURE_LENGTH_ENCODED); diff --git a/veilid-core/src/crypto/types/crypto_typed.rs b/veilid-core/src/crypto/types/crypto_typed.rs index 740f888c..4a59ae20 100644 --- a/veilid-core/src/crypto/types/crypto_typed.rs +++ b/veilid-core/src/crypto/types/crypto_typed.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Tsify)] pub struct CryptoTyped where K: Clone diff --git a/veilid-core/src/crypto/types/crypto_typed_group.rs b/veilid-core/src/crypto/types/crypto_typed_group.rs index 80f717bc..60a34b2e 100644 --- a/veilid-core/src/crypto/types/crypto_typed_group.rs +++ b/veilid-core/src/crypto/types/crypto_typed_group.rs @@ -1,6 +1,8 @@ use super::*; -#[derive(Clone, Debug, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash, Default)] +#[derive( + Clone, Debug, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash, Default, Tsify, +)] #[serde(from = "Vec>", into = "Vec>")] pub struct CryptoTypedGroup where diff --git a/veilid-core/src/crypto/types/mod.rs b/veilid-core/src/crypto/types/mod.rs index af03453d..a1f2026d 100644 --- a/veilid-core/src/crypto/types/mod.rs +++ b/veilid-core/src/crypto/types/mod.rs @@ -6,6 +6,7 @@ use core::fmt; use core::hash::Hash; /// Cryptography version fourcc code +#[declare] pub type CryptoKind = FourCC; /// Sort best crypto kinds first @@ -51,14 +52,24 @@ pub use crypto_typed::*; pub use crypto_typed_group::*; pub use keypair::*; +#[declare] pub type TypedKey = CryptoTyped; +#[declare] pub type TypedSecret = CryptoTyped; +#[declare] pub type TypedKeyPair = CryptoTyped; +#[declare] pub type TypedSignature = CryptoTyped; +#[declare] pub type TypedSharedSecret = CryptoTyped; +#[declare] pub type TypedKeyGroup = CryptoTypedGroup; +#[declare] pub type TypedSecretGroup = CryptoTypedGroup; +#[declare] pub type TypedKeyPairGroup = CryptoTypedGroup; +#[declare] pub type TypedSignatureGroup = CryptoTypedGroup; +#[declare] pub type TypedSharedSecretGroup = CryptoTypedGroup; diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index 63d7db63..1a53fd73 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -126,4 +126,7 @@ use serde::*; use stop_token::*; use thiserror::Error as ThisError; use tracing::*; +use tsify::*; +use tsify_async::*; use veilid_tools::*; +use wasm_bindgen::prelude::*; diff --git a/veilid-core/src/veilid_api/error.rs b/veilid-core/src/veilid_api/error.rs index 6fcc4861..170a4146 100644 --- a/veilid-core/src/veilid_api/error.rs +++ b/veilid-core/src/veilid_api/error.rs @@ -105,9 +105,21 @@ macro_rules! apibail_already_initialized { } #[derive( - ThisError, Clone, Debug, PartialOrd, PartialEq, Eq, Ord, Serialize, Deserialize, JsonSchema, + ThisError, + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + JsonSchema, + Tsify, + TsifyAsync, )] #[serde(tag = "kind")] +#[tsify(into_wasm_abi)] pub enum VeilidAPIError { #[error("Not initialized")] NotInitialized, @@ -213,6 +225,7 @@ impl VeilidAPIError { } } +#[declare] pub type VeilidAPIResult = Result; impl From for VeilidAPIError { diff --git a/veilid-core/src/veilid_api/types/aligned_u64.rs b/veilid-core/src/veilid_api/types/aligned_u64.rs index fbad5696..32b5ceb1 100644 --- a/veilid-core/src/veilid_api/types/aligned_u64.rs +++ b/veilid-core/src/veilid_api/types/aligned_u64.rs @@ -6,7 +6,18 @@ use super::*; /// Supports serializing to string for JSON as well, since JSON can't handle 64-bit numbers to Javascript #[derive( - Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Hash, Serialize, Deserialize, JsonSchema, + Clone, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Copy, + Hash, + Serialize, + Deserialize, + JsonSchema, + Tsify, )] #[repr(C, align(8))] #[serde(transparent)] @@ -117,13 +128,17 @@ impl AlignedU64 { ///////////////////////////////////////////////////////////////////////////////////////////////////// /// Microseconds since epoch +#[declare] pub type Timestamp = AlignedU64; pub fn get_aligned_timestamp() -> Timestamp { get_timestamp().into() } /// Microseconds duration +#[declare] pub type TimestampDuration = AlignedU64; /// Request/Response matching id +#[declare] pub type OperationId = AlignedU64; /// Number of bytes +#[declare] pub type ByteCount = AlignedU64; diff --git a/veilid-core/src/veilid_api/types/app_message_call.rs b/veilid-core/src/veilid_api/types/app_message_call.rs index 500588be..6959ad17 100644 --- a/veilid-core/src/veilid_api/types/app_message_call.rs +++ b/veilid-core/src/veilid_api/types/app_message_call.rs @@ -1,10 +1,11 @@ use super::*; /// Direct statement blob passed to hosting application for processing -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidAppMessage { #[serde(with = "as_human_opt_string")] #[schemars(with = "Option")] + #[tsify(optional)] sender: Option, #[serde(with = "as_human_base64")] @@ -29,10 +30,11 @@ impl VeilidAppMessage { } /// Direct question blob passed to hosting application for processing to send an eventual AppReply -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidAppCall { #[serde(with = "as_human_opt_string")] #[schemars(with = "Option")] + #[tsify(optional)] sender: Option, #[serde(with = "as_human_base64")] diff --git a/veilid-core/src/veilid_api/types/dht/mod.rs b/veilid-core/src/veilid_api/types/dht/mod.rs index 8b757907..9830042b 100644 --- a/veilid-core/src/veilid_api/types/dht/mod.rs +++ b/veilid-core/src/veilid_api/types/dht/mod.rs @@ -11,6 +11,8 @@ pub use value_data::*; pub use value_subkey_range_set::*; /// Value subkey +#[declare] pub type ValueSubkey = u32; /// Value sequence number +#[declare] pub type ValueSeqNum = u32; diff --git a/veilid-core/src/veilid_api/types/dht/value_data.rs b/veilid-core/src/veilid_api/types/dht/value_data.rs index f5aee32e..6a89388d 100644 --- a/veilid-core/src/veilid_api/types/dht/value_data.rs +++ b/veilid-core/src/veilid_api/types/dht/value_data.rs @@ -1,7 +1,9 @@ use super::*; use veilid_api::VeilidAPIResult; -#[derive(Clone, Default, PartialOrd, PartialEq, Eq, Ord, Serialize, Deserialize, JsonSchema)] +#[derive( + Clone, Default, PartialOrd, PartialEq, Eq, Ord, Serialize, Deserialize, JsonSchema, Tsify, +)] pub struct ValueData { /// An increasing sequence number to time-order the DHT record changes seq: ValueSeqNum, diff --git a/veilid-core/src/veilid_api/types/fourcc.rs b/veilid-core/src/veilid_api/types/fourcc.rs index 5995c1b5..8325a48e 100644 --- a/veilid-core/src/veilid_api/types/fourcc.rs +++ b/veilid-core/src/veilid_api/types/fourcc.rs @@ -2,7 +2,18 @@ use super::*; /// FOURCC code #[derive( - Copy, Default, Clone, Hash, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, JsonSchema, + Copy, + Default, + Clone, + Hash, + PartialOrd, + Ord, + PartialEq, + Eq, + Serialize, + Deserialize, + JsonSchema, + Tsify, )] #[serde(try_from = "String")] #[serde(into = "String")] diff --git a/veilid-core/src/veilid_api/types/veilid_log.rs b/veilid-core/src/veilid_api/types/veilid_log.rs index bf9c5963..7289e281 100644 --- a/veilid-core/src/veilid_api/types/veilid_log.rs +++ b/veilid-core/src/veilid_api/types/veilid_log.rs @@ -2,8 +2,9 @@ use super::*; /// Log level for VeilidCore #[derive( - Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Serialize, Deserialize, JsonSchema, + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Serialize, Deserialize, JsonSchema, Tsify, )] +#[tsify(namespace)] pub enum VeilidLogLevel { Error = 1, Warn = 2, @@ -79,9 +80,10 @@ impl fmt::Display for VeilidLogLevel { } } /// A VeilidCore log message with optional backtrace -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidLog { pub log_level: VeilidLogLevel, pub message: String, + #[tsify(optional)] pub backtrace: Option, } diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs index 88ee35b9..496aaeac 100644 --- a/veilid-core/src/veilid_api/types/veilid_state.rs +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -1,7 +1,8 @@ use super::*; /// Attachment abstraction for network 'signal strength' -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, JsonSchema, Tsify)] +#[tsify(namespace, from_wasm_abi, into_wasm_abi)] pub enum AttachmentState { Detached = 0, Attaching = 1, @@ -47,14 +48,14 @@ impl TryFrom for AttachmentState { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidStateAttachment { pub state: AttachmentState, pub public_internet_ready: bool, pub local_network_ready: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct PeerTableData { #[schemars(with = "Vec")] pub node_ids: Vec, @@ -62,7 +63,7 @@ pub struct PeerTableData { pub peer_stats: PeerStats, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidStateNetwork { pub started: bool, pub bps_down: ByteCount, @@ -70,7 +71,7 @@ pub struct VeilidStateNetwork { pub peers: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidRouteChange { #[schemars(with = "Vec")] pub dead_routes: Vec, @@ -78,12 +79,12 @@ pub struct VeilidRouteChange { pub dead_remote_routes: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidStateConfig { pub config: VeilidConfigInner, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidValueChange { #[schemars(with = "String")] pub key: TypedKey, @@ -92,8 +93,9 @@ pub struct VeilidValueChange { pub value: ValueData, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify, TsifyAsync)] #[serde(tag = "kind")] +#[tsify(into_wasm_abi)] pub enum VeilidUpdate { Log(VeilidLog), AppMessage(VeilidAppMessage), @@ -106,7 +108,8 @@ pub enum VeilidUpdate { Shutdown, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify, TsifyAsync)] +#[tsify(into_wasm_abi)] pub struct VeilidState { pub attachment: VeilidStateAttachment, pub network: VeilidStateNetwork, diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 91027978..8aee0819 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -1,7 +1,9 @@ use crate::*; //////////////////////////////////////////////////////////////////////////////////////////////// +#[declare] pub type ConfigCallbackReturn = VeilidAPIResult>; +#[declare] pub type ConfigCallback = Arc ConfigCallbackReturn + Send + Sync>; /// Enable and configure HTTPS access to the Veilid node @@ -14,11 +16,12 @@ pub type ConfigCallback = Arc ConfigCallbackReturn + Send + Sy /// url: 'https://localhost:5150' /// ``` /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigHTTPS { pub enabled: bool, pub listen_address: String, pub path: String, + #[tsify(optional)] pub url: Option, // Fixed URL is not optional for TLS-based protocols and is dynamically validated } @@ -32,11 +35,12 @@ pub struct VeilidConfigHTTPS { /// url: 'https://localhost:5150' /// ``` /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigHTTP { pub enabled: bool, pub listen_address: String, pub path: String, + #[tsify(optional)] pub url: Option, } @@ -46,7 +50,7 @@ pub struct VeilidConfigHTTP { /// /// To be implemented... /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigApplication { pub https: VeilidConfigHTTPS, pub http: VeilidConfigHTTP, @@ -62,11 +66,12 @@ pub struct VeilidConfigApplication { /// public_address: '' /// ``` /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigUDP { pub enabled: bool, pub socket_pool_size: u32, pub listen_address: String, + #[tsify(optional)] pub public_address: Option, } @@ -80,12 +85,13 @@ pub struct VeilidConfigUDP { /// listen_address: ':5150' /// public_address: '' /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigTCP { pub connect: bool, pub listen: bool, pub max_connections: u32, pub listen_address: String, + #[tsify(optional)] pub public_address: Option, } @@ -100,7 +106,7 @@ pub struct VeilidConfigTCP { /// path: 'ws' /// url: 'ws://localhost:5150/ws' /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigWS { pub connect: bool, @@ -108,6 +114,7 @@ pub struct VeilidConfigWS { pub max_connections: u32, pub listen_address: String, pub path: String, + #[tsify(optional)] pub url: Option, } @@ -122,7 +129,7 @@ pub struct VeilidConfigWS { /// path: 'ws' /// url: '' /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigWSS { pub connect: bool, @@ -130,6 +137,7 @@ pub struct VeilidConfigWSS { pub max_connections: u32, pub listen_address: String, pub path: String, + #[tsify(optional)] pub url: Option, // Fixed URL is not optional for TLS-based protocols and is dynamically validated } @@ -140,7 +148,7 @@ pub struct VeilidConfigWSS { /// All protocols are available by default, and the Veilid node will /// sort out which protocol is used for each peer connection. /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigProtocol { pub udp: VeilidConfigUDP, @@ -157,7 +165,7 @@ pub struct VeilidConfigProtocol { /// private_key_path: /path/to/private/key /// connection_initial_timeout_ms: 2000 /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigTLS { pub certificate_path: String, pub private_key_path: String, @@ -166,7 +174,7 @@ pub struct VeilidConfigTLS { /// Configure the Distributed Hash Table (DHT) /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigDHT { pub max_find_node_count: u32, pub resolve_node_timeout_ms: u32, @@ -191,11 +199,13 @@ pub struct VeilidConfigDHT { /// Configure RPC /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigRPC { pub concurrency: u32, pub queue_size: u32, + #[tsify(optional)] pub max_timestamp_behind_ms: Option, + #[tsify(optional)] pub max_timestamp_ahead_ms: Option, pub timeout_ms: u32, pub max_route_hop_count: u8, @@ -204,7 +214,7 @@ pub struct VeilidConfigRPC { /// Configure the network routing table /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigRoutingTable { #[schemars(with = "Vec")] pub node_id: TypedKeyGroup, @@ -220,7 +230,7 @@ pub struct VeilidConfigRoutingTable { // xxx pub enable_local_network: bool, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigNetwork { pub connection_initial_timeout_ms: u32, pub connection_inactivity_timeout_ms: u32, @@ -231,6 +241,7 @@ pub struct VeilidConfigNetwork { pub client_whitelist_timeout_ms: u32, pub reverse_connection_receipt_time_ms: u32, pub hole_punch_receipt_time_ms: u32, + #[tsify(optional)] pub network_key_password: Option, pub routing_table: VeilidConfigRoutingTable, pub rpc: VeilidConfigRPC, @@ -243,34 +254,36 @@ pub struct VeilidConfigNetwork { pub protocol: VeilidConfigProtocol, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigTableStore { pub directory: String, pub delete: bool, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigBlockStore { pub directory: String, pub delete: bool, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigProtectedStore { pub allow_insecure_fallback: bool, pub always_use_insecure_storage: bool, pub directory: String, pub delete: bool, pub device_encryption_key_password: String, + #[tsify(optional)] pub new_device_encryption_key_password: Option, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigCapabilities { pub disable: Vec, } -#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema, Tsify)] +#[tsify(namespace, from_wasm_abi)] pub enum VeilidConfigLogLevel { Off, Error, @@ -357,7 +370,7 @@ impl fmt::Display for VeilidConfigLogLevel { } } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] pub struct VeilidConfigInner { pub program_name: String, pub namespace: String, diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 129840f9..06313daf 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -34,6 +34,8 @@ send_wrapper = "^0" futures-util = { version = "^0" } data-encoding = { version = "^2" } gloo-utils = { version = "^0", features = ["serde"] } +tsify = { version = "0.4.5", features = ["js"] } +serde-wasm-bindgen = "0.5.0" [dev-dependencies] wasm-bindgen-test = "^0" diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index ed6172e7..76501d54 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -19,6 +19,7 @@ use serde::*; use tracing_subscriber::prelude::*; use tracing_subscriber::*; use tracing_wasm::{WASMLayerConfigBuilder, *}; +use tsify::*; use veilid_core::tools::*; use veilid_core::*; use wasm_bindgen::prelude::*; @@ -85,6 +86,7 @@ type APIResult = Result; const APIRESULT_UNDEFINED: APIResult<()> = APIResult::Ok(()); pub fn wrap_api_future_json(future: F) -> Promise +// Result where F: Future> + 'static, T: Serialize + Debug + 'static, @@ -93,6 +95,7 @@ where } pub fn wrap_api_future_plain(future: F) -> Promise +// Result where F: Future> + 'static, JsValue: From, @@ -102,6 +105,7 @@ where } pub fn wrap_api_future_void(future: F) -> Promise +// Result<(), VeilidAPIError> where F: Future> + 'static, { @@ -111,7 +115,7 @@ where ///////////////////////////////////////// // WASM-specific -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Tsify)] pub struct VeilidWASMConfigLoggingPerformance { pub enabled: bool, pub level: veilid_core::VeilidConfigLogLevel, @@ -119,24 +123,25 @@ pub struct VeilidWASMConfigLoggingPerformance { pub logs_in_console: bool, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Tsify)] pub struct VeilidWASMConfigLoggingAPI { pub enabled: bool, pub level: veilid_core::VeilidConfigLogLevel, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Tsify)] pub struct VeilidWASMConfigLogging { pub performance: VeilidWASMConfigLoggingPerformance, pub api: VeilidWASMConfigLoggingAPI, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Tsify)] +#[tsify(from_wasm_abi)] pub struct VeilidWASMConfig { pub logging: VeilidWASMConfigLogging, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Tsify)] pub struct VeilidRouteBlob { pub route_id: veilid_core::RouteId, #[serde(with = "veilid_core::as_human_base64")] @@ -152,13 +157,10 @@ pub fn initialize_veilid_wasm() { static INITIALIZED: AtomicBool = AtomicBool::new(false); #[wasm_bindgen()] -pub fn initialize_veilid_core(platform_config: String) { +pub fn initialize_veilid_core(platform_config: VeilidWASMConfig) { if INITIALIZED.swap(true, Ordering::Relaxed) { return; } - let platform_config: VeilidWASMConfig = veilid_core::deserialize_json(&platform_config) - .expect("failed to deserialize platform config json"); - // Set up subscriber and layers let subscriber = Registry::default(); let mut layers = Vec::new(); @@ -199,9 +201,8 @@ pub fn initialize_veilid_core(platform_config: String) { } #[wasm_bindgen()] -pub fn change_log_level(layer: String, log_level: String) { +pub fn change_log_level(layer: String, log_level: VeilidConfigLogLevel) { let layer = if layer == "all" { "".to_owned() } else { layer }; - let log_level: veilid_core::VeilidConfigLogLevel = deserialize_json(&log_level).unwrap(); let filters = (*FILTERS).borrow(); if layer.is_empty() { // Change all layers @@ -215,65 +216,73 @@ pub fn change_log_level(layer: String, log_level: String) { } } -#[wasm_bindgen()] -pub fn startup_veilid_core(update_callback_js: Function, json_config: String) -> Promise { +#[wasm_bindgen(typescript_custom_section)] +const IUPDATE_VEILID_FUNCTION: &'static str = r#" +type UpdateVeilidFunction = (event: VeilidUpdate) => void; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Function, typescript_type = "UpdateVeilidFunction")] + pub type UpdateVeilidFunction; +} + +#[wasm_bindgen] +pub async fn startup_veilid_core( + update_callback_js: UpdateVeilidFunction, + json_config: String, +) -> Result<(), VeilidAPIError> { let update_callback_js = SendWrapper::new(update_callback_js); - wrap_api_future_void(async move { - let update_callback = Arc::new(move |update: VeilidUpdate| { - let _ret = - match Function::call1(&update_callback_js, &JsValue::UNDEFINED, &to_json(update)) { - Ok(v) => v, - Err(e) => { - console_log(&format!("calling update callback failed: {:?}", e)); - return; - } - }; - }); + let update_callback = Arc::new(move |update: VeilidUpdate| { + let _ret = match Function::call1( + &update_callback_js, + &JsValue::UNDEFINED, + &to_jsvalue(update), + ) { + Ok(v) => v, + Err(e) => { + console_log(&format!("calling update callback failed: {:?}", e)); + return; + } + }; + }); - if VEILID_API.borrow().is_some() { - return Err(veilid_core::VeilidAPIError::AlreadyInitialized); - } + if VEILID_API.borrow().is_some() { + return Err(veilid_core::VeilidAPIError::AlreadyInitialized); + } - let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?; - VEILID_API.replace(Some(veilid_api)); - APIRESULT_UNDEFINED - }) + let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?; + // veilid_core::api_startup(update_callback, config_callback) + VEILID_API.replace(Some(veilid_api)); + Ok(()) } #[wasm_bindgen()] -pub fn get_veilid_state() -> Promise { - wrap_api_future_json(async move { - let veilid_api = get_veilid_api()?; - let core_state = veilid_api.get_state().await?; - APIResult::Ok(core_state) - }) +pub async fn get_veilid_state() -> Result { + let veilid_api = get_veilid_api()?; + let core_state = veilid_api.get_state().await?; + Ok(core_state) } #[wasm_bindgen()] -pub fn attach() -> Promise { - wrap_api_future_void(async move { - let veilid_api = get_veilid_api()?; - veilid_api.attach().await?; - APIRESULT_UNDEFINED - }) +pub async fn attach() -> Result<(), VeilidAPIError> { + let veilid_api = get_veilid_api()?; + veilid_api.attach().await?; + Ok(()) } #[wasm_bindgen()] -pub fn detach() -> Promise { - wrap_api_future_void(async move { - let veilid_api = get_veilid_api()?; - veilid_api.detach().await?; - APIRESULT_UNDEFINED - }) +pub async fn detach() -> Result<(), VeilidAPIError> { + let veilid_api = get_veilid_api()?; + veilid_api.detach().await?; + Ok(()) } #[wasm_bindgen()] -pub fn shutdown_veilid_core() -> Promise { - wrap_api_future_void(async move { - let veilid_api = take_veilid_api()?; - veilid_api.shutdown().await; - APIRESULT_UNDEFINED - }) +pub async fn shutdown_veilid_core() -> Result<(), VeilidAPIError> { + let veilid_api = take_veilid_api()?; + veilid_api.shutdown().await; + Ok(()) } fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 { @@ -287,13 +296,11 @@ fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 { } #[wasm_bindgen()] -pub fn routing_context() -> Promise { - wrap_api_future_plain(async move { - let veilid_api = get_veilid_api()?; - let routing_context = veilid_api.routing_context(); - let new_id = add_routing_context(routing_context); - APIResult::Ok(new_id) - }) +pub async fn routing_context() -> Result { + let veilid_api = get_veilid_api()?; + let routing_context = veilid_api.routing_context(); + let new_id = add_routing_context(routing_context); + Ok(new_id) } #[wasm_bindgen()] @@ -1440,12 +1447,10 @@ pub fn now() -> u64 { } #[wasm_bindgen()] -pub fn debug(command: String) -> Promise { - wrap_api_future_plain(async move { - let veilid_api = get_veilid_api()?; - let out = veilid_api.debug(command).await?; - APIResult::Ok(out) - }) +pub async fn debug(command: String) -> Result { + let veilid_api = get_veilid_api()?; + let out = veilid_api.debug(command).await?; + APIResult::Ok(out) } #[wasm_bindgen()] From 0abc9a8b457a332d9f4d474713f4fff8f0b4d0fb Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Fri, 18 Aug 2023 10:34:40 -0400 Subject: [PATCH 2/9] Revert breaking changes to WASM API --- veilid-wasm/src/lib.rs | 124 +++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 76501d54..015fe026 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -86,7 +86,6 @@ type APIResult = Result; const APIRESULT_UNDEFINED: APIResult<()> = APIResult::Ok(()); pub fn wrap_api_future_json(future: F) -> Promise -// Result where F: Future> + 'static, T: Serialize + Debug + 'static, @@ -95,7 +94,6 @@ where } pub fn wrap_api_future_plain(future: F) -> Promise -// Result where F: Future> + 'static, JsValue: From, @@ -105,7 +103,6 @@ where } pub fn wrap_api_future_void(future: F) -> Promise -// Result<(), VeilidAPIError> where F: Future> + 'static, { @@ -157,10 +154,13 @@ pub fn initialize_veilid_wasm() { static INITIALIZED: AtomicBool = AtomicBool::new(false); #[wasm_bindgen()] -pub fn initialize_veilid_core(platform_config: VeilidWASMConfig) { +pub fn initialize_veilid_core(platform_config: String) { if INITIALIZED.swap(true, Ordering::Relaxed) { return; } + let platform_config: VeilidWASMConfig = veilid_core::deserialize_json(&platform_config) + .expect("failed to deserialize platform config json"); + // Set up subscriber and layers let subscriber = Registry::default(); let mut layers = Vec::new(); @@ -201,8 +201,9 @@ pub fn initialize_veilid_core(platform_config: VeilidWASMConfig) { } #[wasm_bindgen()] -pub fn change_log_level(layer: String, log_level: VeilidConfigLogLevel) { +pub fn change_log_level(layer: String, log_level: String) { let layer = if layer == "all" { "".to_owned() } else { layer }; + let log_level: veilid_core::VeilidConfigLogLevel = deserialize_json(&log_level).unwrap(); let filters = (*FILTERS).borrow(); if layer.is_empty() { // Change all layers @@ -221,68 +222,65 @@ const IUPDATE_VEILID_FUNCTION: &'static str = r#" type UpdateVeilidFunction = (event: VeilidUpdate) => void; "#; -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(extends = Function, typescript_type = "UpdateVeilidFunction")] - pub type UpdateVeilidFunction; -} - -#[wasm_bindgen] -pub async fn startup_veilid_core( - update_callback_js: UpdateVeilidFunction, - json_config: String, -) -> Result<(), VeilidAPIError> { +#[wasm_bindgen()] +pub fn startup_veilid_core(update_callback_js: Function, json_config: String) -> Promise { let update_callback_js = SendWrapper::new(update_callback_js); - let update_callback = Arc::new(move |update: VeilidUpdate| { - let _ret = match Function::call1( - &update_callback_js, - &JsValue::UNDEFINED, - &to_jsvalue(update), - ) { - Ok(v) => v, - Err(e) => { - console_log(&format!("calling update callback failed: {:?}", e)); - return; - } - }; - }); + wrap_api_future_void(async move { + let update_callback = Arc::new(move |update: VeilidUpdate| { + let _ret = + match Function::call1(&update_callback_js, &JsValue::UNDEFINED, &to_json(update)) { + Ok(v) => v, + Err(e) => { + console_log(&format!("calling update callback failed: {:?}", e)); + return; + } + }; + }); - if VEILID_API.borrow().is_some() { - return Err(veilid_core::VeilidAPIError::AlreadyInitialized); - } + if VEILID_API.borrow().is_some() { + return Err(veilid_core::VeilidAPIError::AlreadyInitialized); + } - let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?; - // veilid_core::api_startup(update_callback, config_callback) - VEILID_API.replace(Some(veilid_api)); - Ok(()) + let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?; + VEILID_API.replace(Some(veilid_api)); + APIRESULT_UNDEFINED + }) } #[wasm_bindgen()] -pub async fn get_veilid_state() -> Result { - let veilid_api = get_veilid_api()?; - let core_state = veilid_api.get_state().await?; - Ok(core_state) +pub fn get_veilid_state() -> Promise { + wrap_api_future_json(async move { + let veilid_api = get_veilid_api()?; + let core_state = veilid_api.get_state().await?; + APIResult::Ok(core_state) + }) } #[wasm_bindgen()] -pub async fn attach() -> Result<(), VeilidAPIError> { - let veilid_api = get_veilid_api()?; - veilid_api.attach().await?; - Ok(()) +pub fn attach() -> Promise { + wrap_api_future_void(async move { + let veilid_api = get_veilid_api()?; + veilid_api.attach().await?; + APIRESULT_UNDEFINED + }) } #[wasm_bindgen()] -pub async fn detach() -> Result<(), VeilidAPIError> { - let veilid_api = get_veilid_api()?; - veilid_api.detach().await?; - Ok(()) +pub fn detach() -> Promise { + wrap_api_future_void(async move { + let veilid_api = get_veilid_api()?; + veilid_api.detach().await?; + APIRESULT_UNDEFINED + }) } #[wasm_bindgen()] -pub async fn shutdown_veilid_core() -> Result<(), VeilidAPIError> { - let veilid_api = take_veilid_api()?; - veilid_api.shutdown().await; - Ok(()) +pub fn shutdown_veilid_core() -> Promise { + wrap_api_future_void(async move { + let veilid_api = take_veilid_api()?; + veilid_api.shutdown().await; + APIRESULT_UNDEFINED + }) } fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 { @@ -296,11 +294,13 @@ fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 { } #[wasm_bindgen()] -pub async fn routing_context() -> Result { - let veilid_api = get_veilid_api()?; - let routing_context = veilid_api.routing_context(); - let new_id = add_routing_context(routing_context); - Ok(new_id) +pub fn routing_context() -> Promise { + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let routing_context = veilid_api.routing_context(); + let new_id = add_routing_context(routing_context); + APIResult::Ok(new_id) + }) } #[wasm_bindgen()] @@ -1447,10 +1447,12 @@ pub fn now() -> u64 { } #[wasm_bindgen()] -pub async fn debug(command: String) -> Result { - let veilid_api = get_veilid_api()?; - let out = veilid_api.debug(command).await?; - APIResult::Ok(out) +pub fn debug(command: String) -> Promise { + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let out = veilid_api.debug(command).await?; + APIResult::Ok(out) + }) } #[wasm_bindgen()] From 779532b624a7cd201f8906efe033bfd475b7519a Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Fri, 18 Aug 2023 18:32:04 -0400 Subject: [PATCH 3/9] Add new VeilidTable and VeilidClient to WASM API These new interfaces have been reworked (compared to the original API) to emit (mostly) proper TypeScript types. --- .../src/crypto/types/crypto_typed_group.rs | 1 + veilid-wasm/README.md | 6 + veilid-wasm/src/lib.rs | 8 +- veilid-wasm/src/veilid_client_js.rs | 152 +++++++++++++++ veilid-wasm/src/veilid_table_js.rs | 182 ++++++++++++++++++ 5 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 veilid-wasm/README.md create mode 100644 veilid-wasm/src/veilid_client_js.rs create mode 100644 veilid-wasm/src/veilid_table_js.rs diff --git a/veilid-core/src/crypto/types/crypto_typed_group.rs b/veilid-core/src/crypto/types/crypto_typed_group.rs index 60a34b2e..ff93df00 100644 --- a/veilid-core/src/crypto/types/crypto_typed_group.rs +++ b/veilid-core/src/crypto/types/crypto_typed_group.rs @@ -4,6 +4,7 @@ use super::*; Clone, Debug, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash, Default, Tsify, )] #[serde(from = "Vec>", into = "Vec>")] +// TODO: figure out hot to TS type this as `string`, since it's converted to string via the JSON API. pub struct CryptoTypedGroup where K: Clone diff --git a/veilid-wasm/README.md b/veilid-wasm/README.md new file mode 100644 index 00000000..c4ac3045 --- /dev/null +++ b/veilid-wasm/README.md @@ -0,0 +1,6 @@ +# veilid-wasm + +## Notes + +- [`wasm_bindgen`](https://rustwasm.github.io/wasm-bindgen/) is used to generate interop code between JavaScript and Rust, as well as basic TypeScript types. +- [`tsify`](https://github.com/madonoharu/tsify) is used to export TypeScript types along-side [`wasm_bindgen`](https://rustwasm.github.io/wasm-bindgen/) and [`serde_wasm_bindgen`](https://github.com/cloudflare/serde-wasm-bindgen), and enables serialization/deserialization. diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 015fe026..ac4f4aaf 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -25,6 +25,9 @@ use veilid_core::*; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::*; +pub mod veilid_client_js; +pub mod veilid_table_js; + // Allocator extern crate wee_alloc; #[global_allocator] @@ -385,7 +388,7 @@ pub fn routing_context_app_message(id: u32, target_string: String, message: Stri let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_message", "id", id)); }; routing_context.clone() }; @@ -1460,7 +1463,8 @@ pub fn veilid_version_string() -> String { veilid_core::veilid_version_string() } -#[derive(Serialize)] +#[derive(Serialize, Tsify)] +#[tsify(into_wasm_abi)] pub struct VeilidVersion { pub major: u32, pub minor: u32, diff --git a/veilid-wasm/src/veilid_client_js.rs b/veilid-wasm/src/veilid_client_js.rs new file mode 100644 index 00000000..1f2f8f1b --- /dev/null +++ b/veilid-wasm/src/veilid_client_js.rs @@ -0,0 +1,152 @@ +#![allow(non_snake_case)] +use super::*; + +#[wasm_bindgen(typescript_custom_section)] +const IUPDATE_VEILID_FUNCTION: &'static str = r#" +type UpdateVeilidFunction = (event: VeilidUpdate) => void; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Function, typescript_type = "UpdateVeilidFunction")] + pub type UpdateVeilidFunction; +} + +#[wasm_bindgen(js_name = veilidClient)] +pub struct VeilidClient {} + +// Since this implementation doesn't contain a `new` fn that's marked as a constructor, +// and none of the member fns take a &self arg, +// this is just a namespace/class of static functions. +#[wasm_bindgen(js_class = veilidClient)] +impl VeilidClient { + pub async fn initializeCore(platformConfig: VeilidWASMConfig) { + if INITIALIZED.swap(true, Ordering::Relaxed) { + return; + } + console_error_panic_hook::set_once(); + + // Set up subscriber and layers + let subscriber = Registry::default(); + let mut layers = Vec::new(); + let mut filters = (*FILTERS).borrow_mut(); + + // Performance logger + if platformConfig.logging.performance.enabled { + let filter = + veilid_core::VeilidLayerFilter::new(platformConfig.logging.performance.level, None); + let layer = WASMLayer::new( + WASMLayerConfigBuilder::new() + .set_report_logs_in_timings(platformConfig.logging.performance.logs_in_timings) + .set_console_config(if platformConfig.logging.performance.logs_in_console { + ConsoleConfig::ReportWithConsoleColor + } else { + ConsoleConfig::NoReporting + }) + .build(), + ) + .with_filter(filter.clone()); + filters.insert("performance", filter); + layers.push(layer.boxed()); + }; + + // API logger + if platformConfig.logging.api.enabled { + let filter = + veilid_core::VeilidLayerFilter::new(platformConfig.logging.api.level, None); + let layer = veilid_core::ApiTracingLayer::get().with_filter(filter.clone()); + filters.insert("api", filter); + layers.push(layer.boxed()); + } + + let subscriber = subscriber.with(layers); + subscriber + .try_init() + .map_err(|e| format!("failed to initialize logging: {}", e)) + .expect("failed to initalize WASM platform"); + } + + pub async fn startupCore( + update_callback_js: UpdateVeilidFunction, + json_config: String, + ) -> Result<(), VeilidAPIError> { + let update_callback_js = SendWrapper::new(update_callback_js); + let update_callback = Arc::new(move |update: VeilidUpdate| { + let _ret = match Function::call1( + &update_callback_js, + &JsValue::UNDEFINED, + &to_jsvalue(update), + ) { + Ok(v) => v, + Err(e) => { + console_log(&format!("calling update callback failed: {:?}", e)); + return; + } + }; + }); + + if VEILID_API.borrow().is_some() { + return Err(veilid_core::VeilidAPIError::AlreadyInitialized); + } + + let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?; + VEILID_API.replace(Some(veilid_api)); + Ok(()) + } + + // TODO: can we refine the TS type of `layer`? + pub fn changeLogLevel(layer: String, log_level: VeilidConfigLogLevel) { + let layer = if layer == "all" { "".to_owned() } else { layer }; + let filters = (*FILTERS).borrow(); + if layer.is_empty() { + // Change all layers + for f in filters.values() { + f.set_max_level(log_level); + } + } else { + // Change a specific layer + let f = filters.get(layer.as_str()).unwrap(); + f.set_max_level(log_level); + } + } + + pub async fn shutdownCore() -> Result<(), VeilidAPIError> { + let veilid_api = take_veilid_api()?; + veilid_api.shutdown().await; + Ok(()) + } + + pub async fn getState() -> Result { + let veilid_api = get_veilid_api()?; + let core_state = veilid_api.get_state().await?; + Ok(core_state) + } + + pub async fn attach() -> Result<(), VeilidAPIError> { + let veilid_api = get_veilid_api()?; + veilid_api.attach().await?; + Ok(()) + } + + pub async fn detach() -> Result<(), VeilidAPIError> { + let veilid_api = get_veilid_api()?; + veilid_api.detach().await?; + Ok(()) + } + + pub async fn debug(command: String) -> Result { + let veilid_api = get_veilid_api()?; + let out = veilid_api.debug(command).await?; + APIResult::Ok(out) + } + + pub fn version() -> VeilidVersion { + let (major, minor, patch) = veilid_core::veilid_version(); + let vv = super::VeilidVersion { + major, + minor, + patch, + }; + vv + } +} diff --git a/veilid-wasm/src/veilid_table_js.rs b/veilid-wasm/src/veilid_table_js.rs new file mode 100644 index 00000000..dd7abe72 --- /dev/null +++ b/veilid-wasm/src/veilid_table_js.rs @@ -0,0 +1,182 @@ +#![allow(non_snake_case)] +use super::*; + +#[wasm_bindgen()] +pub struct VeilidTable { + id: u32, + tableName: String, + columnCount: u32, +} + +#[wasm_bindgen()] +impl VeilidTable { + #[wasm_bindgen(constructor)] + pub fn new(tableName: String, columnCount: u32) -> VeilidTable { + VeilidTable { + id: 0, + tableName, + columnCount, + } + } + + pub async fn openTable(&mut self) -> Result { + let veilid_api = get_veilid_api()?; + let tstore = veilid_api.table_store()?; + let table_db = tstore + .open(&self.tableName, self.columnCount) + .await + .map_err(veilid_core::VeilidAPIError::generic)?; + let new_id = add_table_db(table_db); + self.id = new_id; + APIResult::Ok(new_id) + } + + pub fn releaseTable(&mut self) -> bool { + let mut tdbs = (*TABLE_DBS).borrow_mut(); + let status = tdbs.remove(&self.id); + self.id = 0; + if status.is_none() { + return false; + } + return true; + } + + pub async fn deleteTable(&mut self) -> Result { + self.releaseTable(); + + let veilid_api = get_veilid_api()?; + let tstore = veilid_api.table_store()?; + let deleted = tstore + .delete(&self.tableName) + .await + .map_err(veilid_core::VeilidAPIError::generic)?; + APIResult::Ok(deleted) + } + + async fn ensureOpen(&mut self) { + if self.id == 0 { + let _ = self.openTable().await; + } + } + + pub async fn load( + &mut self, + columnId: u32, + key: String, + ) -> Result, VeilidAPIError> { + self.ensureOpen().await; + + let table_db = { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_load", "id", self.id)); + }; + table_db.clone() + }; + + let out = table_db.load(columnId, key.as_bytes()).await?.unwrap(); + let out = Some(str::from_utf8(&out).unwrap().to_owned()); + // let out = serde_wasm_bindgen::to_value(&out) + // .expect("Could not parse using serde_wasm_bindgen"); + APIResult::Ok(out) + } + + pub async fn store( + &mut self, + columnId: u32, + key: String, + value: String, + ) -> Result<(), VeilidAPIError> { + self.ensureOpen().await; + + let table_db = { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", self.id)); + }; + table_db.clone() + }; + + table_db + .store(columnId, key.as_bytes(), value.as_bytes()) + .await?; + APIRESULT_UNDEFINED + } + + pub async fn delete( + &mut self, + columnId: u32, + key: String, + ) -> Result, VeilidAPIError> { + self.ensureOpen().await; + + let table_db = { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_delete", "id", self.id)); + }; + table_db.clone() + }; + + // TODO: will crash when trying to .unwrap() of None (trying to delete key that doesn't exist) + let out = table_db.delete(columnId, key.as_bytes()).await?.unwrap(); + let out = Some(str::from_utf8(&out).unwrap().to_owned()); + APIResult::Ok(out) + } + + // TODO try and figure out how to result a String[], maybe Box<[String]>? + pub async fn getKeys(&mut self, columnId: u32) -> Result { + self.ensureOpen().await; + + let table_db = { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", self.id)); + }; + table_db.clone() + }; + + let keys = table_db.clone().get_keys(columnId).await?; + let out: Vec = keys + .into_iter() + .map(|k| str::from_utf8(&k).unwrap().to_owned()) + .collect(); + let out = + serde_wasm_bindgen::to_value(&out).expect("Could not parse using serde_wasm_bindgen"); + + APIResult::Ok(out) + } + + pub async fn transact(&mut self) -> u32 { + self.ensureOpen().await; + + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&self.id) else { + return 0; + }; + let tdbt = table_db.clone().transact(); + let tdbtid = add_table_db_transaction(tdbt); + return tdbtid; + } + + // TODO: placeholders for transaction functions + // pub async fn releaseTransaction(&mut self) { + // self.ensureOpen().await; + // } + + // pub async fn commitTransaction(&mut self) { + // self.ensureOpen().await; + // } + + // pub async fn rollbackTransaction(&mut self) { + // self.ensureOpen().await; + // } + + // pub async fn storeTransaction(&mut self, tableId: u32, key: String, value: String) { + // self.ensureOpen().await; + // } + + // pub async fn deleteTransaction(&mut self) { + // self.ensureOpen().await; + // } +} From 9aeec3cfa8d34e873dafd938069448d5dfc30326 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 31 Aug 2023 22:01:00 -0400 Subject: [PATCH 4/9] build fixes and cleanup --- Cargo.lock | 9 -- Cargo.toml | 1 - tsify-async/Cargo.toml | 14 ---- tsify-async/src/lib.rs | 30 ------- veilid-core/Cargo.toml | 5 +- .../src/crypto/types/byte_array_types.rs | 12 +-- veilid-core/src/crypto/types/crypto_typed.rs | 3 +- .../src/crypto/types/crypto_typed_group.rs | 5 +- veilid-core/src/crypto/types/mod.rs | 22 ++--- veilid-core/src/lib.rs | 5 +- veilid-core/src/veilid_api/error.rs | 18 +--- .../src/veilid_api/types/aligned_u64.rs | 22 ++--- .../src/veilid_api/types/app_message_call.rs | 10 ++- veilid-core/src/veilid_api/types/dht/mod.rs | 4 +- .../src/veilid_api/types/dht/value_data.rs | 5 +- veilid-core/src/veilid_api/types/fourcc.rs | 14 +--- .../src/veilid_api/types/veilid_log.rs | 9 +- .../src/veilid_api/types/veilid_state.rs | 36 +++++--- veilid-core/src/veilid_config.rs | 83 ++++++++++++------- veilid-core/src/wasm_helpers.rs | 21 +++++ veilid-wasm/src/lib.rs | 18 ++-- 21 files changed, 160 insertions(+), 186 deletions(-) delete mode 100644 tsify-async/Cargo.toml delete mode 100644 tsify-async/src/lib.rs create mode 100644 veilid-core/src/wasm_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 015ad59a..cdc6fc79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5396,15 +5396,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "tsify-async" -version = "0.1.0" -dependencies = [ - "quote", - "serde-wasm-bindgen", - "syn 1.0.109", -] - [[package]] name = "tsify-macros" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index eaa3bf6f..61d53ad7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "veilid-cli", "veilid-flutter/rust", "veilid-wasm", - "tsify-async" ] exclude = ["./external"] diff --git a/tsify-async/Cargo.toml b/tsify-async/Cargo.toml deleted file mode 100644 index 192733fc..00000000 --- a/tsify-async/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "tsify-async" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -proc-macro = true - -[dependencies] -syn = "1.0" -quote = "1.0" -serde-wasm-bindgen = "0.5.0" diff --git a/tsify-async/src/lib.rs b/tsify-async/src/lib.rs deleted file mode 100644 index 4e4f75b7..00000000 --- a/tsify-async/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copied from https://github.com/madonoharu/tsify/issues/24#issuecomment-1670228789 -// TODO: I think this had more to do with: -// the trait `From` is not implemented for `wasm_bindgen::JsValue` -// than it does with tsify itself... Maybe rename to something else? - -use proc_macro::TokenStream; -use quote::quote; -use syn; - -#[proc_macro_derive(TsifyAsync)] -pub fn tsify_async_macro_derive(input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let ast = syn::parse(input).unwrap(); - - // Build the trait implementation - impl_tsify_async_macro(&ast) -} - -fn impl_tsify_async_macro(ast: &syn::DeriveInput) -> TokenStream { - let name = &ast.ident; - let gen = quote! { - impl From<#name> for JsValue { - fn from(value: #name) -> Self { - serde_wasm_bindgen::to_value(&value).unwrap() - } - } - }; - gen.into() -} diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 1a1be691..96f2d9aa 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -140,9 +140,6 @@ lz4_flex = { version = "0.11.1", default-features = false, features = [ "safe-encode", "safe-decode", ] } -tsify = { version = "0.4.5", features = ["js"] } -wasm-bindgen = "^0" -serde-wasm-bindgen = "0.5.0" # Dependencies for native builds only # Linux, Windows, Mac, iOS, Android @@ -202,6 +199,8 @@ wasm-bindgen = "0.2.87" js-sys = "0.3.64" wasm-bindgen-futures = "0.4.37" send_wrapper = { version = "0.6.0", features = ["futures"] } +tsify = { version = "0.4.5", features = ["js"] } +serde-wasm-bindgen = "0.5.0" # Network ws_stream_wasm = "0.7.4" diff --git a/veilid-core/src/crypto/types/byte_array_types.rs b/veilid-core/src/crypto/types/byte_array_types.rs index 84995829..d3bdee80 100644 --- a/veilid-core/src/crypto/types/byte_array_types.rs +++ b/veilid-core/src/crypto/types/byte_array_types.rs @@ -293,17 +293,17 @@ macro_rules! byte_array_type { byte_array_type!(CryptoKey, CRYPTO_KEY_LENGTH, CRYPTO_KEY_LENGTH_ENCODED); -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type PublicKey = CryptoKey; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type SecretKey = CryptoKey; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type HashDigest = CryptoKey; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type SharedSecret = CryptoKey; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type RouteId = CryptoKey; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type CryptoKeyDistance = CryptoKey; byte_array_type!(Signature, SIGNATURE_LENGTH, SIGNATURE_LENGTH_ENCODED); diff --git a/veilid-core/src/crypto/types/crypto_typed.rs b/veilid-core/src/crypto/types/crypto_typed.rs index 4a59ae20..4717b72a 100644 --- a/veilid-core/src/crypto/types/crypto_typed.rs +++ b/veilid-core/src/crypto/types/crypto_typed.rs @@ -1,6 +1,7 @@ use super::*; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Tsify)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct CryptoTyped where K: Clone diff --git a/veilid-core/src/crypto/types/crypto_typed_group.rs b/veilid-core/src/crypto/types/crypto_typed_group.rs index ff93df00..77d0974c 100644 --- a/veilid-core/src/crypto/types/crypto_typed_group.rs +++ b/veilid-core/src/crypto/types/crypto_typed_group.rs @@ -1,8 +1,7 @@ use super::*; -#[derive( - Clone, Debug, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash, Default, Tsify, -)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash, Default)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[serde(from = "Vec>", into = "Vec>")] // TODO: figure out hot to TS type this as `string`, since it's converted to string via the JSON API. pub struct CryptoTypedGroup diff --git a/veilid-core/src/crypto/types/mod.rs b/veilid-core/src/crypto/types/mod.rs index a1f2026d..e3f24a81 100644 --- a/veilid-core/src/crypto/types/mod.rs +++ b/veilid-core/src/crypto/types/mod.rs @@ -6,7 +6,7 @@ use core::fmt; use core::hash::Hash; /// Cryptography version fourcc code -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type CryptoKind = FourCC; /// Sort best crypto kinds first @@ -52,24 +52,24 @@ pub use crypto_typed::*; pub use crypto_typed_group::*; pub use keypair::*; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedKey = CryptoTyped; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedSecret = CryptoTyped; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedKeyPair = CryptoTyped; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedSignature = CryptoTyped; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedSharedSecret = CryptoTyped; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedKeyGroup = CryptoTypedGroup; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedSecretGroup = CryptoTypedGroup; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedKeyPairGroup = CryptoTypedGroup; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedSignatureGroup = CryptoTypedGroup; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TypedSharedSecretGroup = CryptoTypedGroup; diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index 1a53fd73..f2cb5df9 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -56,6 +56,7 @@ mod table_store; mod veilid_api; mod veilid_config; mod veilid_layer_filter; +mod wasm_helpers; pub use self::api_tracing_layer::ApiTracingLayer; pub use self::core_context::{api_startup, api_startup_json, UpdateCallback}; @@ -126,7 +127,5 @@ use serde::*; use stop_token::*; use thiserror::Error as ThisError; use tracing::*; -use tsify::*; -use tsify_async::*; use veilid_tools::*; -use wasm_bindgen::prelude::*; +use wasm_helpers::*; diff --git a/veilid-core/src/veilid_api/error.rs b/veilid-core/src/veilid_api/error.rs index 170a4146..d6999118 100644 --- a/veilid-core/src/veilid_api/error.rs +++ b/veilid-core/src/veilid_api/error.rs @@ -105,21 +105,10 @@ macro_rules! apibail_already_initialized { } #[derive( - ThisError, - Clone, - Debug, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - JsonSchema, - Tsify, - TsifyAsync, + ThisError, Clone, Debug, PartialOrd, PartialEq, Eq, Ord, Serialize, Deserialize, JsonSchema, )] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] #[serde(tag = "kind")] -#[tsify(into_wasm_abi)] pub enum VeilidAPIError { #[error("Not initialized")] NotInitialized, @@ -157,6 +146,7 @@ pub enum VeilidAPIError { #[error("Generic: {message}")] Generic { message: String }, } +from_impl_to_jsvalue!(VeilidAPIError); impl VeilidAPIError { pub fn not_initialized() -> Self { @@ -225,7 +215,7 @@ impl VeilidAPIError { } } -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type VeilidAPIResult = Result; impl From for VeilidAPIError { diff --git a/veilid-core/src/veilid_api/types/aligned_u64.rs b/veilid-core/src/veilid_api/types/aligned_u64.rs index 32b5ceb1..1e2e9332 100644 --- a/veilid-core/src/veilid_api/types/aligned_u64.rs +++ b/veilid-core/src/veilid_api/types/aligned_u64.rs @@ -6,19 +6,9 @@ use super::*; /// Supports serializing to string for JSON as well, since JSON can't handle 64-bit numbers to Javascript #[derive( - Clone, - Default, - PartialEq, - Eq, - PartialOrd, - Ord, - Copy, - Hash, - Serialize, - Deserialize, - JsonSchema, - Tsify, + Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Hash, Serialize, Deserialize, JsonSchema, )] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[repr(C, align(8))] #[serde(transparent)] pub struct AlignedU64( @@ -128,17 +118,17 @@ impl AlignedU64 { ///////////////////////////////////////////////////////////////////////////////////////////////////// /// Microseconds since epoch -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type Timestamp = AlignedU64; pub fn get_aligned_timestamp() -> Timestamp { get_timestamp().into() } /// Microseconds duration -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type TimestampDuration = AlignedU64; /// Request/Response matching id -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type OperationId = AlignedU64; /// Number of bytes -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type ByteCount = AlignedU64; diff --git a/veilid-core/src/veilid_api/types/app_message_call.rs b/veilid-core/src/veilid_api/types/app_message_call.rs index 6959ad17..01ec3d76 100644 --- a/veilid-core/src/veilid_api/types/app_message_call.rs +++ b/veilid-core/src/veilid_api/types/app_message_call.rs @@ -1,11 +1,12 @@ use super::*; /// Direct statement blob passed to hosting application for processing -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidAppMessage { #[serde(with = "as_human_opt_string")] #[schemars(with = "Option")] - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] sender: Option, #[serde(with = "as_human_base64")] @@ -30,11 +31,12 @@ impl VeilidAppMessage { } /// Direct question blob passed to hosting application for processing to send an eventual AppReply -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidAppCall { #[serde(with = "as_human_opt_string")] #[schemars(with = "Option")] - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] sender: Option, #[serde(with = "as_human_base64")] diff --git a/veilid-core/src/veilid_api/types/dht/mod.rs b/veilid-core/src/veilid_api/types/dht/mod.rs index 9830042b..bd7b93eb 100644 --- a/veilid-core/src/veilid_api/types/dht/mod.rs +++ b/veilid-core/src/veilid_api/types/dht/mod.rs @@ -11,8 +11,8 @@ pub use value_data::*; pub use value_subkey_range_set::*; /// Value subkey -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type ValueSubkey = u32; /// Value sequence number -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type ValueSeqNum = u32; diff --git a/veilid-core/src/veilid_api/types/dht/value_data.rs b/veilid-core/src/veilid_api/types/dht/value_data.rs index 6a89388d..9a3d0fe7 100644 --- a/veilid-core/src/veilid_api/types/dht/value_data.rs +++ b/veilid-core/src/veilid_api/types/dht/value_data.rs @@ -1,9 +1,8 @@ use super::*; use veilid_api::VeilidAPIResult; -#[derive( - Clone, Default, PartialOrd, PartialEq, Eq, Ord, Serialize, Deserialize, JsonSchema, Tsify, -)] +#[derive(Clone, Default, PartialOrd, PartialEq, Eq, Ord, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct ValueData { /// An increasing sequence number to time-order the DHT record changes seq: ValueSeqNum, diff --git a/veilid-core/src/veilid_api/types/fourcc.rs b/veilid-core/src/veilid_api/types/fourcc.rs index 8325a48e..21f8db50 100644 --- a/veilid-core/src/veilid_api/types/fourcc.rs +++ b/veilid-core/src/veilid_api/types/fourcc.rs @@ -2,19 +2,9 @@ use super::*; /// FOURCC code #[derive( - Copy, - Default, - Clone, - Hash, - PartialOrd, - Ord, - PartialEq, - Eq, - Serialize, - Deserialize, - JsonSchema, - Tsify, + Copy, Default, Clone, Hash, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, JsonSchema, )] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[serde(try_from = "String")] #[serde(into = "String")] pub struct FourCC(pub [u8; 4]); diff --git a/veilid-core/src/veilid_api/types/veilid_log.rs b/veilid-core/src/veilid_api/types/veilid_log.rs index 7289e281..2e8c9bf6 100644 --- a/veilid-core/src/veilid_api/types/veilid_log.rs +++ b/veilid-core/src/veilid_api/types/veilid_log.rs @@ -2,9 +2,9 @@ use super::*; /// Log level for VeilidCore #[derive( - Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Serialize, Deserialize, JsonSchema, Tsify, + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Serialize, Deserialize, JsonSchema, )] -#[tsify(namespace)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(namespace))] pub enum VeilidLogLevel { Error = 1, Warn = 2, @@ -80,10 +80,11 @@ impl fmt::Display for VeilidLogLevel { } } /// A VeilidCore log message with optional backtrace -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidLog { pub log_level: VeilidLogLevel, pub message: String, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub backtrace: Option, } diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs index 496aaeac..8443572b 100644 --- a/veilid-core/src/veilid_api/types/veilid_state.rs +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -1,8 +1,12 @@ use super::*; /// Attachment abstraction for network 'signal strength' -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, JsonSchema, Tsify)] -#[tsify(namespace, from_wasm_abi, into_wasm_abi)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[cfg_attr( + target_arch = "wasm32", + derive(Tsify), + tsify(namespace, from_wasm_abi, into_wasm_abi) +)] pub enum AttachmentState { Detached = 0, Attaching = 1, @@ -48,14 +52,16 @@ impl TryFrom for AttachmentState { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidStateAttachment { pub state: AttachmentState, pub public_internet_ready: bool, pub local_network_ready: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct PeerTableData { #[schemars(with = "Vec")] pub node_ids: Vec, @@ -63,7 +69,8 @@ pub struct PeerTableData { pub peer_stats: PeerStats, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidStateNetwork { pub started: bool, pub bps_down: ByteCount, @@ -71,7 +78,8 @@ pub struct VeilidStateNetwork { pub peers: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidRouteChange { #[schemars(with = "Vec")] pub dead_routes: Vec, @@ -79,12 +87,14 @@ pub struct VeilidRouteChange { pub dead_remote_routes: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidStateConfig { pub config: VeilidConfigInner, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidValueChange { #[schemars(with = "String")] pub key: TypedKey, @@ -93,9 +103,9 @@ pub struct VeilidValueChange { pub value: ValueData, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify, TsifyAsync)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] #[serde(tag = "kind")] -#[tsify(into_wasm_abi)] pub enum VeilidUpdate { Log(VeilidLog), AppMessage(VeilidAppMessage), @@ -107,11 +117,13 @@ pub enum VeilidUpdate { ValueChange(VeilidValueChange), Shutdown, } +from_impl_to_jsvalue!(VeilidUpdate); -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify, TsifyAsync)] -#[tsify(into_wasm_abi)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] pub struct VeilidState { pub attachment: VeilidStateAttachment, pub network: VeilidStateNetwork, pub config: VeilidStateConfig, } +from_impl_to_jsvalue!(VeilidState); diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 8aee0819..d8c6d67e 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -1,9 +1,9 @@ use crate::*; //////////////////////////////////////////////////////////////////////////////////////////////// -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type ConfigCallbackReturn = VeilidAPIResult>; -#[declare] +#[cfg_attr(target_arch = "wasm32", declare)] pub type ConfigCallback = Arc ConfigCallbackReturn + Send + Sync>; /// Enable and configure HTTPS access to the Veilid node @@ -16,12 +16,13 @@ pub type ConfigCallback = Arc ConfigCallbackReturn + Send + Sy /// url: 'https://localhost:5150' /// ``` /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigHTTPS { pub enabled: bool, pub listen_address: String, pub path: String, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub url: Option, // Fixed URL is not optional for TLS-based protocols and is dynamically validated } @@ -35,12 +36,13 @@ pub struct VeilidConfigHTTPS { /// url: 'https://localhost:5150' /// ``` /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigHTTP { pub enabled: bool, pub listen_address: String, pub path: String, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub url: Option, } @@ -50,7 +52,8 @@ pub struct VeilidConfigHTTP { /// /// To be implemented... /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigApplication { pub https: VeilidConfigHTTPS, pub http: VeilidConfigHTTP, @@ -66,12 +69,13 @@ pub struct VeilidConfigApplication { /// public_address: '' /// ``` /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigUDP { pub enabled: bool, pub socket_pool_size: u32, pub listen_address: String, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub public_address: Option, } @@ -85,13 +89,14 @@ pub struct VeilidConfigUDP { /// listen_address: ':5150' /// public_address: '' /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigTCP { pub connect: bool, pub listen: bool, pub max_connections: u32, pub listen_address: String, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub public_address: Option, } @@ -106,7 +111,8 @@ pub struct VeilidConfigTCP { /// path: 'ws' /// url: 'ws://localhost:5150/ws' /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigWS { pub connect: bool, @@ -114,7 +120,7 @@ pub struct VeilidConfigWS { pub max_connections: u32, pub listen_address: String, pub path: String, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub url: Option, } @@ -129,7 +135,8 @@ pub struct VeilidConfigWS { /// path: 'ws' /// url: '' /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigWSS { pub connect: bool, @@ -137,7 +144,7 @@ pub struct VeilidConfigWSS { pub max_connections: u32, pub listen_address: String, pub path: String, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub url: Option, // Fixed URL is not optional for TLS-based protocols and is dynamically validated } @@ -148,7 +155,8 @@ pub struct VeilidConfigWSS { /// All protocols are available by default, and the Veilid node will /// sort out which protocol is used for each peer connection. /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigProtocol { pub udp: VeilidConfigUDP, @@ -165,7 +173,8 @@ pub struct VeilidConfigProtocol { /// private_key_path: /path/to/private/key /// connection_initial_timeout_ms: 2000 /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigTLS { pub certificate_path: String, pub private_key_path: String, @@ -174,7 +183,8 @@ pub struct VeilidConfigTLS { /// Configure the Distributed Hash Table (DHT) /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigDHT { pub max_find_node_count: u32, pub resolve_node_timeout_ms: u32, @@ -199,13 +209,14 @@ pub struct VeilidConfigDHT { /// Configure RPC /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigRPC { pub concurrency: u32, pub queue_size: u32, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub max_timestamp_behind_ms: Option, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub max_timestamp_ahead_ms: Option, pub timeout_ms: u32, pub max_route_hop_count: u8, @@ -214,7 +225,8 @@ pub struct VeilidConfigRPC { /// Configure the network routing table /// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigRoutingTable { #[schemars(with = "Vec")] pub node_id: TypedKeyGroup, @@ -230,7 +242,8 @@ pub struct VeilidConfigRoutingTable { // xxx pub enable_local_network: bool, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigNetwork { pub connection_initial_timeout_ms: u32, pub connection_inactivity_timeout_ms: u32, @@ -241,7 +254,7 @@ pub struct VeilidConfigNetwork { pub client_whitelist_timeout_ms: u32, pub reverse_connection_receipt_time_ms: u32, pub hole_punch_receipt_time_ms: u32, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub network_key_password: Option, pub routing_table: VeilidConfigRoutingTable, pub rpc: VeilidConfigRPC, @@ -254,36 +267,41 @@ pub struct VeilidConfigNetwork { pub protocol: VeilidConfigProtocol, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigTableStore { pub directory: String, pub delete: bool, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigBlockStore { pub directory: String, pub delete: bool, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigProtectedStore { pub allow_insecure_fallback: bool, pub always_use_insecure_storage: bool, pub directory: String, pub delete: bool, pub device_encryption_key_password: String, - #[tsify(optional)] + #[cfg_attr(target_arch = "wasm32", tsify(optional))] pub new_device_encryption_key_password: Option, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigCapabilities { pub disable: Vec, } -#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema, Tsify)] -#[tsify(namespace, from_wasm_abi)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(namespace, from_wasm_abi))] pub enum VeilidConfigLogLevel { Off, Error, @@ -370,7 +388,8 @@ impl fmt::Display for VeilidConfigLogLevel { } } -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Tsify)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidConfigInner { pub program_name: String, pub namespace: String, diff --git a/veilid-core/src/wasm_helpers.rs b/veilid-core/src/wasm_helpers.rs new file mode 100644 index 00000000..c2f8994b --- /dev/null +++ b/veilid-core/src/wasm_helpers.rs @@ -0,0 +1,21 @@ +cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + pub use tsify::*; + pub use wasm_bindgen::prelude::*; + + macro_rules! from_impl_to_jsvalue { + ($name: ident) => { + impl From<$name> for JsValue { + fn from(value: $name) -> Self { + serde_wasm_bindgen::to_value(&value).unwrap() + } + } + } + } + } else { + macro_rules! from_impl_to_jsvalue { + ($name: ident) => {} + } + } +} +pub(crate) use from_impl_to_jsvalue; diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index ac4f4aaf..95269819 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -115,7 +115,8 @@ where ///////////////////////////////////////// // WASM-specific -#[derive(Debug, Deserialize, Serialize, Tsify)] +#[derive(Debug, Deserialize, Serialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidWASMConfigLoggingPerformance { pub enabled: bool, pub level: veilid_core::VeilidConfigLogLevel, @@ -123,25 +124,29 @@ pub struct VeilidWASMConfigLoggingPerformance { pub logs_in_console: bool, } -#[derive(Debug, Deserialize, Serialize, Tsify)] +#[derive(Debug, Deserialize, Serialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidWASMConfigLoggingAPI { pub enabled: bool, pub level: veilid_core::VeilidConfigLogLevel, } -#[derive(Debug, Deserialize, Serialize, Tsify)] +#[derive(Debug, Deserialize, Serialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidWASMConfigLogging { pub performance: VeilidWASMConfigLoggingPerformance, pub api: VeilidWASMConfigLoggingAPI, } -#[derive(Debug, Deserialize, Serialize, Tsify)] +#[derive(Debug, Deserialize, Serialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[tsify(from_wasm_abi)] pub struct VeilidWASMConfig { pub logging: VeilidWASMConfigLogging, } -#[derive(Debug, Deserialize, Serialize, Tsify)] +#[derive(Debug, Deserialize, Serialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidRouteBlob { pub route_id: veilid_core::RouteId, #[serde(with = "veilid_core::as_human_base64")] @@ -1463,7 +1468,8 @@ pub fn veilid_version_string() -> String { veilid_core::veilid_version_string() } -#[derive(Serialize, Tsify)] +#[derive(Serialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] #[tsify(into_wasm_abi)] pub struct VeilidVersion { pub major: u32, From c5d7922fc5b6d541f0e39de960def2bf0aa4365b Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Sat, 2 Sep 2023 14:15:38 -0400 Subject: [PATCH 5/9] Add VeilidRoutingContext class for WASM --- .../network_manager/wasm/.cargo/config.toml | 2 + .../types/dht/dht_record_descriptor.rs | 4 + .../src/veilid_api/types/dht/schema/mod.rs | 1 + .../src/veilid_api/types/dht/value_data.rs | 2 + veilid-core/src/veilid_api/types/safety.rs | 5 + veilid-wasm/.cargo/config.toml | 2 + veilid-wasm/src/lib.rs | 7 +- veilid-wasm/src/veilid_routing_context_js.rs | 293 ++++++++++++++++++ ...ilid_table_js.rs => veilid_table_db_js.rs} | 28 +- 9 files changed, 323 insertions(+), 21 deletions(-) create mode 100644 veilid-core/src/network_manager/wasm/.cargo/config.toml create mode 100644 veilid-wasm/.cargo/config.toml create mode 100644 veilid-wasm/src/veilid_routing_context_js.rs rename veilid-wasm/src/{veilid_table_js.rs => veilid_table_db_js.rs} (88%) diff --git a/veilid-core/src/network_manager/wasm/.cargo/config.toml b/veilid-core/src/network_manager/wasm/.cargo/config.toml new file mode 100644 index 00000000..f4e8c002 --- /dev/null +++ b/veilid-core/src/network_manager/wasm/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs index 251d1e28..43881601 100644 --- a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs +++ b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs @@ -2,16 +2,20 @@ use super::*; /// DHT Record Descriptor #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct DHTRecordDescriptor { /// DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] #[schemars(with = "String")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] key: TypedKey, /// The public key of the owner #[schemars(with = "String")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] owner: PublicKey, /// If this key is being created: Some(the secret key of the owner) /// If this key is just being opened: None #[schemars(with = "Option")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string | undefined"))] owner_secret: Option, /// The schema in use associated with the key schema: DHTSchema, diff --git a/veilid-core/src/veilid_api/types/dht/schema/mod.rs b/veilid-core/src/veilid_api/types/dht/schema/mod.rs index ec54ae30..1b9bf4c3 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/mod.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/mod.rs @@ -9,6 +9,7 @@ pub use smpl::*; /// Enum over all the supported DHT Schemas #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)] #[serde(tag = "kind")] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi))] pub enum DHTSchema { DFLT(DHTSchemaDFLT), SMPL(DHTSchemaSMPL), diff --git a/veilid-core/src/veilid_api/types/dht/value_data.rs b/veilid-core/src/veilid_api/types/dht/value_data.rs index 9a3d0fe7..c7cbdb42 100644 --- a/veilid-core/src/veilid_api/types/dht/value_data.rs +++ b/veilid-core/src/veilid_api/types/dht/value_data.rs @@ -10,10 +10,12 @@ pub struct ValueData { /// The contents of a DHT Record #[serde(with = "as_human_base64")] #[schemars(with = "String")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] data: Vec, /// The public identity key of the writer of the data #[schemars(with = "String")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] writer: PublicKey, } impl ValueData { diff --git a/veilid-core/src/veilid_api/types/safety.rs b/veilid-core/src/veilid_api/types/safety.rs index 8e49cf5f..749efce9 100644 --- a/veilid-core/src/veilid_api/types/safety.rs +++ b/veilid-core/src/veilid_api/types/safety.rs @@ -4,6 +4,7 @@ use super::*; #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, )] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi, namespace))] pub enum Sequencing { NoPreference = 0, PreferOrdered = 1, @@ -20,6 +21,7 @@ impl Default for Sequencing { #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, )] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi, namespace))] pub enum Stability { LowLatency = 0, Reliable = 1, @@ -35,6 +37,7 @@ impl Default for Stability { #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, )] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi, namespace))] pub enum SafetySelection { /// Don't use a safety route, only specify the sequencing preference Unsafe(Sequencing), @@ -61,9 +64,11 @@ impl Default for SafetySelection { #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, )] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct SafetySpec { /// preferred safety route set id if it still exists #[schemars(with = "Option")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string | undefined"))] pub preferred_route: Option, /// must be greater than 0 pub hop_count: usize, diff --git a/veilid-wasm/.cargo/config.toml b/veilid-wasm/.cargo/config.toml new file mode 100644 index 00000000..f4e8c002 --- /dev/null +++ b/veilid-wasm/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 95269819..5203d431 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -26,7 +26,8 @@ use wasm_bindgen::prelude::*; use wasm_bindgen_futures::*; pub mod veilid_client_js; -pub mod veilid_table_js; +pub mod veilid_routing_context_js; +pub mod veilid_table_db_js; // Allocator extern crate wee_alloc; @@ -139,8 +140,7 @@ pub struct VeilidWASMConfigLogging { } #[derive(Debug, Deserialize, Serialize)] -#[cfg_attr(target_arch = "wasm32", derive(Tsify))] -#[tsify(from_wasm_abi)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi))] pub struct VeilidWASMConfig { pub logging: VeilidWASMConfigLogging, } @@ -150,6 +150,7 @@ pub struct VeilidWASMConfig { pub struct VeilidRouteBlob { pub route_id: veilid_core::RouteId, #[serde(with = "veilid_core::as_human_base64")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub blob: Vec, } diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs new file mode 100644 index 00000000..a03c62e9 --- /dev/null +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -0,0 +1,293 @@ +#![allow(non_snake_case)] +use super::*; + +#[wasm_bindgen()] +pub struct VeilidRoutingContext { + id: u32, +} + +#[wasm_bindgen()] +impl VeilidRoutingContext { + #[wasm_bindgen(constructor)] + pub fn new(id: u32) -> Self { + Self { id } + } + + pub fn createWithoutPrivacy() -> VeilidAPIResult { + let veilid_api = get_veilid_api()?; + let routing_context = veilid_api.routing_context(); + let id = add_routing_context(routing_context); + Ok(VeilidRoutingContext { id }) + } + + pub async fn createWithPrivacy() -> VeilidAPIResult { + let veilid_api = get_veilid_api()?; + let routing_context = veilid_api.routing_context().with_privacy()?; + let id = add_routing_context(routing_context); + Ok(VeilidRoutingContext { id }) + } + + pub async fn createWithCustomPrivacy( + safetySelection: SafetySelection, + ) -> VeilidAPIResult { + let veilid_api = get_veilid_api()?; + let routing_context = veilid_api + .routing_context() + .with_custom_privacy(safetySelection)?; + let id = add_routing_context(routing_context); + Ok(VeilidRoutingContext { id }) + } + + pub fn createWithSequencing(sequencing: Sequencing) -> VeilidAPIResult { + let veilid_api = get_veilid_api()?; + let routing_context = veilid_api.routing_context().with_sequencing(sequencing); + let id = add_routing_context(routing_context); + Ok(VeilidRoutingContext { id }) + } + + pub async fn appMessage(&self, target_string: String, message: String) -> VeilidAPIResult<()> { + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_message", "id", self.id)); + }; + routing_context.clone() + }; + + let veilid_api = get_veilid_api()?; + let target = veilid_api.parse_as_target(target_string).await?; + routing_context + .app_message(target, message.into_bytes()) + .await?; + APIRESULT_UNDEFINED + } + + pub async fn appCall( + &self, + id: u32, + target_string: String, + request: String, + ) -> VeilidAPIResult { + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", self.id)); + }; + routing_context.clone() + }; + + let veilid_api = get_veilid_api()?; + let target = veilid_api.parse_as_target(target_string).await?; + let answer = routing_context + .app_call(target, request.into_bytes()) + .await?; + // let answer = data_encoding::BASE64URL_NOPAD.encode(&answer); + let answer = String::from_utf8_lossy(&answer).into_owned(); + APIResult::Ok(answer) + } + + pub async fn createDhtRecord(&self, schema: JsValue, kind: u32) -> VeilidAPIResult { + let schema: DHTSchema = serde_wasm_bindgen::from_value(schema).unwrap(); + let crypto_kind = if kind == 0 { + None + } else { + Some(veilid_core::FourCC::from(kind)) + }; + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_create_dht_record", "id", self.id)); + }; + routing_context.clone() + }; + + let dht_record_descriptor = routing_context + .create_dht_record(schema, crypto_kind) + .await?; + let out = serde_wasm_bindgen::to_value(&dht_record_descriptor).unwrap(); + APIResult::Ok(out) + } + + pub async fn openDhtRecord( + &self, + key: String, + writer: Option, + ) -> VeilidAPIResult { + let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + let writer: Option = + writer.map(|s| veilid_core::deserialize_json(&s).unwrap()); + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_open_dht_record", "id", self.id)); + }; + routing_context.clone() + }; + let dht_record_descriptor = routing_context.open_dht_record(key, writer).await?; + let out = serde_wasm_bindgen::to_value(&dht_record_descriptor).unwrap(); + APIResult::Ok(out) + } + + pub async fn closeDhtRecord(&self, key: String) -> VeilidAPIResult<()> { + let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_close_dht_record", "id", self.id)); + }; + routing_context.clone() + }; + routing_context.close_dht_record(key).await?; + APIRESULT_UNDEFINED + } + + pub async fn deleteDhtRecord(&self, key: String) -> VeilidAPIResult<()> { + let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_delete_dht_record", "id", self.id)); + }; + routing_context.clone() + }; + routing_context.delete_dht_record(key).await?; + APIRESULT_UNDEFINED + } + + pub async fn getDhtValue( + &self, + key: String, + subKey: u32, + forceRefresh: bool, + ) -> VeilidAPIResult { + let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_get_dht_value", "id", self.id)); + }; + routing_context.clone() + }; + let res = routing_context + .get_dht_value(key, subKey, forceRefresh) + .await?; + let out = serde_wasm_bindgen::to_value(&res).unwrap(); + APIResult::Ok(out) + } + + pub async fn setDhtValue( + &self, + key: String, + subKey: u32, + data: String, + ) -> VeilidAPIResult { + let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + let data: Vec = data_encoding::BASE64URL_NOPAD + .decode(&data.as_bytes()) + .unwrap(); + + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_set_dht_value", "id", self.id)); + }; + routing_context.clone() + }; + let res = routing_context.set_dht_value(key, subKey, data).await?; + let out = serde_wasm_bindgen::to_value(&res).unwrap(); + APIResult::Ok(out) + } + + // pub async fn watchDhtValues( + // &self, + // key: String, + // subKeys: ValueSubkeyRangeSet, + // expiration: Timestamp, + // count: u32, + // ) -> VeilidAPIResult { + // let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + // let subkeys: veilid_core::ValueSubkeyRangeSet = + // veilid_core::deserialize_json(&subkeys).unwrap(); + // let expiration = veilid_core::Timestamp::from_str(&expiration).unwrap(); + + // let routing_context = { + // let rc = (*ROUTING_CONTEXTS).borrow(); + // let Some(routing_context) = rc.get(&id) else { + // return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_watch_dht_values", "id", self.id)); + // }; + // routing_context.clone() + // }; + // let res = routing_context + // .watch_dht_values(key, subkeys, expiration, count) + // .await?; + // APIResult::Ok(res.to_string()) + // } + + // pub async fn cancelDhtWatch(id: u32, key: String, subkeys: String) -> Promise { + // let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + // let subkeys: veilid_core::ValueSubkeyRangeSet = + // veilid_core::deserialize_json(&subkeys).unwrap(); + + // let routing_context = { + // let rc = (*ROUTING_CONTEXTS).borrow(); + // let Some(routing_context) = rc.get(&id) else { + // return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_cancel_dht_watch", "id", self.id)); + // }; + // routing_context.clone() + // }; + // let res = routing_context.cancel_dht_watch(key, subkeys).await?; + // APIResult::Ok(res) + // } +} + +#[wasm_bindgen()] +pub async fn newPrivateRoute() -> VeilidAPIResult { + let veilid_api = get_veilid_api()?; + + let (route_id, blob) = veilid_api.new_private_route().await?; + + let route_blob = VeilidRouteBlob { route_id, blob }; + let out = serde_wasm_bindgen::to_value(&route_blob).unwrap(); + APIResult::Ok(out) +} + +#[wasm_bindgen()] +pub async fn newCustomPrivateRoute( + stability: Stability, + sequencing: Sequencing, +) -> VeilidAPIResult { + let veilid_api = get_veilid_api()?; + + let (route_id, blob) = veilid_api + .new_custom_private_route(&veilid_core::VALID_CRYPTO_KINDS, stability, sequencing) + .await?; + + let route_blob = VeilidRouteBlob { route_id, blob }; + let out = serde_wasm_bindgen::to_value(&route_blob).unwrap(); + APIResult::Ok(out) +} + +#[wasm_bindgen()] +pub async fn releasePrivateRoute(routeId: String) -> VeilidAPIResult<()> { + let route_id: veilid_core::RouteId = veilid_core::deserialize_json(&routeId).unwrap(); + let veilid_api = get_veilid_api()?; + veilid_api.release_private_route(route_id)?; + APIRESULT_UNDEFINED +} + +#[wasm_bindgen()] +pub async fn appCallReply(callId: String, message: String) -> VeilidAPIResult<()> { + let call_id = match callId.parse() { + Ok(v) => v, + Err(e) => { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + e, "call_id", callId, + )) + } + }; + let veilid_api = get_veilid_api()?; + veilid_api + .app_call_reply(call_id, message.into_bytes()) + .await?; + APIRESULT_UNDEFINED +} diff --git a/veilid-wasm/src/veilid_table_js.rs b/veilid-wasm/src/veilid_table_db_js.rs similarity index 88% rename from veilid-wasm/src/veilid_table_js.rs rename to veilid-wasm/src/veilid_table_db_js.rs index dd7abe72..79b5e8e9 100644 --- a/veilid-wasm/src/veilid_table_js.rs +++ b/veilid-wasm/src/veilid_table_db_js.rs @@ -2,24 +2,24 @@ use super::*; #[wasm_bindgen()] -pub struct VeilidTable { +pub struct VeilidTableDB { id: u32, tableName: String, columnCount: u32, } #[wasm_bindgen()] -impl VeilidTable { +impl VeilidTableDB { #[wasm_bindgen(constructor)] - pub fn new(tableName: String, columnCount: u32) -> VeilidTable { - VeilidTable { + pub fn new(tableName: String, columnCount: u32) -> VeilidTableDB { + VeilidTableDB { id: 0, tableName, columnCount, } } - pub async fn openTable(&mut self) -> Result { + pub async fn openTable(&mut self) -> VeilidAPIResult { let veilid_api = get_veilid_api()?; let tstore = veilid_api.table_store()?; let table_db = tstore @@ -41,7 +41,7 @@ impl VeilidTable { return true; } - pub async fn deleteTable(&mut self) -> Result { + pub async fn deleteTable(&mut self) -> VeilidAPIResult { self.releaseTable(); let veilid_api = get_veilid_api()?; @@ -59,11 +59,7 @@ impl VeilidTable { } } - pub async fn load( - &mut self, - columnId: u32, - key: String, - ) -> Result, VeilidAPIError> { + pub async fn load(&mut self, columnId: u32, key: String) -> VeilidAPIResult> { self.ensureOpen().await; let table_db = { @@ -86,7 +82,7 @@ impl VeilidTable { columnId: u32, key: String, value: String, - ) -> Result<(), VeilidAPIError> { + ) -> VeilidAPIResult<()> { self.ensureOpen().await; let table_db = { @@ -103,11 +99,7 @@ impl VeilidTable { APIRESULT_UNDEFINED } - pub async fn delete( - &mut self, - columnId: u32, - key: String, - ) -> Result, VeilidAPIError> { + pub async fn delete(&mut self, columnId: u32, key: String) -> VeilidAPIResult> { self.ensureOpen().await; let table_db = { @@ -125,7 +117,7 @@ impl VeilidTable { } // TODO try and figure out how to result a String[], maybe Box<[String]>? - pub async fn getKeys(&mut self, columnId: u32) -> Result { + pub async fn getKeys(&mut self, columnId: u32) -> VeilidAPIResult { self.ensureOpen().await; let table_db = { From ca11f6075d10c45edf610d24662cffc08bd57a53 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Sun, 3 Sep 2023 23:25:04 -0400 Subject: [PATCH 6/9] (wasm) Add VeilidCrypto class, refine interfaces for VeilidRoutingContext --- .../src/crypto/types/byte_array_types.rs | 1 + veilid-core/src/crypto/types/crypto_typed.rs | 2 +- veilid-core/src/crypto/types/keypair.rs | 8 + .../src/veilid_api/types/aligned_u64.rs | 1 + .../src/veilid_api/types/app_message_call.rs | 3 +- .../types/dht/dht_record_descriptor.rs | 9 +- .../src/veilid_api/types/dht/schema/dflt.rs | 1 + .../src/veilid_api/types/dht/schema/smpl.rs | 3 + .../src/veilid_api/types/dht/value_data.rs | 4 +- veilid-core/src/veilid_api/types/safety.rs | 2 +- veilid-core/src/veilid_api/types/stats.rs | 5 + .../src/veilid_api/types/veilid_state.rs | 1 + veilid-wasm/src/lib.rs | 11 +- veilid-wasm/src/veilid_crypto_js.rs | 482 ++++++++++++++++++ veilid-wasm/src/veilid_routing_context_js.rs | 156 +++--- veilid-wasm/src/wasm_helpers.rs | 21 + 16 files changed, 623 insertions(+), 87 deletions(-) create mode 100644 veilid-wasm/src/veilid_crypto_js.rs create mode 100644 veilid-wasm/src/wasm_helpers.rs diff --git a/veilid-core/src/crypto/types/byte_array_types.rs b/veilid-core/src/crypto/types/byte_array_types.rs index d3bdee80..d47a323a 100644 --- a/veilid-core/src/crypto/types/byte_array_types.rs +++ b/veilid-core/src/crypto/types/byte_array_types.rs @@ -78,6 +78,7 @@ where macro_rules! byte_array_type { ($name:ident, $size:expr, $encoded_size:expr) => { #[derive(Clone, Copy, Hash)] + #[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] pub struct $name { pub bytes: [u8; $size], } diff --git a/veilid-core/src/crypto/types/crypto_typed.rs b/veilid-core/src/crypto/types/crypto_typed.rs index 4717b72a..39f4bd59 100644 --- a/veilid-core/src/crypto/types/crypto_typed.rs +++ b/veilid-core/src/crypto/types/crypto_typed.rs @@ -1,7 +1,7 @@ use super::*; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] pub struct CryptoTyped where K: Clone diff --git a/veilid-core/src/crypto/types/keypair.rs b/veilid-core/src/crypto/types/keypair.rs index f8e6e6da..333a5d0b 100644 --- a/veilid-core/src/crypto/types/keypair.rs +++ b/veilid-core/src/crypto/types/keypair.rs @@ -1,10 +1,18 @@ use super::*; #[derive(Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq, Hash)] +#[cfg_attr( + target_arch = "wasm32", + derive(Tsify), + tsify(from_wasm_abi, into_wasm_abi) +)] pub struct KeyPair { + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub key: PublicKey, + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub secret: SecretKey, } +from_impl_to_jsvalue!(KeyPair); impl KeyPair { pub fn new(key: PublicKey, secret: SecretKey) -> Self { diff --git a/veilid-core/src/veilid_api/types/aligned_u64.rs b/veilid-core/src/veilid_api/types/aligned_u64.rs index 1e2e9332..510cdba3 100644 --- a/veilid-core/src/veilid_api/types/aligned_u64.rs +++ b/veilid-core/src/veilid_api/types/aligned_u64.rs @@ -14,6 +14,7 @@ use super::*; pub struct AlignedU64( #[serde(with = "as_human_string")] #[schemars(with = "String")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] u64, ); diff --git a/veilid-core/src/veilid_api/types/app_message_call.rs b/veilid-core/src/veilid_api/types/app_message_call.rs index 01ec3d76..5af5b692 100644 --- a/veilid-core/src/veilid_api/types/app_message_call.rs +++ b/veilid-core/src/veilid_api/types/app_message_call.rs @@ -6,11 +6,12 @@ use super::*; pub struct VeilidAppMessage { #[serde(with = "as_human_opt_string")] #[schemars(with = "Option")] - #[cfg_attr(target_arch = "wasm32", tsify(optional))] + #[cfg_attr(target_arch = "wasm32", tsify(optional, type = "string"))] sender: Option, #[serde(with = "as_human_base64")] #[schemars(with = "String")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] message: Vec, } diff --git a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs index 43881601..32c93ffd 100644 --- a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs +++ b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs @@ -2,7 +2,11 @@ use super::*; /// DHT Record Descriptor #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr( + target_arch = "wasm32", + derive(Tsify), + tsify(from_wasm_abi, into_wasm_abi) +)] pub struct DHTRecordDescriptor { /// DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] #[schemars(with = "String")] @@ -15,11 +19,12 @@ pub struct DHTRecordDescriptor { /// If this key is being created: Some(the secret key of the owner) /// If this key is just being opened: None #[schemars(with = "Option")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string | undefined"))] + #[cfg_attr(target_arch = "wasm32", tsify(optional, type = "string"))] owner_secret: Option, /// The schema in use associated with the key schema: DHTSchema, } +from_impl_to_jsvalue!(DHTRecordDescriptor); impl DHTRecordDescriptor { pub fn new( diff --git a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs index b2c6b2b7..bb4e7c6e 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs @@ -2,6 +2,7 @@ use super::*; /// Default DHT Schema (DFLT) #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi))] pub struct DHTSchemaDFLT { /// Owner subkey count pub o_cnt: u16, diff --git a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs index 572b93e8..d6da5ea6 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs @@ -2,9 +2,11 @@ use super::*; /// Simple DHT Schema (SMPL) Member #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi))] pub struct DHTSchemaSMPLMember { /// Member key #[schemars(with = "String")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub m_key: PublicKey, /// Member subkey count pub m_cnt: u16, @@ -12,6 +14,7 @@ pub struct DHTSchemaSMPLMember { /// Simple DHT Schema (SMPL) #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi))] pub struct DHTSchemaSMPL { /// Owner subkey count pub o_cnt: u16, diff --git a/veilid-core/src/veilid_api/types/dht/value_data.rs b/veilid-core/src/veilid_api/types/dht/value_data.rs index c7cbdb42..34d6d8ac 100644 --- a/veilid-core/src/veilid_api/types/dht/value_data.rs +++ b/veilid-core/src/veilid_api/types/dht/value_data.rs @@ -2,7 +2,7 @@ use super::*; use veilid_api::VeilidAPIResult; #[derive(Clone, Default, PartialOrd, PartialEq, Eq, Ord, Serialize, Deserialize, JsonSchema)] -#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(into_wasm_abi))] pub struct ValueData { /// An increasing sequence number to time-order the DHT record changes seq: ValueSeqNum, @@ -18,6 +18,8 @@ pub struct ValueData { #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] writer: PublicKey, } +from_impl_to_jsvalue!(ValueData); + impl ValueData { pub const MAX_LEN: usize = 32768; diff --git a/veilid-core/src/veilid_api/types/safety.rs b/veilid-core/src/veilid_api/types/safety.rs index 749efce9..18c7b247 100644 --- a/veilid-core/src/veilid_api/types/safety.rs +++ b/veilid-core/src/veilid_api/types/safety.rs @@ -68,7 +68,7 @@ impl Default for SafetySelection { pub struct SafetySpec { /// preferred safety route set id if it still exists #[schemars(with = "Option")] - #[cfg_attr(target_arch = "wasm32", tsify(type = "string | undefined"))] + #[cfg_attr(target_arch = "wasm32", tsify(optional, type = "string"))] pub preferred_route: Option, /// must be greater than 0 pub hop_count: usize, diff --git a/veilid-core/src/veilid_api/types/stats.rs b/veilid-core/src/veilid_api/types/stats.rs index 836cb728..5a6b27a8 100644 --- a/veilid-core/src/veilid_api/types/stats.rs +++ b/veilid-core/src/veilid_api/types/stats.rs @@ -1,6 +1,7 @@ use super::*; #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct LatencyStats { pub fastest: TimestampDuration, // fastest latency in the ROLLING_LATENCIES_SIZE last latencies pub average: TimestampDuration, // average latency over the ROLLING_LATENCIES_SIZE last latencies @@ -8,6 +9,7 @@ pub struct LatencyStats { } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct TransferStats { pub total: ByteCount, // total amount transferred ever pub maximum: ByteCount, // maximum rate over the ROLLING_TRANSFERS_SIZE last amounts @@ -16,12 +18,14 @@ pub struct TransferStats { } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct TransferStatsDownUp { pub down: TransferStats, pub up: TransferStats, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct RPCStats { pub messages_sent: u32, // number of rpcs that have been sent in the total_time range pub messages_rcvd: u32, // number of rpcs that have been received in the total_time range @@ -34,6 +38,7 @@ pub struct RPCStats { } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct PeerStats { pub time_added: Timestamp, // when the peer was added to the routing table pub rpc_stats: RPCStats, // information about RPCs diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs index 8443572b..d6f5f836 100644 --- a/veilid-core/src/veilid_api/types/veilid_state.rs +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -97,6 +97,7 @@ pub struct VeilidStateConfig { #[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidValueChange { #[schemars(with = "String")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub key: TypedKey, pub subkeys: Vec, pub count: u32, diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 5203d431..29647d6b 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -26,9 +26,13 @@ use wasm_bindgen::prelude::*; use wasm_bindgen_futures::*; pub mod veilid_client_js; +pub mod veilid_crypto_js; pub mod veilid_routing_context_js; pub mod veilid_table_db_js; +mod wasm_helpers; +use wasm_helpers::*; + // Allocator extern crate wee_alloc; #[global_allocator] @@ -146,13 +150,18 @@ pub struct VeilidWASMConfig { } #[derive(Debug, Deserialize, Serialize)] -#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr( + target_arch = "wasm32", + derive(Tsify), + tsify(from_wasm_abi, into_wasm_abi) +)] pub struct VeilidRouteBlob { pub route_id: veilid_core::RouteId, #[serde(with = "veilid_core::as_human_base64")] #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub blob: Vec, } +from_impl_to_jsvalue!(VeilidRouteBlob); // WASM Bindings diff --git a/veilid-wasm/src/veilid_crypto_js.rs b/veilid-wasm/src/veilid_crypto_js.rs new file mode 100644 index 00000000..ecef57ca --- /dev/null +++ b/veilid-wasm/src/veilid_crypto_js.rs @@ -0,0 +1,482 @@ +#![allow(non_snake_case)] +use super::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "string[]")] + pub type ValidCryptoKinds; +} + +#[wasm_bindgen(js_class = veilidCrypto)] +pub struct VeilidCrypto {} + +#[wasm_bindgen(js_class = veilidCrypto)] +impl VeilidCrypto { + pub fn validCryptoKinds() -> ValidCryptoKinds { + let res = veilid_core::VALID_CRYPTO_KINDS + .iter() + .map(|k| (*k).to_string()); + res.map(JsValue::from) + .collect::() + .unchecked_into::() + } + + pub fn bestCryptoKind() -> String { + veilid_core::best_crypto_kind().to_string() + } + + pub fn cachedDh(kind: String, key: String, secret: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; + let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_cached_dh", + "kind", + kind.to_string(), + ) + })?; + let out = csv.cached_dh(&key, &secret)?; + APIResult::Ok(out.to_string()) + } + + pub fn computeDh(kind: String, key: String, secret: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; + let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_compute_dh", + "kind", + kind.to_string(), + ) + })?; + let out = csv.compute_dh(&key, &secret)?; + APIResult::Ok(out.to_string()) + } + + pub fn randomBytes(kind: String, len: u32) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_random_bytes", + "kind", + kind.to_string(), + ) + })?; + let out = csv.random_bytes(len); + let out = data_encoding::BASE64URL_NOPAD.encode(&out); + APIResult::Ok(out) + } + + pub fn defaultSaltLength(kind: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_default_salt_length", + "kind", + kind.to_string(), + ) + })?; + let out = csv.default_salt_length(); + APIResult::Ok(out) + } + + pub fn hashPassword(kind: String, password: String, salt: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode(password.as_bytes()) + .unwrap(); + let salt: Vec = data_encoding::BASE64URL_NOPAD + .decode(salt.as_bytes()) + .unwrap(); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_hash_password", + "kind", + kind.to_string(), + ) + })?; + let out = csv.hash_password(&password, &salt)?; + APIResult::Ok(out) + } + + pub fn verifyPassword( + kind: String, + password: String, + password_hash: String, + ) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode(password.as_bytes()) + .unwrap(); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_verify_password", + "kind", + kind.to_string(), + ) + })?; + let out = csv.verify_password(&password, &password_hash)?; + APIResult::Ok(out) + } + + pub fn deriveSharedSecret( + kind: String, + password: String, + salt: String, + ) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode(password.as_bytes()) + .unwrap(); + let salt: Vec = data_encoding::BASE64URL_NOPAD + .decode(salt.as_bytes()) + .unwrap(); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_derive_shared_secret", + "kind", + kind.to_string(), + ) + })?; + let out = csv.derive_shared_secret(&password, &salt)?; + APIResult::Ok(out.to_string()) + } + + pub fn randomNonce(kind: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_random_nonce", + "kind", + kind.to_string(), + ) + })?; + let out = csv.random_nonce(); + APIResult::Ok(out.to_string()) + } + + pub fn randomSharedSecret(kind: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_random_shared_secret", + "kind", + kind.to_string(), + ) + })?; + let out = csv.random_shared_secret(); + APIResult::Ok(out.to_string()) + } + + pub fn generateKeyPair(kind: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_generate_key_pair", + "kind", + kind.to_string(), + ) + })?; + let out = csv.generate_keypair(); + APIResult::Ok(out) + } + + pub fn generateHash(kind: String, data: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let data: Vec = data_encoding::BASE64URL_NOPAD + .decode(data.as_bytes()) + .unwrap(); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_generate_hash", + "kind", + kind.to_string(), + ) + })?; + let out = csv.generate_hash(&data); + APIResult::Ok(out.to_string()) + } + + pub fn validateKeyPair(kind: String, key: String, secret: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; + let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_validate_key_pair", + "kind", + kind.to_string(), + ) + })?; + let out = csv.validate_keypair(&key, &secret); + APIResult::Ok(out) + } + + pub fn validateHash(kind: String, data: String, hash: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let data: Vec = data_encoding::BASE64URL_NOPAD + .decode(data.as_bytes()) + .unwrap(); + + let hash: veilid_core::HashDigest = veilid_core::HashDigest::from_str(&hash)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_validate_hash", + "kind", + kind.to_string(), + ) + })?; + let out = csv.validate_hash(&data, &hash); + APIResult::Ok(out) + } + + pub fn distance(kind: String, key1: String, key2: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let key1: veilid_core::CryptoKey = veilid_core::CryptoKey::from_str(&key1)?; + let key2: veilid_core::CryptoKey = veilid_core::CryptoKey::from_str(&key2)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_distance", + "kind", + kind.to_string(), + ) + })?; + let out = csv.distance(&key1, &key2); + APIResult::Ok(out.to_string()) + } + + pub fn sign( + kind: String, + key: String, + secret: String, + data: String, + ) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; + let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?; + + let data: Vec = data_encoding::BASE64URL_NOPAD + .decode(data.as_bytes()) + .unwrap(); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument("crypto_sign", "kind", kind.to_string()) + })?; + let out = csv.sign(&key, &secret, &data)?; + APIResult::Ok(out.to_string()) + } + + pub fn verify( + kind: String, + key: String, + data: String, + signature: String, + ) -> VeilidAPIResult<()> { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; + let data: Vec = data_encoding::BASE64URL_NOPAD + .decode(data.as_bytes()) + .unwrap(); + let signature: veilid_core::Signature = veilid_core::Signature::from_str(&signature)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) + })?; + csv.verify(&key, &data, &signature)?; + APIRESULT_UNDEFINED + } + + pub fn aeadOverhead(kind: String) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_aead_overhead", + "kind", + kind.to_string(), + ) + })?; + let out = csv.aead_overhead(); + APIResult::Ok(out) + } + + pub fn decryptAead( + kind: String, + body: String, + nonce: String, + shared_secret: String, + associated_data: Option, + ) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let body: Vec = data_encoding::BASE64URL_NOPAD + .decode(body.as_bytes()) + .unwrap(); + + let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?; + + let shared_secret: veilid_core::SharedSecret = + veilid_core::SharedSecret::from_str(&shared_secret)?; + + let associated_data: Option> = associated_data.map(|ad| { + data_encoding::BASE64URL_NOPAD + .decode(ad.as_bytes()) + .unwrap() + }); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_decrypt_aead", + "kind", + kind.to_string(), + ) + })?; + let out = csv.decrypt_aead( + &body, + &nonce, + &shared_secret, + match &associated_data { + Some(ad) => Some(ad.as_slice()), + None => None, + }, + )?; + let out = data_encoding::BASE64URL_NOPAD.encode(&out); + APIResult::Ok(out) + } + + pub fn encryptAead( + kind: String, + body: String, + nonce: String, + shared_secret: String, + associated_data: Option, + ) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let body: Vec = data_encoding::BASE64URL_NOPAD + .decode(body.as_bytes()) + .unwrap(); + + let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce).unwrap(); + + let shared_secret: veilid_core::SharedSecret = + veilid_core::SharedSecret::from_str(&shared_secret).unwrap(); + + let associated_data: Option> = associated_data.map(|ad| { + data_encoding::BASE64URL_NOPAD + .decode(ad.as_bytes()) + .unwrap() + }); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_encrypt_aead", + "kind", + kind.to_string(), + ) + })?; + let out = csv.encrypt_aead( + &body, + &nonce, + &shared_secret, + match &associated_data { + Some(ad) => Some(ad.as_slice()), + None => None, + }, + )?; + let out = data_encoding::BASE64URL_NOPAD.encode(&out); + APIResult::Ok(out) + } + + pub fn cryptNoAuth( + kind: String, + body: String, + nonce: String, + shared_secret: String, + ) -> VeilidAPIResult { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; + + let mut body: Vec = data_encoding::BASE64URL_NOPAD + .decode(body.as_bytes()) + .unwrap(); + + let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce).unwrap(); + + let shared_secret: veilid_core::SharedSecret = + veilid_core::SharedSecret::from_str(&shared_secret).unwrap(); + + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_crypt_no_auth", + "kind", + kind.to_string(), + ) + })?; + csv.crypt_in_place_no_auth(&mut body, &nonce, &shared_secret); + let out = data_encoding::BASE64URL_NOPAD.encode(&body); + APIResult::Ok(out) + } +} diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index a03c62e9..8bb3f656 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -13,6 +13,7 @@ impl VeilidRoutingContext { Self { id } } + // Factories pub fn createWithoutPrivacy() -> VeilidAPIResult { let veilid_api = get_veilid_api()?; let routing_context = veilid_api.routing_context(); @@ -45,6 +46,54 @@ impl VeilidRoutingContext { Ok(VeilidRoutingContext { id }) } + // Static methods + pub async fn newPrivateRoute() -> VeilidAPIResult { + let veilid_api = get_veilid_api()?; + + let (route_id, blob) = veilid_api.new_private_route().await?; + + let route_blob = VeilidRouteBlob { route_id, blob }; + APIResult::Ok(route_blob) + } + + pub async fn newCustomPrivateRoute( + stability: Stability, + sequencing: Sequencing, + ) -> VeilidAPIResult { + let veilid_api = get_veilid_api()?; + + let (route_id, blob) = veilid_api + .new_custom_private_route(&veilid_core::VALID_CRYPTO_KINDS, stability, sequencing) + .await?; + + let route_blob = VeilidRouteBlob { route_id, blob }; + APIResult::Ok(route_blob) + } + + pub async fn releasePrivateRoute(routeId: String) -> VeilidAPIResult<()> { + let route_id: veilid_core::RouteId = veilid_core::deserialize_json(&routeId).unwrap(); + let veilid_api = get_veilid_api()?; + veilid_api.release_private_route(route_id)?; + APIRESULT_UNDEFINED + } + + pub async fn appCallReply(callId: String, message: String) -> VeilidAPIResult<()> { + let call_id = match callId.parse() { + Ok(v) => v, + Err(e) => { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + e, "call_id", callId, + )) + } + }; + let veilid_api = get_veilid_api()?; + veilid_api + .app_call_reply(call_id, message.into_bytes()) + .await?; + APIRESULT_UNDEFINED + } + + // Instance methods pub async fn appMessage(&self, target_string: String, message: String) -> VeilidAPIResult<()> { let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); @@ -62,12 +111,7 @@ impl VeilidRoutingContext { APIRESULT_UNDEFINED } - pub async fn appCall( - &self, - id: u32, - target_string: String, - request: String, - ) -> VeilidAPIResult { + pub async fn appCall(&self, target_string: String, request: String) -> VeilidAPIResult { let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&self.id) else { @@ -86,12 +130,15 @@ impl VeilidRoutingContext { APIResult::Ok(answer) } - pub async fn createDhtRecord(&self, schema: JsValue, kind: u32) -> VeilidAPIResult { - let schema: DHTSchema = serde_wasm_bindgen::from_value(schema).unwrap(); - let crypto_kind = if kind == 0 { + pub async fn createDhtRecord( + &self, + schema: DHTSchema, + kind: String, + ) -> VeilidAPIResult { + let crypto_kind = if kind.is_empty() { None } else { - Some(veilid_core::FourCC::from(kind)) + Some(veilid_core::FourCC::from_str(&kind)?) }; let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); @@ -104,18 +151,21 @@ impl VeilidRoutingContext { let dht_record_descriptor = routing_context .create_dht_record(schema, crypto_kind) .await?; - let out = serde_wasm_bindgen::to_value(&dht_record_descriptor).unwrap(); - APIResult::Ok(out) + APIResult::Ok(dht_record_descriptor) } + /// @param {string} writer - Stringified key pair in the form of `key:secret`. pub async fn openDhtRecord( &self, key: String, writer: Option, - ) -> VeilidAPIResult { - let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); - let writer: Option = - writer.map(|s| veilid_core::deserialize_json(&s).unwrap()); + ) -> VeilidAPIResult { + let key = TypedKey::from_str(&key).unwrap(); + let writer = match writer { + Some(writer) => Some(KeyPair::from_str(&writer).unwrap()), + _ => None, + }; + let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&self.id) else { @@ -124,12 +174,11 @@ impl VeilidRoutingContext { routing_context.clone() }; let dht_record_descriptor = routing_context.open_dht_record(key, writer).await?; - let out = serde_wasm_bindgen::to_value(&dht_record_descriptor).unwrap(); - APIResult::Ok(out) + APIResult::Ok(dht_record_descriptor) } pub async fn closeDhtRecord(&self, key: String) -> VeilidAPIResult<()> { - let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + let key = TypedKey::from_str(&key).unwrap(); let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&self.id) else { @@ -142,7 +191,7 @@ impl VeilidRoutingContext { } pub async fn deleteDhtRecord(&self, key: String) -> VeilidAPIResult<()> { - let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + let key = TypedKey::from_str(&key).unwrap(); let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&self.id) else { @@ -159,8 +208,8 @@ impl VeilidRoutingContext { key: String, subKey: u32, forceRefresh: bool, - ) -> VeilidAPIResult { - let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + ) -> VeilidAPIResult> { + let key = TypedKey::from_str(&key).unwrap(); let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&self.id) else { @@ -171,17 +220,17 @@ impl VeilidRoutingContext { let res = routing_context .get_dht_value(key, subKey, forceRefresh) .await?; - let out = serde_wasm_bindgen::to_value(&res).unwrap(); - APIResult::Ok(out) + APIResult::Ok(res) } + /// @param {string} data - Base64Url (no padding) encoded data string pub async fn setDhtValue( &self, key: String, subKey: u32, data: String, - ) -> VeilidAPIResult { - let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); + ) -> VeilidAPIResult> { + let key = TypedKey::from_str(&key).unwrap(); let data: Vec = data_encoding::BASE64URL_NOPAD .decode(&data.as_bytes()) .unwrap(); @@ -194,8 +243,7 @@ impl VeilidRoutingContext { routing_context.clone() }; let res = routing_context.set_dht_value(key, subKey, data).await?; - let out = serde_wasm_bindgen::to_value(&res).unwrap(); - APIResult::Ok(out) + APIResult::Ok(res) } // pub async fn watchDhtValues( @@ -239,55 +287,3 @@ impl VeilidRoutingContext { // APIResult::Ok(res) // } } - -#[wasm_bindgen()] -pub async fn newPrivateRoute() -> VeilidAPIResult { - let veilid_api = get_veilid_api()?; - - let (route_id, blob) = veilid_api.new_private_route().await?; - - let route_blob = VeilidRouteBlob { route_id, blob }; - let out = serde_wasm_bindgen::to_value(&route_blob).unwrap(); - APIResult::Ok(out) -} - -#[wasm_bindgen()] -pub async fn newCustomPrivateRoute( - stability: Stability, - sequencing: Sequencing, -) -> VeilidAPIResult { - let veilid_api = get_veilid_api()?; - - let (route_id, blob) = veilid_api - .new_custom_private_route(&veilid_core::VALID_CRYPTO_KINDS, stability, sequencing) - .await?; - - let route_blob = VeilidRouteBlob { route_id, blob }; - let out = serde_wasm_bindgen::to_value(&route_blob).unwrap(); - APIResult::Ok(out) -} - -#[wasm_bindgen()] -pub async fn releasePrivateRoute(routeId: String) -> VeilidAPIResult<()> { - let route_id: veilid_core::RouteId = veilid_core::deserialize_json(&routeId).unwrap(); - let veilid_api = get_veilid_api()?; - veilid_api.release_private_route(route_id)?; - APIRESULT_UNDEFINED -} - -#[wasm_bindgen()] -pub async fn appCallReply(callId: String, message: String) -> VeilidAPIResult<()> { - let call_id = match callId.parse() { - Ok(v) => v, - Err(e) => { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( - e, "call_id", callId, - )) - } - }; - let veilid_api = get_veilid_api()?; - veilid_api - .app_call_reply(call_id, message.into_bytes()) - .await?; - APIRESULT_UNDEFINED -} diff --git a/veilid-wasm/src/wasm_helpers.rs b/veilid-wasm/src/wasm_helpers.rs new file mode 100644 index 00000000..2792a228 --- /dev/null +++ b/veilid-wasm/src/wasm_helpers.rs @@ -0,0 +1,21 @@ +cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + pub use tsify::*; + pub use wasm_bindgen::prelude::*; + + macro_rules! from_impl_to_jsvalue { + ($name: ident) => { + impl From<$name> for JsValue { + fn from(value: $name) -> Self { + serde_wasm_bindgen::to_value(&value).unwrap() + } + } + } + } + } else { + macro_rules! from_impl_to_jsvalue { + ($name: ident) => {} + } + } +} +pub(crate) use from_impl_to_jsvalue; From 93963e9d0846fae051b7db385fe82bc0c86eb515 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Mon, 4 Sep 2023 14:04:57 -0400 Subject: [PATCH 7/9] (wasm) Cleanup and refactoring, add TableDB transactions, copy over docs for JSDoc. --- .../src/veilid_api/types/veilid_state.rs | 3 + veilid-wasm/src/lib.rs | 11 + veilid-wasm/src/veilid_client_js.rs | 41 +++- veilid-wasm/src/veilid_crypto_js.rs | 148 ++++++------ veilid-wasm/src/veilid_routing_context_js.rs | 215 +++++++++++------- veilid-wasm/src/veilid_table_db_js.rs | 195 +++++++++------- veilid-wasm/src/wasm_helpers.rs | 17 ++ 7 files changed, 366 insertions(+), 264 deletions(-) diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs index d6f5f836..6c3b4e60 100644 --- a/veilid-core/src/veilid_api/types/veilid_state.rs +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -64,6 +64,7 @@ pub struct VeilidStateAttachment { #[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct PeerTableData { #[schemars(with = "Vec")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string[]"))] pub node_ids: Vec, pub peer_address: String, pub peer_stats: PeerStats, @@ -82,8 +83,10 @@ pub struct VeilidStateNetwork { #[cfg_attr(target_arch = "wasm32", derive(Tsify))] pub struct VeilidRouteChange { #[schemars(with = "Vec")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub dead_routes: Vec, #[schemars(with = "Vec")] + #[cfg_attr(target_arch = "wasm32", tsify(type = "string"))] pub dead_remote_routes: Vec, } diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 29647d6b..77767ea5 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -65,6 +65,17 @@ fn take_veilid_api() -> Result Vec { + data_encoding::BASE64URL_NOPAD + .decode(b64.as_bytes()) + .unwrap() +} + +pub fn marshall(data: &Vec) -> String { + data_encoding::BASE64URL_NOPAD.encode(data) +} + // JSON Helpers for WASM pub fn to_json(val: T) -> JsValue { JsValue::from_str(&serialize_json(val)) diff --git a/veilid-wasm/src/veilid_client_js.rs b/veilid-wasm/src/veilid_client_js.rs index 1f2f8f1b..0629adb9 100644 --- a/veilid-wasm/src/veilid_client_js.rs +++ b/veilid-wasm/src/veilid_client_js.rs @@ -66,10 +66,16 @@ impl VeilidClient { .expect("failed to initalize WASM platform"); } + /// Initialize a Veilid node, with the configuration in JSON format + /// + /// Must be called only once at the start of an application + /// + /// @param {UpdateVeilidFunction} update_callback_js - called when internal state of the Veilid node changes, for example, when app-level messages are received, when private routes die and need to be reallocated, or when routing table states change + /// @param {string} json_config - called at startup to supply a JSON configuration object. pub async fn startupCore( update_callback_js: UpdateVeilidFunction, json_config: String, - ) -> Result<(), VeilidAPIError> { + ) -> APIResult<()> { let update_callback_js = SendWrapper::new(update_callback_js); let update_callback = Arc::new(move |update: VeilidUpdate| { let _ret = match Function::call1( @@ -86,12 +92,12 @@ impl VeilidClient { }); if VEILID_API.borrow().is_some() { - return Err(veilid_core::VeilidAPIError::AlreadyInitialized); + return APIResult::Err(veilid_core::VeilidAPIError::AlreadyInitialized); } let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?; VEILID_API.replace(Some(veilid_api)); - Ok(()) + APIRESULT_UNDEFINED } // TODO: can we refine the TS type of `layer`? @@ -110,36 +116,42 @@ impl VeilidClient { } } - pub async fn shutdownCore() -> Result<(), VeilidAPIError> { + /// Shut down Veilid and terminate the API. + pub async fn shutdownCore() -> APIResult<()> { let veilid_api = take_veilid_api()?; veilid_api.shutdown().await; - Ok(()) + APIRESULT_UNDEFINED } - pub async fn getState() -> Result { + /// Get a full copy of the current state of Veilid. + pub async fn getState() -> APIResult { let veilid_api = get_veilid_api()?; let core_state = veilid_api.get_state().await?; - Ok(core_state) + APIResult::Ok(core_state) } - pub async fn attach() -> Result<(), VeilidAPIError> { + /// Connect to the network. + pub async fn attach() -> APIResult<()> { let veilid_api = get_veilid_api()?; veilid_api.attach().await?; - Ok(()) + APIRESULT_UNDEFINED } - pub async fn detach() -> Result<(), VeilidAPIError> { + /// Disconnect from the network. + pub async fn detach() -> APIResult<()> { let veilid_api = get_veilid_api()?; veilid_api.detach().await?; - Ok(()) + APIRESULT_UNDEFINED } - pub async fn debug(command: String) -> Result { + /// Execute an 'internal debug command'. + pub async fn debug(command: String) -> APIResult { let veilid_api = get_veilid_api()?; let out = veilid_api.debug(command).await?; APIResult::Ok(out) } + /// Return the cargo package version of veilid-core, in object format. pub fn version() -> VeilidVersion { let (major, minor, patch) = veilid_core::veilid_version(); let vv = super::VeilidVersion { @@ -149,4 +161,9 @@ impl VeilidClient { }; vv } + + /// Return the cargo package version of veilid-core, in string format. + pub fn versionString() -> String { + veilid_core::veilid_version_string() + } } diff --git a/veilid-wasm/src/veilid_crypto_js.rs b/veilid-wasm/src/veilid_crypto_js.rs index ecef57ca..350bac32 100644 --- a/veilid-wasm/src/veilid_crypto_js.rs +++ b/veilid-wasm/src/veilid_crypto_js.rs @@ -7,43 +7,45 @@ extern "C" { pub type ValidCryptoKinds; } -#[wasm_bindgen(js_class = veilidCrypto)] +#[wasm_bindgen(js_name = veilidCrypto)] pub struct VeilidCrypto {} +// Since this implementation doesn't contain a `new` fn that's marked as a constructor, +// and none of the member fns take a &self arg, +// this is just a namespace/class of static functions. #[wasm_bindgen(js_class = veilidCrypto)] impl VeilidCrypto { - pub fn validCryptoKinds() -> ValidCryptoKinds { + pub fn validCryptoKinds() -> StringArray { let res = veilid_core::VALID_CRYPTO_KINDS .iter() - .map(|k| (*k).to_string()); - res.map(JsValue::from) - .collect::() - .unchecked_into::() + .map(|k| (*k).to_string()) + .collect(); + into_unchecked_string_array(res) } pub fn bestCryptoKind() -> String { veilid_core::best_crypto_kind().to_string() } - pub fn cachedDh(kind: String, key: String, secret: String) -> VeilidAPIResult { + pub fn cachedDh(kind: String, key: String, secret: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_cached_dh", "kind", kind.to_string(), ) })?; - let out = csv.cached_dh(&key, &secret)?; + let out = crypto_system.cached_dh(&key, &secret)?; APIResult::Ok(out.to_string()) } - pub fn computeDh(kind: String, key: String, secret: String) -> VeilidAPIResult { + pub fn computeDh(kind: String, key: String, secret: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; @@ -51,51 +53,51 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_compute_dh", "kind", kind.to_string(), ) })?; - let out = csv.compute_dh(&key, &secret)?; + let out = crypto_system.compute_dh(&key, &secret)?; APIResult::Ok(out.to_string()) } - pub fn randomBytes(kind: String, len: u32) -> VeilidAPIResult { + pub fn randomBytes(kind: String, len: u32) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_random_bytes", "kind", kind.to_string(), ) })?; - let out = csv.random_bytes(len); + let out = crypto_system.random_bytes(len); let out = data_encoding::BASE64URL_NOPAD.encode(&out); APIResult::Ok(out) } - pub fn defaultSaltLength(kind: String) -> VeilidAPIResult { + pub fn defaultSaltLength(kind: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_default_salt_length", "kind", kind.to_string(), ) })?; - let out = csv.default_salt_length(); + let out = crypto_system.default_salt_length(); APIResult::Ok(out) } - pub fn hashPassword(kind: String, password: String, salt: String) -> VeilidAPIResult { + pub fn hashPassword(kind: String, password: String, salt: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let password: Vec = data_encoding::BASE64URL_NOPAD .decode(password.as_bytes()) @@ -106,14 +108,14 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_hash_password", "kind", kind.to_string(), ) })?; - let out = csv.hash_password(&password, &salt)?; + let out = crypto_system.hash_password(&password, &salt)?; APIResult::Ok(out) } @@ -121,7 +123,7 @@ impl VeilidCrypto { kind: String, password: String, password_hash: String, - ) -> VeilidAPIResult { + ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let password: Vec = data_encoding::BASE64URL_NOPAD .decode(password.as_bytes()) @@ -129,22 +131,18 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_verify_password", "kind", kind.to_string(), ) })?; - let out = csv.verify_password(&password, &password_hash)?; + let out = crypto_system.verify_password(&password, &password_hash)?; APIResult::Ok(out) } - pub fn deriveSharedSecret( - kind: String, - password: String, - salt: String, - ) -> VeilidAPIResult { + pub fn deriveSharedSecret(kind: String, password: String, salt: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let password: Vec = data_encoding::BASE64URL_NOPAD .decode(password.as_bytes()) @@ -155,66 +153,66 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_derive_shared_secret", "kind", kind.to_string(), ) })?; - let out = csv.derive_shared_secret(&password, &salt)?; + let out = crypto_system.derive_shared_secret(&password, &salt)?; APIResult::Ok(out.to_string()) } - pub fn randomNonce(kind: String) -> VeilidAPIResult { + pub fn randomNonce(kind: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_random_nonce", "kind", kind.to_string(), ) })?; - let out = csv.random_nonce(); + let out = crypto_system.random_nonce(); APIResult::Ok(out.to_string()) } - pub fn randomSharedSecret(kind: String) -> VeilidAPIResult { + pub fn randomSharedSecret(kind: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_random_shared_secret", "kind", kind.to_string(), ) })?; - let out = csv.random_shared_secret(); + let out = crypto_system.random_shared_secret(); APIResult::Ok(out.to_string()) } - pub fn generateKeyPair(kind: String) -> VeilidAPIResult { + pub fn generateKeyPair(kind: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_generate_key_pair", "kind", kind.to_string(), ) })?; - let out = csv.generate_keypair(); + let out = crypto_system.generate_keypair(); APIResult::Ok(out) } - pub fn generateHash(kind: String, data: String) -> VeilidAPIResult { + pub fn generateHash(kind: String, data: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let data: Vec = data_encoding::BASE64URL_NOPAD @@ -223,18 +221,18 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_generate_hash", "kind", kind.to_string(), ) })?; - let out = csv.generate_hash(&data); + let out = crypto_system.generate_hash(&data); APIResult::Ok(out.to_string()) } - pub fn validateKeyPair(kind: String, key: String, secret: String) -> VeilidAPIResult { + pub fn validateKeyPair(kind: String, key: String, secret: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; @@ -242,18 +240,18 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_validate_key_pair", "kind", kind.to_string(), ) })?; - let out = csv.validate_keypair(&key, &secret); + let out = crypto_system.validate_keypair(&key, &secret); APIResult::Ok(out) } - pub fn validateHash(kind: String, data: String, hash: String) -> VeilidAPIResult { + pub fn validateHash(kind: String, data: String, hash: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let data: Vec = data_encoding::BASE64URL_NOPAD @@ -264,18 +262,18 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_validate_hash", "kind", kind.to_string(), ) })?; - let out = csv.validate_hash(&data, &hash); + let out = crypto_system.validate_hash(&data, &hash); APIResult::Ok(out) } - pub fn distance(kind: String, key1: String, key2: String) -> VeilidAPIResult { + pub fn distance(kind: String, key1: String, key2: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key1: veilid_core::CryptoKey = veilid_core::CryptoKey::from_str(&key1)?; @@ -283,23 +281,18 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_distance", "kind", kind.to_string(), ) })?; - let out = csv.distance(&key1, &key2); + let out = crypto_system.distance(&key1, &key2); APIResult::Ok(out.to_string()) } - pub fn sign( - kind: String, - key: String, - secret: String, - data: String, - ) -> VeilidAPIResult { + pub fn sign(kind: String, key: String, secret: String, data: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; @@ -311,19 +304,14 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument("crypto_sign", "kind", kind.to_string()) })?; - let out = csv.sign(&key, &secret, &data)?; + let out = crypto_system.sign(&key, &secret, &data)?; APIResult::Ok(out.to_string()) } - pub fn verify( - kind: String, - key: String, - data: String, - signature: String, - ) -> VeilidAPIResult<()> { + pub fn verify(kind: String, key: String, data: String, signature: String) -> APIResult<()> { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; @@ -334,26 +322,26 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) })?; - csv.verify(&key, &data, &signature)?; + crypto_system.verify(&key, &data, &signature)?; APIRESULT_UNDEFINED } - pub fn aeadOverhead(kind: String) -> VeilidAPIResult { + pub fn aeadOverhead(kind: String) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_aead_overhead", "kind", kind.to_string(), ) })?; - let out = csv.aead_overhead(); + let out = crypto_system.aead_overhead(); APIResult::Ok(out) } @@ -363,7 +351,7 @@ impl VeilidCrypto { nonce: String, shared_secret: String, associated_data: Option, - ) -> VeilidAPIResult { + ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let body: Vec = data_encoding::BASE64URL_NOPAD @@ -383,14 +371,14 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_decrypt_aead", "kind", kind.to_string(), ) })?; - let out = csv.decrypt_aead( + let out = crypto_system.decrypt_aead( &body, &nonce, &shared_secret, @@ -409,7 +397,7 @@ impl VeilidCrypto { nonce: String, shared_secret: String, associated_data: Option, - ) -> VeilidAPIResult { + ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let body: Vec = data_encoding::BASE64URL_NOPAD @@ -429,14 +417,14 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_encrypt_aead", "kind", kind.to_string(), ) })?; - let out = csv.encrypt_aead( + let out = crypto_system.encrypt_aead( &body, &nonce, &shared_secret, @@ -454,7 +442,7 @@ impl VeilidCrypto { body: String, nonce: String, shared_secret: String, - ) -> VeilidAPIResult { + ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let mut body: Vec = data_encoding::BASE64URL_NOPAD @@ -468,14 +456,14 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let csv = crypto.get(kind).ok_or_else(|| { + let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument( "crypto_crypt_no_auth", "kind", kind.to_string(), ) })?; - csv.crypt_in_place_no_auth(&mut body, &nonce, &shared_secret); + crypto_system.crypt_in_place_no_auth(&mut body, &nonce, &shared_secret); let out = data_encoding::BASE64URL_NOPAD.encode(&body); APIResult::Ok(out) } diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index 8bb3f656..76f4ed20 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -8,46 +8,71 @@ pub struct VeilidRoutingContext { #[wasm_bindgen()] impl VeilidRoutingContext { + /// Don't use this constructor directly. + /// Use one of the `VeilidRoutingContext.create___()` factory methods instead. + /// @deprecated #[wasm_bindgen(constructor)] pub fn new(id: u32) -> Self { Self { id } } - // Factories - pub fn createWithoutPrivacy() -> VeilidAPIResult { + // -------------------------------- + // Constructor factories + // -------------------------------- + + /// Get a new RoutingContext object to use to send messages over the Veilid network. + pub fn createWithoutPrivacy() -> APIResult { let veilid_api = get_veilid_api()?; let routing_context = veilid_api.routing_context(); let id = add_routing_context(routing_context); Ok(VeilidRoutingContext { id }) } - pub async fn createWithPrivacy() -> VeilidAPIResult { + /// Turn on sender privacy, enabling the use of safety routes. + /// + /// Default values for hop count, stability and sequencing preferences are used. + /// + /// Hop count default is dependent on config, but is set to 1 extra hop. + /// Stability default is to choose 'low latency' routes, preferring them over long-term reliability. + /// Sequencing default is to have no preference for ordered vs unordered message delivery + /// To modify these defaults, use `VeilidRoutingContext.createWithCustomPrivacy()`. + pub fn createWithPrivacy() -> APIResult { let veilid_api = get_veilid_api()?; let routing_context = veilid_api.routing_context().with_privacy()?; let id = add_routing_context(routing_context); Ok(VeilidRoutingContext { id }) } - pub async fn createWithCustomPrivacy( - safetySelection: SafetySelection, - ) -> VeilidAPIResult { + /// Turn on privacy using a custom `SafetySelection` + pub fn createWithCustomPrivacy( + safety_selection: SafetySelection, + ) -> APIResult { let veilid_api = get_veilid_api()?; let routing_context = veilid_api .routing_context() - .with_custom_privacy(safetySelection)?; + .with_custom_privacy(safety_selection)?; let id = add_routing_context(routing_context); Ok(VeilidRoutingContext { id }) } - pub fn createWithSequencing(sequencing: Sequencing) -> VeilidAPIResult { + /// Use a specified `Sequencing` preference, with or without privacy. + pub fn createWithSequencing(sequencing: Sequencing) -> APIResult { let veilid_api = get_veilid_api()?; let routing_context = veilid_api.routing_context().with_sequencing(sequencing); let id = add_routing_context(routing_context); Ok(VeilidRoutingContext { id }) } + // -------------------------------- // Static methods - pub async fn newPrivateRoute() -> VeilidAPIResult { + // -------------------------------- + + /// Allocate a new private route set with default cryptography and network options. + /// Returns a route id and a publishable 'blob' with the route encrypted with each crypto kind. + /// Those nodes importing the blob will have their choice of which crypto kind to use. + /// + /// Returns a route id and 'blob' that can be published over some means (DHT or otherwise) to be imported by another Veilid node. + pub async fn newPrivateRoute() -> APIResult { let veilid_api = get_veilid_api()?; let (route_id, blob) = veilid_api.new_private_route().await?; @@ -56,10 +81,15 @@ impl VeilidRoutingContext { APIResult::Ok(route_blob) } + /// Allocate a new private route and specify a specific cryptosystem, stability and sequencing preference. + /// Returns a route id and a publishable 'blob' with the route encrypted with each crypto kind. + /// Those nodes importing the blob will have their choice of which crypto kind to use. + /// + /// Returns a route id and 'blob' that can be published over some means (DHT or otherwise) to be imported by another Veilid node. pub async fn newCustomPrivateRoute( stability: Stability, sequencing: Sequencing, - ) -> VeilidAPIResult { + ) -> APIResult { let veilid_api = get_veilid_api()?; let (route_id, blob) = veilid_api @@ -70,19 +100,26 @@ impl VeilidRoutingContext { APIResult::Ok(route_blob) } - pub async fn releasePrivateRoute(routeId: String) -> VeilidAPIResult<()> { - let route_id: veilid_core::RouteId = veilid_core::deserialize_json(&routeId).unwrap(); + /// Release either a locally allocated or remotely imported private route. + /// + /// This will deactivate the route and free its resources and it can no longer be sent to or received from. + pub fn releasePrivateRoute(route_id: String) -> APIResult<()> { + let route_id: veilid_core::RouteId = veilid_core::deserialize_json(&route_id).unwrap(); let veilid_api = get_veilid_api()?; veilid_api.release_private_route(route_id)?; APIRESULT_UNDEFINED } - pub async fn appCallReply(callId: String, message: String) -> VeilidAPIResult<()> { - let call_id = match callId.parse() { + /// Respond to an AppCall received over a VeilidUpdate::AppCall. + /// + /// * `call_id` - specifies which call to reply to, and it comes from a VeilidUpdate::AppCall, specifically the VeilidAppCall::id() value. + /// * `message` - is an answer blob to be returned by the remote node's RoutingContext::app_call() function, and may be up to 32768 bytes + pub async fn appCallReply(call_id: String, message: String) -> APIResult<()> { + let call_id = match call_id.parse() { Ok(v) => v, Err(e) => { return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( - e, "call_id", callId, + e, "call_id", call_id, )) } }; @@ -93,15 +130,26 @@ impl VeilidRoutingContext { APIRESULT_UNDEFINED } + // -------------------------------- // Instance methods - pub async fn appMessage(&self, target_string: String, message: String) -> VeilidAPIResult<()> { - let routing_context = { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_message", "id", self.id)); - }; - routing_context.clone() + // -------------------------------- + fn getRoutingContext(&self) -> APIResult { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("getRoutingContext", "id", self.id)); }; + APIResult::Ok(routing_context.clone()) + } + + /// App-level unidirectional message that does not expect any value to be returned. + /// + /// Veilid apps may use this for arbitrary message passing. + /// + /// @param {string} target - can be either a direct node id or a private route. + /// @param {string} message - an arbitrary message blob of up to `32768` bytes. + #[wasm_bindgen(skip_jsdoc)] + pub async fn appMessage(&self, target_string: String, message: String) -> APIResult<()> { + let routing_context = self.getRoutingContext()?; let veilid_api = get_veilid_api()?; let target = veilid_api.parse_as_target(target_string).await?; @@ -111,42 +159,43 @@ impl VeilidRoutingContext { APIRESULT_UNDEFINED } - pub async fn appCall(&self, target_string: String, request: String) -> VeilidAPIResult { - let routing_context = { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", self.id)); - }; - routing_context.clone() - }; + /// App-level bidirectional call that expects a response to be returned. + /// + /// Veilid apps may use this for arbitrary message passing. + /// + /// @param {string} target_string - can be either a direct node id or a private route, base64Url encoded. + /// @param {string} message - an arbitrary message blob of up to `32768` bytes, base64Url encoded. + /// @returns an answer blob of up to `32768` bytes, base64Url encoded. + #[wasm_bindgen(skip_jsdoc)] + pub async fn appCall(&self, target_string: String, request: String) -> APIResult { + let request: Vec = data_encoding::BASE64URL_NOPAD + .decode(&request.as_bytes()) + .unwrap(); + let routing_context = self.getRoutingContext()?; let veilid_api = get_veilid_api()?; let target = veilid_api.parse_as_target(target_string).await?; - let answer = routing_context - .app_call(target, request.into_bytes()) - .await?; - // let answer = data_encoding::BASE64URL_NOPAD.encode(&answer); - let answer = String::from_utf8_lossy(&answer).into_owned(); + let answer = routing_context.app_call(target, request).await?; + let answer = data_encoding::BASE64URL_NOPAD.encode(&answer); APIResult::Ok(answer) } + /// DHT Records Creates a new DHT record a specified crypto kind and schema + /// + /// The record is considered 'open' after the create operation succeeds. + /// + /// @returns the newly allocated DHT record's key if successful. pub async fn createDhtRecord( &self, schema: DHTSchema, kind: String, - ) -> VeilidAPIResult { + ) -> APIResult { let crypto_kind = if kind.is_empty() { None } else { Some(veilid_core::FourCC::from_str(&kind)?) }; - let routing_context = { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_create_dht_record", "id", self.id)); - }; - routing_context.clone() - }; + let routing_context = self.getRoutingContext()?; let dht_record_descriptor = routing_context .create_dht_record(schema, crypto_kind) @@ -154,94 +203,88 @@ impl VeilidRoutingContext { APIResult::Ok(dht_record_descriptor) } - /// @param {string} writer - Stringified key pair in the form of `key:secret`. + /// Opens a DHT record at a specific key. + /// + /// Associates a secret if one is provided to provide writer capability. Records may only be opened or created. To re-open with a different routing context, first close the value. + /// + /// @returns the DHT record descriptor for the opened record if successful. + /// @param {string} writer - Stringified key pair, in the form of `key:secret` where `key` and `secret` are base64Url encoded. + /// @param {string} key - key of the DHT record. + #[wasm_bindgen(skip_jsdoc)] pub async fn openDhtRecord( &self, key: String, writer: Option, - ) -> VeilidAPIResult { + ) -> APIResult { let key = TypedKey::from_str(&key).unwrap(); let writer = match writer { Some(writer) => Some(KeyPair::from_str(&writer).unwrap()), _ => None, }; - let routing_context = { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_open_dht_record", "id", self.id)); - }; - routing_context.clone() - }; + let routing_context = self.getRoutingContext()?; let dht_record_descriptor = routing_context.open_dht_record(key, writer).await?; APIResult::Ok(dht_record_descriptor) } - pub async fn closeDhtRecord(&self, key: String) -> VeilidAPIResult<()> { + /// Closes a DHT record at a specific key that was opened with create_dht_record or open_dht_record. + /// + /// Closing a record allows you to re-open it with a different routing context + pub async fn closeDhtRecord(&self, key: String) -> APIResult<()> { let key = TypedKey::from_str(&key).unwrap(); - let routing_context = { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_close_dht_record", "id", self.id)); - }; - routing_context.clone() - }; + let routing_context = self.getRoutingContext()?; routing_context.close_dht_record(key).await?; APIRESULT_UNDEFINED } - pub async fn deleteDhtRecord(&self, key: String) -> VeilidAPIResult<()> { + /// Deletes a DHT record at a specific key + /// + /// If the record is opened, it must be closed before it is deleted. + /// Deleting a record does not delete it from the network, but will remove the storage of the record locally, + /// and will prevent its value from being refreshed on the network by this node. + pub async fn deleteDhtRecord(&self, key: String) -> APIResult<()> { let key = TypedKey::from_str(&key).unwrap(); - let routing_context = { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_delete_dht_record", "id", self.id)); - }; - routing_context.clone() - }; + let routing_context = self.getRoutingContext()?; routing_context.delete_dht_record(key).await?; APIRESULT_UNDEFINED } + /// Gets the latest value of a subkey. + /// + /// May pull the latest value from the network, but by settings 'force_refresh' you can force a network data refresh. + /// + /// Returns `undefined` if the value subkey has not yet been set. + /// Returns base64Url encoded `data` if the value subkey has valid data. pub async fn getDhtValue( &self, key: String, subKey: u32, forceRefresh: bool, - ) -> VeilidAPIResult> { + ) -> APIResult> { let key = TypedKey::from_str(&key).unwrap(); - let routing_context = { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_get_dht_value", "id", self.id)); - }; - routing_context.clone() - }; + let routing_context = self.getRoutingContext()?; let res = routing_context .get_dht_value(key, subKey, forceRefresh) .await?; APIResult::Ok(res) } - /// @param {string} data - Base64Url (no padding) encoded data string + /// Pushes a changed subkey value to the network + /// + /// Returns `undefined` if the value was successfully put. + /// Returns base64Url encoded `data` if the value put was older than the one available on the network. pub async fn setDhtValue( &self, key: String, subKey: u32, data: String, - ) -> VeilidAPIResult> { + ) -> APIResult> { let key = TypedKey::from_str(&key).unwrap(); let data: Vec = data_encoding::BASE64URL_NOPAD .decode(&data.as_bytes()) .unwrap(); - let routing_context = { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_set_dht_value", "id", self.id)); - }; - routing_context.clone() - }; + let routing_context = self.getRoutingContext()?; let res = routing_context.set_dht_value(key, subKey, data).await?; APIResult::Ok(res) } @@ -252,7 +295,7 @@ impl VeilidRoutingContext { // subKeys: ValueSubkeyRangeSet, // expiration: Timestamp, // count: u32, - // ) -> VeilidAPIResult { + // ) -> APIResult { // let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); // let subkeys: veilid_core::ValueSubkeyRangeSet = // veilid_core::deserialize_json(&subkeys).unwrap(); diff --git a/veilid-wasm/src/veilid_table_db_js.rs b/veilid-wasm/src/veilid_table_db_js.rs index 79b5e8e9..87cf9ccf 100644 --- a/veilid-wasm/src/veilid_table_db_js.rs +++ b/veilid-wasm/src/veilid_table_db_js.rs @@ -10,16 +10,28 @@ pub struct VeilidTableDB { #[wasm_bindgen()] impl VeilidTableDB { + /// If the column count is greater than an existing TableDB's column count, + /// the database will be upgraded to add the missing columns. #[wasm_bindgen(constructor)] - pub fn new(tableName: String, columnCount: u32) -> VeilidTableDB { - VeilidTableDB { + pub fn new(tableName: String, columnCount: u32) -> Self { + Self { id: 0, tableName, columnCount, } } - pub async fn openTable(&mut self) -> VeilidAPIResult { + fn getTableDB(&self) -> APIResult { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("getTableDB", "id", self.id)); + }; + APIResult::Ok(table_db.clone()) + } + + /// Get or create the TableDB database table. + /// This is called automatically when performing actions on the TableDB. + pub async fn openTable(&mut self) -> APIResult { let veilid_api = get_veilid_api()?; let tstore = veilid_api.table_store()?; let table_db = tstore @@ -31,6 +43,7 @@ impl VeilidTableDB { APIResult::Ok(new_id) } + /// Release the TableDB instance from memory. pub fn releaseTable(&mut self) -> bool { let mut tdbs = (*TABLE_DBS).borrow_mut(); let status = tdbs.remove(&self.id); @@ -41,7 +54,8 @@ impl VeilidTableDB { return true; } - pub async fn deleteTable(&mut self) -> VeilidAPIResult { + /// Delete this TableDB. + pub async fn deleteTable(&mut self) -> APIResult { self.releaseTable(); let veilid_api = get_veilid_api()?; @@ -59,116 +73,125 @@ impl VeilidTableDB { } } - pub async fn load(&mut self, columnId: u32, key: String) -> VeilidAPIResult> { + /// Read a key from a column in the TableDB immediately. + pub async fn load(&mut self, columnId: u32, key: String) -> APIResult> { self.ensureOpen().await; + let key = unmarshall(key); + let table_db = self.getTableDB()?; - let table_db = { - let table_dbs = (*TABLE_DBS).borrow(); - let Some(table_db) = table_dbs.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_load", "id", self.id)); - }; - table_db.clone() - }; - - let out = table_db.load(columnId, key.as_bytes()).await?.unwrap(); - let out = Some(str::from_utf8(&out).unwrap().to_owned()); - // let out = serde_wasm_bindgen::to_value(&out) - // .expect("Could not parse using serde_wasm_bindgen"); + let out = table_db.load(columnId, &key).await?.unwrap(); + let out = Some(marshall(&out)); APIResult::Ok(out) } - pub async fn store( - &mut self, - columnId: u32, - key: String, - value: String, - ) -> VeilidAPIResult<()> { + /// Store a key with a value in a column in the TableDB. + /// Performs a single transaction immediately. + pub async fn store(&mut self, columnId: u32, key: String, value: String) -> APIResult<()> { self.ensureOpen().await; + let key = unmarshall(key); + let value = unmarshall(value); + let table_db = self.getTableDB()?; - let table_db = { - let table_dbs = (*TABLE_DBS).borrow(); - let Some(table_db) = table_dbs.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", self.id)); - }; - table_db.clone() - }; - - table_db - .store(columnId, key.as_bytes(), value.as_bytes()) - .await?; + table_db.store(columnId, &key, &value).await?; APIRESULT_UNDEFINED } - pub async fn delete(&mut self, columnId: u32, key: String) -> VeilidAPIResult> { + /// Delete key with from a column in the TableDB. + pub async fn delete(&mut self, columnId: u32, key: String) -> APIResult> { self.ensureOpen().await; + let key = unmarshall(key); + let table_db = self.getTableDB()?; - let table_db = { - let table_dbs = (*TABLE_DBS).borrow(); - let Some(table_db) = table_dbs.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_delete", "id", self.id)); - }; - table_db.clone() - }; - - // TODO: will crash when trying to .unwrap() of None (trying to delete key that doesn't exist) - let out = table_db.delete(columnId, key.as_bytes()).await?.unwrap(); - let out = Some(str::from_utf8(&out).unwrap().to_owned()); + let out = table_db.delete(columnId, &key).await?.unwrap(); + let out = Some(marshall(&out)); APIResult::Ok(out) } - // TODO try and figure out how to result a String[], maybe Box<[String]>? - pub async fn getKeys(&mut self, columnId: u32) -> VeilidAPIResult { + /// Get the list of keys in a column of the TableDB. + /// + /// Returns an array of base64Url encoded keys. + pub async fn getKeys(&mut self, columnId: u32) -> APIResult { self.ensureOpen().await; - - let table_db = { - let table_dbs = (*TABLE_DBS).borrow(); - let Some(table_db) = table_dbs.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", self.id)); - }; - table_db.clone() - }; + let table_db = self.getTableDB()?; let keys = table_db.clone().get_keys(columnId).await?; - let out: Vec = keys - .into_iter() - .map(|k| str::from_utf8(&k).unwrap().to_owned()) - .collect(); - let out = - serde_wasm_bindgen::to_value(&out).expect("Could not parse using serde_wasm_bindgen"); + let out: Vec = keys.into_iter().map(|k| marshall(&k)).collect(); + let out = into_unchecked_string_array(out); APIResult::Ok(out) } - pub async fn transact(&mut self) -> u32 { + /// Start a TableDB write transaction. + /// The transaction object must be committed or rolled back before dropping. + pub async fn createTransaction(&mut self) -> APIResult { self.ensureOpen().await; + let table_db = self.getTableDB()?; - let table_dbs = (*TABLE_DBS).borrow(); - let Some(table_db) = table_dbs.get(&self.id) else { - return 0; - }; - let tdbt = table_db.clone().transact(); - let tdbtid = add_table_db_transaction(tdbt); - return tdbtid; + let transaction = table_db.transact(); + let transaction_id = add_table_db_transaction(transaction); + APIResult::Ok(VeilidTableDBTransaction { id: transaction_id }) + } +} + +#[wasm_bindgen] +pub struct VeilidTableDBTransaction { + id: u32, +} + +#[wasm_bindgen] +impl VeilidTableDBTransaction { + /// Don't use this constructor directly. + /// Use `.createTransaction()` on an instance of `VeilidTableDB` instead. + /// @deprecated + #[wasm_bindgen(constructor)] + pub fn new(id: u32) -> Self { + Self { id } } - // TODO: placeholders for transaction functions - // pub async fn releaseTransaction(&mut self) { - // self.ensureOpen().await; - // } + fn getTransaction(&self) -> APIResult { + let transactions = (*TABLE_DB_TRANSACTIONS).borrow(); + let Some(transaction) = transactions.get(&self.id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("getTransaction", "id", &self.id)); + }; + APIResult::Ok(transaction.clone()) + } - // pub async fn commitTransaction(&mut self) { - // self.ensureOpen().await; - // } + /// Releases the transaction from memory. + pub fn releaseTransaction(&mut self) -> bool { + let mut transactions = (*TABLE_DB_TRANSACTIONS).borrow_mut(); + self.id = 0; + if transactions.remove(&self.id).is_none() { + return false; + } + return true; + } - // pub async fn rollbackTransaction(&mut self) { - // self.ensureOpen().await; - // } + /// Commit the transaction. Performs all actions atomically. + pub async fn commit(&self) -> APIResult<()> { + let transaction = self.getTransaction()?; + transaction.commit().await + } - // pub async fn storeTransaction(&mut self, tableId: u32, key: String, value: String) { - // self.ensureOpen().await; - // } + /// Rollback the transaction. Does nothing to the TableDB. + pub fn rollback(&self) -> APIResult<()> { + let transaction = self.getTransaction()?; + transaction.rollback(); + APIRESULT_UNDEFINED + } - // pub async fn deleteTransaction(&mut self) { - // self.ensureOpen().await; - // } + /// Store a key with a value in a column in the TableDB. + /// Does not modify TableDB until `.commit()` is called. + pub fn store(&self, col: u32, key: String, value: String) -> APIResult<()> { + let key = unmarshall(key); + let value = unmarshall(value); + let transaction = self.getTransaction()?; + transaction.store(col, &key, &value) + } + + /// Delete key with from a column in the TableDB + pub fn deleteKey(&self, col: u32, key: String) -> APIResult<()> { + let key = unmarshall(key); + let transaction = self.getTransaction()?; + transaction.delete(col, &key) + } } diff --git a/veilid-wasm/src/wasm_helpers.rs b/veilid-wasm/src/wasm_helpers.rs index 2792a228..f8e93468 100644 --- a/veilid-wasm/src/wasm_helpers.rs +++ b/veilid-wasm/src/wasm_helpers.rs @@ -1,3 +1,5 @@ +use super::*; + cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { pub use tsify::*; @@ -19,3 +21,18 @@ cfg_if::cfg_if! { } } pub(crate) use from_impl_to_jsvalue; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "string[]")] + pub type StringArray; +} + +/// Convert a `Vec` into a `js_sys::Array` with the type of `string[]` +pub(crate) fn into_unchecked_string_array(items: Vec) -> StringArray { + items + .iter() + .map(JsValue::from) + .collect::() + .unchecked_into::() // TODO: can I do this a better way? +} From 6b7301a963c5ce3c7f0200864492972110988303 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Mon, 4 Sep 2023 17:34:49 -0400 Subject: [PATCH 8/9] (wasm) Improved memory management, track by struct so drop works, use --weak-ref for wasm-bindgen --- veilid-wasm/src/veilid_routing_context_js.rs | 49 ++++++++-------- veilid-wasm/src/veilid_table_db_js.rs | 59 +++++++------------- veilid-wasm/wasm_build.sh | 4 +- 3 files changed, 46 insertions(+), 66 deletions(-) diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index 76f4ed20..50db6fe7 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -3,7 +3,7 @@ use super::*; #[wasm_bindgen()] pub struct VeilidRoutingContext { - id: u32, + inner_routing_context: Option, } #[wasm_bindgen()] @@ -12,8 +12,10 @@ impl VeilidRoutingContext { /// Use one of the `VeilidRoutingContext.create___()` factory methods instead. /// @deprecated #[wasm_bindgen(constructor)] - pub fn new(id: u32) -> Self { - Self { id } + pub fn new() -> Self { + Self { + inner_routing_context: None, + } } // -------------------------------- @@ -24,8 +26,9 @@ impl VeilidRoutingContext { pub fn createWithoutPrivacy() -> APIResult { let veilid_api = get_veilid_api()?; let routing_context = veilid_api.routing_context(); - let id = add_routing_context(routing_context); - Ok(VeilidRoutingContext { id }) + Ok(VeilidRoutingContext { + inner_routing_context: Some(routing_context), + }) } /// Turn on sender privacy, enabling the use of safety routes. @@ -39,8 +42,9 @@ impl VeilidRoutingContext { pub fn createWithPrivacy() -> APIResult { let veilid_api = get_veilid_api()?; let routing_context = veilid_api.routing_context().with_privacy()?; - let id = add_routing_context(routing_context); - Ok(VeilidRoutingContext { id }) + Ok(VeilidRoutingContext { + inner_routing_context: Some(routing_context), + }) } /// Turn on privacy using a custom `SafetySelection` @@ -51,16 +55,18 @@ impl VeilidRoutingContext { let routing_context = veilid_api .routing_context() .with_custom_privacy(safety_selection)?; - let id = add_routing_context(routing_context); - Ok(VeilidRoutingContext { id }) + Ok(VeilidRoutingContext { + inner_routing_context: Some(routing_context), + }) } /// Use a specified `Sequencing` preference, with or without privacy. pub fn createWithSequencing(sequencing: Sequencing) -> APIResult { let veilid_api = get_veilid_api()?; let routing_context = veilid_api.routing_context().with_sequencing(sequencing); - let id = add_routing_context(routing_context); - Ok(VeilidRoutingContext { id }) + Ok(VeilidRoutingContext { + inner_routing_context: Some(routing_context), + }) } // -------------------------------- @@ -115,6 +121,7 @@ impl VeilidRoutingContext { /// * `call_id` - specifies which call to reply to, and it comes from a VeilidUpdate::AppCall, specifically the VeilidAppCall::id() value. /// * `message` - is an answer blob to be returned by the remote node's RoutingContext::app_call() function, and may be up to 32768 bytes pub async fn appCallReply(call_id: String, message: String) -> APIResult<()> { + let message = unmarshall(message); let call_id = match call_id.parse() { Ok(v) => v, Err(e) => { @@ -124,9 +131,7 @@ impl VeilidRoutingContext { } }; let veilid_api = get_veilid_api()?; - veilid_api - .app_call_reply(call_id, message.into_bytes()) - .await?; + veilid_api.app_call_reply(call_id, message).await?; APIRESULT_UNDEFINED } @@ -134,9 +139,8 @@ impl VeilidRoutingContext { // Instance methods // -------------------------------- fn getRoutingContext(&self) -> APIResult { - let rc = (*ROUTING_CONTEXTS).borrow(); - let Some(routing_context) = rc.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("getRoutingContext", "id", self.id)); + let Some(routing_context) = &self.inner_routing_context else { + return APIResult::Err(veilid_core::VeilidAPIError::generic("Unable to getRoutingContext instance. inner_routing_context is None.")); }; APIResult::Ok(routing_context.clone()) } @@ -150,12 +154,11 @@ impl VeilidRoutingContext { #[wasm_bindgen(skip_jsdoc)] pub async fn appMessage(&self, target_string: String, message: String) -> APIResult<()> { let routing_context = self.getRoutingContext()?; + let message = unmarshall(message); let veilid_api = get_veilid_api()?; let target = veilid_api.parse_as_target(target_string).await?; - routing_context - .app_message(target, message.into_bytes()) - .await?; + routing_context.app_message(target, message).await?; APIRESULT_UNDEFINED } @@ -168,15 +171,13 @@ impl VeilidRoutingContext { /// @returns an answer blob of up to `32768` bytes, base64Url encoded. #[wasm_bindgen(skip_jsdoc)] pub async fn appCall(&self, target_string: String, request: String) -> APIResult { - let request: Vec = data_encoding::BASE64URL_NOPAD - .decode(&request.as_bytes()) - .unwrap(); + let request: Vec = unmarshall(request); let routing_context = self.getRoutingContext()?; let veilid_api = get_veilid_api()?; let target = veilid_api.parse_as_target(target_string).await?; let answer = routing_context.app_call(target, request).await?; - let answer = data_encoding::BASE64URL_NOPAD.encode(&answer); + let answer = marshall(&answer); APIResult::Ok(answer) } diff --git a/veilid-wasm/src/veilid_table_db_js.rs b/veilid-wasm/src/veilid_table_db_js.rs index 87cf9ccf..2a8c235a 100644 --- a/veilid-wasm/src/veilid_table_db_js.rs +++ b/veilid-wasm/src/veilid_table_db_js.rs @@ -3,7 +3,7 @@ use super::*; #[wasm_bindgen()] pub struct VeilidTableDB { - id: u32, + inner_table_db: Option, tableName: String, columnCount: u32, } @@ -15,48 +15,35 @@ impl VeilidTableDB { #[wasm_bindgen(constructor)] pub fn new(tableName: String, columnCount: u32) -> Self { Self { - id: 0, + inner_table_db: None, tableName, columnCount, } } fn getTableDB(&self) -> APIResult { - let table_dbs = (*TABLE_DBS).borrow(); - let Some(table_db) = table_dbs.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("getTableDB", "id", self.id)); + let Some(table_db) = &self.inner_table_db else { + return APIResult::Err(veilid_core::VeilidAPIError::generic("Unable to getTableDB instance. Ensure you've called openTable().")); }; APIResult::Ok(table_db.clone()) } /// Get or create the TableDB database table. /// This is called automatically when performing actions on the TableDB. - pub async fn openTable(&mut self) -> APIResult { + pub async fn openTable(&mut self) -> APIResult<()> { let veilid_api = get_veilid_api()?; let tstore = veilid_api.table_store()?; let table_db = tstore .open(&self.tableName, self.columnCount) .await .map_err(veilid_core::VeilidAPIError::generic)?; - let new_id = add_table_db(table_db); - self.id = new_id; - APIResult::Ok(new_id) - } - - /// Release the TableDB instance from memory. - pub fn releaseTable(&mut self) -> bool { - let mut tdbs = (*TABLE_DBS).borrow_mut(); - let status = tdbs.remove(&self.id); - self.id = 0; - if status.is_none() { - return false; - } - return true; + self.inner_table_db = Some(table_db); + APIRESULT_UNDEFINED } /// Delete this TableDB. pub async fn deleteTable(&mut self) -> APIResult { - self.releaseTable(); + self.inner_table_db = None; let veilid_api = get_veilid_api()?; let tstore = veilid_api.table_store()?; @@ -68,7 +55,7 @@ impl VeilidTableDB { } async fn ensureOpen(&mut self) { - if self.id == 0 { + if self.inner_table_db.is_none() { let _ = self.openTable().await; } } @@ -128,14 +115,15 @@ impl VeilidTableDB { let table_db = self.getTableDB()?; let transaction = table_db.transact(); - let transaction_id = add_table_db_transaction(transaction); - APIResult::Ok(VeilidTableDBTransaction { id: transaction_id }) + APIResult::Ok(VeilidTableDBTransaction { + inner_transaction: Some(transaction), + }) } } #[wasm_bindgen] pub struct VeilidTableDBTransaction { - id: u32, + inner_transaction: Option, } #[wasm_bindgen] @@ -144,28 +132,19 @@ impl VeilidTableDBTransaction { /// Use `.createTransaction()` on an instance of `VeilidTableDB` instead. /// @deprecated #[wasm_bindgen(constructor)] - pub fn new(id: u32) -> Self { - Self { id } + pub fn new() -> Self { + Self { + inner_transaction: None, + } } fn getTransaction(&self) -> APIResult { - let transactions = (*TABLE_DB_TRANSACTIONS).borrow(); - let Some(transaction) = transactions.get(&self.id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("getTransaction", "id", &self.id)); + let Some(transaction) = &self.inner_transaction else { + return APIResult::Err(veilid_core::VeilidAPIError::generic("Unable to getTransaction instance. inner_transaction is None.")); }; APIResult::Ok(transaction.clone()) } - /// Releases the transaction from memory. - pub fn releaseTransaction(&mut self) -> bool { - let mut transactions = (*TABLE_DB_TRANSACTIONS).borrow_mut(); - self.id = 0; - if transactions.remove(&self.id).is_none() { - return false; - } - return true; - } - /// Commit the transaction. Performs all actions atomically. pub async fn commit(&self) -> APIResult<()> { let transaction = self.getTransaction()?; diff --git a/veilid-wasm/wasm_build.sh b/veilid-wasm/wasm_build.sh index 7a6bb0f5..5cf70dc6 100755 --- a/veilid-wasm/wasm_build.sh +++ b/veilid-wasm/wasm_build.sh @@ -37,7 +37,7 @@ if [[ "$1" == "release" ]]; then cargo build --target wasm32-unknown-unknown --release mkdir -p $OUTPUTDIR - wasm-bindgen --out-dir $OUTPUTDIR --target web $INPUTDIR/veilid_wasm.wasm + wasm-bindgen --out-dir $OUTPUTDIR --target web --weak-refs $INPUTDIR/veilid_wasm.wasm wasm-strip $OUTPUTDIR/veilid_wasm_bg.wasm else OUTPUTDIR=../target/wasm32-unknown-unknown/debug/pkg @@ -45,7 +45,7 @@ else RUSTFLAGS="-O -g $RUSTFLAGS" cargo build --target wasm32-unknown-unknown mkdir -p $OUTPUTDIR - wasm-bindgen --out-dir $OUTPUTDIR --target web --keep-debug --debug $INPUTDIR/veilid_wasm.wasm + wasm-bindgen --out-dir $OUTPUTDIR --target web --weak-refs --keep-debug --debug $INPUTDIR/veilid_wasm.wasm ./wasm-sourcemap.py $OUTPUTDIR/veilid_wasm_bg.wasm -o $OUTPUTDIR/veilid_wasm_bg.wasm.map --dwarfdump $DWARFDUMP # wasm-strip $OUTPUTDIR/veilid_wasm_bg.wasm fi From 5aeda006a4f12dddbea372c8c8bff200f581bf92 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Mon, 4 Sep 2023 22:13:58 -0400 Subject: [PATCH 9/9] (wasm) basic working unit tests --- Cargo.lock | 1 + veilid-wasm/Cargo.toml | 1 + veilid-wasm/tests/web.rs | 342 +++++++++++++++++++++------------------ veilid-wasm/wasm_test.sh | 4 + 4 files changed, 195 insertions(+), 153 deletions(-) create mode 100755 veilid-wasm/wasm_test.sh diff --git a/Cargo.lock b/Cargo.lock index cdc6fc79..1883fa09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5879,6 +5879,7 @@ dependencies = [ "gloo-utils 0.2.0", "js-sys", "lazy_static", + "parking_lot 0.12.1", "send_wrapper 0.6.0", "serde", "serde-wasm-bindgen", diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 06313daf..059cfd70 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -39,3 +39,4 @@ serde-wasm-bindgen = "0.5.0" [dev-dependencies] wasm-bindgen-test = "^0" +parking_lot = "0.12.1" diff --git a/veilid-wasm/tests/web.rs b/veilid-wasm/tests/web.rs index 8022f2bb..0c251687 100644 --- a/veilid-wasm/tests/web.rs +++ b/veilid-wasm/tests/web.rs @@ -1,182 +1,218 @@ //! Test suite for the Web and headless browsers. - -//XXXXXXXXXXXXXXX -//XXX DOES NOT WORK. +//! These tests only work with WASM_BINDGEN_USE_NO_MODULE=true env var, +//! as otherwise there's no way to access the generated wasm bindings from inside JS. #![cfg(target_arch = "wasm32")] extern crate alloc; extern crate wasm_bindgen_test; -use core::sync::atomic::AtomicBool; +use js_sys::*; +use parking_lot::Once; use veilid_wasm::*; +use wasm_bindgen::*; +use wasm_bindgen_futures::*; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); -static SETUP_ONCE: AtomicBool = AtomicBool::new(false); +static SETUP_ONCE: Once = Once::new(); pub fn setup() -> () { - if SETUP_ONCE - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - console_log("setup()"); + SETUP_ONCE.call_once(|| { + console_log!("setup()"); console_error_panic_hook::set_once(); - wasm_logger::init(wasm_logger::Config::new(Level::Trace)); init_callbacks(); - } + }) } -// xxx needs updating to new keys and veilid_api object + fn init_callbacks() { assert_eq!(js_sys::eval(r#" window.sleep = (milliseconds) => { return new Promise(resolve => setTimeout(resolve, milliseconds)) }; - window.stateChangeCallback = async (stateChange) => { console.log("State change: " + stateChange); }; - window.configCallback = (configKey) => { - switch(configKey) { - case "namespace": return ""; - case "capabilities.disable": return []; - case "tablestore.directory": return ""; - case "network.routing_table.node_id": return []; - case "network.routing_table.node_id_secret": return []; - case "network.routing_table.bootstrap": return []; - case "network.routing_table.limit_over_attached": return 64; - case "network.routing_table.limit_fully_attached": return 32; - case "network.routing_table.limit_attached_strong": return 16; - case "network.routing_table.limit_attached_good": return 8; - case "network.routing_table.limit_attached_weak": return 4; - case "network.rpc.concurrency": return 2; - case "network.rpc.queue_size": return 128; - case "network.rpc.max_timestamp_behind": return 10000000; - case "network.rpc.max_timestamp_ahead": return 10000000; - case "network.rpc.timeout": return 10000000; - case "network.rpc.max_route_hop_count": return 4; - case "network.rpc.default_route_hop_count": return 1; - case "network.dht.max_find_node_count": return 20; - case "network.dht.resolve_node_timeout": return 10000; - case "network.dht.resolve_node_count": return 1; - case "network.dht.resolve_node_fanout": return 4; - case "network.dht.get_value_timeout": return 10000; - case "network.dht.get_value_count": return 3; - case "network.dht.get_value_fanout": return 4; - case "network.dht.set_value_timeout": return 10000; - case "network.dht.set_value_count": return 5; - case "network.dht.set_value_fanout": return 4; - case "network.dht.min_peer_count": return 20; - case "network.dht.min_peer_refresh_time": return 2000000; - case "network.dht.validate_dial_info_receipt_time": return 5000000; - case "network.upnp": return false; - case "network.detect_address_changes": return true; - case "network.address_filter": return true; - case "network.restricted_nat_retries": return 3; - case "network.tls.certificate_path": return ""; - case "network.tls.private_key_path": return ""; - case "network.application.path": return "/app"; - case "network.application.https.enabled": return false; - case "network.application.https.listen_address": return ""; - case "network.application.http.enabled": return false; - case "network.application.http.listen_address": return ""; - case "network.protocol.udp.enabled": return false; - case "network.protocol.udp.socket_pool_size": return 0; - case "network.protocol.udp.listen_address": return ""; - case "network.protocol.udp.public_address": return ""; - case "network.protocol.tcp.connect": return false; - case "network.protocol.tcp.listen": return false; - case "network.protocol.tcp.max_connections": return 32; - case "network.protocol.tcp.listen_address": return ""; - case "network.protocol.tcp.public_address": return ""; - case "network.protocol.ws.connect": return true; - case "network.protocol.ws.listen": return false; - case "network.protocol.ws.max_connections": return 16; - case "network.protocol.ws.listen_address": return ""; - case "network.protocol.ws.path": return "/ws"; - case "network.protocol.ws.public_address": return ""; - case "network.protocol.wss.connect": return true; - case "network.protocol.wss.listen": return false; - case "network.protocol.wss.max_connections": return 16; - case "network.protocol.wss.listen_address": return ""; - case "network.protocol.wss.path": return "/ws"; - case "network.protocol.wss.public_address": return ""; - default: - console.log("config key '" + key +"' doesn't exist"); break; - } + window.stateChangeCallback = async (stateChange) => { + delete stateChange.peers; // makes logs less verbose + console.log("State change: ", JSON.stringify(stateChange, null, 2)); }; + window.veilidCoreInitConfig = { + logging: { + api: { + enabled: true, + level: 'Info', + }, + performance: { + enabled: false, + level: 'Info', + logs_in_timings: false, + logs_in_console: false, + }, + }, + }; + + window.veilidCoreStartupConfig = { + program_name: 'veilid-wasm-test', + namespace: '', + capabilities: { + disable: [], + }, + protected_store: { + allow_insecure_fallback: true, + always_use_insecure_storage: true, + directory: '', + delete: false, + device_encryption_key_password: 'some-user-secret-value', + // "new_device_encryption_key_password": "an-updated-user-secret-value" + }, + table_store: { + directory: '', + delete: false, + }, + block_store: { + directory: '', + delete: false, + }, + network: { + connection_initial_timeout_ms: 2000, + connection_inactivity_timeout_ms: 60000, + max_connections_per_ip4: 32, + max_connections_per_ip6_prefix: 32, + max_connections_per_ip6_prefix_size: 56, + max_connection_frequency_per_min: 128, + client_whitelist_timeout_ms: 300000, + reverse_connection_receipt_time_ms: 5000, + hole_punch_receipt_time_ms: 5000, + network_key_password: '', + disable_capabilites: [], + routing_table: { + node_id: [], + node_id_secret: [], + bootstrap: [ + 'ws://bootstrap.veilid.net:5150/ws', + ], + limit_over_attached: 64, + limit_fully_attached: 32, + limit_attached_strong: 16, + limit_attached_good: 8, + limit_attached_weak: 4, + }, + rpc: { + concurrency: 0, + queue_size: 1024, + max_timestamp_behind_ms: 10000, + max_timestamp_ahead_ms: 10000, + timeout_ms: 5000, + max_route_hop_count: 4, + default_route_hop_count: 1, + }, + dht: { + max_find_node_count: 20, + resolve_node_timeout_ms: 10000, + resolve_node_count: 1, + resolve_node_fanout: 4, + get_value_timeout_ms: 10000, + get_value_count: 3, + get_value_fanout: 4, + set_value_timeout_ms: 10000, + set_value_count: 5, + set_value_fanout: 4, + min_peer_count: 20, + min_peer_refresh_time_ms: 60000, + validate_dial_info_receipt_time_ms: 2000, + local_subkey_cache_size: 128, + local_max_subkey_cache_memory_mb: 256, + remote_subkey_cache_size: 1024, + remote_max_records: 65536, + remote_max_subkey_cache_memory_mb: 256, + remote_max_storage_space_mb: 0, + }, + upnp: true, + detect_address_changes: true, + restricted_nat_retries: 0, + tls: { + certificate_path: '', + private_key_path: '', + connection_initial_timeout_ms: 2000, + }, + application: { + https: { + enabled: false, + listen_address: ':5150', + path: 'app', + }, + http: { + enabled: false, + listen_address: ':5150', + path: 'app', + }, + }, + protocol: { + udp: { + enabled: false, + socket_pool_size: 0, + listen_address: '', + }, + tcp: { + connect: false, + listen: false, + max_connections: 32, + listen_address: '', + }, + ws: { + connect: true, + listen: true, + max_connections: 16, + listen_address: ':5150', + path: 'ws', + }, + wss: { + connect: true, + listen: false, + max_connections: 16, + listen_address: '', + path: 'ws', + }, + }, + }, + }; true "#).expect("failed to eval"), JsValue::TRUE); } -/////////////////////////////////////////////////////////////////////////////////////////////////// -/// - -#[wasm_bindgen_test] -fn test_construct() { - setup(); - - assert_eq!( - js_sys::eval( - r#" - let vc = new VeilidCore(); - true - "# - ) - .expect("failed to eval"), - JsValue::TRUE - ); -} - -#[wasm_bindgen_test(async)] -async fn test_startup_shutdown() { - setup(); - - assert_eq!( - JsFuture::from( - js_sys::eval( - r#" - (async function() { - let vc = new VeilidCore(); - await vc.startup(window.stateChangeCallback, window.configCallback); - await vc.shutdown(); - return true; - })().then(v => { - console.log("finished: " + v); - return v; - }); - "# - ) - .expect("failed to eval") +/// Helper for converting an eval Promise result into a JsValue +async fn eval_promise(source: &str) -> JsValue { + JsFuture::from( + eval(source) + .expect("Failed to eval") .dyn_into::() - .unwrap() - ) - .await, - Ok(JsValue::TRUE) - ); + .unwrap(), + ) + .await + .unwrap() } -#[wasm_bindgen_test(async)] -async fn test_attach_detach() { +// ---------------------------------------------------------------- + +// TODO: now that veilidClient uses a single instance of VeilidAPI, +// subsequent tests fail because veilidCore has already been initialized. +#[wasm_bindgen_test()] +async fn test_kitchen_sink() { setup(); - assert_eq!( - JsFuture::from( - js_sys::eval( - r#" - (async function() { - let vc = new VeilidCore(); - await vc.startup(window.stateChangeCallback, window.configCallback); - await vc.attach(); - await window.sleep(1000); - await vc.detach(); - await vc.shutdown(); - return true; - })().then(v => { - console.log("finished: " + v); - return v; - }); - "# - ) - .expect("failed to eval") - .dyn_into::() - .unwrap() - ) - .await, - Ok(JsValue::TRUE) - ); + let res = eval_promise( + r#" + (async function () { + const { veilidClient } = wasm_bindgen; // only accessible in no_module mode. + veilidClient.initializeCore(window.veilidCoreInitConfig); + await veilidClient.startupCore(window.stateChangeCallback, JSON.stringify(window.veilidCoreStartupConfig)); + + console.log(veilidClient.versionString()); + await veilidClient.attach(); + + await sleep(10000); + await veilidClient.detach(); + await veilidClient.shutdownCore(); + + return true; + })(); + "#, + ).await; + + assert_eq!(res, JsValue::TRUE); } diff --git a/veilid-wasm/wasm_test.sh b/veilid-wasm/wasm_test.sh new file mode 100755 index 00000000..27209505 --- /dev/null +++ b/veilid-wasm/wasm_test.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -eo pipefail + +WASM_BINDGEN_USE_NO_MODULE=true wasm-pack test --firefox "$@"