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? +}