From 7569c08adaa80a2becb74045246f2eb7c80381ac Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 May 2022 10:40:13 +0200 Subject: [PATCH 01/58] feat(crypto) Add `wasm-bindgen` as a dep and simplify `Cargo.toml`. --- crates/matrix-sdk-crypto/Cargo.toml | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 8b3efda5c..f7687f30f 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -49,22 +49,14 @@ thiserror = "1.0.30" tracing = "0.1.34" zeroize = { version = "1.3.0", features = ["zeroize_derive"] } -[target.'cfg(target_arch = "wasm32")'.dependencies.ruma] -version = "0.6.1" -features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +ruma = { version = "0.6.1", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } +vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "e09c93f2c8df9770793abeec57ed984d5e1f3834" } -[target.'cfg(target_arch = "wasm32")'.dependencies.vodozemac] -git = "https://github.com/matrix-org/vodozemac" -rev = "e09c93f2c8df9770793abeec57ed984d5e1f3834" -features = ["js"] - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.ruma] -version = "0.6.1" -features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.vodozemac] -git = "https://github.com/matrix-org/vodozemac" -rev = "e09c93f2c8df9770793abeec57ed984d5e1f3834" +[target.'cfg(target_arch = "wasm32")'.dependencies] +ruma = { version = "0.6.1", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } +vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "e09c93f2c8df9770793abeec57ed984d5e1f3834", features = ["js"] } +wasm-bindgen = "0.2.80" [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] } From 7817af9aa6d274a5cbb46fbe1f33f5da03636206 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 May 2022 10:40:32 +0200 Subject: [PATCH 02/58] feat(crypto) Add the `js` feature. This patch updates to code to raise a compilation error if the `js` feature is used for another architecture than `wasm32`. --- crates/matrix-sdk-crypto/Cargo.toml | 1 + crates/matrix-sdk-crypto/src/lib.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index f7687f30f..3635ba628 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -20,6 +20,7 @@ default = [] qrcode = ["matrix-qrcode"] backups_v1 = ["olm-rs", "bs58"] docsrs = [] +js = [] # Testing helpers for implementations based upon this testing = ["http"] diff --git a/crates/matrix-sdk-crypto/src/lib.rs b/crates/matrix-sdk-crypto/src/lib.rs index 0a7cb642b..d98dfd56b 100644 --- a/crates/matrix-sdk-crypto/src/lib.rs +++ b/crates/matrix-sdk-crypto/src/lib.rs @@ -16,6 +16,11 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] +#[cfg(all(feature = "js", not(target_arch = "wasm32")))] +compile_error!( + "The `js` feature must be enabled only for the `wasm32` target (either `wasm32-unknown-unknown` or `wasm32-wasi`)." +); + #[cfg(feature = "backups_v1")] pub mod backups; mod error; From d3c20b2a133c0a3a4e7338ebdefdcc284585b2f1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 May 2022 10:51:18 +0200 Subject: [PATCH 03/58] feat(crypto) Generate a `cdylib` for the crate. Ask `rustc` to generate a dynamic system library, which will be useful to generate a Wasm module. --- crates/matrix-sdk-crypto/Cargo.toml | 3 +++ crates/matrix-sdk-crypto/src/lib.rs | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 3635ba628..5e31b4945 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -15,6 +15,9 @@ version = "0.4.1" features = ["docsrs"] rustdoc-args = ["--cfg", "docsrs"] +[lib] +crate-type = ["cdylib"] + [features] default = [] qrcode = ["matrix-qrcode"] diff --git a/crates/matrix-sdk-crypto/src/lib.rs b/crates/matrix-sdk-crypto/src/lib.rs index d98dfd56b..2f58f8418 100644 --- a/crates/matrix-sdk-crypto/src/lib.rs +++ b/crates/matrix-sdk-crypto/src/lib.rs @@ -15,8 +15,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] - -#[cfg(all(feature = "js", not(target_arch = "wasm32")))] +#![cfg(all(feature = "js", not(target_arch = "wasm32")))] compile_error!( "The `js` feature must be enabled only for the `wasm32` target (either `wasm32-unknown-unknown` or `wasm32-wasi`)." ); From 5c6a6464c4a7cc79d8e6251dd51dcb2a7bb49013 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 May 2022 11:50:40 +0200 Subject: [PATCH 04/58] chore(crypto) Fix a typo in the code. --- crates/matrix-sdk-crypto/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/lib.rs b/crates/matrix-sdk-crypto/src/lib.rs index 2f58f8418..ea981d3cc 100644 --- a/crates/matrix-sdk-crypto/src/lib.rs +++ b/crates/matrix-sdk-crypto/src/lib.rs @@ -15,7 +15,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] -#![cfg(all(feature = "js", not(target_arch = "wasm32")))] +#[cfg(all(feature = "js", not(target_arch = "wasm32")))] compile_error!( "The `js` feature must be enabled only for the `wasm32` target (either `wasm32-unknown-unknown` or `wasm32-wasi`)." ); From 0fe0910feadf005db179a1a77c3614cdbd19508d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 May 2022 13:23:16 +0200 Subject: [PATCH 05/58] feat(crypto) Reduce Wasm binary size by enabling LTO. --- Cargo.toml | 3 +++ crates/matrix-sdk-crypto/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 795fafab0..fe375c1e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,3 +2,6 @@ members = ["benchmarks", "crates/*", "labs/*", "xtask"] # xtask and labs should only be compiled when invoked explicitly default-members = ["benchmarks", "crates/*"] + +[profile.release] +lto = true \ No newline at end of file diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 5e31b4945..d19d67348 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -74,4 +74,4 @@ tokio = { version = "1.7.0", default-features = false, features = ["macros", "rt [target.'cfg(target_arch = "wasm32")'.dev-dependencies] getrandom = { version = "0.2.6", features = ["js"] } -wasm-bindgen-test = "0.3.24" +wasm-bindgen-test = "0.3.24" \ No newline at end of file From 3318789a8b3bbacee12e1cea200ee289cac8966c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 May 2022 17:22:16 +0200 Subject: [PATCH 06/58] feat(crypto) Implement `UserId`, `DeviceId` and `OlmMachine` in Wasm for JS. --- crates/matrix-sdk-crypto/Cargo.toml | 6 ++-- crates/matrix-sdk-crypto/src/js/mod.rs | 40 +++++++++++++++++++++++++ crates/matrix-sdk-crypto/src/lib.rs | 3 ++ crates/matrix-sdk-crypto/src/machine.rs | 26 ++++++++++++++++ examples/js/Makefile | 2 ++ examples/js/index.js | 14 +++++++++ 6 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 crates/matrix-sdk-crypto/src/js/mod.rs create mode 100644 examples/js/Makefile create mode 100644 examples/js/index.js diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index d19d67348..3e197c718 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -54,13 +54,15 @@ tracing = "0.1.34" zeroize = { version = "1.3.0", features = ["zeroize_derive"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -ruma = { version = "0.6.1", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } +ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "e09c93f2c8df9770793abeec57ed984d5e1f3834" } [target.'cfg(target_arch = "wasm32")'.dependencies] -ruma = { version = "0.6.1", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } +ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "e09c93f2c8df9770793abeec57ed984d5e1f3834", features = ["js"] } wasm-bindgen = "0.2.80" +wasm-bindgen-futures = "0.4.30" +js-sys = "0.3.49" [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] } diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto/src/js/mod.rs new file mode 100644 index 000000000..e9ab50793 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/mod.rs @@ -0,0 +1,40 @@ +//! Additional API that can be useful from JavaScript. + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub struct UserId { + pub(crate) inner: ruma::OwnedUserId, +} + +#[wasm_bindgen] +impl UserId { + #[wasm_bindgen(constructor)] + pub fn new(id: &str) -> Result { + Ok(Self { inner: ruma::UserId::parse(id).map_err(|e| e.to_string())? }) + } + + pub fn localpart(&self) -> String { + self.inner.localpart().to_owned() + } + + #[wasm_bindgen(getter, js_name = "isHistorical")] + pub fn is_historical(&self) -> bool { + self.inner.is_historical() + } +} + +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub struct DeviceId { + pub(crate) inner: ruma::OwnedDeviceId, +} + +#[wasm_bindgen] +impl DeviceId { + #[wasm_bindgen(constructor)] + pub fn new(id: &str) -> DeviceId { + Self { inner: id.into() } + } +} diff --git a/crates/matrix-sdk-crypto/src/lib.rs b/crates/matrix-sdk-crypto/src/lib.rs index ea981d3cc..1f2e59395 100644 --- a/crates/matrix-sdk-crypto/src/lib.rs +++ b/crates/matrix-sdk-crypto/src/lib.rs @@ -26,6 +26,7 @@ mod error; mod file_encryption; mod gossiping; mod identities; +mod js; mod machine; pub mod olm; mod requests; @@ -82,6 +83,8 @@ pub use identities::{ Device, LocalTrust, MasterPubkey, OwnUserIdentity, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities, ReadOnlyUserIdentity, UserDevices, UserIdentities, UserIdentity, }; +#[cfg(feature = "js")] +pub use js::*; pub use machine::OlmMachine; #[cfg(feature = "qrcode")] pub use matrix_qrcode; diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 0d5ee4bd0..20d9244c5 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -47,10 +47,14 @@ use ruma::{ }; use serde_json::Value; use tracing::{debug, error, info, trace, warn}; +#[cfg(feature = "js")] +use wasm_bindgen::prelude::*; use zeroize::Zeroize; #[cfg(feature = "backups_v1")] use crate::backups::BackupMachine; +#[cfg(feature = "js")] +use crate::js; use crate::{ error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult}, gossiping::GossipMachine, @@ -72,6 +76,7 @@ use crate::{ /// State machine implementation of the Olm/Megolm encryption protocol used for /// Matrix end to end encryption. +#[cfg_attr(feature = "js", wasm_bindgen)] #[derive(Clone)] pub struct OlmMachine { /// The unique user id that owns this account. @@ -1514,6 +1519,27 @@ impl OlmMachine { &self.backup_machine } } + +#[cfg_attr(feature = "js", wasm_bindgen)] +impl OlmMachine { + #[wasm_bindgen(constructor)] + pub async fn js_new(user_id: js::UserId, device_id: js::DeviceId) -> js_sys::Promise { + js_sys::Promise::new(&mut |resolve, reject| { + let user_id = user_id.inner.clone(); + let device_id = device_id.inner.clone(); + + wasm_bindgen_futures::spawn_local(async move { + let result = Self::new(user_id.as_ref(), device_id.as_ref()).await; + + resolve + .call1(&wasm_bindgen::JsValue::UNDEFINED, &wasm_bindgen::JsValue::from(result)) + .unwrap_throw(); + }) + }) + } + //pub async fn new(user_id: &UserId, device_id: &DeviceId) -> Self { +} + #[cfg(any(feature = "testing", test))] pub(crate) mod testing { #![allow(dead_code)] diff --git a/examples/js/Makefile b/examples/js/Makefile new file mode 100644 index 000000000..0f840ce07 --- /dev/null +++ b/examples/js/Makefile @@ -0,0 +1,2 @@ +build: + wasm-pack build --target nodejs ../../crates/matrix-sdk-crypto --features js diff --git a/examples/js/index.js b/examples/js/index.js new file mode 100644 index 000000000..c94be972b --- /dev/null +++ b/examples/js/index.js @@ -0,0 +1,14 @@ +const { UserId, DeviceId, OlmMachine } = require('../../crates/matrix-sdk-crypto/pkg'); + +async function run_example() { + const user_id = new UserId('@alice:example.org'); + console.log(user_id); + + const device_id = new DeviceId("DEVICE_ID"); + console.log(device_id); + + const olm_machine = await new OlmMachine(user_id, device_id); + console.log(olm_machine); +} + +run_example(); From 3aa46fa746a33be6f9cb2c4cdf75208b5be0d2c1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 May 2022 15:44:49 +0200 Subject: [PATCH 07/58] feat(crypto) Implement `OlmMachine.receive_sync_changes` & friends. --- .../matrix-sdk-crypto/src/js/identifiers.rs | 56 ++++++++++++ crates/matrix-sdk-crypto/src/js/machine.rs | 85 +++++++++++++++++++ crates/matrix-sdk-crypto/src/js/mod.rs | 41 +-------- .../matrix-sdk-crypto/src/js/sync_events.rs | 50 +++++++++++ crates/matrix-sdk-crypto/src/machine.rs | 25 ------ examples/js/Makefile | 2 +- examples/js/index.js | 24 ++++-- 7 files changed, 214 insertions(+), 69 deletions(-) create mode 100644 crates/matrix-sdk-crypto/src/js/identifiers.rs create mode 100644 crates/matrix-sdk-crypto/src/js/machine.rs create mode 100644 crates/matrix-sdk-crypto/src/js/sync_events.rs diff --git a/crates/matrix-sdk-crypto/src/js/identifiers.rs b/crates/matrix-sdk-crypto/src/js/identifiers.rs new file mode 100644 index 000000000..e581d4dbd --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/identifiers.rs @@ -0,0 +1,56 @@ +//! Types for [Matrix](https://matrix.org/) identifiers for devices, +//! events, keys, rooms, servers, users and URIs. + +use wasm_bindgen::prelude::*; + +/// A Matrix [user ID]. +/// +/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub struct UserId { + pub(crate) inner: ruma::OwnedUserId, +} + +#[wasm_bindgen] +impl UserId { + /// Parse/validate and create a new `UserId`. + #[wasm_bindgen(constructor)] + pub fn new(id: &str) -> Result { + Ok(Self { inner: ruma::UserId::parse(id).map_err(|e| e.to_string())? }) + } + + /// Returns the user's localpart. + pub fn localpart(&self) -> String { + self.inner.localpart().to_owned() + } + + /// Whether this user ID is a historical one. + /// + /// A historical user ID is one that doesn't conform to the latest + /// specification of the user ID grammar but is still accepted + /// because it was previously allowed. + #[wasm_bindgen(getter, js_name = "isHistorical")] + pub fn is_historical(&self) -> bool { + self.inner.is_historical() + } +} + +/// A Matrix key ID. +/// +/// Device identifiers in Matrix are completely opaque character +/// sequences. This type is provided simply for its semantic value. +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub struct DeviceId { + pub(crate) inner: ruma::OwnedDeviceId, +} + +#[wasm_bindgen] +impl DeviceId { + /// Create a new `DeviceId`. + #[wasm_bindgen(constructor)] + pub fn new(id: &str) -> DeviceId { + Self { inner: id.into() } + } +} diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs new file mode 100644 index 000000000..89c6f68e8 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -0,0 +1,85 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use js_sys::{Array, Map, Promise, Set}; +use ruma::{DeviceKeyAlgorithm, UInt}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::js::{identifiers, sync_events}; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: String); +} + +#[wasm_bindgen] +#[derive(Debug)] +pub struct OlmMachine { + inner: Arc, +} + +#[cfg_attr(feature = "js", wasm_bindgen)] +impl OlmMachine { + #[wasm_bindgen(constructor)] + pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise { + let user_id = user_id.inner.clone(); + let device_id = device_id.inner.clone(); + + future_to_promise(async move { + Ok(JsValue::from(OlmMachine { + inner: Arc::new(crate::OlmMachine::new(user_id.as_ref(), device_id.as_ref()).await), + })) + }) + } + + pub fn receive_sync_changes( + &self, + to_device_events: &str, + changed_devices: &sync_events::DeviceLists, + one_time_key_counts: &Map, + unused_fallback_keys: &Set, + ) -> Promise { + let to_device_events = serde_json::from_str(to_device_events).unwrap(); + let changed_devices = changed_devices.inner.clone(); + let one_time_key_counts: BTreeMap = one_time_key_counts + .entries() + .into_iter() + .filter_map(|js_value| { + let pair = Array::from(&js_value.ok()?); + let (key, value) = ( + DeviceKeyAlgorithm::from(pair.at(0).as_string()?), + UInt::new(pair.at(1).as_f64()? as u64)?, + ); + + Some((key, value)) + }) + .collect(); + + let unused_fallback_keys: Option> = Some( + unused_fallback_keys + .values() + .into_iter() + .filter_map(|js_value| Some(DeviceKeyAlgorithm::from(js_value.ok()?.as_string()?))) + .collect(), + ); + + let me = self.inner.clone(); + + future_to_promise(async move { + Ok(JsValue::from( + serde_json::to_string( + &me.receive_sync_changes( + to_device_events, + &changed_devices, + &one_time_key_counts, + unused_fallback_keys.as_deref(), + ) + .await + .unwrap(), + ) + .unwrap(), + )) + }) + } +} diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto/src/js/mod.rs index e9ab50793..8090ba327 100644 --- a/crates/matrix-sdk-crypto/src/js/mod.rs +++ b/crates/matrix-sdk-crypto/src/js/mod.rs @@ -1,40 +1,5 @@ //! Additional API that can be useful from JavaScript. -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -#[derive(Debug, Clone)] -pub struct UserId { - pub(crate) inner: ruma::OwnedUserId, -} - -#[wasm_bindgen] -impl UserId { - #[wasm_bindgen(constructor)] - pub fn new(id: &str) -> Result { - Ok(Self { inner: ruma::UserId::parse(id).map_err(|e| e.to_string())? }) - } - - pub fn localpart(&self) -> String { - self.inner.localpart().to_owned() - } - - #[wasm_bindgen(getter, js_name = "isHistorical")] - pub fn is_historical(&self) -> bool { - self.inner.is_historical() - } -} - -#[wasm_bindgen] -#[derive(Debug, Clone)] -pub struct DeviceId { - pub(crate) inner: ruma::OwnedDeviceId, -} - -#[wasm_bindgen] -impl DeviceId { - #[wasm_bindgen(constructor)] - pub fn new(id: &str) -> DeviceId { - Self { inner: id.into() } - } -} +pub mod identifiers; +pub mod machine; +pub mod sync_events; diff --git a/crates/matrix-sdk-crypto/src/js/sync_events.rs b/crates/matrix-sdk-crypto/src/js/sync_events.rs new file mode 100644 index 000000000..7cd050194 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/sync_events.rs @@ -0,0 +1,50 @@ +//! `GET /_matrix/client/*/sync` + +use js_sys::Array; +use wasm_bindgen::prelude::*; + +use crate::js::identifiers; + +/// Information on E2E device updates. +#[wasm_bindgen] +#[derive(Debug)] +pub struct DeviceLists { + pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists, +} + +#[wasm_bindgen] +impl DeviceLists { + /// Create an empty `DeviceLists`. + #[wasm_bindgen(constructor)] + pub fn new() -> DeviceLists { + Self { inner: Default::default() } + } + + /// Returns true if there are no device list updates. + #[wasm_bindgen(js_name = "isEmpty")] + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// List of users who have updated their device identity keys or who now + /// share an encrypted room with the client since the previous sync + pub fn changed(&self) -> Array { + self.inner + .changed + .iter() + .map(|user| identifiers::UserId { inner: user.clone() }) + .map(JsValue::from) + .collect() + } + + /// List of users who no longer share encrypted rooms since the previous + /// sync response. + pub fn left(&self) -> Array { + self.inner + .left + .iter() + .map(|user| identifiers::UserId { inner: user.clone() }) + .map(JsValue::from) + .collect() + } +} diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 20d9244c5..6bd7f0ad3 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -47,14 +47,10 @@ use ruma::{ }; use serde_json::Value; use tracing::{debug, error, info, trace, warn}; -#[cfg(feature = "js")] -use wasm_bindgen::prelude::*; use zeroize::Zeroize; #[cfg(feature = "backups_v1")] use crate::backups::BackupMachine; -#[cfg(feature = "js")] -use crate::js; use crate::{ error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult}, gossiping::GossipMachine, @@ -76,7 +72,6 @@ use crate::{ /// State machine implementation of the Olm/Megolm encryption protocol used for /// Matrix end to end encryption. -#[cfg_attr(feature = "js", wasm_bindgen)] #[derive(Clone)] pub struct OlmMachine { /// The unique user id that owns this account. @@ -1520,26 +1515,6 @@ impl OlmMachine { } } -#[cfg_attr(feature = "js", wasm_bindgen)] -impl OlmMachine { - #[wasm_bindgen(constructor)] - pub async fn js_new(user_id: js::UserId, device_id: js::DeviceId) -> js_sys::Promise { - js_sys::Promise::new(&mut |resolve, reject| { - let user_id = user_id.inner.clone(); - let device_id = device_id.inner.clone(); - - wasm_bindgen_futures::spawn_local(async move { - let result = Self::new(user_id.as_ref(), device_id.as_ref()).await; - - resolve - .call1(&wasm_bindgen::JsValue::UNDEFINED, &wasm_bindgen::JsValue::from(result)) - .unwrap_throw(); - }) - }) - } - //pub async fn new(user_id: &UserId, device_id: &DeviceId) -> Self { -} - #[cfg(any(feature = "testing", test))] pub(crate) mod testing { #![allow(dead_code)] diff --git a/examples/js/Makefile b/examples/js/Makefile index 0f840ce07..f932f6cb5 100644 --- a/examples/js/Makefile +++ b/examples/js/Makefile @@ -1,2 +1,2 @@ build: - wasm-pack build --target nodejs ../../crates/matrix-sdk-crypto --features js + wasm-pack build --release --target nodejs ../../crates/matrix-sdk-crypto --features js diff --git a/examples/js/index.js b/examples/js/index.js index c94be972b..f37a8dc62 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -1,14 +1,28 @@ -const { UserId, DeviceId, OlmMachine } = require('../../crates/matrix-sdk-crypto/pkg'); +const { UserId, DeviceId, OlmMachine, ToDevice, DeviceLists } = require('../../crates/matrix-sdk-crypto/pkg'); async function run_example() { const user_id = new UserId('@alice:example.org'); - console.log(user_id); - - const device_id = new DeviceId("DEVICE_ID"); - console.log(device_id); + const device_id = new DeviceId('DEVICE_ID'); const olm_machine = await new OlmMachine(user_id, device_id); console.log(olm_machine); + + const to_device_events = '{}'; + const changed_devices = new DeviceLists(); + const one_time_key_counts = new Map(); + one_time_key_counts.set('foo', 42); + one_time_key_counts.set('bar', 153); + const unused_fallback_keys = new Set(); + unused_fallback_keys.add('baz'); + unused_fallback_keys.add('qux'); + + const decrypted_to_device = await olm_machine.receive_sync_changes( + to_device_events, + changed_devices, + one_time_key_counts, + unused_fallback_keys, + ); + console.log(JSON.parse(decrypted_to_device)); } run_example(); From 901715bcf3bc645a81b18d809add304349fb9218 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 11 May 2022 21:26:31 +0200 Subject: [PATCH 08/58] chore(crypto) Generate a smaller Wasm module. --- crates/matrix-sdk-crypto/Cargo.toml | 3 +++ examples/js/Makefile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 3e197c718..0c4bd4ab0 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -15,6 +15,9 @@ version = "0.4.1" features = ["docsrs"] rustdoc-args = ["--cfg", "docsrs"] +[package.metadata.wasm-pack.profile.release] +wasm-opt = ['-Oz'] + [lib] crate-type = ["cdylib"] diff --git a/examples/js/Makefile b/examples/js/Makefile index f932f6cb5..4813643a6 100644 --- a/examples/js/Makefile +++ b/examples/js/Makefile @@ -1,2 +1,2 @@ build: - wasm-pack build --release --target nodejs ../../crates/matrix-sdk-crypto --features js + RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs ../../crates/matrix-sdk-crypto --features js From 7e5eec82c541ac6026dad41689714d67405967fa Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 12 May 2022 14:57:30 +0200 Subject: [PATCH 09/58] feat(crypto) Implement `OlmMachine.outgoing_requests`. --- crates/matrix-sdk-crypto/src/js/machine.rs | 19 +- crates/matrix-sdk-crypto/src/js/mod.rs | 1 + crates/matrix-sdk-crypto/src/js/requests.rs | 233 ++++++++++++++++++++ examples/js/index.js | 4 + 4 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 crates/matrix-sdk-crypto/src/js/requests.rs diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 89c6f68e8..de0657beb 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -55,7 +55,6 @@ impl OlmMachine { Some((key, value)) }) .collect(); - let unused_fallback_keys: Option> = Some( unused_fallback_keys .values() @@ -82,4 +81,22 @@ impl OlmMachine { )) }) } + + pub fn outgoing_requests(&self) -> Promise { + let me = self.inner.clone(); + + future_to_promise(async move { + Ok(JsValue::from( + me.outgoing_requests() + .await + .unwrap() + .into_iter() + .map(TryFrom::try_from) + .collect::, _>>() + .unwrap() + .into_iter() + .collect::(), + )) + }) + } } diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto/src/js/mod.rs index 8090ba327..047841d35 100644 --- a/crates/matrix-sdk-crypto/src/js/mod.rs +++ b/crates/matrix-sdk-crypto/src/js/mod.rs @@ -2,4 +2,5 @@ pub mod identifiers; pub mod machine; +pub mod requests; pub mod sync_events; diff --git a/crates/matrix-sdk-crypto/src/js/requests.rs b/crates/matrix-sdk-crypto/src/js/requests.rs new file mode 100644 index 000000000..d077fbf81 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/requests.rs @@ -0,0 +1,233 @@ +use js_sys::JsString; +use serde_json::json; +use wasm_bindgen::prelude::*; + +use crate::{OutgoingRequest, OutgoingRequests}; + +/// Data for a request to the `upload_keys` API endpoint. +/// +/// Publishes end-to-end encryption keys for the device. +#[derive(Debug)] +#[wasm_bindgen(getter_with_clone)] +pub struct KeysUploadRequest { + /// The request ID. + #[wasm_bindgen(readonly)] + pub request_id: JsString, + + /// A JSON-encoded object of form: + /// + /// ```json + /// {"device_keys": …, "one_time_keys": …} + /// ``` + #[wasm_bindgen(readonly)] + pub body: JsString, +} + +/// Data for a request to the `get_keys` API endpoint. +/// +/// Returns the current devices and identity keys for the given users. +#[derive(Debug)] +#[wasm_bindgen(getter_with_clone)] +pub struct KeysQueryRequest { + /// The request ID. + #[wasm_bindgen(readonly)] + pub request_id: JsString, + + /// A JSON-encoded object of form: + /// + /// ``` + /// {"timeout": …, "device_keys": …, "token": …} + /// ``` + #[wasm_bindgen(readonly)] + pub body: JsString, +} + +/// Data for a request to the `claim_keys` API endpoint. +/// +/// Claims one-time keys for use in pre-key messages. +#[derive(Debug)] +#[wasm_bindgen(getter_with_clone)] +pub struct KeysClaimRequest { + /// The request ID. + #[wasm_bindgen(readonly)] + pub request_id: JsString, + + /// A JSON-encoded object of form: + /// + /// ``` + /// {"timeout": …, "one_time_keys": …} + /// ``` + #[wasm_bindgen(readonly)] + pub body: JsString, +} + +/// Data for a request to the `send_event_to_device` API endpoint. +/// +/// Send an event to a device or devices. +#[derive(Debug)] +#[wasm_bindgen(getter_with_clone)] +pub struct ToDeviceRequest { + /// The request ID. + #[wasm_bindgen(readonly)] + pub request_id: JsString, + + /// A JSON-encoded object of form: + /// + /// ``` + /// {"event_type": …, "transaction_id": …, "messages": …} + /// ``` + #[wasm_bindgen(readonly)] + pub body: JsString, +} + +/// Data for a request to the `upload_signatures` API endpoint. +/// +/// Publishes cross-signing signatures for the user. +#[derive(Debug)] +#[wasm_bindgen(getter_with_clone)] +pub struct SignatureUploadRequest { + /// The request ID. + #[wasm_bindgen(readonly)] + pub request_id: JsString, + + /// A JSON-encoded object of form: + /// + /// ``` + /// {"signed_keys": …, "transaction_id": …, "messages": …} + /// ``` + #[wasm_bindgen(readonly)] + pub body: JsString, +} + +/// A customized owned request type for sending out room messages. +#[derive(Debug)] +#[wasm_bindgen(getter_with_clone)] +pub struct RoomMessageRequest { + /// The request ID. + #[wasm_bindgen(readonly)] + pub request_id: JsString, + + /// A JSON-encoded object of form: + /// + /// ``` + /// {"room_id": …, "transaction_id": …, "content": …} + /// ``` + #[wasm_bindgen(readonly)] + pub body: JsString, +} + +/// A request that will back up a batch of room keys to the server. +#[derive(Debug)] +#[wasm_bindgen(getter_with_clone)] +pub struct KeysBackupRequest { + /// The request ID. + #[wasm_bindgen(readonly)] + pub request_id: JsString, + + /// A JSON-encoded object of form: + /// + /// ``` + /// {"version": …, "rooms": …} + /// ``` + #[wasm_bindgen(readonly)] + pub body: JsString, +} + +// JavaScript has no complex enums like Rust. To return structs of +// different type, we have no choice that hidding everything behind a +// `JsValue`. +impl TryFrom for JsValue { + type Error = serde_json::Error; + + fn try_from(outgoing_request: OutgoingRequest) -> Result { + let request_id: JsString = outgoing_request.request_id().to_string().into(); + + Ok(match outgoing_request.request() { + OutgoingRequests::KeysUpload(request) => { + let body = json!({ + "device_keys": request.device_keys, + "one_time_keys": request.one_time_keys, + }); + + JsValue::from(KeysUploadRequest { + request_id, + body: serde_json::to_string(&body)?.into(), + }) + } + + OutgoingRequests::KeysQuery(request) => { + let body = json!({ + "timeout": request.timeout, + "device_keys": request.device_keys, + "token": request.token, + }); + + JsValue::from(KeysQueryRequest { + request_id, + body: serde_json::to_string(&body)?.into(), + }) + } + + OutgoingRequests::KeysClaim(request) => { + let body = json!({ + "timeout": request.timeout, + "one_time_keys": request.one_time_keys, + }); + + JsValue::from(KeysClaimRequest { + request_id, + body: serde_json::to_string(&body)?.into(), + }) + } + + OutgoingRequests::ToDeviceRequest(request) => { + let body = json!({ + "event_type": request.event_type, + "transaction_id": request.txn_id, + "messages": request.messages, + }); + + JsValue::from(KeysClaimRequest { + request_id, + body: serde_json::to_string(&body)?.into(), + }) + } + + OutgoingRequests::SignatureUpload(request) => { + let body = json!({ + "signed_keys": request.signed_keys, + }); + + JsValue::from(SignatureUploadRequest { + request_id, + body: serde_json::to_string(&body)?.into(), + }) + } + + OutgoingRequests::RoomMessage(request) => { + let body = json!({ + "room_id": request.room_id, + "transaction_id": request.txn_id, + "content": request.content, + }); + + JsValue::from(RoomMessageRequest { + request_id, + body: serde_json::to_string(&body)?.into(), + }) + } + + OutgoingRequests::KeysBackup(request) => { + let body = json!({ + "version": request.version, + "rooms": request.rooms, + }); + + JsValue::from(KeysBackupRequest { + request_id, + body: serde_json::to_string(&body)?.into(), + }) + } + }) + } +} diff --git a/examples/js/index.js b/examples/js/index.js index f37a8dc62..faf4aaf70 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -23,6 +23,10 @@ async function run_example() { unused_fallback_keys, ); console.log(JSON.parse(decrypted_to_device)); + + const outgoing_requests = await olm_machine.outgoing_requests(); + console.log(outgoing_requests); + console.log(JSON.parse(outgoing_requests[0].body)); } run_example(); From fdb9fe21c6086a05c5ac4a2bc91a342161c4ce5d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 12 May 2022 16:05:24 +0200 Subject: [PATCH 10/58] feat(crypto) Remove existing `unwrap` code. --- crates/matrix-sdk-crypto/src/js/errors.rs | 10 ++++++++++ crates/matrix-sdk-crypto/src/js/machine.rs | 18 +++++++++--------- crates/matrix-sdk-crypto/src/js/mod.rs | 1 + 3 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 crates/matrix-sdk-crypto/src/js/errors.rs diff --git a/crates/matrix-sdk-crypto/src/js/errors.rs b/crates/matrix-sdk-crypto/src/js/errors.rs new file mode 100644 index 000000000..e4af29f8f --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/errors.rs @@ -0,0 +1,10 @@ +use std::error::Error; + +use wasm_bindgen::JsValue; + +pub fn any_error_to_jsvalue(error: E) -> JsValue +where + E: Error, +{ + error.to_string().into() +} diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index de0657beb..2bf0dced9 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -5,7 +5,7 @@ use ruma::{DeviceKeyAlgorithm, UInt}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use crate::js::{identifiers, sync_events}; +use crate::js::{errors::any_error_to_jsvalue, identifiers, sync_events}; #[wasm_bindgen] extern "C" { @@ -39,8 +39,8 @@ impl OlmMachine { changed_devices: &sync_events::DeviceLists, one_time_key_counts: &Map, unused_fallback_keys: &Set, - ) -> Promise { - let to_device_events = serde_json::from_str(to_device_events).unwrap(); + ) -> Result { + let to_device_events = serde_json::from_str(to_device_events)?; let changed_devices = changed_devices.inner.clone(); let one_time_key_counts: BTreeMap = one_time_key_counts .entries() @@ -65,7 +65,7 @@ impl OlmMachine { let me = self.inner.clone(); - future_to_promise(async move { + Ok(future_to_promise(async move { Ok(JsValue::from( serde_json::to_string( &me.receive_sync_changes( @@ -75,11 +75,11 @@ impl OlmMachine { unused_fallback_keys.as_deref(), ) .await - .unwrap(), + .map_err(any_error_to_jsvalue)?, ) - .unwrap(), + .map_err(any_error_to_jsvalue)?, )) - }) + })) } pub fn outgoing_requests(&self) -> Promise { @@ -89,11 +89,11 @@ impl OlmMachine { Ok(JsValue::from( me.outgoing_requests() .await - .unwrap() + .map_err(any_error_to_jsvalue)? .into_iter() .map(TryFrom::try_from) .collect::, _>>() - .unwrap() + .map_err(any_error_to_jsvalue)? .into_iter() .collect::(), )) diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto/src/js/mod.rs index 047841d35..faa102f38 100644 --- a/crates/matrix-sdk-crypto/src/js/mod.rs +++ b/crates/matrix-sdk-crypto/src/js/mod.rs @@ -1,5 +1,6 @@ //! Additional API that can be useful from JavaScript. +mod errors; pub mod identifiers; pub mod machine; pub mod requests; From fd4dff79d4a1199fd6c84db276e3469db66bb2cf Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 12 May 2022 16:19:03 +0200 Subject: [PATCH 11/58] feat(crypto) Implement `OlmMachine.user_id`, `.device_id` and `.display_name`. --- crates/matrix-sdk-crypto/src/js/machine.rs | 22 ++++++++++++++++++++++ examples/js/index.js | 5 ++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 2bf0dced9..f5d00bc94 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -33,6 +33,28 @@ impl OlmMachine { }) } + /// The unique user ID that owns this `OlmMachine` instance. + pub fn user_id(&self) -> identifiers::UserId { + identifiers::UserId { inner: self.inner.user_id().to_owned() } + } + + /// The unique device ID that identifies this `OlmMachine`. + pub fn device_id(&self) -> identifiers::DeviceId { + identifiers::DeviceId { inner: self.inner.device_id().to_owned() } + } + + ///// Get the public parts of our Olm identity keys. + //pub fn identity_keys(&self) -> + + /// Get the display name of our own device. + pub fn display_name(&self) -> Promise { + let me = self.inner.clone(); + + future_to_promise(async move { + Ok(JsValue::from(me.display_name().await.map_err(any_error_to_jsvalue)?)) + }) + } + pub fn receive_sync_changes( &self, to_device_events: &str, diff --git a/examples/js/index.js b/examples/js/index.js index faf4aaf70..a9d02b11b 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -6,6 +6,9 @@ async function run_example() { const olm_machine = await new OlmMachine(user_id, device_id); console.log(olm_machine); + console.log('olm_machine.user_id().localpart() =', olm_machine.user_id().localpart()); + console.log('olm_machine.device_id =', olm_machine.device_id()); + console.log('olm_machine.display_name =', await olm_machine.display_name()); const to_device_events = '{}'; const changed_devices = new DeviceLists(); @@ -26,7 +29,7 @@ async function run_example() { const outgoing_requests = await olm_machine.outgoing_requests(); console.log(outgoing_requests); - console.log(JSON.parse(outgoing_requests[0].body)); + //console.log(JSON.parse(outgoing_requests[0].body)); } run_example(); From 569adb7ceb63de2262412898f88343a5b97c8139 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 12 May 2022 16:36:55 +0200 Subject: [PATCH 12/58] feat(crypto) Implement `OlmMachine.identity_keys`. --- crates/matrix-sdk-crypto/src/js/machine.rs | 38 ++++++++++++++++++---- examples/js/index.js | 1 + 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index f5d00bc94..2348ead56 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -7,12 +7,6 @@ use wasm_bindgen_futures::future_to_promise; use crate::js::{errors::any_error_to_jsvalue, identifiers, sync_events}; -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(s: String); -} - #[wasm_bindgen] #[derive(Debug)] pub struct OlmMachine { @@ -44,7 +38,9 @@ impl OlmMachine { } ///// Get the public parts of our Olm identity keys. - //pub fn identity_keys(&self) -> + pub fn identity_keys(&self) -> IdentityKeys { + self.inner.identity_keys().into() + } /// Get the display name of our own device. pub fn display_name(&self) -> Promise { @@ -122,3 +118,31 @@ impl OlmMachine { }) } } + +#[derive(Debug, Clone)] +#[wasm_bindgen] +pub struct Ed25519PublicKey { + inner: vodozemac::Ed25519PublicKey, +} + +#[derive(Debug, Clone)] +#[wasm_bindgen] +pub struct Curve25519PublicKey { + inner: vodozemac::Curve25519PublicKey, +} + +#[derive(Debug)] +#[wasm_bindgen(getter_with_clone)] +pub struct IdentityKeys { + pub ed25519: Ed25519PublicKey, + pub curve25519: Curve25519PublicKey, +} + +impl From for IdentityKeys { + fn from(value: crate::olm::IdentityKeys) -> Self { + Self { + ed25519: Ed25519PublicKey { inner: value.ed25519 }, + curve25519: Curve25519PublicKey { inner: value.curve25519 }, + } + } +} diff --git a/examples/js/index.js b/examples/js/index.js index a9d02b11b..02994f70b 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -9,6 +9,7 @@ async function run_example() { console.log('olm_machine.user_id().localpart() =', olm_machine.user_id().localpart()); console.log('olm_machine.device_id =', olm_machine.device_id()); console.log('olm_machine.display_name =', await olm_machine.display_name()); + console.log('olm_machine.identity_keys =', olm_machine.identity_keys()); const to_device_events = '{}'; const changed_devices = new DeviceLists(); From 08665dcd1c880dd41c604b8cfc7c8710ab9298d2 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 16 May 2022 10:56:54 +0200 Subject: [PATCH 13/58] feat(crypto): Implement our own `future_to_promise` helper to simplify code. `wasm_bindgen_future::future_to_promise` expects a `Future>`. We reimplement this function by expecting a `Future>` where `T: Into`. That way, we apply the type conversions to `JsValue` inside this helper rather than in the call site. Additionally, all errors are managed automatically without having to deal with `JsError` or `JsValue`. It makes the code simpler to read and easier to write from my point of view. --- crates/matrix-sdk-crypto/Cargo.toml | 1 + crates/matrix-sdk-crypto/src/js/errors.rs | 10 ----- crates/matrix-sdk-crypto/src/js/future.rs | 26 +++++++++++ crates/matrix-sdk-crypto/src/js/machine.rs | 50 +++++++++------------- crates/matrix-sdk-crypto/src/js/mod.rs | 2 +- 5 files changed, 48 insertions(+), 41 deletions(-) delete mode 100644 crates/matrix-sdk-crypto/src/js/errors.rs create mode 100644 crates/matrix-sdk-crypto/src/js/future.rs diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 0c4bd4ab0..08f6aa381 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -66,6 +66,7 @@ vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "e09c93f2c8 wasm-bindgen = "0.2.80" wasm-bindgen-futures = "0.4.30" js-sys = "0.3.49" +anyhow = "1.0" [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] } diff --git a/crates/matrix-sdk-crypto/src/js/errors.rs b/crates/matrix-sdk-crypto/src/js/errors.rs deleted file mode 100644 index e4af29f8f..000000000 --- a/crates/matrix-sdk-crypto/src/js/errors.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::error::Error; - -use wasm_bindgen::JsValue; - -pub fn any_error_to_jsvalue(error: E) -> JsValue -where - E: Error, -{ - error.to_string().into() -} diff --git a/crates/matrix-sdk-crypto/src/js/future.rs b/crates/matrix-sdk-crypto/src/js/future.rs new file mode 100644 index 000000000..1e56c7b37 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/future.rs @@ -0,0 +1,26 @@ +use std::future::Future; + +use js_sys::Promise; +use wasm_bindgen::{JsValue, UnwrapThrowExt}; +use wasm_bindgen_futures::spawn_local; + +pub fn future_to_promise(future: F) -> Promise +where + F: Future> + 'static, + T: Into, +{ + let mut future = Some(future); + + Promise::new(&mut |resolve, reject| { + let future = future.take().unwrap_throw(); + + spawn_local(async move { + match future.await { + Ok(value) => resolve.call1(&JsValue::UNDEFINED, &value.into()).unwrap_throw(), + Err(value) => { + reject.call1(&JsValue::UNDEFINED, &value.to_string().into()).unwrap_throw() + } + }; + }); + }) +} diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 2348ead56..f02ffb3a2 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -3,9 +3,8 @@ use std::{collections::BTreeMap, sync::Arc}; use js_sys::{Array, Map, Promise, Set}; use ruma::{DeviceKeyAlgorithm, UInt}; use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::future_to_promise; -use crate::js::{errors::any_error_to_jsvalue, identifiers, sync_events}; +use crate::js::{future::future_to_promise, identifiers, sync_events}; #[wasm_bindgen] #[derive(Debug)] @@ -21,9 +20,9 @@ impl OlmMachine { let device_id = device_id.inner.clone(); future_to_promise(async move { - Ok(JsValue::from(OlmMachine { + Ok(OlmMachine { inner: Arc::new(crate::OlmMachine::new(user_id.as_ref(), device_id.as_ref()).await), - })) + }) }) } @@ -46,9 +45,7 @@ impl OlmMachine { pub fn display_name(&self) -> Promise { let me = self.inner.clone(); - future_to_promise(async move { - Ok(JsValue::from(me.display_name().await.map_err(any_error_to_jsvalue)?)) - }) + future_to_promise(async move { Ok(me.display_name().await?) }) } pub fn receive_sync_changes( @@ -84,19 +81,15 @@ impl OlmMachine { let me = self.inner.clone(); Ok(future_to_promise(async move { - Ok(JsValue::from( - serde_json::to_string( - &me.receive_sync_changes( - to_device_events, - &changed_devices, - &one_time_key_counts, - unused_fallback_keys.as_deref(), - ) - .await - .map_err(any_error_to_jsvalue)?, + Ok(serde_json::to_string( + &me.receive_sync_changes( + to_device_events, + &changed_devices, + &one_time_key_counts, + unused_fallback_keys.as_deref(), ) - .map_err(any_error_to_jsvalue)?, - )) + .await?, + )?) })) } @@ -104,17 +97,14 @@ impl OlmMachine { let me = self.inner.clone(); future_to_promise(async move { - Ok(JsValue::from( - me.outgoing_requests() - .await - .map_err(any_error_to_jsvalue)? - .into_iter() - .map(TryFrom::try_from) - .collect::, _>>() - .map_err(any_error_to_jsvalue)? - .into_iter() - .collect::(), - )) + Ok(me + .outgoing_requests() + .await? + .into_iter() + .map(TryFrom::try_from) + .collect::, _>>()? + .into_iter() + .collect::()) }) } } diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto/src/js/mod.rs index faa102f38..6bd624537 100644 --- a/crates/matrix-sdk-crypto/src/js/mod.rs +++ b/crates/matrix-sdk-crypto/src/js/mod.rs @@ -1,6 +1,6 @@ //! Additional API that can be useful from JavaScript. -mod errors; +mod future; pub mod identifiers; pub mod machine; pub mod requests; From 1d38e547399bd0cc5a0f6694f0d8ead918f22584 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 16 May 2022 11:05:46 +0200 Subject: [PATCH 14/58] feat(crypto): Return a `JsError` rather than a `String`. --- crates/matrix-sdk-crypto/src/js/identifiers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/js/identifiers.rs b/crates/matrix-sdk-crypto/src/js/identifiers.rs index e581d4dbd..2de633a59 100644 --- a/crates/matrix-sdk-crypto/src/js/identifiers.rs +++ b/crates/matrix-sdk-crypto/src/js/identifiers.rs @@ -16,8 +16,8 @@ pub struct UserId { impl UserId { /// Parse/validate and create a new `UserId`. #[wasm_bindgen(constructor)] - pub fn new(id: &str) -> Result { - Ok(Self { inner: ruma::UserId::parse(id).map_err(|e| e.to_string())? }) + pub fn new(id: &str) -> Result { + Ok(Self { inner: ruma::UserId::parse(id)? }) } /// Returns the user's localpart. From f0bb35a96c1f7a4f3e531825e410c278de2ddc30 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 16 May 2022 14:51:46 +0200 Subject: [PATCH 15/58] feat(crypto): Add `changed` and `left` to the constructor of `DeviceLists`. --- .../matrix-sdk-crypto/src/js/sync_events.rs | 44 ++++++++++++++++++- examples/js/index.js | 6 ++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/js/sync_events.rs b/crates/matrix-sdk-crypto/src/js/sync_events.rs index 7cd050194..6e739290a 100644 --- a/crates/matrix-sdk-crypto/src/js/sync_events.rs +++ b/crates/matrix-sdk-crypto/src/js/sync_events.rs @@ -15,9 +15,49 @@ pub struct DeviceLists { #[wasm_bindgen] impl DeviceLists { /// Create an empty `DeviceLists`. + /// + /// `changed` and `left` must be an array of strings representing + /// a user ID, otherwise invalid entries will be filtered out with + /// an error. #[wasm_bindgen(constructor)] - pub fn new() -> DeviceLists { - Self { inner: Default::default() } + pub fn new(changed: Array, left: Array) -> Result { + let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default(); + + inner.changed = changed + .iter() + .map(|user| { + let user = user + .as_string() + .ok_or_else(|| JsError::new("Given user ID is not a string"))?; + let user = ruma::UserId::parse(&user).map_err(|error| { + JsError::new(&format!( + "Given user ID `{}` has an invalid syntax: {}", + user, error, + )) + })?; + + Ok(user) + }) + .collect::, JsError>>()?; + + inner.left = left + .iter() + .map(|user| { + let user = user + .as_string() + .ok_or_else(|| JsError::new("Given user ID is not a string"))?; + let user = ruma::UserId::parse(&user).map_err(|error| { + JsError::new(&format!( + "Given user ID `{}` has an invalid syntax: {}", + user, error, + )) + })?; + + Ok(user) + }) + .collect::, JsError>>()?; + + Ok(Self { inner }) } /// Returns true if there are no device list updates. diff --git a/examples/js/index.js b/examples/js/index.js index 02994f70b..7348383b3 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -12,7 +12,11 @@ async function run_example() { console.log('olm_machine.identity_keys =', olm_machine.identity_keys()); const to_device_events = '{}'; - const changed_devices = new DeviceLists(); + const changed_devices = new DeviceLists( + ['@foo:matrix.org', '@bar:matrix.org'], + ['@baz:matrix.org', '@qux:matrix.org'], + ); + console.log(changed_devices); const one_time_key_counts = new Map(); one_time_key_counts.set('foo', 42); one_time_key_counts.set('bar', 153); From acd5de3cf3ad016022d40820537e6e58ca023bc5 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 16 May 2022 15:08:36 +0200 Subject: [PATCH 16/58] feat(crypto): Implement `ServerName`, and add `UserId.serverName`. --- .../matrix-sdk-crypto/src/js/identifiers.rs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/identifiers.rs b/crates/matrix-sdk-crypto/src/js/identifiers.rs index 2de633a59..536e9fd22 100644 --- a/crates/matrix-sdk-crypto/src/js/identifiers.rs +++ b/crates/matrix-sdk-crypto/src/js/identifiers.rs @@ -25,6 +25,12 @@ impl UserId { self.inner.localpart().to_owned() } + /// Returns the server name of the user ID. + #[wasm_bindgen(js_name = "serverName")] + pub fn server_name(&self) -> ServerName { + ServerName { inner: self.inner.server_name().to_owned() } + } + /// Whether this user ID is a historical one. /// /// A historical user ID is one that doesn't conform to the latest @@ -54,3 +60,44 @@ impl DeviceId { Self { inner: id.into() } } } + +/// A Matrix-spec compliant [server name]. +/// +/// It consists of a host and an optional port (separated by a colon if +/// present). +/// +/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name +#[wasm_bindgen] +#[derive(Debug)] +pub struct ServerName { + inner: ruma::OwnedServerName, +} + +#[wasm_bindgen] +impl ServerName { + /// Parse/validate and create a new `ServerName`. + #[wasm_bindgen(constructor)] + pub fn new(name: &str) -> Result { + Ok(Self { inner: ruma::ServerName::parse(name)? }) + } + + /// Returns the host of the server name. + /// + /// That is: Return the part of the server before `:` or the + /// full server name if there is no port. + pub fn host(&self) -> String { + self.inner.host().to_owned() + } + + /// Returns the port of the server name if any. + pub fn port(&self) -> Option { + self.inner.port() + } + + /// Returns true if and only if the server name is an IPv4 or IPv6 + /// address. + #[wasm_bindgen(js_name = "isIpLiteral")] + pub fn is_ip_literal(&self) -> bool { + self.inner.is_ip_literal() + } +} From 2d15f758da69f672cdfbed6d3633f3fe882df48b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 16 May 2022 15:12:23 +0200 Subject: [PATCH 17/58] feat(crypto): Use JavaScript naming style for methods. --- crates/matrix-sdk-crypto/src/js/machine.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index f02ffb3a2..e5d3d2fcb 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -27,27 +27,32 @@ impl OlmMachine { } /// The unique user ID that owns this `OlmMachine` instance. + #[wasm_bindgen(js_name = "userId")] pub fn user_id(&self) -> identifiers::UserId { identifiers::UserId { inner: self.inner.user_id().to_owned() } } /// The unique device ID that identifies this `OlmMachine`. + #[wasm_bindgen(js_name = "deviceId")] pub fn device_id(&self) -> identifiers::DeviceId { identifiers::DeviceId { inner: self.inner.device_id().to_owned() } } ///// Get the public parts of our Olm identity keys. + #[wasm_bindgen(js_name = "identityKeys")] pub fn identity_keys(&self) -> IdentityKeys { self.inner.identity_keys().into() } /// Get the display name of our own device. + #[wasm_bindgen(js_name = "displayName")] pub fn display_name(&self) -> Promise { let me = self.inner.clone(); future_to_promise(async move { Ok(me.display_name().await?) }) } + #[wasm_bindgen(js_name = "receiveSyncChanges")] pub fn receive_sync_changes( &self, to_device_events: &str, @@ -93,6 +98,7 @@ impl OlmMachine { })) } + #[wasm_bindgen(js_name = "outgoingRequests")] pub fn outgoing_requests(&self) -> Promise { let me = self.inner.clone(); From 056f34883f665cb77a58fcfc2a6c0347734a0eaf Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 16 May 2022 15:23:58 +0200 Subject: [PATCH 18/58] feat(crypto): Implement `OlmMachine.trackedUsers`. --- crates/matrix-sdk-crypto/src/js/machine.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index e5d3d2fcb..6677ec4ad 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -52,6 +52,22 @@ impl OlmMachine { future_to_promise(async move { Ok(me.display_name().await?) }) } + /// Get all the tracked users of our own device. + #[wasm_bindgen(js_name = "trackedUsers")] + pub fn tracked_users(&self) -> Set { + let set = Set::new(&JsValue::UNDEFINED); + + self.inner + .tracked_users() + .into_iter() + .map(|user| identifiers::UserId { inner: user }) + .for_each(|user| { + set.add(&user.into()); + }); + + set + } + #[wasm_bindgen(js_name = "receiveSyncChanges")] pub fn receive_sync_changes( &self, From 9f159ff5a43bfce5a0e3c6b92d319facb9a26e82 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 16 May 2022 17:48:23 +0200 Subject: [PATCH 19/58] feat(crypto): Implement `OlmMachine.mark_request_as_sent`. To implement this method, a new `responses::OwnedResponse` intermediate type was needed. In addition to that, the `http` crate is now required when the `js` feature is enabled. --- crates/matrix-sdk-crypto/Cargo.toml | 4 +- crates/matrix-sdk-crypto/src/js/machine.rs | 85 +++++++++++++++++++- crates/matrix-sdk-crypto/src/js/mod.rs | 1 + crates/matrix-sdk-crypto/src/js/requests.rs | 15 +++- crates/matrix-sdk-crypto/src/js/responses.rs | 84 +++++++++++++++++++ examples/js/index.js | 13 +-- 6 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 crates/matrix-sdk-crypto/src/js/responses.rs diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 08f6aa381..59818f82d 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -26,7 +26,7 @@ default = [] qrcode = ["matrix-qrcode"] backups_v1 = ["olm-rs", "bs58"] docsrs = [] -js = [] +js = ["http"] # Testing helpers for implementations based upon this testing = ["http"] @@ -43,7 +43,7 @@ ctr = "0.9.1" dashmap = "5.2.0" futures-util = { version = "0.3.21", default-features = false, features = ["alloc"] } hmac = "0.12.1" -http = { version = "0.2.6", optional = true } # feature = testing only +http = { version = "0.2.6", optional = true } # feature = testing only, or if `js` is enabled matrix-qrcode = { version = "0.2.0", path = "../matrix-qrcode", optional = true } matrix-sdk-common = { version = "0.4.0", path = "../matrix-sdk-common" } olm-rs = { version = "2.2.0", features = ["serde"], optional = true } diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 6677ec4ad..54b24af6d 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -1,10 +1,18 @@ use std::{collections::BTreeMap, sync::Arc}; use js_sys::{Array, Map, Promise, Set}; -use ruma::{DeviceKeyAlgorithm, UInt}; +use ruma::{ + api::IncomingResponse as RumaIncomingResponse, DeviceKeyAlgorithm, OwnedTransactionId, UInt, +}; use wasm_bindgen::prelude::*; -use crate::js::{future::future_to_promise, identifiers, sync_events}; +use crate::js::{ + future::future_to_promise, + identifiers, + requests::RequestType, + responses::{self, response_from_string}, + sync_events, +}; #[wasm_bindgen] #[derive(Debug)] @@ -53,6 +61,8 @@ impl OlmMachine { } /// Get all the tracked users of our own device. + /// + /// Returns a `Set`. #[wasm_bindgen(js_name = "trackedUsers")] pub fn tracked_users(&self) -> Set { let set = Set::new(&JsValue::UNDEFINED); @@ -114,6 +124,20 @@ impl OlmMachine { })) } + /// Get the outgoing requests that need to be sent out. + /// + /// This returns a list of `JsValue` to represent either: + /// * `KeysUploadRequest`, + /// * `KeysQueryRequest`, + /// * `KeysClaimRequest`, + /// * `ToDeviceRequest`, + /// * `SignatureUploadRequest`, + /// * `RoomMessageRequest` or + /// * `KeysBackupRequest`. + /// + /// Those requests need to be sent out to the server and the + /// responses need to be passed back to the state machine using + /// `mark_request_as_sent`. #[wasm_bindgen(js_name = "outgoingRequests")] pub fn outgoing_requests(&self) -> Promise { let me = self.inner.clone(); @@ -129,6 +153,63 @@ impl OlmMachine { .collect::()) }) } + + /// Mark the request with the given request ID as sent (see + /// `outgoing_requests`). + /// + /// `request_id` represents the unique ID of the request that was + /// sent out. This is needed to couple the response with the now + /// sent out request. `response_type` represents the type of the + /// request that was sent out. `response` represents the response + /// that was received from the server after the outgoing request + /// was sent out. ` + #[wasm_bindgen(js_name = "markRequestAsSent")] + pub fn mark_request_as_sent( + &self, + request_id: &str, + request_type: RequestType, + response: &str, + ) -> Result { + let transaction_id = OwnedTransactionId::from(request_id); + let response = response_from_string(response).map_err(JsError::from)?; + + let incoming_response: responses::OwnedResponse = match request_type { + RequestType::KeysUpload => { + responses::KeysUploadResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::KeysQuery => { + responses::KeysQueryResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::KeysClaim => { + responses::KeysClaimResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::ToDevice => { + responses::ToDeviceResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::SignatureUpload => { + responses::SignatureUploadResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::RoomMessage => { + responses::RoomMessageResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::KeysBackup => { + responses::KeysBackupResponse::try_from_http_response(response).map(Into::into) + } + } + .map_err(JsError::from)?; + + let me = self.inner.clone(); + + Ok(future_to_promise(async move { + Ok(me.mark_request_as_sent(&transaction_id, &incoming_response).await.map(|_| true)?) + })) + } } #[derive(Debug, Clone)] diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto/src/js/mod.rs index 6bd624537..c1acc4bd9 100644 --- a/crates/matrix-sdk-crypto/src/js/mod.rs +++ b/crates/matrix-sdk-crypto/src/js/mod.rs @@ -4,4 +4,5 @@ mod future; pub mod identifiers; pub mod machine; pub mod requests; +pub mod responses; pub mod sync_events; diff --git a/crates/matrix-sdk-crypto/src/js/requests.rs b/crates/matrix-sdk-crypto/src/js/requests.rs index d077fbf81..16c88d3b1 100644 --- a/crates/matrix-sdk-crypto/src/js/requests.rs +++ b/crates/matrix-sdk-crypto/src/js/requests.rs @@ -134,7 +134,7 @@ pub struct KeysBackupRequest { } // JavaScript has no complex enums like Rust. To return structs of -// different type, we have no choice that hidding everything behind a +// different types, we have no choice that hidding everything behind a // `JsValue`. impl TryFrom for JsValue { type Error = serde_json::Error; @@ -231,3 +231,16 @@ impl TryFrom for JsValue { }) } } + +/// Represent the type of a request. +#[wasm_bindgen] +#[derive(Debug)] +pub enum RequestType { + KeysUpload, + KeysQuery, + KeysClaim, + ToDevice, + SignatureUpload, + RoomMessage, + KeysBackup, +} diff --git a/crates/matrix-sdk-crypto/src/js/responses.rs b/crates/matrix-sdk-crypto/src/js/responses.rs new file mode 100644 index 000000000..715d13523 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/responses.rs @@ -0,0 +1,84 @@ +pub(crate) use ruma::api::client::{ + backup::add_backup_keys::v3::Response as KeysBackupResponse, + keys::{ + claim_keys::v3::Response as KeysClaimResponse, get_keys::v3::Response as KeysQueryResponse, + upload_keys::v3::Response as KeysUploadResponse, + upload_signatures::v3::Response as SignatureUploadResponse, + }, + message::send_message_event::v3::Response as RoomMessageResponse, + to_device::send_event_to_device::v3::Response as ToDeviceResponse, +}; + +use crate::IncomingResponse; + +pub(crate) fn response_from_string(body: &str) -> http::Result>> { + http::Response::builder().status(200).body(body.as_bytes().to_vec()) +} + +/// Intermediate private type to store an incoming owned response, +/// without the need to manage lifetime. +pub(crate) enum OwnedResponse { + KeysUpload(KeysUploadResponse), + KeysQuery(KeysQueryResponse), + KeysClaim(KeysClaimResponse), + ToDevice(ToDeviceResponse), + SignatureUpload(SignatureUploadResponse), + RoomMessage(RoomMessageResponse), + KeysBackup(KeysBackupResponse), +} + +impl From for OwnedResponse { + fn from(response: KeysUploadResponse) -> Self { + OwnedResponse::KeysUpload(response) + } +} + +impl From for OwnedResponse { + fn from(response: KeysQueryResponse) -> Self { + OwnedResponse::KeysQuery(response) + } +} + +impl From for OwnedResponse { + fn from(response: KeysClaimResponse) -> Self { + OwnedResponse::KeysClaim(response) + } +} + +impl From for OwnedResponse { + fn from(response: ToDeviceResponse) -> Self { + OwnedResponse::ToDevice(response) + } +} + +impl From for OwnedResponse { + fn from(response: SignatureUploadResponse) -> Self { + Self::SignatureUpload(response) + } +} + +impl From for OwnedResponse { + fn from(response: RoomMessageResponse) -> Self { + OwnedResponse::RoomMessage(response) + } +} + +impl From for OwnedResponse { + fn from(r: KeysBackupResponse) -> Self { + Self::KeysBackup(r) + } +} + +impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> { + fn from(response: &'a OwnedResponse) -> Self { + match response { + OwnedResponse::KeysUpload(response) => IncomingResponse::KeysUpload(response), + OwnedResponse::KeysQuery(response) => IncomingResponse::KeysQuery(response), + OwnedResponse::KeysClaim(response) => IncomingResponse::KeysClaim(response), + OwnedResponse::ToDevice(response) => IncomingResponse::ToDevice(response), + OwnedResponse::SignatureUpload(response) => IncomingResponse::SignatureUpload(response), + OwnedResponse::RoomMessage(response) => IncomingResponse::RoomMessage(response), + OwnedResponse::KeysBackup(response) => IncomingResponse::KeysBackup(response), + } + } +} diff --git a/examples/js/index.js b/examples/js/index.js index 7348383b3..384e1a464 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -6,10 +6,11 @@ async function run_example() { const olm_machine = await new OlmMachine(user_id, device_id); console.log(olm_machine); - console.log('olm_machine.user_id().localpart() =', olm_machine.user_id().localpart()); - console.log('olm_machine.device_id =', olm_machine.device_id()); - console.log('olm_machine.display_name =', await olm_machine.display_name()); - console.log('olm_machine.identity_keys =', olm_machine.identity_keys()); + console.log('olm_machine.userId().localpart() =', olm_machine.userId().localpart()); + console.log('olm_machine.deviceId() =', olm_machine.deviceId()); + console.log('olm_machine.displayName() =', await olm_machine.displayName()); + console.log('olm_machine.identityKeys() =', olm_machine.identityKeys()); + console.log('olm_machine.trackedUsers() = ', olm_machine.trackedUsers()); const to_device_events = '{}'; const changed_devices = new DeviceLists( @@ -24,7 +25,7 @@ async function run_example() { unused_fallback_keys.add('baz'); unused_fallback_keys.add('qux'); - const decrypted_to_device = await olm_machine.receive_sync_changes( + const decrypted_to_device = await olm_machine.receiveSyncChanges( to_device_events, changed_devices, one_time_key_counts, @@ -32,7 +33,7 @@ async function run_example() { ); console.log(JSON.parse(decrypted_to_device)); - const outgoing_requests = await olm_machine.outgoing_requests(); + const outgoing_requests = await olm_machine.outgoingRequests(); console.log(outgoing_requests); //console.log(JSON.parse(outgoing_requests[0].body)); } From 86c6e601bdd044d7c27906cbe4b17d727b588af3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 16 May 2022 18:18:33 +0200 Subject: [PATCH 20/58] chore(crypto): Refactor the code with `TryFrom` on a tuple. --- crates/matrix-sdk-crypto/src/js/future.rs | 2 +- crates/matrix-sdk-crypto/src/js/machine.rs | 36 +--------------- crates/matrix-sdk-crypto/src/js/responses.rs | 43 +++++++++++++++++++- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/js/future.rs b/crates/matrix-sdk-crypto/src/js/future.rs index 1e56c7b37..04b2ef4cb 100644 --- a/crates/matrix-sdk-crypto/src/js/future.rs +++ b/crates/matrix-sdk-crypto/src/js/future.rs @@ -4,7 +4,7 @@ use js_sys::Promise; use wasm_bindgen::{JsValue, UnwrapThrowExt}; use wasm_bindgen_futures::spawn_local; -pub fn future_to_promise(future: F) -> Promise +pub(crate) fn future_to_promise(future: F) -> Promise where F: Future> + 'static, T: Into, diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 54b24af6d..00e7f9080 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -1,9 +1,7 @@ use std::{collections::BTreeMap, sync::Arc}; use js_sys::{Array, Map, Promise, Set}; -use ruma::{ - api::IncomingResponse as RumaIncomingResponse, DeviceKeyAlgorithm, OwnedTransactionId, UInt, -}; +use ruma::{DeviceKeyAlgorithm, OwnedTransactionId, UInt}; use wasm_bindgen::prelude::*; use crate::js::{ @@ -172,37 +170,7 @@ impl OlmMachine { ) -> Result { let transaction_id = OwnedTransactionId::from(request_id); let response = response_from_string(response).map_err(JsError::from)?; - - let incoming_response: responses::OwnedResponse = match request_type { - RequestType::KeysUpload => { - responses::KeysUploadResponse::try_from_http_response(response).map(Into::into) - } - - RequestType::KeysQuery => { - responses::KeysQueryResponse::try_from_http_response(response).map(Into::into) - } - - RequestType::KeysClaim => { - responses::KeysClaimResponse::try_from_http_response(response).map(Into::into) - } - - RequestType::ToDevice => { - responses::ToDeviceResponse::try_from_http_response(response).map(Into::into) - } - - RequestType::SignatureUpload => { - responses::SignatureUploadResponse::try_from_http_response(response).map(Into::into) - } - - RequestType::RoomMessage => { - responses::RoomMessageResponse::try_from_http_response(response).map(Into::into) - } - - RequestType::KeysBackup => { - responses::KeysBackupResponse::try_from_http_response(response).map(Into::into) - } - } - .map_err(JsError::from)?; + let incoming_response = responses::OwnedResponse::try_from((request_type, response))?; let me = self.inner.clone(); diff --git a/crates/matrix-sdk-crypto/src/js/responses.rs b/crates/matrix-sdk-crypto/src/js/responses.rs index 715d13523..60368f486 100644 --- a/crates/matrix-sdk-crypto/src/js/responses.rs +++ b/crates/matrix-sdk-crypto/src/js/responses.rs @@ -8,8 +8,10 @@ pub(crate) use ruma::api::client::{ message::send_message_event::v3::Response as RoomMessageResponse, to_device::send_event_to_device::v3::Response as ToDeviceResponse, }; +use ruma::api::IncomingResponse as RumaIncomingResponse; +use wasm_bindgen::prelude::*; -use crate::IncomingResponse; +use crate::{js::requests::RequestType, IncomingResponse}; pub(crate) fn response_from_string(body: &str) -> http::Result>> { http::Response::builder().status(200).body(body.as_bytes().to_vec()) @@ -69,6 +71,45 @@ impl From for OwnedResponse { } } +impl TryFrom<(RequestType, http::Response>)> for OwnedResponse { + type Error = JsError; + + fn try_from( + (request_type, response): (RequestType, http::Response>), + ) -> Result { + match request_type { + RequestType::KeysUpload => { + KeysUploadResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::KeysQuery => { + KeysQueryResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::KeysClaim => { + KeysClaimResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::ToDevice => { + ToDeviceResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::SignatureUpload => { + SignatureUploadResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::RoomMessage => { + RoomMessageResponse::try_from_http_response(response).map(Into::into) + } + + RequestType::KeysBackup => { + KeysBackupResponse::try_from_http_response(response).map(Into::into) + } + } + .map_err(JsError::from) + } +} + impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> { fn from(response: &'a OwnedResponse) -> Self { match response { From 245181522647f8106b11f61dc199317e5f74e5f3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 09:29:19 +0200 Subject: [PATCH 21/58] feat(crypto): Implement `*PublicKey.to_base64` and `.length`. --- crates/matrix-sdk-crypto/src/js/machine.rs | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 00e7f9080..8aea93750 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -186,12 +186,38 @@ pub struct Ed25519PublicKey { inner: vodozemac::Ed25519PublicKey, } +#[wasm_bindgen] +impl Ed25519PublicKey { + #[wasm_bindgen(getter)] + pub fn length(&self) -> usize { + vodozemac::Ed25519PublicKey::LENGTH + } + + #[wasm_bindgen(js_name = "toBase64")] + pub fn to_base64(&self) -> String { + self.inner.to_base64() + } +} + #[derive(Debug, Clone)] #[wasm_bindgen] pub struct Curve25519PublicKey { inner: vodozemac::Curve25519PublicKey, } +#[wasm_bindgen] +impl Curve25519PublicKey { + #[wasm_bindgen(getter)] + pub fn length(&self) -> usize { + vodozemac::Curve25519PublicKey::LENGTH + } + + #[wasm_bindgen(js_name = "toBase64")] + pub fn to_base64(&self) -> String { + self.inner.to_base64() + } +} + #[derive(Debug)] #[wasm_bindgen(getter_with_clone)] pub struct IdentityKeys { From 444ff9936b87875a4f05cf503861ae697920650b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 09:37:04 +0200 Subject: [PATCH 22/58] feat(crypto): Implement `RoomId`. --- .../matrix-sdk-crypto/src/js/identifiers.rs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/identifiers.rs b/crates/matrix-sdk-crypto/src/js/identifiers.rs index 536e9fd22..efc782202 100644 --- a/crates/matrix-sdk-crypto/src/js/identifiers.rs +++ b/crates/matrix-sdk-crypto/src/js/identifiers.rs @@ -61,6 +61,35 @@ impl DeviceId { } } +/// A Matrix [room ID]. +/// +/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub struct RoomId { + pub(crate) inner: ruma::OwnedRoomId, +} + +#[wasm_bindgen] +impl RoomId { + /// Parse/validate and create a new `UserId`. + #[wasm_bindgen(constructor)] + pub fn new(id: &str) -> Result { + Ok(Self { inner: ruma::RoomId::parse(id)? }) + } + + /// Returns the user's localpart. + pub fn localpart(&self) -> String { + self.inner.localpart().to_owned() + } + + /// Returns the server name of the user ID. + #[wasm_bindgen(js_name = "serverName")] + pub fn server_name(&self) -> ServerName { + ServerName { inner: self.inner.server_name().to_owned() } + } +} + /// A Matrix-spec compliant [server name]. /// /// It consists of a host and an optional port (separated by a colon if From ee713f928f7b1aa47b17473272b33fff8bb4fb24 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 10:07:01 +0200 Subject: [PATCH 23/58] feat(crypto): Implement `OlmMachine.encrypt` and `.invalidate_group_session`. --- crates/matrix-sdk-crypto/src/js/machine.rs | 54 +++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 8aea93750..dfbdd10d5 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -1,7 +1,11 @@ use std::{collections::BTreeMap, sync::Arc}; use js_sys::{Array, Map, Promise, Set}; -use ruma::{DeviceKeyAlgorithm, OwnedTransactionId, UInt}; +use ruma::{ + events::{AnyMessageLikeEventContent, EventContent}, + DeviceKeyAlgorithm, OwnedTransactionId, UInt, +}; +use serde_json::value::RawValue as RawJsonValue; use wasm_bindgen::prelude::*; use crate::js::{ @@ -178,6 +182,54 @@ impl OlmMachine { Ok(me.mark_request_as_sent(&transaction_id, &incoming_response).await.map(|_| true)?) })) } + + /// Encrypt a room message for the given room. + /// + /// Beware that a group session needs to be shared before this + /// method can be called using the `share_group_session` method. + /// + /// Since group sessions can expire or become invalid if the room + /// membership changes, client authors should check with the + /// `should_share_group_session` method if a new group session + /// needs to be shared. + /// + /// `room_id` is the ID of the room for which the message should + /// be encrypted. `event_type` is the type of the event. `content` + /// is the plaintext content of the message that should be + /// encrypted. + /// + /// # Panics + /// + /// Panics if a group session for the given room wasn't shared beforehand. + pub fn encrypt( + &self, + room_id: &identifiers::RoomId, + event_type: &str, + content: &str, + ) -> Result { + let room_id = room_id.inner.clone(); + let content: Box = serde_json::from_str(content).map_err(JsError::from)?; + let content = + AnyMessageLikeEventContent::from_parts(event_type, &content).map_err(JsError::from)?; + + let me = self.inner.clone(); + + Ok(future_to_promise(async move { + Ok(serde_json::to_string(&me.encrypt(&room_id, content).await?)?) + })) + } + + /// Invalidate the currently active outbound group session for the + /// given room. + /// + /// Returns true if a session was invalidated, false if there was + /// no session to invalidate. + pub fn invalidate_group_session(&self, room_id: &identifiers::RoomId) -> Promise { + let room_id = room_id.inner.clone(); + let me = self.inner.clone(); + + future_to_promise(async move { Ok(me.invalidate_group_session(&room_id).await?) }) + } } #[derive(Debug, Clone)] From 30e9189f7b33aa7d81be5f1af5dad1a79c3603c3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 10:14:38 +0200 Subject: [PATCH 24/58] fix(crypto): Use JavaScript naming convention for `OlmMachine.invalidate_group_session`. --- crates/matrix-sdk-crypto/src/js/machine.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index dfbdd10d5..a5c4c3cb4 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -224,6 +224,7 @@ impl OlmMachine { /// /// Returns true if a session was invalidated, false if there was /// no session to invalidate. + #[wasm_bindgen(js_name = "invalidateGroupSession")] pub fn invalidate_group_session(&self, room_id: &identifiers::RoomId) -> Promise { let room_id = room_id.inner.clone(); let me = self.inner.clone(); From dc5b15e799d1ffaede09a40c04f48bc7969b9b84 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 10:59:11 +0200 Subject: [PATCH 25/58] fix(crypto): `OutgoingRequest::ToDeviceRequest` was not correctly mapped. --- crates/matrix-sdk-crypto/src/js/requests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/js/requests.rs b/crates/matrix-sdk-crypto/src/js/requests.rs index 16c88d3b1..c36d389c0 100644 --- a/crates/matrix-sdk-crypto/src/js/requests.rs +++ b/crates/matrix-sdk-crypto/src/js/requests.rs @@ -187,7 +187,7 @@ impl TryFrom for JsValue { "messages": request.messages, }); - JsValue::from(KeysClaimRequest { + JsValue::from(ToDeviceRequest { request_id, body: serde_json::to_string(&body)?.into(), }) From 8b94aed27ffcf2a339ed336a8777f83837ab138a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 10:59:33 +0200 Subject: [PATCH 26/58] fix(crypto): Use `txn_id` for `transaction_id`. --- crates/matrix-sdk-crypto/src/js/requests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/js/requests.rs b/crates/matrix-sdk-crypto/src/js/requests.rs index c36d389c0..4d08356e0 100644 --- a/crates/matrix-sdk-crypto/src/js/requests.rs +++ b/crates/matrix-sdk-crypto/src/js/requests.rs @@ -74,7 +74,7 @@ pub struct ToDeviceRequest { /// A JSON-encoded object of form: /// /// ``` - /// {"event_type": …, "transaction_id": …, "messages": …} + /// {"event_type": …, "txn_id": …, "messages": …} /// ``` #[wasm_bindgen(readonly)] pub body: JsString, @@ -93,7 +93,7 @@ pub struct SignatureUploadRequest { /// A JSON-encoded object of form: /// /// ``` - /// {"signed_keys": …, "transaction_id": …, "messages": …} + /// {"signed_keys": …, "txn_id": …, "messages": …} /// ``` #[wasm_bindgen(readonly)] pub body: JsString, @@ -110,7 +110,7 @@ pub struct RoomMessageRequest { /// A JSON-encoded object of form: /// /// ``` - /// {"room_id": …, "transaction_id": …, "content": …} + /// {"room_id": …, "txn_id": …, "content": …} /// ``` #[wasm_bindgen(readonly)] pub body: JsString, @@ -183,7 +183,7 @@ impl TryFrom for JsValue { OutgoingRequests::ToDeviceRequest(request) => { let body = json!({ "event_type": request.event_type, - "transaction_id": request.txn_id, + "txn_id": request.txn_id, "messages": request.messages, }); @@ -207,7 +207,7 @@ impl TryFrom for JsValue { OutgoingRequests::RoomMessage(request) => { let body = json!({ "room_id": request.room_id, - "transaction_id": request.txn_id, + "txn_id": request.txn_id, "content": request.content, }); From e9d37d7c4e1aa8db375cbb81a94f90124bbcebfd Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 10:59:48 +0200 Subject: [PATCH 27/58] docs(crypto): Add missing docs. --- crates/matrix-sdk-crypto/src/js/responses.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/responses.rs b/crates/matrix-sdk-crypto/src/js/responses.rs index 60368f486..03b19274d 100644 --- a/crates/matrix-sdk-crypto/src/js/responses.rs +++ b/crates/matrix-sdk-crypto/src/js/responses.rs @@ -1,3 +1,5 @@ +//! Types related to responses. + pub(crate) use ruma::api::client::{ backup::add_backup_keys::v3::Response as KeysBackupResponse, keys::{ From c20349f46ff6073306db3443791409db891db784 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 11:00:27 +0200 Subject: [PATCH 28/58] feat(crypto): Implement `OlmMachine.share_group_session`. --- crates/matrix-sdk-crypto/src/js/events.rs | 46 ++++++++++ crates/matrix-sdk-crypto/src/js/machine.rs | 97 +++++++++++++++++++++- crates/matrix-sdk-crypto/src/js/mod.rs | 1 + 3 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 crates/matrix-sdk-crypto/src/js/events.rs diff --git a/crates/matrix-sdk-crypto/src/js/events.rs b/crates/matrix-sdk-crypto/src/js/events.rs new file mode 100644 index 000000000..427db2fc4 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/js/events.rs @@ -0,0 +1,46 @@ +//! Types related to events. + +use wasm_bindgen::prelude::*; + +/// Who can see a room's history. +#[derive(Debug, Clone)] +#[wasm_bindgen] +pub enum HistoryVisibility { + /// Previous events are accessible to newly joined members from + /// the point they were invited onwards. + /// + /// Events stop being accessible when the member's state changes + /// to something other than *invite* or *join*. + Invited, + + /// Previous events are accessible to newly joined members from + /// the point they joined the room onwards. + /// + /// Events stop being accessible when the member's state changes + /// to something other than *join*. + Joined, + + /// Previous events are always accessible to newly joined members. + /// + /// All events in the room are accessible, even those sent when + /// the member was not a part of the room. + Shared, + + /// All events while this is the `HistoryVisibility` value may be + /// shared by any participating homeserver with anyone, regardless + /// of whether they have ever joined the room. + WorldReadable, +} + +impl From for ruma::events::room::history_visibility::HistoryVisibility { + fn from(value: HistoryVisibility) -> Self { + use HistoryVisibility::*; + + match value { + Invited => Self::Invited, + Joined => Self::Joined, + Shared => Self::Shared, + WorldReadable => Self::WorldReadable, + } + } +} diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index a5c4c3cb4..e494275e5 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::{collections::BTreeMap, sync::Arc, time::Duration}; use js_sys::{Array, Map, Promise, Set}; use ruma::{ @@ -9,6 +9,7 @@ use serde_json::value::RawValue as RawJsonValue; use wasm_bindgen::prelude::*; use crate::js::{ + events, future::future_to_promise, identifiers, requests::RequestType, @@ -231,10 +232,50 @@ impl OlmMachine { future_to_promise(async move { Ok(me.invalidate_group_session(&room_id).await?) }) } + + #[wasm_bindgen(js_name = "shareGroupSession")] + pub fn share_group_session( + &self, + room_id: &identifiers::RoomId, + users: &Array, + encryption_settings: &EncryptionSettings, + ) -> Result { + let room_id = room_id.inner.clone(); + let users = users + .iter() + .map(|user| { + let user = user + .as_string() + .ok_or_else(|| JsError::new("Given user ID is not a string"))?; + let user = ruma::UserId::parse(&user).map_err(|error| { + JsError::new(&format!( + "Given user ID `{}` has an invalid syntax: {}", + user, error + )) + })?; + + Ok(user) + }) + .collect::, JsError>>()?; + let encryption_settings = crate::olm::EncryptionSettings::from(encryption_settings); + + let me = self.inner.clone(); + + Ok(future_to_promise(async move { + Ok(serde_json::to_string( + &me.share_group_session( + &room_id, + users.iter().by_ref().map(AsRef::as_ref), + encryption_settings, + ) + .await?, + )?) + })) + } } -#[derive(Debug, Clone)] #[wasm_bindgen] +#[derive(Debug, Clone)] pub struct Ed25519PublicKey { inner: vodozemac::Ed25519PublicKey, } @@ -252,8 +293,8 @@ impl Ed25519PublicKey { } } -#[derive(Debug, Clone)] #[wasm_bindgen] +#[derive(Debug, Clone)] pub struct Curve25519PublicKey { inner: vodozemac::Curve25519PublicKey, } @@ -271,8 +312,8 @@ impl Curve25519PublicKey { } } -#[derive(Debug)] #[wasm_bindgen(getter_with_clone)] +#[derive(Debug)] pub struct IdentityKeys { pub ed25519: Ed25519PublicKey, pub curve25519: Curve25519PublicKey, @@ -286,3 +327,51 @@ impl From for IdentityKeys { } } } + +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub enum EncryptionAlgorithm { + /// Olm version 1 using Curve25519, AES-256, and SHA-256. + OlmV1Curve25519AesSha2, + + /// Megolm version 1 using AES-256 and SHA-256. + MegolmV1AesSha2, +} + +impl From for ruma::EventEncryptionAlgorithm { + fn from(value: EncryptionAlgorithm) -> Self { + use EncryptionAlgorithm::*; + + match value { + OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, + MegolmV1AesSha2 => Self::MegolmV1AesSha2, + } + } +} + +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct EncryptionSettings { + pub algorithm: EncryptionAlgorithm, + + /// A duration expressed in microseconds. + #[wasm_bindgen(js_name = "rotationPeriod")] + pub rotation_period: u64, + + #[wasm_bindgen(js_name = "rotationPeriodMessages")] + pub rotation_period_messages: u64, + + #[wasm_bindgen(js_name = "historyVisibility")] + pub history_visibility: events::HistoryVisibility, +} + +impl From<&EncryptionSettings> for crate::olm::EncryptionSettings { + fn from(value: &EncryptionSettings) -> Self { + Self { + algorithm: value.algorithm.clone().into(), + rotation_period: Duration::from_micros(value.rotation_period), + rotation_period_msgs: value.rotation_period_messages, + history_visibility: value.history_visibility.clone().into(), + } + } +} diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto/src/js/mod.rs index c1acc4bd9..0e8112fcf 100644 --- a/crates/matrix-sdk-crypto/src/js/mod.rs +++ b/crates/matrix-sdk-crypto/src/js/mod.rs @@ -1,5 +1,6 @@ //! Additional API that can be useful from JavaScript. +pub mod events; mod future; pub mod identifiers; pub mod machine; From a024c9b268c090ce3fdc72639dc5eeee38d4c73f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 11:05:16 +0200 Subject: [PATCH 29/58] docs(crypto): Add missing documentation. --- crates/matrix-sdk-crypto/src/js/machine.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index e494275e5..7b6626901 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -233,6 +233,11 @@ impl OlmMachine { future_to_promise(async move { Ok(me.invalidate_group_session(&room_id).await?) }) } + /// Get to-device requests to share a group session with users in a room. + /// + /// `room_id` is the room ID. `users` is an array of strings + /// representing user IDs. `encryption_settings` are an + /// `EncryptionSettings` object. #[wasm_bindgen(js_name = "shareGroupSession")] pub fn share_group_session( &self, From f01cfe42b4764a3077d44fe4fafcc4a6a3f558b4 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 11:32:18 +0200 Subject: [PATCH 30/58] feat(crypto): Implement `EncryptionSettings.new` with default values. --- crates/matrix-sdk-crypto/src/js/events.rs | 14 +++++++++++ crates/matrix-sdk-crypto/src/js/machine.rs | 29 ++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/events.rs b/crates/matrix-sdk-crypto/src/js/events.rs index 427db2fc4..7576a4dd9 100644 --- a/crates/matrix-sdk-crypto/src/js/events.rs +++ b/crates/matrix-sdk-crypto/src/js/events.rs @@ -44,3 +44,17 @@ impl From for ruma::events::room::history_visibility::History } } } + +impl Into for ruma::events::room::history_visibility::HistoryVisibility { + fn into(self) -> HistoryVisibility { + use HistoryVisibility::*; + + match self { + Self::Invited => Invited, + Self::Joined => Joined, + Self::Shared => Shared, + Self::WorldReadable => WorldReadable, + _ => unreachable!("Unkonwn variant"), + } + } +} diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 7b6626901..2cdb06be0 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -354,9 +354,22 @@ impl From for ruma::EventEncryptionAlgorithm { } } +impl Into for ruma::EventEncryptionAlgorithm { + fn into(self) -> EncryptionAlgorithm { + use EncryptionAlgorithm::*; + + match self { + Self::OlmV1Curve25519AesSha2 => OlmV1Curve25519AesSha2, + Self::MegolmV1AesSha2 => MegolmV1AesSha2, + _ => unreachable!("Unknown variant"), + } + } +} + #[wasm_bindgen(getter_with_clone)] #[derive(Debug, Clone)] pub struct EncryptionSettings { + /// The algorith, see `EncryptionAlgorithm`. pub algorithm: EncryptionAlgorithm, /// A duration expressed in microseconds. @@ -370,6 +383,22 @@ pub struct EncryptionSettings { pub history_visibility: events::HistoryVisibility, } +#[wasm_bindgen] +impl EncryptionSettings { + /// Create a new `EncryptionSettings` with default values. + #[wasm_bindgen(constructor)] + pub fn new() -> EncryptionSettings { + let default = crate::olm::EncryptionSettings::default(); + + Self { + algorithm: default.algorithm.into(), + rotation_period: default.rotation_period.as_micros().try_into().unwrap(), + rotation_period_messages: default.rotation_period_msgs, + history_visibility: default.history_visibility.into(), + } + } +} + impl From<&EncryptionSettings> for crate::olm::EncryptionSettings { fn from(value: &EncryptionSettings) -> Self { Self { From 8161360852baceebdb660845af314ca57461d714 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 13:32:59 +0200 Subject: [PATCH 31/58] feat(crypto): Add `toString` methods on identifier objects. --- crates/matrix-sdk-crypto/src/js/identifiers.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/identifiers.rs b/crates/matrix-sdk-crypto/src/js/identifiers.rs index efc782202..7623b54a7 100644 --- a/crates/matrix-sdk-crypto/src/js/identifiers.rs +++ b/crates/matrix-sdk-crypto/src/js/identifiers.rs @@ -40,6 +40,12 @@ impl UserId { pub fn is_historical(&self) -> bool { self.inner.is_historical() } + + /// Return the user ID as a string. + #[wasm_bindgen(js_name = "toString")] + pub fn to_string(&self) -> String { + self.inner.as_str().to_owned() + } } /// A Matrix key ID. @@ -59,6 +65,12 @@ impl DeviceId { pub fn new(id: &str) -> DeviceId { Self { inner: id.into() } } + + /// Return the device ID as a string. + #[wasm_bindgen(js_name = "toString")] + pub fn to_string(&self) -> String { + self.inner.as_str().to_owned() + } } /// A Matrix [room ID]. @@ -88,6 +100,12 @@ impl RoomId { pub fn server_name(&self) -> ServerName { ServerName { inner: self.inner.server_name().to_owned() } } + + /// Return the device ID as a string. + #[wasm_bindgen(js_name = "toString")] + pub fn to_string(&self) -> String { + self.inner.as_str().to_owned() + } } /// A Matrix-spec compliant [server name]. From 16b5eebe234cdfa70dfff66a58d8bf4e0458727f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 16:18:10 +0200 Subject: [PATCH 32/58] feat(crypto): Implement a hacky `downcast` function. --- crates/matrix-sdk-crypto/src/js/mod.rs | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto/src/js/mod.rs index 0e8112fcf..e3833d1f5 100644 --- a/crates/matrix-sdk-crypto/src/js/mod.rs +++ b/crates/matrix-sdk-crypto/src/js/mod.rs @@ -7,3 +7,34 @@ pub mod machine; pub mod requests; pub mod responses; pub mod sync_events; + +use js_sys::{Object, Reflect}; +use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*}; + +/// A really hacky and dirty code to downcast a `JsValue` to `T: +/// RefFromWasmAbi`, inspired by +/// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288. +/// +/// The returned value is a likely to be `wasm_bindgen::__ref::Ref`. +fn downcast(value: &JsValue, classname: &str) -> Result +where + T: RefFromWasmAbi, +{ + let constructor_name = Object::get_prototype_of(&value).constructor().name(); + + if constructor_name == classname { + let pointer = Reflect::get(value, &JsValue::from_str("ptr")) + .map_err(|_| JsError::new("Failed to read the `JsValue` pointer"))?; + let pointer = pointer + .as_f64() + .ok_or_else(|| JsError::new("Failed to read the `JsValue` pointer as a `f64`"))? + as u32; + + Ok(unsafe { T::ref_from_abi(pointer) }) + } else { + Err(JsError::new(&format!( + "Expect an `{}` instance, received `{}` instead", + classname, constructor_name, + ))) + } +} From da8699fe408031a4a0a35d0b817876a0a46445d5 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 16:18:58 +0200 Subject: [PATCH 33/58] feat(crypto): `DeviceLists.new` expects `Array`s now. --- .../matrix-sdk-crypto/src/js/sync_events.rs | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/js/sync_events.rs b/crates/matrix-sdk-crypto/src/js/sync_events.rs index 6e739290a..a37c7c14f 100644 --- a/crates/matrix-sdk-crypto/src/js/sync_events.rs +++ b/crates/matrix-sdk-crypto/src/js/sync_events.rs @@ -3,7 +3,7 @@ use js_sys::Array; use wasm_bindgen::prelude::*; -use crate::js::identifiers; +use crate::js::{downcast, identifiers}; /// Information on E2E device updates. #[wasm_bindgen] @@ -17,44 +17,21 @@ impl DeviceLists { /// Create an empty `DeviceLists`. /// /// `changed` and `left` must be an array of strings representing - /// a user ID, otherwise invalid entries will be filtered out with - /// an error. + /// a user ID. Ideally, we should pass a `UserId` object instance, + /// but it's a limitation of `wasm-bindgen` (a workaround is + /// possible but it will slow down performance). #[wasm_bindgen(constructor)] pub fn new(changed: Array, left: Array) -> Result { let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default(); inner.changed = changed .iter() - .map(|user| { - let user = user - .as_string() - .ok_or_else(|| JsError::new("Given user ID is not a string"))?; - let user = ruma::UserId::parse(&user).map_err(|error| { - JsError::new(&format!( - "Given user ID `{}` has an invalid syntax: {}", - user, error, - )) - })?; - - Ok(user) - }) + .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) .collect::, JsError>>()?; inner.left = left .iter() - .map(|user| { - let user = user - .as_string() - .ok_or_else(|| JsError::new("Given user ID is not a string"))?; - let user = ruma::UserId::parse(&user).map_err(|error| { - JsError::new(&format!( - "Given user ID `{}` has an invalid syntax: {}", - user, error, - )) - })?; - - Ok(user) - }) + .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) .collect::, JsError>>()?; Ok(Self { inner }) From db30ef6ee48f91453eaa96a5e88f1377fe7bb982 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 17 May 2022 16:55:25 +0200 Subject: [PATCH 34/58] test(crypto): Add tests for the Wasm API. --- crates/matrix-sdk-crypto/tests/js/events.js | 10 ++ .../matrix-sdk-crypto/tests/js/identifiers.js | 37 ++++++ crates/matrix-sdk-crypto/tests/js/machine.js | 112 ++++++++++++++++++ crates/matrix-sdk-crypto/tests/js/requests.js | 41 +++++++ .../matrix-sdk-crypto/tests/js/sync_events.js | 23 ++++ examples/js/Makefile | 3 + 6 files changed, 226 insertions(+) create mode 100644 crates/matrix-sdk-crypto/tests/js/events.js create mode 100644 crates/matrix-sdk-crypto/tests/js/identifiers.js create mode 100644 crates/matrix-sdk-crypto/tests/js/machine.js create mode 100644 crates/matrix-sdk-crypto/tests/js/requests.js create mode 100644 crates/matrix-sdk-crypto/tests/js/sync_events.js diff --git a/crates/matrix-sdk-crypto/tests/js/events.js b/crates/matrix-sdk-crypto/tests/js/events.js new file mode 100644 index 000000000..a572c3e2f --- /dev/null +++ b/crates/matrix-sdk-crypto/tests/js/events.js @@ -0,0 +1,10 @@ +const { HistoryVisibility } = require('../../pkg/matrix_sdk_crypto'); +const test = require('node:test'); +const assert = require('node:assert/strict'); + +test('HistoryVisibility', (t) => { + assert.equal(HistoryVisibility.Invited, 0); + assert.equal(HistoryVisibility.Joined, 1); + assert.equal(HistoryVisibility.Shared, 2); + assert.equal(HistoryVisibility.WorldReadable, 3); +}); diff --git a/crates/matrix-sdk-crypto/tests/js/identifiers.js b/crates/matrix-sdk-crypto/tests/js/identifiers.js new file mode 100644 index 000000000..0315b4e1a --- /dev/null +++ b/crates/matrix-sdk-crypto/tests/js/identifiers.js @@ -0,0 +1,37 @@ +const { UserId, DeviceId, RoomId, ServerName } = require('../../pkg/matrix_sdk_crypto'); +const test = require('node:test'); +const assert = require('node:assert/strict'); + +test('UserId', (t) => { + assert.throws(() => { new UserId('@foobar') }, Error, 'An invalid user ID must throw an error'); + + const user = new UserId('@foo:bar.org'); + + assert.equal(user.localpart(), 'foo', 'Localpart is present'); + assert.ok(user.serverName() instanceof ServerName, 'Server name is present'); + assert.equal(user.isHistorical, false, 'User ID is not historical'); + assert.equal(user.toString(), '@foo:bar.org', 'Can read the user ID as a string'); +}); + +test('DeviceId', (t) => { + assert.equal(new DeviceId('foo').toString(), 'foo', 'Can read the device ID as a string'); +}); + +test('RoomId', (t) => { + assert.throws(() => { new UserId('!foo') }, Error, 'An invalid room ID must throw an error'); + + const room = new RoomId('!foo:bar.org'); + + assert.equal(room.localpart(), 'foo', 'Localpart is present'); + assert.ok(room.serverName() instanceof ServerName, 'Server name is present'); + assert.equal(room.toString(), '!foo:bar.org', 'Can read the room ID as a string'); +}); + +test('ServerName', (t) => { + assert.throws(() => { new ServerName('@foobar') }, Error, 'An invalid server name must throw an error'); + + assert.equal(new ServerName('foo.org').host(), 'foo.org', 'Host is present'); + assert.equal(new ServerName('foo.org').port(), undefined, 'Port is absent'); + assert.equal(new ServerName('foo.org:1234').port(), 1234, 'Port is present'); + assert.equal(new ServerName('foo.org').isIpLiteral(), false, 'Server name is not an IP literal'); +}); diff --git a/crates/matrix-sdk-crypto/tests/js/machine.js b/crates/matrix-sdk-crypto/tests/js/machine.js new file mode 100644 index 000000000..10c0e89ea --- /dev/null +++ b/crates/matrix-sdk-crypto/tests/js/machine.js @@ -0,0 +1,112 @@ +const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, UserId, DeviceId, OlmMachine, DeviceLists, KeysUploadRequest, KeysQueryRequest } = require('../../pkg/matrix_sdk_crypto'); +const test = require('node:test'); +const assert = require('node:assert/strict'); + +test('EncryptionAlgorithm', (t) => { + assert.equal(EncryptionAlgorithm.OlmV1Curve25519AesSha2, 0); + assert.equal(EncryptionAlgorithm.MegolmV1AesSha2, 1); +}); + +test('EncryptionSettings', (t) => { + let es = new EncryptionSettings(); + + assert.equal(es.algorithm, EncryptionAlgorithm.MegolmV1AesSha2, 'It has a default algorithm'); + assert.equal(es.rotationPeriod, 604800000000n, 'It has a default rotation period'); + assert.equal(es.rotationPeriodMessages, 100n, 'It has a default message rotation period'); + assert.equal(es.historyVisibility, HistoryVisibility.Shared, 'It has a default history visibility'); + + es.algorithm = EncryptionSettings.OlmV1Curve25519AesSha2; + assert.equal(es.algorithm, EncryptionAlgorithm.OlmV1Curve25519AesSha2, 'It has a new algorithm'); + assert.throws(() => { es.algorithm = 42 }, Error, 'Enum values are validated'); + + es.rotationPeriod = 42n; + assert.equal(es.rotationPeriod, 42n, 'It has a new rotation period'); + + es.rotationPeriodMessages = 153n; + assert.equal(es.rotationPeriodMessages, 153n, 'It has a new message rotation period'); + + es.historyVisibility = HistoryVisibility.WorldReadable; + assert.equal(es.historyVisibility, HistoryVisibility.WorldReadable, 'It has a new history visibility'); + assert.throws(() => { es.historyVisibility = 42 }, Error, 'Enum values are validated'); +}); + +test('OlmMachine', async (t) => { + const user_id = new UserId('@foo:bar.org'); + const device_id = new DeviceId('baz'); + + await t.test('Construct', async (t) => { + const machine = await new OlmMachine(user_id, device_id); + + assert.ok(machine instanceof OlmMachine); + assert.equal(machine.userId().toString(), '@foo:bar.org', 'User ID is present'); + assert.equal(machine.deviceId().toString(), 'baz', 'Device ID is present'); + }); + + await t.test('Identity keys', async (t) => { + const machine = await new OlmMachine(user_id, device_id); + const identity_keys = machine.identityKeys(); + + assert.match(identity_keys.ed25519.toBase64(), /^[A-Za-z0-9+/]+$/, 'Ed25519 can be base64-encoded'); + assert.match(identity_keys.curve25519.toBase64(), /^[A-Za-z0-9+/]+$/, 'Curve25519 can be base64-encoded'); + assert.ok(identity_keys.curve25519.length > 0, 'Curve25519\'s length is greater than zero'); + }); + + await t.test('Display name', async (t) => { + const machine = await new OlmMachine(user_id, device_id); + + assert.equal(await machine.displayName(), undefined, 'Display name is absent by default'); + }); + + await t.test('Tracked users', async (t) => { + const machine = await new OlmMachine(user_id, device_id); + const tracked_users = machine.trackedUsers(); + + assert.ok(tracked_users instanceof Set, 'Tracket users are stored in a `Set`'); + assert.equal(tracked_users.size, 0, 'No tracked users by default'); + }); + + await t.test('Receive sync changes', async (t) => { + const machine = await new OlmMachine(user_id, device_id); + const to_device_events = JSON.stringify({}); + const changed_devices = new DeviceLists( + [new UserId('@foo:matrix.org'), new UserId('@bar:matrix.org')], + [new UserId('@baz:matrix.org'), new UserId('@qux:matrix.org')], + ); + const one_time_key_counts = new Map(); + one_time_key_counts.set('foo', 42); + one_time_key_counts.set('bar', 153); + const unused_fallback_keys = new Set(); + unused_fallback_keys.add('baz'); + unused_fallback_keys.add('qux'); + + const decrypted_to_device = JSON.parse( + await machine.receiveSyncChanges( + to_device_events, + changed_devices, + one_time_key_counts, + unused_fallback_keys, + ) + ); + + assert.deepEqual(decrypted_to_device, {}, 'Nothing to do by default'); + }); + + await t.test('Outgoing requests', async (t) => { + const machine = await new OlmMachine(user_id, device_id); + const outgoing_requests = await machine.outgoingRequests(); + + assert.ok(outgoing_requests instanceof Array, 'Outgoing requests are stored in an `Array`'); + assert.equal(outgoing_requests.length, 2, 'There is 2 outgoing requests'); + + const request1 = outgoing_requests[0]; + const request2 = outgoing_requests[1]; + + assert.ok(request1 instanceof KeysUploadRequest, 'First request is `KeysUploadRequest'); + assert.ok(request1.request_id.length > 0, 'First request has an ID'); + assert.ok(JSON.parse(request1.body) instanceof Object, 'First request has a valid body'); + + assert.ok(request2 instanceof KeysQueryRequest, 'Second request is `KeysQueryRequest`'); + assert.ok(request2.request_id.length > 0, 'Second request has an ID'); + assert.ok(JSON.parse(request2.body) instanceof Object, 'Second request has a valid body'); + }); +}); diff --git a/crates/matrix-sdk-crypto/tests/js/requests.js b/crates/matrix-sdk-crypto/tests/js/requests.js new file mode 100644 index 000000000..9c12decf3 --- /dev/null +++ b/crates/matrix-sdk-crypto/tests/js/requests.js @@ -0,0 +1,41 @@ +const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../../pkg/matrix_sdk_crypto'); +const test = require('node:test'); +const assert = require('node:assert/strict'); + +test('RequestType', (t) => { + assert.equal(RequestType.KeysUpload, 0); + assert.equal(RequestType.KeysQuery, 1); + assert.equal(RequestType.KeysClaim, 2); + assert.equal(RequestType.ToDevice, 3); + assert.equal(RequestType.SignatureUpload, 4); + assert.equal(RequestType.RoomMessage, 5); + assert.equal(RequestType.KeysBackup, 6); +}); + +test('KeysUploadRequest', (t) => { + assert.ok(new KeysUploadRequest()); +}); + +test('KeysQueryRequest', (t) => { + assert.ok(new KeysQueryRequest()); +}); + +test('KeysClaimRequest', (t) => { + assert.ok(new KeysClaimRequest()); +}); + +test('ToDeviceRequest', (t) => { + assert.ok(new ToDeviceRequest()); +}); + +test('SignatureUploadRequest', (t) => { + assert.ok(new SignatureUploadRequest()); +}); + +test('RoomMessageRequest', (t) => { + assert.ok(new RoomMessageRequest()); +}); + +test('KeysBackupRequest', (t) => { + assert.ok(new KeysBackupRequest()); +}); diff --git a/crates/matrix-sdk-crypto/tests/js/sync_events.js b/crates/matrix-sdk-crypto/tests/js/sync_events.js new file mode 100644 index 000000000..9d5ec7215 --- /dev/null +++ b/crates/matrix-sdk-crypto/tests/js/sync_events.js @@ -0,0 +1,23 @@ +const { DeviceLists, UserId } = require('../../pkg/matrix_sdk_crypto'); +const test = require('node:test'); +const assert = require('node:assert/strict'); + +test('DeviceLists', (t) => { + const empty = new DeviceLists([], []); + + assert.equal(empty.isEmpty(), true, 'List is empty'); + assert.equal(empty.changed().length, 0, 'No user ID changed'); + assert.equal(empty.left().length, 0, 'No user ID left'); + + const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]); + + assert.equal(list.isEmpty(), false, 'List is not empty'); + + const changed = list.changed(); + assert.equal(changed.length, 1, 'There is one user ID changed'); + assert.equal(changed[0].toString(), '@foo:bar.org', 'The user ID changed is correct'); + + const left = list.left(); + assert.equal(left.length, 1, 'There is one user ID left'); + assert.equal(left[0].toString(), '@baz:qux.org', 'The user ID left is correct'); +}); diff --git a/examples/js/Makefile b/examples/js/Makefile index 4813643a6..a47b7a27f 100644 --- a/examples/js/Makefile +++ b/examples/js/Makefile @@ -1,2 +1,5 @@ build: RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs ../../crates/matrix-sdk-crypto --features js + +test: + node --test ../../crates/matrix-sdk-crypto/tests/js/**.js From cdb252be5e5660cd1d563722b204a4f165239695 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 19 May 2022 14:29:36 +0200 Subject: [PATCH 35/58] chore(crypto): Move everything inside `crates/matrix-sdk-crypto/`. --- crates/matrix-sdk-crypto/js/Makefile | 5 +++ crates/matrix-sdk-crypto/tests/js/events.js | 2 +- .../matrix-sdk-crypto/tests/js/identifiers.js | 2 +- crates/matrix-sdk-crypto/tests/js/machine.js | 2 +- crates/matrix-sdk-crypto/tests/js/requests.js | 2 +- .../matrix-sdk-crypto/tests/js/sync_events.js | 2 +- examples/js/Makefile | 5 --- examples/js/index.js | 41 ------------------- 8 files changed, 10 insertions(+), 51 deletions(-) create mode 100644 crates/matrix-sdk-crypto/js/Makefile delete mode 100644 examples/js/Makefile delete mode 100644 examples/js/index.js diff --git a/crates/matrix-sdk-crypto/js/Makefile b/crates/matrix-sdk-crypto/js/Makefile new file mode 100644 index 000000000..a57dc67e6 --- /dev/null +++ b/crates/matrix-sdk-crypto/js/Makefile @@ -0,0 +1,5 @@ +build: + RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-dir ./js/pkg ../ --features js + +test: + node --test ../tests/js/**.js diff --git a/crates/matrix-sdk-crypto/tests/js/events.js b/crates/matrix-sdk-crypto/tests/js/events.js index a572c3e2f..881d86e06 100644 --- a/crates/matrix-sdk-crypto/tests/js/events.js +++ b/crates/matrix-sdk-crypto/tests/js/events.js @@ -1,4 +1,4 @@ -const { HistoryVisibility } = require('../../pkg/matrix_sdk_crypto'); +const { HistoryVisibility } = require('../../js/pkg/matrix_sdk_crypto'); const test = require('node:test'); const assert = require('node:assert/strict'); diff --git a/crates/matrix-sdk-crypto/tests/js/identifiers.js b/crates/matrix-sdk-crypto/tests/js/identifiers.js index 0315b4e1a..38442111a 100644 --- a/crates/matrix-sdk-crypto/tests/js/identifiers.js +++ b/crates/matrix-sdk-crypto/tests/js/identifiers.js @@ -1,4 +1,4 @@ -const { UserId, DeviceId, RoomId, ServerName } = require('../../pkg/matrix_sdk_crypto'); +const { UserId, DeviceId, RoomId, ServerName } = require('../../js/pkg/matrix_sdk_crypto'); const test = require('node:test'); const assert = require('node:assert/strict'); diff --git a/crates/matrix-sdk-crypto/tests/js/machine.js b/crates/matrix-sdk-crypto/tests/js/machine.js index 10c0e89ea..4c9c7b6a2 100644 --- a/crates/matrix-sdk-crypto/tests/js/machine.js +++ b/crates/matrix-sdk-crypto/tests/js/machine.js @@ -1,4 +1,4 @@ -const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, UserId, DeviceId, OlmMachine, DeviceLists, KeysUploadRequest, KeysQueryRequest } = require('../../pkg/matrix_sdk_crypto'); +const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, UserId, DeviceId, OlmMachine, DeviceLists, KeysUploadRequest, KeysQueryRequest } = require('../../js/pkg/matrix_sdk_crypto'); const test = require('node:test'); const assert = require('node:assert/strict'); diff --git a/crates/matrix-sdk-crypto/tests/js/requests.js b/crates/matrix-sdk-crypto/tests/js/requests.js index 9c12decf3..00c82511a 100644 --- a/crates/matrix-sdk-crypto/tests/js/requests.js +++ b/crates/matrix-sdk-crypto/tests/js/requests.js @@ -1,4 +1,4 @@ -const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../../pkg/matrix_sdk_crypto'); +const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../../js/pkg/matrix_sdk_crypto'); const test = require('node:test'); const assert = require('node:assert/strict'); diff --git a/crates/matrix-sdk-crypto/tests/js/sync_events.js b/crates/matrix-sdk-crypto/tests/js/sync_events.js index 9d5ec7215..46cf63e38 100644 --- a/crates/matrix-sdk-crypto/tests/js/sync_events.js +++ b/crates/matrix-sdk-crypto/tests/js/sync_events.js @@ -1,4 +1,4 @@ -const { DeviceLists, UserId } = require('../../pkg/matrix_sdk_crypto'); +const { DeviceLists, UserId } = require('../../js/pkg/matrix_sdk_crypto'); const test = require('node:test'); const assert = require('node:assert/strict'); diff --git a/examples/js/Makefile b/examples/js/Makefile deleted file mode 100644 index a47b7a27f..000000000 --- a/examples/js/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -build: - RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs ../../crates/matrix-sdk-crypto --features js - -test: - node --test ../../crates/matrix-sdk-crypto/tests/js/**.js diff --git a/examples/js/index.js b/examples/js/index.js deleted file mode 100644 index 384e1a464..000000000 --- a/examples/js/index.js +++ /dev/null @@ -1,41 +0,0 @@ -const { UserId, DeviceId, OlmMachine, ToDevice, DeviceLists } = require('../../crates/matrix-sdk-crypto/pkg'); - -async function run_example() { - const user_id = new UserId('@alice:example.org'); - const device_id = new DeviceId('DEVICE_ID'); - - const olm_machine = await new OlmMachine(user_id, device_id); - console.log(olm_machine); - console.log('olm_machine.userId().localpart() =', olm_machine.userId().localpart()); - console.log('olm_machine.deviceId() =', olm_machine.deviceId()); - console.log('olm_machine.displayName() =', await olm_machine.displayName()); - console.log('olm_machine.identityKeys() =', olm_machine.identityKeys()); - console.log('olm_machine.trackedUsers() = ', olm_machine.trackedUsers()); - - const to_device_events = '{}'; - const changed_devices = new DeviceLists( - ['@foo:matrix.org', '@bar:matrix.org'], - ['@baz:matrix.org', '@qux:matrix.org'], - ); - console.log(changed_devices); - const one_time_key_counts = new Map(); - one_time_key_counts.set('foo', 42); - one_time_key_counts.set('bar', 153); - const unused_fallback_keys = new Set(); - unused_fallback_keys.add('baz'); - unused_fallback_keys.add('qux'); - - const decrypted_to_device = await olm_machine.receiveSyncChanges( - to_device_events, - changed_devices, - one_time_key_counts, - unused_fallback_keys, - ); - console.log(JSON.parse(decrypted_to_device)); - - const outgoing_requests = await olm_machine.outgoingRequests(); - console.log(outgoing_requests); - //console.log(JSON.parse(outgoing_requests[0].body)); -} - -run_example(); From 6b2df7afdd7ac82735f2538026fbf7fa5406c32c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 19 May 2022 14:30:09 +0200 Subject: [PATCH 36/58] feat(crypto): Implement `OlmMachine.get_missing_sessions. --- crates/matrix-sdk-crypto/src/js/machine.rs | 86 ++++++++++---- crates/matrix-sdk-crypto/src/js/requests.rs | 119 +++++++++----------- 2 files changed, 112 insertions(+), 93 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 2cdb06be0..a0b9035d5 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -9,10 +9,9 @@ use serde_json::value::RawValue as RawJsonValue; use wasm_bindgen::prelude::*; use crate::js::{ - events, + downcast, events, future::future_to_promise, - identifiers, - requests::RequestType, + identifiers, requests, responses::{self, response_from_string}, sync_events, }; @@ -170,11 +169,11 @@ impl OlmMachine { pub fn mark_request_as_sent( &self, request_id: &str, - request_type: RequestType, + request_type: requests::RequestType, response: &str, ) -> Result { let transaction_id = OwnedTransactionId::from(request_id); - let response = response_from_string(response).map_err(JsError::from)?; + let response = response_from_string(response)?; let incoming_response = responses::OwnedResponse::try_from((request_type, response))?; let me = self.inner.clone(); @@ -209,9 +208,8 @@ impl OlmMachine { content: &str, ) -> Result { let room_id = room_id.inner.clone(); - let content: Box = serde_json::from_str(content).map_err(JsError::from)?; - let content = - AnyMessageLikeEventContent::from_parts(event_type, &content).map_err(JsError::from)?; + let content: Box = serde_json::from_str(content)?; + let content = AnyMessageLikeEventContent::from_parts(event_type, &content)?; let me = self.inner.clone(); @@ -235,9 +233,9 @@ impl OlmMachine { /// Get to-device requests to share a group session with users in a room. /// - /// `room_id` is the room ID. `users` is an array of strings - /// representing user IDs. `encryption_settings` are an - /// `EncryptionSettings` object. + /// `room_id` is the room ID. `users` is an array of `UserId` + /// objects. `encryption_settings` are an `EncryptionSettings` + /// object. #[wasm_bindgen(js_name = "shareGroupSession")] pub fn share_group_session( &self, @@ -248,19 +246,7 @@ impl OlmMachine { let room_id = room_id.inner.clone(); let users = users .iter() - .map(|user| { - let user = user - .as_string() - .ok_or_else(|| JsError::new("Given user ID is not a string"))?; - let user = ruma::UserId::parse(&user).map_err(|error| { - JsError::new(&format!( - "Given user ID `{}` has an invalid syntax: {}", - user, error - )) - })?; - - Ok(user) - }) + .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) .collect::, JsError>>()?; let encryption_settings = crate::olm::EncryptionSettings::from(encryption_settings); @@ -270,13 +256,63 @@ impl OlmMachine { Ok(serde_json::to_string( &me.share_group_session( &room_id, - users.iter().by_ref().map(AsRef::as_ref), + users.iter().map(AsRef::as_ref), encryption_settings, ) .await?, )?) })) } + + /// Get the a key claiming request for the user/device pairs that + /// we are missing Olm sessions for. + /// + /// Returns `NULL` if no key claiming request needs to be sent + /// out, otherwise it returns an `Array` where the first key is + /// the transaction ID as a string, and the second key is the keys + /// claim request serialized to JSON. + /// + /// Sessions need to be established between devices so group + /// sessions for a room can be shared with them. + /// + /// This should be called every time a group session needs to be + /// shared as well as between sync calls. After a sync some + /// devices may request room keys without us having a valid Olm + /// session with them, making it impossible to server the room key + /// request, thus it’s necessary to check for missing sessions + /// between sync as well. + /// + /// Note: Care should be taken that only one such request at a + /// time is in flight, e.g. using a lock. + /// + /// The response of a successful key claiming requests needs to be + /// passed to the `OlmMachine` with the `mark_request_as_sent`. + /// + /// `users` represents the list of users that we should check if + /// we lack a session with one of their devices. This can be an + /// empty iterator when calling this method between sync requests. + #[wasm_bindgen(js_name = "getMissingSessions")] + pub fn get_missing_sessions(&self, users: &Array) -> Result { + let users = users + .iter() + .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) + .collect::, JsError>>()?; + + let me = self.inner.clone(); + + Ok(future_to_promise(async move { + match me.get_missing_sessions(users.iter().map(AsRef::as_ref)).await? { + Some((transaction_id, keys_claim_request)) => { + Ok(JsValue::from(requests::KeysClaimRequest::try_from(( + transaction_id.to_string(), + &keys_claim_request, + ))?)) + } + + None => Ok(JsValue::NULL), + } + })) + } } #[wasm_bindgen] diff --git a/crates/matrix-sdk-crypto/src/js/requests.rs b/crates/matrix-sdk-crypto/src/js/requests.rs index 4d08356e0..ea871dd1c 100644 --- a/crates/matrix-sdk-crypto/src/js/requests.rs +++ b/crates/matrix-sdk-crypto/src/js/requests.rs @@ -1,8 +1,18 @@ use js_sys::JsString; -use serde_json::json; +use ruma::api::client::keys::{ + claim_keys::v3::Request as RumaKeysClaimRequest, + upload_keys::v3::Request as RumaKeysUploadRequest, + upload_signatures::v3::Request as RumaSignatureUploadRequest, +}; use wasm_bindgen::prelude::*; -use crate::{OutgoingRequest, OutgoingRequests}; +use crate::{ + requests::{ + KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest, + RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest, + }, + OutgoingRequest, OutgoingRequests, +}; /// Data for a request to the `upload_keys` API endpoint. /// @@ -133,6 +143,37 @@ pub struct KeysBackupRequest { pub body: JsString, } +macro_rules! request { + ($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => { + impl TryFrom<(String, &$ruma_request)> for $request { + type Error = serde_json::Error; + + fn try_from( + (request_id, request): (String, &$ruma_request), + ) -> Result { + let mut map = serde_json::Map::new(); + $( + map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap()); + )+ + let value = serde_json::Value::Object(map); + + Ok($request { + request_id: request_id.into(), + body: serde_json::to_string(&value)?.into(), + }) + } + } + }; +} + +request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys); +request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token); +request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys); +request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages); +request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys); +request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content); +request!(KeysBackupRequest from RumaKeysBackupRequest maps fields version, rooms); + // JavaScript has no complex enums like Rust. To return structs of // different types, we have no choice that hidding everything behind a // `JsValue`. @@ -140,93 +181,35 @@ impl TryFrom for JsValue { type Error = serde_json::Error; fn try_from(outgoing_request: OutgoingRequest) -> Result { - let request_id: JsString = outgoing_request.request_id().to_string().into(); + let request_id = outgoing_request.request_id().to_string(); Ok(match outgoing_request.request() { OutgoingRequests::KeysUpload(request) => { - let body = json!({ - "device_keys": request.device_keys, - "one_time_keys": request.one_time_keys, - }); - - JsValue::from(KeysUploadRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(KeysUploadRequest::try_from((request_id, request))?) } OutgoingRequests::KeysQuery(request) => { - let body = json!({ - "timeout": request.timeout, - "device_keys": request.device_keys, - "token": request.token, - }); - - JsValue::from(KeysQueryRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(KeysQueryRequest::try_from((request_id, request))?) } OutgoingRequests::KeysClaim(request) => { - let body = json!({ - "timeout": request.timeout, - "one_time_keys": request.one_time_keys, - }); - - JsValue::from(KeysClaimRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(KeysClaimRequest::try_from((request_id, request))?) } OutgoingRequests::ToDeviceRequest(request) => { - let body = json!({ - "event_type": request.event_type, - "txn_id": request.txn_id, - "messages": request.messages, - }); - - JsValue::from(ToDeviceRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(ToDeviceRequest::try_from((request_id, request))?) } OutgoingRequests::SignatureUpload(request) => { - let body = json!({ - "signed_keys": request.signed_keys, - }); - - JsValue::from(SignatureUploadRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(SignatureUploadRequest::try_from((request_id, request))?) } OutgoingRequests::RoomMessage(request) => { - let body = json!({ - "room_id": request.room_id, - "txn_id": request.txn_id, - "content": request.content, - }); - - JsValue::from(RoomMessageRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(RoomMessageRequest::try_from((request_id, request))?) } OutgoingRequests::KeysBackup(request) => { - let body = json!({ - "version": request.version, - "rooms": request.rooms, - }); - - JsValue::from(KeysBackupRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(KeysBackupRequest::try_from((request_id, request))?) } }) } From f3ab1ae276fdf7fef7a99978da15bbce2721585d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 19 May 2022 14:41:08 +0200 Subject: [PATCH 37/58] chore(crypto): Fix a typo. --- crates/matrix-sdk-crypto/src/js/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/js/events.rs b/crates/matrix-sdk-crypto/src/js/events.rs index 7576a4dd9..98d06afe8 100644 --- a/crates/matrix-sdk-crypto/src/js/events.rs +++ b/crates/matrix-sdk-crypto/src/js/events.rs @@ -54,7 +54,7 @@ impl Into for ruma::events::room::history_visibility::History Self::Joined => Joined, Self::Shared => Shared, Self::WorldReadable => WorldReadable, - _ => unreachable!("Unkonwn variant"), + _ => unreachable!("Unknown variant"), } } } From 250b85dc79fc32f1c4207135e8187334134938a2 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 31 May 2022 08:38:13 +0200 Subject: [PATCH 38/58] feat(crypto): Extract `js` module to its own crate: `matrix-sdk-crypto-js`. --- crates/matrix-sdk-crypto-js/Cargo.toml | 47 +++++++++++++++++++ crates/matrix-sdk-crypto-js/README.md | 0 .../js/Makefile | 2 +- .../js => matrix-sdk-crypto-js/src}/events.rs | 0 .../js => matrix-sdk-crypto-js/src}/future.rs | 0 .../src}/identifiers.rs | 0 .../src/lib.rs} | 22 ++++++++- .../src}/machine.rs | 21 +++++---- .../src}/requests.rs | 21 +++++---- .../src}/responses.rs | 3 +- .../src}/sync_events.rs | 2 +- .../tests/js/events.js | 0 .../tests/js/identifiers.js | 0 .../tests/js/machine.js | 0 .../tests/js/requests.js | 0 .../tests/js/sync_events.js | 0 crates/matrix-sdk-crypto/Cargo.toml | 23 +-------- crates/matrix-sdk-crypto/src/lib.rs | 9 +--- 18 files changed, 98 insertions(+), 52 deletions(-) create mode 100644 crates/matrix-sdk-crypto-js/Cargo.toml create mode 100644 crates/matrix-sdk-crypto-js/README.md rename crates/{matrix-sdk-crypto => matrix-sdk-crypto-js}/js/Makefile (60%) rename crates/{matrix-sdk-crypto/src/js => matrix-sdk-crypto-js/src}/events.rs (100%) rename crates/{matrix-sdk-crypto/src/js => matrix-sdk-crypto-js/src}/future.rs (100%) rename crates/{matrix-sdk-crypto/src/js => matrix-sdk-crypto-js/src}/identifiers.rs (100%) rename crates/{matrix-sdk-crypto/src/js/mod.rs => matrix-sdk-crypto-js/src/lib.rs} (56%) rename crates/{matrix-sdk-crypto/src/js => matrix-sdk-crypto-js/src}/machine.rs (95%) rename crates/{matrix-sdk-crypto/src/js => matrix-sdk-crypto-js/src}/requests.rs (96%) rename crates/{matrix-sdk-crypto/src/js => matrix-sdk-crypto-js/src}/responses.rs (98%) rename crates/{matrix-sdk-crypto/src/js => matrix-sdk-crypto-js/src}/sync_events.rs (98%) rename crates/{matrix-sdk-crypto => matrix-sdk-crypto-js}/tests/js/events.js (100%) rename crates/{matrix-sdk-crypto => matrix-sdk-crypto-js}/tests/js/identifiers.js (100%) rename crates/{matrix-sdk-crypto => matrix-sdk-crypto-js}/tests/js/machine.js (100%) rename crates/{matrix-sdk-crypto => matrix-sdk-crypto-js}/tests/js/requests.js (100%) rename crates/{matrix-sdk-crypto => matrix-sdk-crypto-js}/tests/js/sync_events.js (100%) diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml new file mode 100644 index 000000000..7efccb75b --- /dev/null +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -0,0 +1,47 @@ +[package] +authors = ["Ivan Enderlin "] +description = "Matrix encryption library" +edition = "2021" +homepage = "https://github.com/matrix-org/matrix-rust-sdk" +keywords = ["matrix", "chat", "messaging", "ruma", "nio"] +license = "Apache-2.0" +name = "matrix-sdk-crypto-js" +readme = "README.md" +repository = "https://github.com/matrix-org/matrix-rust-sdk" +rust-version = "1.60" +version = "0.5.0" + +[package.metadata.docs.rs] +features = ["docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.wasm-pack.profile.release] +wasm-opt = ['-Oz'] + +[lib] +crate-type = ["cdylib"] + +[features] +default = ["js"] +qrcode = ["matrix-sdk-crypto/qrcode"] +backups_v1 = ["matrix-sdk-crypto/backups_v1"] +docsrs = [] +js = [] +nodejs = [] + +[dependencies] +matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } +http = "0.2.6" +serde_json = "1.0.79" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } +vodozemac = "0.2.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } +vodozemac = { version = "0.2.0", features = ["js"] } +wasm-bindgen = "0.2.80" +wasm-bindgen-futures = "0.4.30" +js-sys = "0.3.49" +anyhow = "1.0" \ No newline at end of file diff --git a/crates/matrix-sdk-crypto-js/README.md b/crates/matrix-sdk-crypto-js/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/crates/matrix-sdk-crypto/js/Makefile b/crates/matrix-sdk-crypto-js/js/Makefile similarity index 60% rename from crates/matrix-sdk-crypto/js/Makefile rename to crates/matrix-sdk-crypto-js/js/Makefile index a57dc67e6..fd2542a6a 100644 --- a/crates/matrix-sdk-crypto/js/Makefile +++ b/crates/matrix-sdk-crypto-js/js/Makefile @@ -1,5 +1,5 @@ build: - RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-dir ./js/pkg ../ --features js + RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./js/pkg ../ --features js test: node --test ../tests/js/**.js diff --git a/crates/matrix-sdk-crypto/src/js/events.rs b/crates/matrix-sdk-crypto-js/src/events.rs similarity index 100% rename from crates/matrix-sdk-crypto/src/js/events.rs rename to crates/matrix-sdk-crypto-js/src/events.rs diff --git a/crates/matrix-sdk-crypto/src/js/future.rs b/crates/matrix-sdk-crypto-js/src/future.rs similarity index 100% rename from crates/matrix-sdk-crypto/src/js/future.rs rename to crates/matrix-sdk-crypto-js/src/future.rs diff --git a/crates/matrix-sdk-crypto/src/js/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs similarity index 100% rename from crates/matrix-sdk-crypto/src/js/identifiers.rs rename to crates/matrix-sdk-crypto-js/src/identifiers.rs diff --git a/crates/matrix-sdk-crypto/src/js/mod.rs b/crates/matrix-sdk-crypto-js/src/lib.rs similarity index 56% rename from crates/matrix-sdk-crypto/src/js/mod.rs rename to crates/matrix-sdk-crypto-js/src/lib.rs index e3833d1f5..acc9d95bc 100644 --- a/crates/matrix-sdk-crypto/src/js/mod.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -1,4 +1,24 @@ -//! Additional API that can be useful from JavaScript. +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn(missing_docs, missing_debug_implementations)] +#[cfg(all(feature = "js", not(target_arch = "wasm32")))] +compile_error!( + "The `js` feature must be enabled only for the `wasm32` target (either `wasm32-unknown-unknown` or `wasm32-wasi`)." +); pub mod events; mod future; diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs similarity index 95% rename from crates/matrix-sdk-crypto/src/js/machine.rs rename to crates/matrix-sdk-crypto-js/src/machine.rs index a0b9035d5..ba1e59cf7 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -8,10 +8,11 @@ use ruma::{ use serde_json::value::RawValue as RawJsonValue; use wasm_bindgen::prelude::*; -use crate::js::{ +use crate::{ downcast, events, future::future_to_promise, identifiers, requests, + requests::OutgoingRequest, responses::{self, response_from_string}, sync_events, }; @@ -19,7 +20,7 @@ use crate::js::{ #[wasm_bindgen] #[derive(Debug)] pub struct OlmMachine { - inner: Arc, + inner: Arc, } #[cfg_attr(feature = "js", wasm_bindgen)] @@ -31,7 +32,9 @@ impl OlmMachine { future_to_promise(async move { Ok(OlmMachine { - inner: Arc::new(crate::OlmMachine::new(user_id.as_ref(), device_id.as_ref()).await), + inner: Arc::new( + matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref()).await, + ), }) }) } @@ -149,6 +152,7 @@ impl OlmMachine { .outgoing_requests() .await? .into_iter() + .map(OutgoingRequest) .map(TryFrom::try_from) .collect::, _>>()? .into_iter() @@ -248,7 +252,8 @@ impl OlmMachine { .iter() .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) .collect::, JsError>>()?; - let encryption_settings = crate::olm::EncryptionSettings::from(encryption_settings); + let encryption_settings = + matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings); let me = self.inner.clone(); @@ -360,8 +365,8 @@ pub struct IdentityKeys { pub curve25519: Curve25519PublicKey, } -impl From for IdentityKeys { - fn from(value: crate::olm::IdentityKeys) -> Self { +impl From for IdentityKeys { + fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self { Self { ed25519: Ed25519PublicKey { inner: value.ed25519 }, curve25519: Curve25519PublicKey { inner: value.curve25519 }, @@ -424,7 +429,7 @@ impl EncryptionSettings { /// Create a new `EncryptionSettings` with default values. #[wasm_bindgen(constructor)] pub fn new() -> EncryptionSettings { - let default = crate::olm::EncryptionSettings::default(); + let default = matrix_sdk_crypto::olm::EncryptionSettings::default(); Self { algorithm: default.algorithm.into(), @@ -435,7 +440,7 @@ impl EncryptionSettings { } } -impl From<&EncryptionSettings> for crate::olm::EncryptionSettings { +impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings { fn from(value: &EncryptionSettings) -> Self { Self { algorithm: value.algorithm.clone().into(), diff --git a/crates/matrix-sdk-crypto/src/js/requests.rs b/crates/matrix-sdk-crypto-js/src/requests.rs similarity index 96% rename from crates/matrix-sdk-crypto/src/js/requests.rs rename to crates/matrix-sdk-crypto-js/src/requests.rs index ea871dd1c..06b1366be 100644 --- a/crates/matrix-sdk-crypto/src/js/requests.rs +++ b/crates/matrix-sdk-crypto-js/src/requests.rs @@ -1,4 +1,11 @@ use js_sys::JsString; +use matrix_sdk_crypto::{ + requests::{ + KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest, + RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest, + }, + OutgoingRequests, +}; use ruma::api::client::keys::{ claim_keys::v3::Request as RumaKeysClaimRequest, upload_keys::v3::Request as RumaKeysUploadRequest, @@ -6,14 +13,6 @@ use ruma::api::client::keys::{ }; use wasm_bindgen::prelude::*; -use crate::{ - requests::{ - KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest, - RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest, - }, - OutgoingRequest, OutgoingRequests, -}; - /// Data for a request to the `upload_keys` API endpoint. /// /// Publishes end-to-end encryption keys for the device. @@ -177,13 +176,15 @@ request!(KeysBackupRequest from RumaKeysBackupRequest maps fields version, rooms // JavaScript has no complex enums like Rust. To return structs of // different types, we have no choice that hidding everything behind a // `JsValue`. +pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest); + impl TryFrom for JsValue { type Error = serde_json::Error; fn try_from(outgoing_request: OutgoingRequest) -> Result { - let request_id = outgoing_request.request_id().to_string(); + let request_id = outgoing_request.0.request_id().to_string(); - Ok(match outgoing_request.request() { + Ok(match outgoing_request.0.request() { OutgoingRequests::KeysUpload(request) => { JsValue::from(KeysUploadRequest::try_from((request_id, request))?) } diff --git a/crates/matrix-sdk-crypto/src/js/responses.rs b/crates/matrix-sdk-crypto-js/src/responses.rs similarity index 98% rename from crates/matrix-sdk-crypto/src/js/responses.rs rename to crates/matrix-sdk-crypto-js/src/responses.rs index 03b19274d..080347dbb 100644 --- a/crates/matrix-sdk-crypto/src/js/responses.rs +++ b/crates/matrix-sdk-crypto-js/src/responses.rs @@ -1,5 +1,6 @@ //! Types related to responses. +use matrix_sdk_crypto::IncomingResponse; pub(crate) use ruma::api::client::{ backup::add_backup_keys::v3::Response as KeysBackupResponse, keys::{ @@ -13,7 +14,7 @@ pub(crate) use ruma::api::client::{ use ruma::api::IncomingResponse as RumaIncomingResponse; use wasm_bindgen::prelude::*; -use crate::{js::requests::RequestType, IncomingResponse}; +use crate::requests::RequestType; pub(crate) fn response_from_string(body: &str) -> http::Result>> { http::Response::builder().status(200).body(body.as_bytes().to_vec()) diff --git a/crates/matrix-sdk-crypto/src/js/sync_events.rs b/crates/matrix-sdk-crypto-js/src/sync_events.rs similarity index 98% rename from crates/matrix-sdk-crypto/src/js/sync_events.rs rename to crates/matrix-sdk-crypto-js/src/sync_events.rs index a37c7c14f..6dfcec056 100644 --- a/crates/matrix-sdk-crypto/src/js/sync_events.rs +++ b/crates/matrix-sdk-crypto-js/src/sync_events.rs @@ -3,7 +3,7 @@ use js_sys::Array; use wasm_bindgen::prelude::*; -use crate::js::{downcast, identifiers}; +use crate::{downcast, identifiers}; /// Information on E2E device updates. #[wasm_bindgen] diff --git a/crates/matrix-sdk-crypto/tests/js/events.js b/crates/matrix-sdk-crypto-js/tests/js/events.js similarity index 100% rename from crates/matrix-sdk-crypto/tests/js/events.js rename to crates/matrix-sdk-crypto-js/tests/js/events.js diff --git a/crates/matrix-sdk-crypto/tests/js/identifiers.js b/crates/matrix-sdk-crypto-js/tests/js/identifiers.js similarity index 100% rename from crates/matrix-sdk-crypto/tests/js/identifiers.js rename to crates/matrix-sdk-crypto-js/tests/js/identifiers.js diff --git a/crates/matrix-sdk-crypto/tests/js/machine.js b/crates/matrix-sdk-crypto-js/tests/js/machine.js similarity index 100% rename from crates/matrix-sdk-crypto/tests/js/machine.js rename to crates/matrix-sdk-crypto-js/tests/js/machine.js diff --git a/crates/matrix-sdk-crypto/tests/js/requests.js b/crates/matrix-sdk-crypto-js/tests/js/requests.js similarity index 100% rename from crates/matrix-sdk-crypto/tests/js/requests.js rename to crates/matrix-sdk-crypto-js/tests/js/requests.js diff --git a/crates/matrix-sdk-crypto/tests/js/sync_events.js b/crates/matrix-sdk-crypto-js/tests/js/sync_events.js similarity index 100% rename from crates/matrix-sdk-crypto/tests/js/sync_events.js rename to crates/matrix-sdk-crypto-js/tests/js/sync_events.js diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 817b0a0a8..a9e0eaa48 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -15,18 +15,11 @@ version = "0.5.0" features = ["docsrs"] rustdoc-args = ["--cfg", "docsrs"] -[package.metadata.wasm-pack.profile.release] -wasm-opt = ['-Oz'] - -[lib] -crate-type = ["cdylib"] - [features] default = [] qrcode = ["matrix-sdk-qrcode"] backups_v1 = ["olm-rs", "bs58"] docsrs = [] -js = ["http"] # Testing helpers for implementations based upon this testing = ["http"] @@ -54,19 +47,9 @@ sha2 = "0.10.2" thiserror = "1.0.30" tracing = "0.1.34" zeroize = { version = "1.3.0", features = ["zeroize_derive"] } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "e09c93f2c8df9770793abeec57ed984d5e1f3834" } -[target.'cfg(target_arch = "wasm32")'.dependencies] -ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } -vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "e09c93f2c8df9770793abeec57ed984d5e1f3834", features = ["js"] } -wasm-bindgen = "0.2.80" -wasm-bindgen-futures = "0.4.30" -js-sys = "0.3.49" -anyhow = "1.0" - [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] } http = "0.2.6" @@ -75,8 +58,4 @@ matches = "0.1.9" matrix-sdk-test = { version = "0.5.0", path = "../matrix-sdk-test" } proptest = { version = "1.0.0", default-features = false, features = ["std"] } # required for async_test macro -tokio = { version = "1.7.0", default-features = false, features = ["macros", "rt-multi-thread"] } - -[target.'cfg(target_arch = "wasm32")'.dev-dependencies] -getrandom = { version = "0.2.6", features = ["js"] } -wasm-bindgen-test = "0.3.24" \ No newline at end of file +tokio = { version = "1.7.0", default-features = false, features = ["macros", "rt-multi-thread"] } \ No newline at end of file diff --git a/crates/matrix-sdk-crypto/src/lib.rs b/crates/matrix-sdk-crypto/src/lib.rs index aa3357472..e8f85b5c5 100644 --- a/crates/matrix-sdk-crypto/src/lib.rs +++ b/crates/matrix-sdk-crypto/src/lib.rs @@ -15,10 +15,6 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] -#[cfg(all(feature = "js", not(target_arch = "wasm32")))] -compile_error!( - "The `js` feature must be enabled only for the `wasm32` target (either `wasm32-unknown-unknown` or `wasm32-wasi`)." -); #[cfg(feature = "backups_v1")] pub mod backups; @@ -26,10 +22,9 @@ mod error; mod file_encryption; mod gossiping; mod identities; -mod js; mod machine; pub mod olm; -mod requests; +pub mod requests; mod session_manager; pub mod store; pub mod types; @@ -83,8 +78,6 @@ pub use identities::{ Device, LocalTrust, MasterPubkey, OwnUserIdentity, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities, ReadOnlyUserIdentity, UserDevices, UserIdentities, UserIdentity, }; -#[cfg(feature = "js")] -pub use js::*; pub use machine::OlmMachine; #[cfg(feature = "qrcode")] pub use matrix_sdk_qrcode; From 6c472f873f908c202cda1d503b74200defceec0d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 23 May 2022 15:40:37 +0200 Subject: [PATCH 39/58] feat(crypto-js): Start adding support for NodeJS. --- crates/matrix-sdk-crypto-js/Cargo.toml | 16 +++++++----- crates/matrix-sdk-crypto-js/build.rs | 7 ++++++ crates/matrix-sdk-crypto-js/nodejs/Makefile | 5 ++++ crates/matrix-sdk-crypto-js/package.json | 27 +++++++++++++++++++++ crates/matrix-sdk-crypto-js/src/errors.rs | 16 ++++++++++++ crates/matrix-sdk-crypto-js/src/lib.rs | 9 +++++++ 6 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 crates/matrix-sdk-crypto-js/build.rs create mode 100644 crates/matrix-sdk-crypto-js/nodejs/Makefile create mode 100644 crates/matrix-sdk-crypto-js/package.json create mode 100644 crates/matrix-sdk-crypto-js/src/errors.rs diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml index 7efccb75b..6080b04ca 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -22,21 +22,20 @@ wasm-opt = ['-Oz'] crate-type = ["cdylib"] [features] -default = ["js"] +default = [] qrcode = ["matrix-sdk-crypto/qrcode"] -backups_v1 = ["matrix-sdk-crypto/backups_v1"] docsrs = [] js = [] -nodejs = [] +nodejs = ["napi", "napi-derive"] [dependencies] matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } -http = "0.2.6" -serde_json = "1.0.79" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = "0.2.0" +napi = { version = "2.4.3", default-features = false, features = ["napi4"], optional = true } +napi-derive = { version = "2.4.1", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } @@ -44,4 +43,9 @@ vodozemac = { version = "0.2.0", features = ["js"] } wasm-bindgen = "0.2.80" wasm-bindgen-futures = "0.4.30" js-sys = "0.3.49" -anyhow = "1.0" \ No newline at end of file +serde_json = "1.0.79" +http = "0.2.6" +anyhow = "1.0" + +[target.'cfg(not(target_arch = "wasm32"))'.build-dependencies] +napi-build = "2.0.0" \ No newline at end of file diff --git a/crates/matrix-sdk-crypto-js/build.rs b/crates/matrix-sdk-crypto-js/build.rs new file mode 100644 index 000000000..46bfd15de --- /dev/null +++ b/crates/matrix-sdk-crypto-js/build.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "nodejs")] +fn main() { + napi_build::setup(); +} + +#[cfg(not(feature = "nodejs"))] +fn main() {} diff --git a/crates/matrix-sdk-crypto-js/nodejs/Makefile b/crates/matrix-sdk-crypto-js/nodejs/Makefile new file mode 100644 index 000000000..2bdfdad2c --- /dev/null +++ b/crates/matrix-sdk-crypto-js/nodejs/Makefile @@ -0,0 +1,5 @@ +build: + cd .. && napi build --platform --release --features nodejs + +test: + echo 'nop' diff --git a/crates/matrix-sdk-crypto-js/package.json b/crates/matrix-sdk-crypto-js/package.json new file mode 100644 index 000000000..be38b651c --- /dev/null +++ b/crates/matrix-sdk-crypto-js/package.json @@ -0,0 +1,27 @@ +{ + "name": "matrix-sdk-crypto", + "version": "0.5.0", + "main": "index.js", + "types": "index.d.ts", + "napi": { + "name": "matrix-sdk-crypto", + "triples": { + "additional": [ + "aarch64-apple-darwin" + ] + } + }, + "license": "MIT", + "devDependencies": { + "@napi-rs/cli": "^2.9.0", + "ava": "^4.2.0" + }, + "engines": { + "node": ">= 10" + }, + "scripts": { + "artifacts": "napi artifacts", + "build": "napi build --platform --release", + "test": "ava" + } +} diff --git a/crates/matrix-sdk-crypto-js/src/errors.rs b/crates/matrix-sdk-crypto-js/src/errors.rs new file mode 100644 index 000000000..cec0d369a --- /dev/null +++ b/crates/matrix-sdk-crypto-js/src/errors.rs @@ -0,0 +1,16 @@ +#[cfg(feature = "js")] +pub type Error = wasm_bindgen::JsError; + +#[cfg(feature = "nodejs")] +#[derive(Debug)] +pub struct Error(napi::Error); + +#[cfg(feature = "nodejs")] +impl From for Error +where + E: std::error::Error, +{ + fn from(error: E) -> Self { + Self(napi::Error::from_reason(error.to_string())) + } +} diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index acc9d95bc..765a32557 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -15,11 +15,19 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] + +#[cfg(all(not(feature = "js"), not(feature = "nodejs")))] +compile_error!("One of the following features must be enabled: `js` or `nodejs`"); + +#[cfg(all(feature = "js", feature = "nodejs"))] +compile_error!("The `js` and `nodejs` features are mutually exclusive."); + #[cfg(all(feature = "js", not(target_arch = "wasm32")))] compile_error!( "The `js` feature must be enabled only for the `wasm32` target (either `wasm32-unknown-unknown` or `wasm32-wasi`)." ); +//mod errors; pub mod events; mod future; pub mod identifiers; @@ -36,6 +44,7 @@ use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*}; /// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288. /// /// The returned value is a likely to be `wasm_bindgen::__ref::Ref`. +#[cfg(feature = "js")] fn downcast(value: &JsValue, classname: &str) -> Result where T: RefFromWasmAbi, From bce11b209e94fe8a2db386aeaeec0e6f01e7fdb4 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 23 May 2022 15:55:31 +0200 Subject: [PATCH 40/58] feat(crypto-js): Implement `OlmMachine.update_tracked_users`. --- crates/matrix-sdk-crypto-js/src/machine.rs | 25 +++++++++++++++++++ .../matrix-sdk-crypto-js/tests/js/machine.js | 7 ++++++ 2 files changed, 32 insertions(+) diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index ba1e59cf7..e1a4eaa36 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -83,6 +83,31 @@ impl OlmMachine { set } + /// Update the tracked users. + /// + /// `users` is an iterator over user IDs that should be marked for + /// tracking. + /// + /// This will mark users that weren't seen before for a key query + /// and tracking. + /// + /// If the user is already known to the Olm machine, it will not + /// be considered for a key query. + #[wasm_bindgen(js_name = "updateTrackedUsers")] + pub fn update_tracked_users(&self, users: &Array) -> Result { + let users = users + .iter() + .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) + .collect::, JsError>>()?; + + let me = self.inner.clone(); + + Ok(future_to_promise(async move { + me.update_tracked_users(users.iter().map(AsRef::as_ref)).await; + Ok(JsValue::UNDEFINED) + })) + } + #[wasm_bindgen(js_name = "receiveSyncChanges")] pub fn receive_sync_changes( &self, diff --git a/crates/matrix-sdk-crypto-js/tests/js/machine.js b/crates/matrix-sdk-crypto-js/tests/js/machine.js index 4c9c7b6a2..d887a3ba4 100644 --- a/crates/matrix-sdk-crypto-js/tests/js/machine.js +++ b/crates/matrix-sdk-crypto-js/tests/js/machine.js @@ -65,6 +65,13 @@ test('OlmMachine', async (t) => { assert.equal(tracked_users.size, 0, 'No tracked users by default'); }); + await t.test('Update tracked users', async (t) => { + const machine = await new OlmMachine(user_id, device_id); + const update_tracked_users = await machine.updateTrackedUsers([new UserId('@foo:matrix.org'), new UserId('@bar:matrix.org')]); + + assert.equal(update_tracked_users, undefined, 'Updating tracked users returns nothing'); + }); + await t.test('Receive sync changes', async (t) => { const machine = await new OlmMachine(user_id, device_id); const to_device_events = JSON.stringify({}); From 1a731ec385d13340e00bf1afe7f7871e607bc664 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 23 May 2022 15:59:04 +0200 Subject: [PATCH 41/58] feat(crypto-js): Rename `OlmMachine.encrypt` to `.encrypt_room_event`. --- crates/matrix-sdk-crypto-js/src/machine.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index e1a4eaa36..2fede223d 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -230,7 +230,8 @@ impl OlmMachine { /// # Panics /// /// Panics if a group session for the given room wasn't shared beforehand. - pub fn encrypt( + #[wasm_bindgen(js_name = "encryptRoomEvent")] + pub fn encrypt_room_event( &self, room_id: &identifiers::RoomId, event_type: &str, @@ -243,7 +244,7 @@ impl OlmMachine { let me = self.inner.clone(); Ok(future_to_promise(async move { - Ok(serde_json::to_string(&me.encrypt(&room_id, content).await?)?) + Ok(serde_json::to_string(&me.encrypt_room_event(&room_id, content).await?)?) })) } From 4df5ee6087a4ddf864d34274ab0ecbeac6bbdd33 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 23 May 2022 17:23:00 +0200 Subject: [PATCH 42/58] chore(crypto-js): Improve the `Makefile` for NodeJS support. --- crates/matrix-sdk-crypto-js/nodejs/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/matrix-sdk-crypto-js/nodejs/Makefile b/crates/matrix-sdk-crypto-js/nodejs/Makefile index 2bdfdad2c..cd75e5d27 100644 --- a/crates/matrix-sdk-crypto-js/nodejs/Makefile +++ b/crates/matrix-sdk-crypto-js/nodejs/Makefile @@ -1,5 +1,9 @@ build: cd .. && napi build --platform --release --features nodejs + test -f ../index.js && mv ../index.js pkg/ || true + test -f ../index.d.ts && mv ../index.d.ts pkg/ || true + test -f ../matrix-sdk-crypto.*.node && mv ../matrix-sdk-crypto.*.node pkg/ || true + echo '*' > pkg/.gitignore test: echo 'nop' From afef9103a3d338f15f26b82d7a050dffbf0828eb Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 24 May 2022 11:54:41 +0200 Subject: [PATCH 43/58] feat(crypto-js): Continue to port the API to NodeJS. --- crates/matrix-sdk-crypto-js/Cargo.toml | 2 +- crates/matrix-sdk-crypto-js/src/errors.rs | 7 + crates/matrix-sdk-crypto-js/src/events.rs | 7 +- .../matrix-sdk-crypto-js/src/identifiers.rs | 171 +++++++++++++++--- crates/matrix-sdk-crypto-js/src/prelude.rs | 8 + 5 files changed, 164 insertions(+), 31 deletions(-) create mode 100644 crates/matrix-sdk-crypto-js/src/prelude.rs diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml index 6080b04ca..2b4f35bae 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -34,7 +34,7 @@ matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = "0.2.0" -napi = { version = "2.4.3", default-features = false, features = ["napi4"], optional = true } +napi = { git = "https://github.com/Hywan/napi-rs", branch = "feat-tonapivalue-u16", default-features = false, features = ["napi4"], optional = true } napi-derive = { version = "2.4.1", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/matrix-sdk-crypto-js/src/errors.rs b/crates/matrix-sdk-crypto-js/src/errors.rs index cec0d369a..413ef54a9 100644 --- a/crates/matrix-sdk-crypto-js/src/errors.rs +++ b/crates/matrix-sdk-crypto-js/src/errors.rs @@ -14,3 +14,10 @@ where Self(napi::Error::from_reason(error.to_string())) } } + +#[cfg(feature = "nodejs")] +impl Into for Error { + fn into(self) -> napi::Error { + self.0 + } +} diff --git a/crates/matrix-sdk-crypto-js/src/events.rs b/crates/matrix-sdk-crypto-js/src/events.rs index 98d06afe8..a79dd8f6e 100644 --- a/crates/matrix-sdk-crypto-js/src/events.rs +++ b/crates/matrix-sdk-crypto-js/src/events.rs @@ -1,10 +1,11 @@ //! Types related to events. -use wasm_bindgen::prelude::*; +use crate::prelude::*; /// Who can see a room's history. -#[derive(Debug, Clone)] -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] +#[cfg_attr(feature = "nodejs", napi)] +#[derive(Debug)] pub enum HistoryVisibility { /// Previous events are accessible to newly joined members from /// the point they were invited onwards. diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 7623b54a7..5b1d21675 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -1,22 +1,22 @@ //! Types for [Matrix](https://matrix.org/) identifiers for devices, //! events, keys, rooms, servers, users and URIs. -use wasm_bindgen::prelude::*; +use crate::prelude::*; /// A Matrix [user ID]. /// /// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers -#[wasm_bindgen] -#[derive(Debug, Clone)] +#[cfg_attr(feature = "js", wasm_bindgen)] +#[cfg_attr(feature = "nodejs", napi)] pub struct UserId { pub(crate) inner: ruma::OwnedUserId, } -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] impl UserId { /// Parse/validate and create a new `UserId`. - #[wasm_bindgen(constructor)] - pub fn new(id: &str) -> Result { + #[cfg_attr(feature = "js", wasm_bindgen(constructor))] + pub fn new(id: &str) -> Result { Ok(Self { inner: ruma::UserId::parse(id)? }) } @@ -26,7 +26,7 @@ impl UserId { } /// Returns the server name of the user ID. - #[wasm_bindgen(js_name = "serverName")] + #[cfg_attr(feature = "js", wasm_bindgen(js_name = "serverName"))] pub fn server_name(&self) -> ServerName { ServerName { inner: self.inner.server_name().to_owned() } } @@ -36,57 +36,113 @@ impl UserId { /// A historical user ID is one that doesn't conform to the latest /// specification of the user ID grammar but is still accepted /// because it was previously allowed. - #[wasm_bindgen(getter, js_name = "isHistorical")] + #[cfg_attr(feature = "js", wasm_bindgen(getter, js_name = "isHistorical"))] pub fn is_historical(&self) -> bool { self.inner.is_historical() } /// Return the user ID as a string. - #[wasm_bindgen(js_name = "toString")] + #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toString"))] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } } +#[cfg(feature = "nodejs")] +#[napi] +impl UserId { + /// Parse/validate and create a new `UserId`. + #[napi(constructor)] + pub fn new_(id: String) -> Result { + Self::new(id.as_ref()).map_err(Into::::into) + } + + /// Returns the user's localpart. + #[napi(js_name = "localpart")] + pub fn localpart_(&self) -> String { + self.localpart() + } + + /// Returns the server name of the user ID. + #[napi(js_name = "serverName")] + pub fn server_name_(&self) -> ServerName { + self.server_name() + } + + /// Whether this user ID is a historical one. + /// + /// A historical user ID is one that doesn't conform to the latest + /// specification of the user ID grammar but is still accepted + /// because it was previously allowed. + #[napi(getter, js_name = "isHistorical")] + pub fn is_historical_(&self) -> bool { + self.is_historical() + } + + /// Return the user ID as a string. + #[napi(js_name = "toString")] + pub fn to_string_(&self) -> String { + self.to_string() + } +} + /// A Matrix key ID. /// /// Device identifiers in Matrix are completely opaque character /// sequences. This type is provided simply for its semantic value. -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] +#[cfg_attr(feature = "nodejs", napi)] #[derive(Debug, Clone)] pub struct DeviceId { pub(crate) inner: ruma::OwnedDeviceId, } -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] impl DeviceId { /// Create a new `DeviceId`. - #[wasm_bindgen(constructor)] + #[cfg_attr(feature = "js", wasm_bindgen(constructor))] pub fn new(id: &str) -> DeviceId { Self { inner: id.into() } } /// Return the device ID as a string. - #[wasm_bindgen(js_name = "toString")] + #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toString"))] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } } +#[cfg(feature = "nodejs")] +#[napi] +impl DeviceId { + /// Create a new `DeviceId`. + #[napi(constructor)] + pub fn new_(id: String) -> DeviceId { + Self::new(id.as_ref()) + } + + /// Return the device ID as a string. + #[napi(js_name = "toString")] + pub fn to_string_(&self) -> String { + self.to_string() + } +} + /// A Matrix [room ID]. /// /// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] +#[cfg_attr(feature = "nodejs", napi)] #[derive(Debug, Clone)] pub struct RoomId { pub(crate) inner: ruma::OwnedRoomId, } -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] impl RoomId { - /// Parse/validate and create a new `UserId`. - #[wasm_bindgen(constructor)] - pub fn new(id: &str) -> Result { + /// Parse/validate and create a new `RoomId`. + #[cfg_attr(feature = "js", wasm_bindgen(constructor))] + pub fn new(id: &str) -> Result { Ok(Self { inner: ruma::RoomId::parse(id)? }) } @@ -95,36 +151,65 @@ impl RoomId { self.inner.localpart().to_owned() } - /// Returns the server name of the user ID. - #[wasm_bindgen(js_name = "serverName")] + /// Returns the server name of the room ID. + #[cfg_attr(feature = "js", wasm_bindgen(js_name = "serverName"))] pub fn server_name(&self) -> ServerName { ServerName { inner: self.inner.server_name().to_owned() } } - /// Return the device ID as a string. - #[wasm_bindgen(js_name = "toString")] + /// Return the room ID as a string. + #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toString"))] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } } +#[cfg(feature = "nodejs")] +#[napi] +impl RoomId { + /// Parse/validate and create a new `RoomId`. + #[napi(constructor)] + pub fn new_(id: String) -> Result { + Self::new(id.as_ref()).map_err(Into::::into) + } + + /// Returns the user's localpart. + #[napi(js_name = "localpart")] + pub fn localpart_(&self) -> String { + self.localpart() + } + + /// Returns the server name of the room ID. + #[napi(js_name = "serverName")] + pub fn server_name_(&self) -> ServerName { + self.server_name() + } + + /// Return the room ID as a string. + #[napi(js_name = "toString")] + pub fn to_string_(&self) -> String { + self.to_string() + } +} + /// A Matrix-spec compliant [server name]. /// /// It consists of a host and an optional port (separated by a colon if /// present). /// /// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] +#[cfg_attr(feature = "nodejs", napi)] #[derive(Debug)] pub struct ServerName { inner: ruma::OwnedServerName, } -#[wasm_bindgen] +#[cfg_attr(feature = "js", wasm_bindgen)] impl ServerName { /// Parse/validate and create a new `ServerName`. - #[wasm_bindgen(constructor)] - pub fn new(name: &str) -> Result { + #[cfg_attr(feature = "js", wasm_bindgen(constructor))] + pub fn new(name: &str) -> Result { Ok(Self { inner: ruma::ServerName::parse(name)? }) } @@ -143,8 +228,40 @@ impl ServerName { /// Returns true if and only if the server name is an IPv4 or IPv6 /// address. - #[wasm_bindgen(js_name = "isIpLiteral")] + #[cfg_attr(feature = "js", wasm_bindgen(js_name = "isIpLiteral"))] pub fn is_ip_literal(&self) -> bool { self.inner.is_ip_literal() } } + +#[cfg(feature = "nodejs")] +#[napi] +impl ServerName { + /// Parse/validate and create a new `ServerName`. + #[napi(constructor)] + pub fn new_(name: String) -> Result { + Self::new(name.as_ref()).map_err(Into::::into) + } + + /// Returns the host of the server name. + /// + /// That is: Return the part of the server before `:` or the + /// full server name if there is no port. + #[napi(js_name = "host")] + pub fn host_(&self) -> String { + self.host() + } + + /// Returns the port of the server name if any. + #[napi(js_name = "port")] + pub fn port_(&self) -> Option { + self.port() + } + + /// Returns true if and only if the server name is an IPv4 or IPv6 + /// address. + #[napi(js_name = "isIpLiteral")] + pub fn is_ip_literal_(&self) -> bool { + self.is_ip_literal() + } +} diff --git a/crates/matrix-sdk-crypto-js/src/prelude.rs b/crates/matrix-sdk-crypto-js/src/prelude.rs new file mode 100644 index 000000000..baa28a63d --- /dev/null +++ b/crates/matrix-sdk-crypto-js/src/prelude.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "nodejs")] +pub use napi::bindgen_prelude::ToNapiValue; +#[cfg(feature = "nodejs")] +pub use napi_derive::napi; +#[cfg(feature = "js")] +pub use wasm_bindgen::prelude::*; + +pub use crate::errors::Error; From ee648144a2c428a945ebf41cea953f28001a3255 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 24 May 2022 14:17:37 +0200 Subject: [PATCH 44/58] feat(crypto-nodejs): Split into a new crate: `matrix-sdk-crypto-nodejs`. Why? Because `napi` and `wasm-bindgen` are too different. At this step, the most notable bugs are the way `napi` is handling its proc macros. There is too much conflicts when used with `#[cfg_attr]`. It makes the code repetitive and harder to read and to understand (and also to compile, we must be very careful). But on the short-term, quickly, we will see more notable differences between `wasm-bindgen` and `napi`, e.g. with array (in `wasm-bindgen`, we can downcast array items into particular types, with `napi` it's going to be a very different code). Instead of fighting the proc macros bugs now, and having to split the code later inside the same crate, we believe it's a good idea to split the code now into 2 crates. At first we will see obvious code duplications, but on the short-term, the code is likely to be more and more different. --- crates/matrix-sdk-crypto-js/.cargo/config | 2 + crates/matrix-sdk-crypto-js/Cargo.toml | 17 +- crates/matrix-sdk-crypto-js/build.rs | 7 - crates/matrix-sdk-crypto-js/js/Makefile | 2 +- crates/matrix-sdk-crypto-js/src/events.rs | 7 +- .../matrix-sdk-crypto-js/src/identifiers.rs | 164 +++-------------- crates/matrix-sdk-crypto-js/src/lib.rs | 14 +- crates/matrix-sdk-crypto-js/src/machine.rs | 2 +- crates/matrix-sdk-crypto-js/src/prelude.rs | 8 - crates/matrix-sdk-crypto-nodejs/Cargo.toml | 34 ++++ crates/matrix-sdk-crypto-nodejs/README.md | 0 crates/matrix-sdk-crypto-nodejs/build.rs | 3 + .../nodejs/Makefile | 2 +- .../package.json | 0 .../src/errors.rs | 6 - crates/matrix-sdk-crypto-nodejs/src/events.rs | 61 +++++++ .../src/identifiers.rs | 167 ++++++++++++++++++ crates/matrix-sdk-crypto-nodejs/src/lib.rs | 28 +++ 18 files changed, 328 insertions(+), 196 deletions(-) create mode 100644 crates/matrix-sdk-crypto-js/.cargo/config delete mode 100644 crates/matrix-sdk-crypto-js/build.rs delete mode 100644 crates/matrix-sdk-crypto-js/src/prelude.rs create mode 100644 crates/matrix-sdk-crypto-nodejs/Cargo.toml create mode 100644 crates/matrix-sdk-crypto-nodejs/README.md create mode 100644 crates/matrix-sdk-crypto-nodejs/build.rs rename crates/{matrix-sdk-crypto-js => matrix-sdk-crypto-nodejs}/nodejs/Makefile (80%) rename crates/{matrix-sdk-crypto-js => matrix-sdk-crypto-nodejs}/package.json (100%) rename crates/{matrix-sdk-crypto-js => matrix-sdk-crypto-nodejs}/src/errors.rs (67%) create mode 100644 crates/matrix-sdk-crypto-nodejs/src/events.rs create mode 100644 crates/matrix-sdk-crypto-nodejs/src/identifiers.rs create mode 100644 crates/matrix-sdk-crypto-nodejs/src/lib.rs diff --git a/crates/matrix-sdk-crypto-js/.cargo/config b/crates/matrix-sdk-crypto-js/.cargo/config new file mode 100644 index 000000000..435ed755e --- /dev/null +++ b/crates/matrix-sdk-crypto-js/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" \ No newline at end of file diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml index 2b4f35bae..f7d69aa01 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Ivan Enderlin "] -description = "Matrix encryption library" +description = "Matrix encryption library, for JavaScript" edition = "2021" homepage = "https://github.com/matrix-org/matrix-rust-sdk" keywords = ["matrix", "chat", "messaging", "ruma", "nio"] @@ -25,19 +25,9 @@ crate-type = ["cdylib"] default = [] qrcode = ["matrix-sdk-crypto/qrcode"] docsrs = [] -js = [] -nodejs = ["napi", "napi-derive"] [dependencies] matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } -vodozemac = "0.2.0" -napi = { git = "https://github.com/Hywan/napi-rs", branch = "feat-tonapivalue-u16", default-features = false, features = ["napi4"], optional = true } -napi-derive = { version = "2.4.1", optional = true } - -[target.'cfg(target_arch = "wasm32")'.dependencies] ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { version = "0.2.0", features = ["js"] } wasm-bindgen = "0.2.80" @@ -45,7 +35,4 @@ wasm-bindgen-futures = "0.4.30" js-sys = "0.3.49" serde_json = "1.0.79" http = "0.2.6" -anyhow = "1.0" - -[target.'cfg(not(target_arch = "wasm32"))'.build-dependencies] -napi-build = "2.0.0" \ No newline at end of file +anyhow = "1.0" \ No newline at end of file diff --git a/crates/matrix-sdk-crypto-js/build.rs b/crates/matrix-sdk-crypto-js/build.rs deleted file mode 100644 index 46bfd15de..000000000 --- a/crates/matrix-sdk-crypto-js/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[cfg(feature = "nodejs")] -fn main() { - napi_build::setup(); -} - -#[cfg(not(feature = "nodejs"))] -fn main() {} diff --git a/crates/matrix-sdk-crypto-js/js/Makefile b/crates/matrix-sdk-crypto-js/js/Makefile index fd2542a6a..71e9e1c82 100644 --- a/crates/matrix-sdk-crypto-js/js/Makefile +++ b/crates/matrix-sdk-crypto-js/js/Makefile @@ -1,5 +1,5 @@ build: - RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./js/pkg ../ --features js + RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./js/pkg ../ test: node --test ../tests/js/**.js diff --git a/crates/matrix-sdk-crypto-js/src/events.rs b/crates/matrix-sdk-crypto-js/src/events.rs index a79dd8f6e..32c2af186 100644 --- a/crates/matrix-sdk-crypto-js/src/events.rs +++ b/crates/matrix-sdk-crypto-js/src/events.rs @@ -1,11 +1,10 @@ //! Types related to events. -use crate::prelude::*; +use wasm_bindgen::prelude::*; /// Who can see a room's history. -#[cfg_attr(feature = "js", wasm_bindgen)] -#[cfg_attr(feature = "nodejs", napi)] -#[derive(Debug)] +#[wasm_bindgen] +#[derive(Debug, Clone)] pub enum HistoryVisibility { /// Previous events are accessible to newly joined members from /// the point they were invited onwards. diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 5b1d21675..417acd680 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -1,22 +1,21 @@ //! Types for [Matrix](https://matrix.org/) identifiers for devices, //! events, keys, rooms, servers, users and URIs. -use crate::prelude::*; +use wasm_bindgen::prelude::*; /// A Matrix [user ID]. /// /// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers -#[cfg_attr(feature = "js", wasm_bindgen)] -#[cfg_attr(feature = "nodejs", napi)] +#[wasm_bindgen] pub struct UserId { pub(crate) inner: ruma::OwnedUserId, } -#[cfg_attr(feature = "js", wasm_bindgen)] +#[wasm_bindgen] impl UserId { /// Parse/validate and create a new `UserId`. - #[cfg_attr(feature = "js", wasm_bindgen(constructor))] - pub fn new(id: &str) -> Result { + #[wasm_bindgen(constructor)] + pub fn new(id: &str) -> Result { Ok(Self { inner: ruma::UserId::parse(id)? }) } @@ -26,7 +25,7 @@ impl UserId { } /// Returns the server name of the user ID. - #[cfg_attr(feature = "js", wasm_bindgen(js_name = "serverName"))] + #[wasm_bindgen(js_name = "serverName")] pub fn server_name(&self) -> ServerName { ServerName { inner: self.inner.server_name().to_owned() } } @@ -36,113 +35,57 @@ impl UserId { /// A historical user ID is one that doesn't conform to the latest /// specification of the user ID grammar but is still accepted /// because it was previously allowed. - #[cfg_attr(feature = "js", wasm_bindgen(getter, js_name = "isHistorical"))] + #[wasm_bindgen(getter, js_name = "isHistorical")] pub fn is_historical(&self) -> bool { self.inner.is_historical() } /// Return the user ID as a string. - #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toString"))] + #[wasm_bindgen(js_name = "toString")] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } } -#[cfg(feature = "nodejs")] -#[napi] -impl UserId { - /// Parse/validate and create a new `UserId`. - #[napi(constructor)] - pub fn new_(id: String) -> Result { - Self::new(id.as_ref()).map_err(Into::::into) - } - - /// Returns the user's localpart. - #[napi(js_name = "localpart")] - pub fn localpart_(&self) -> String { - self.localpart() - } - - /// Returns the server name of the user ID. - #[napi(js_name = "serverName")] - pub fn server_name_(&self) -> ServerName { - self.server_name() - } - - /// Whether this user ID is a historical one. - /// - /// A historical user ID is one that doesn't conform to the latest - /// specification of the user ID grammar but is still accepted - /// because it was previously allowed. - #[napi(getter, js_name = "isHistorical")] - pub fn is_historical_(&self) -> bool { - self.is_historical() - } - - /// Return the user ID as a string. - #[napi(js_name = "toString")] - pub fn to_string_(&self) -> String { - self.to_string() - } -} - /// A Matrix key ID. /// /// Device identifiers in Matrix are completely opaque character /// sequences. This type is provided simply for its semantic value. -#[cfg_attr(feature = "js", wasm_bindgen)] -#[cfg_attr(feature = "nodejs", napi)] +#[wasm_bindgen] #[derive(Debug, Clone)] pub struct DeviceId { pub(crate) inner: ruma::OwnedDeviceId, } -#[cfg_attr(feature = "js", wasm_bindgen)] +#[wasm_bindgen] impl DeviceId { /// Create a new `DeviceId`. - #[cfg_attr(feature = "js", wasm_bindgen(constructor))] + #[wasm_bindgen(constructor)] pub fn new(id: &str) -> DeviceId { Self { inner: id.into() } } /// Return the device ID as a string. - #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toString"))] + #[wasm_bindgen(js_name = "toString")] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } } -#[cfg(feature = "nodejs")] -#[napi] -impl DeviceId { - /// Create a new `DeviceId`. - #[napi(constructor)] - pub fn new_(id: String) -> DeviceId { - Self::new(id.as_ref()) - } - - /// Return the device ID as a string. - #[napi(js_name = "toString")] - pub fn to_string_(&self) -> String { - self.to_string() - } -} - /// A Matrix [room ID]. /// /// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids -#[cfg_attr(feature = "js", wasm_bindgen)] -#[cfg_attr(feature = "nodejs", napi)] +#[wasm_bindgen] #[derive(Debug, Clone)] pub struct RoomId { pub(crate) inner: ruma::OwnedRoomId, } -#[cfg_attr(feature = "js", wasm_bindgen)] +#[wasm_bindgen] impl RoomId { /// Parse/validate and create a new `RoomId`. - #[cfg_attr(feature = "js", wasm_bindgen(constructor))] - pub fn new(id: &str) -> Result { + #[wasm_bindgen(constructor)] + pub fn new(id: &str) -> Result { Ok(Self { inner: ruma::RoomId::parse(id)? }) } @@ -152,64 +95,35 @@ impl RoomId { } /// Returns the server name of the room ID. - #[cfg_attr(feature = "js", wasm_bindgen(js_name = "serverName"))] + #[wasm_bindgen(js_name = "serverName")] pub fn server_name(&self) -> ServerName { ServerName { inner: self.inner.server_name().to_owned() } } /// Return the room ID as a string. - #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toString"))] + #[wasm_bindgen(js_name = "toString")] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } } -#[cfg(feature = "nodejs")] -#[napi] -impl RoomId { - /// Parse/validate and create a new `RoomId`. - #[napi(constructor)] - pub fn new_(id: String) -> Result { - Self::new(id.as_ref()).map_err(Into::::into) - } - - /// Returns the user's localpart. - #[napi(js_name = "localpart")] - pub fn localpart_(&self) -> String { - self.localpart() - } - - /// Returns the server name of the room ID. - #[napi(js_name = "serverName")] - pub fn server_name_(&self) -> ServerName { - self.server_name() - } - - /// Return the room ID as a string. - #[napi(js_name = "toString")] - pub fn to_string_(&self) -> String { - self.to_string() - } -} - /// A Matrix-spec compliant [server name]. /// /// It consists of a host and an optional port (separated by a colon if /// present). /// /// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name -#[cfg_attr(feature = "js", wasm_bindgen)] -#[cfg_attr(feature = "nodejs", napi)] +#[wasm_bindgen] #[derive(Debug)] pub struct ServerName { inner: ruma::OwnedServerName, } -#[cfg_attr(feature = "js", wasm_bindgen)] +#[wasm_bindgen] impl ServerName { /// Parse/validate and create a new `ServerName`. - #[cfg_attr(feature = "js", wasm_bindgen(constructor))] - pub fn new(name: &str) -> Result { + #[wasm_bindgen(constructor)] + pub fn new(name: &str) -> Result { Ok(Self { inner: ruma::ServerName::parse(name)? }) } @@ -228,40 +142,8 @@ impl ServerName { /// Returns true if and only if the server name is an IPv4 or IPv6 /// address. - #[cfg_attr(feature = "js", wasm_bindgen(js_name = "isIpLiteral"))] + #[wasm_bindgen(js_name = "isIpLiteral")] pub fn is_ip_literal(&self) -> bool { self.inner.is_ip_literal() } } - -#[cfg(feature = "nodejs")] -#[napi] -impl ServerName { - /// Parse/validate and create a new `ServerName`. - #[napi(constructor)] - pub fn new_(name: String) -> Result { - Self::new(name.as_ref()).map_err(Into::::into) - } - - /// Returns the host of the server name. - /// - /// That is: Return the part of the server before `:` or the - /// full server name if there is no port. - #[napi(js_name = "host")] - pub fn host_(&self) -> String { - self.host() - } - - /// Returns the port of the server name if any. - #[napi(js_name = "port")] - pub fn port_(&self) -> Option { - self.port() - } - - /// Returns true if and only if the server name is an IPv4 or IPv6 - /// address. - #[napi(js_name = "isIpLiteral")] - pub fn is_ip_literal_(&self) -> bool { - self.is_ip_literal() - } -} diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index 765a32557..0c3bef81e 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -16,18 +16,9 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] -#[cfg(all(not(feature = "js"), not(feature = "nodejs")))] -compile_error!("One of the following features must be enabled: `js` or `nodejs`"); +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] +compile_error!("This crate is designed to only be compiled to `wasm32-unknown-unknown`."); -#[cfg(all(feature = "js", feature = "nodejs"))] -compile_error!("The `js` and `nodejs` features are mutually exclusive."); - -#[cfg(all(feature = "js", not(target_arch = "wasm32")))] -compile_error!( - "The `js` feature must be enabled only for the `wasm32` target (either `wasm32-unknown-unknown` or `wasm32-wasi`)." -); - -//mod errors; pub mod events; mod future; pub mod identifiers; @@ -44,7 +35,6 @@ use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*}; /// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288. /// /// The returned value is a likely to be `wasm_bindgen::__ref::Ref`. -#[cfg(feature = "js")] fn downcast(value: &JsValue, classname: &str) -> Result where T: RefFromWasmAbi, diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index 2fede223d..18efb5a05 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -23,7 +23,7 @@ pub struct OlmMachine { inner: Arc, } -#[cfg_attr(feature = "js", wasm_bindgen)] +#[wasm_bindgen] impl OlmMachine { #[wasm_bindgen(constructor)] pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise { diff --git a/crates/matrix-sdk-crypto-js/src/prelude.rs b/crates/matrix-sdk-crypto-js/src/prelude.rs deleted file mode 100644 index baa28a63d..000000000 --- a/crates/matrix-sdk-crypto-js/src/prelude.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[cfg(feature = "nodejs")] -pub use napi::bindgen_prelude::ToNapiValue; -#[cfg(feature = "nodejs")] -pub use napi_derive::napi; -#[cfg(feature = "js")] -pub use wasm_bindgen::prelude::*; - -pub use crate::errors::Error; diff --git a/crates/matrix-sdk-crypto-nodejs/Cargo.toml b/crates/matrix-sdk-crypto-nodejs/Cargo.toml new file mode 100644 index 000000000..a21878de1 --- /dev/null +++ b/crates/matrix-sdk-crypto-nodejs/Cargo.toml @@ -0,0 +1,34 @@ +[package] +authors = ["Ivan Enderlin "] +description = "Matrix encryption library, for NodeJS" +edition = "2021" +homepage = "https://github.com/matrix-org/matrix-rust-sdk" +keywords = ["matrix", "chat", "messaging", "ruma", "nio"] +license = "Apache-2.0" +name = "matrix-sdk-crypto-nodejs" +readme = "README.md" +repository = "https://github.com/matrix-org/matrix-rust-sdk" +rust-version = "1.60" +version = "0.5.0" + +[package.metadata.docs.rs] +features = ["docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[lib] +crate-type = ["cdylib"] + +[features] +default = [] +qrcode = ["matrix-sdk-crypto/qrcode"] +docsrs = [] + +[dependencies] +matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } +ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } +vodozemac = "0.2.0" +napi = { git = "https://github.com/Hywan/napi-rs", branch = "feat-tonapivalue-u16", default-features = false, features = ["napi4"] } +napi-derive = "2.4.1" + +[build-dependencies] +napi-build = "2.0.0" \ No newline at end of file diff --git a/crates/matrix-sdk-crypto-nodejs/README.md b/crates/matrix-sdk-crypto-nodejs/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/crates/matrix-sdk-crypto-nodejs/build.rs b/crates/matrix-sdk-crypto-nodejs/build.rs new file mode 100644 index 000000000..0f1b01002 --- /dev/null +++ b/crates/matrix-sdk-crypto-nodejs/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/crates/matrix-sdk-crypto-js/nodejs/Makefile b/crates/matrix-sdk-crypto-nodejs/nodejs/Makefile similarity index 80% rename from crates/matrix-sdk-crypto-js/nodejs/Makefile rename to crates/matrix-sdk-crypto-nodejs/nodejs/Makefile index cd75e5d27..f2091d5bd 100644 --- a/crates/matrix-sdk-crypto-js/nodejs/Makefile +++ b/crates/matrix-sdk-crypto-nodejs/nodejs/Makefile @@ -1,5 +1,5 @@ build: - cd .. && napi build --platform --release --features nodejs + cd .. && napi build --platform --release test -f ../index.js && mv ../index.js pkg/ || true test -f ../index.d.ts && mv ../index.d.ts pkg/ || true test -f ../matrix-sdk-crypto.*.node && mv ../matrix-sdk-crypto.*.node pkg/ || true diff --git a/crates/matrix-sdk-crypto-js/package.json b/crates/matrix-sdk-crypto-nodejs/package.json similarity index 100% rename from crates/matrix-sdk-crypto-js/package.json rename to crates/matrix-sdk-crypto-nodejs/package.json diff --git a/crates/matrix-sdk-crypto-js/src/errors.rs b/crates/matrix-sdk-crypto-nodejs/src/errors.rs similarity index 67% rename from crates/matrix-sdk-crypto-js/src/errors.rs rename to crates/matrix-sdk-crypto-nodejs/src/errors.rs index 413ef54a9..a22312994 100644 --- a/crates/matrix-sdk-crypto-js/src/errors.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/errors.rs @@ -1,11 +1,6 @@ -#[cfg(feature = "js")] -pub type Error = wasm_bindgen::JsError; - -#[cfg(feature = "nodejs")] #[derive(Debug)] pub struct Error(napi::Error); -#[cfg(feature = "nodejs")] impl From for Error where E: std::error::Error, @@ -15,7 +10,6 @@ where } } -#[cfg(feature = "nodejs")] impl Into for Error { fn into(self) -> napi::Error { self.0 diff --git a/crates/matrix-sdk-crypto-nodejs/src/events.rs b/crates/matrix-sdk-crypto-nodejs/src/events.rs new file mode 100644 index 000000000..eed3c7459 --- /dev/null +++ b/crates/matrix-sdk-crypto-nodejs/src/events.rs @@ -0,0 +1,61 @@ +//! Types related to events. + +use napi::bindgen_prelude::ToNapiValue; +use napi_derive::*; + +/// Who can see a room's history. +#[napi] +#[derive(Debug)] +pub enum HistoryVisibility { + /// Previous events are accessible to newly joined members from + /// the point they were invited onwards. + /// + /// Events stop being accessible when the member's state changes + /// to something other than *invite* or *join*. + Invited, + + /// Previous events are accessible to newly joined members from + /// the point they joined the room onwards. + /// + /// Events stop being accessible when the member's state changes + /// to something other than *join*. + Joined, + + /// Previous events are always accessible to newly joined members. + /// + /// All events in the room are accessible, even those sent when + /// the member was not a part of the room. + Shared, + + /// All events while this is the `HistoryVisibility` value may be + /// shared by any participating homeserver with anyone, regardless + /// of whether they have ever joined the room. + WorldReadable, +} + +impl From for ruma::events::room::history_visibility::HistoryVisibility { + fn from(value: HistoryVisibility) -> Self { + use HistoryVisibility::*; + + match value { + Invited => Self::Invited, + Joined => Self::Joined, + Shared => Self::Shared, + WorldReadable => Self::WorldReadable, + } + } +} + +impl Into for ruma::events::room::history_visibility::HistoryVisibility { + fn into(self) -> HistoryVisibility { + use HistoryVisibility::*; + + match self { + Self::Invited => Invited, + Self::Joined => Joined, + Self::Shared => Shared, + Self::WorldReadable => WorldReadable, + _ => unreachable!("Unknown variant"), + } + } +} diff --git a/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs b/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs new file mode 100644 index 000000000..c44e2779c --- /dev/null +++ b/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs @@ -0,0 +1,167 @@ +//! Types for [Matrix](https://matrix.org/) identifiers for devices, +//! events, keys, rooms, servers, users and URIs. + +use napi_derive::*; + +use crate::errors::*; + +/// A Matrix [user ID]. +/// +/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers +#[napi] +pub struct UserId { + pub(crate) inner: ruma::OwnedUserId, +} + +#[napi] +impl UserId { + /// Parse/validate and create a new `UserId`. + #[napi(constructor)] + pub fn new(id: String) -> Result { + Ok(Self { + inner: ruma::UserId::parse(id.as_str()) + .map_err(Error::from) + .map_err(Into::::into)?, + }) + } + + /// Returns the user's localpart. + #[napi] + pub fn localpart(&self) -> String { + self.inner.localpart().to_owned() + } + + /// Returns the server name of the user ID. + #[napi(js_name = "serverName")] + pub fn server_name(&self) -> ServerName { + ServerName { inner: self.inner.server_name().to_owned() } + } + + /// Whether this user ID is a historical one. + /// + /// A historical user ID is one that doesn't conform to the latest + /// specification of the user ID grammar but is still accepted + /// because it was previously allowed. + #[napi(getter, js_name = "isHistorical")] + pub fn is_historical(&self) -> bool { + self.inner.is_historical() + } + + /// Return the user ID as a string. + #[napi(js_name = "toString")] + pub fn to_string(&self) -> String { + self.inner.as_str().to_owned() + } +} + +/// A Matrix key ID. +/// +/// Device identifiers in Matrix are completely opaque character +/// sequences. This type is provided simply for its semantic value. +#[napi] +#[derive(Debug, Clone)] +pub struct DeviceId { + pub(crate) inner: ruma::OwnedDeviceId, +} + +#[napi] +impl DeviceId { + /// Create a new `DeviceId`. + #[napi(constructor)] + pub fn new(id: String) -> DeviceId { + Self { inner: id.into() } + } + + /// Return the device ID as a string. + #[napi(js_name = "toString")] + pub fn to_string(&self) -> String { + self.inner.as_str().to_owned() + } +} + +/// A Matrix [room ID]. +/// +/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids +#[napi] +#[derive(Debug, Clone)] +pub struct RoomId { + pub(crate) inner: ruma::OwnedRoomId, +} + +#[napi] +impl RoomId { + /// Parse/validate and create a new `RoomId`. + #[napi(constructor)] + pub fn new(id: String) -> Result { + Ok(Self { + inner: ruma::RoomId::parse(id) + .map_err(Error::from) + .map_err(Into::::into)?, + }) + } + + /// Returns the user's localpart. + #[napi] + pub fn localpart(&self) -> String { + self.inner.localpart().to_owned() + } + + /// Returns the server name of the room ID. + #[napi(js_name = "serverName")] + pub fn server_name(&self) -> ServerName { + ServerName { inner: self.inner.server_name().to_owned() } + } + + /// Return the room ID as a string. + #[napi(js_name = "toString")] + pub fn to_string(&self) -> String { + self.inner.as_str().to_owned() + } +} + +/// A Matrix-spec compliant [server name]. +/// +/// It consists of a host and an optional port (separated by a colon if +/// present). +/// +/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name +#[napi] +#[derive(Debug)] +pub struct ServerName { + inner: ruma::OwnedServerName, +} + +#[napi] +impl ServerName { + /// Parse/validate and create a new `ServerName`. + #[napi(constructor)] + pub fn new(name: String) -> Result { + Ok(Self { + inner: ruma::ServerName::parse(name) + .map_err(Error::from) + .map_err(Into::::into)?, + }) + } + + /// Returns the host of the server name. + /// + /// That is: Return the part of the server before `:` or the + /// full server name if there is no port. + #[napi] + pub fn host(&self) -> String { + self.inner.host().to_owned() + } + + /// Returns the port of the server name if any. + #[napi] + pub fn port(&self) -> Option { + self.inner.port() + } + + /// Returns true if and only if the server name is an IPv4 or IPv6 + /// address. + #[napi(js_name = "isIpLiteral")] + pub fn is_ip_literal(&self) -> bool { + self.inner.is_ip_literal() + } +} diff --git a/crates/matrix-sdk-crypto-nodejs/src/lib.rs b/crates/matrix-sdk-crypto-nodejs/src/lib.rs new file mode 100644 index 000000000..f4ce1f1f2 --- /dev/null +++ b/crates/matrix-sdk-crypto-nodejs/src/lib.rs @@ -0,0 +1,28 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn(missing_docs, missing_debug_implementations)] + +mod errors; +pub mod events; +//mod future; +pub mod identifiers; +//pub mod machine; +//pub mod requests; +//pub mod responses; +//pub mod sync_events; + +pub use crate::errors::Error; From 3194ad1f9a2df1389d89e4cc4ebf36d7a8f2aa47 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 24 May 2022 16:08:11 +0200 Subject: [PATCH 45/58] feat(crypto-js): Implement `OlmMachine.get_verification`. --- .../matrix-sdk-crypto-js/src/identifiers.rs | 18 + crates/matrix-sdk-crypto-js/src/lib.rs | 1 + crates/matrix-sdk-crypto-js/src/machine.rs | 27 +- .../matrix-sdk-crypto-js/src/sync_events.rs | 4 +- .../matrix-sdk-crypto-js/src/verifications.rs | 354 ++++++++++++++++++ 5 files changed, 401 insertions(+), 3 deletions(-) create mode 100644 crates/matrix-sdk-crypto-js/src/verifications.rs diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 417acd680..8742cbc64 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -11,6 +11,12 @@ pub struct UserId { pub(crate) inner: ruma::OwnedUserId, } +impl UserId { + pub(crate) fn new_with(inner: ruma::OwnedUserId) -> Self { + Self { inner } + } +} + #[wasm_bindgen] impl UserId { /// Parse/validate and create a new `UserId`. @@ -57,6 +63,12 @@ pub struct DeviceId { pub(crate) inner: ruma::OwnedDeviceId, } +impl DeviceId { + pub(crate) fn new_with(inner: ruma::OwnedDeviceId) -> Self { + Self { inner } + } +} + #[wasm_bindgen] impl DeviceId { /// Create a new `DeviceId`. @@ -81,6 +93,12 @@ pub struct RoomId { pub(crate) inner: ruma::OwnedRoomId, } +impl RoomId { + pub(crate) fn new_with(inner: ruma::OwnedRoomId) -> Self { + Self { inner } + } +} + #[wasm_bindgen] impl RoomId { /// Parse/validate and create a new `RoomId`. diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index 0c3bef81e..f93cde86f 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -23,6 +23,7 @@ pub mod events; mod future; pub mod identifiers; pub mod machine; +pub mod verifications; pub mod requests; pub mod responses; pub mod sync_events; diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index 18efb5a05..8ffade45c 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -14,7 +14,7 @@ use crate::{ identifiers, requests, requests::OutgoingRequest, responses::{self, response_from_string}, - sync_events, + sync_events, verifications, }; #[wasm_bindgen] @@ -344,6 +344,31 @@ impl OlmMachine { } })) } + + /// Get a verification object for the given user ID with the given flow ID. + /// + /// Returns a list of `JsValue` to represent either (depending on + /// how the Wasm module has been compiled): + /// * `Sas` (enabled), + /// * `Qr` + #[cfg_attr(feature = "qrcode", doc = "(enabled).")] + #[cfg_attr(not(feature = "qrcode"), doc = "(disabled).")] + /// + /// If a verification mode is missing, please try to compile the + /// Wasm module with different features. + #[wasm_bindgen(js_name = "getVerification")] + pub fn get_verification( + &self, + user_id: &identifiers::UserId, + flow_id: &str, + ) -> Result { + self.inner + .get_verification(user_id.inner.as_ref(), flow_id) + .map(verifications::Verification) + .map(JsValue::try_from) + .transpose() + .map(|r| r.unwrap_or_else(|| JsValue::UNDEFINED)) + } } #[wasm_bindgen] diff --git a/crates/matrix-sdk-crypto-js/src/sync_events.rs b/crates/matrix-sdk-crypto-js/src/sync_events.rs index 6dfcec056..a94528197 100644 --- a/crates/matrix-sdk-crypto-js/src/sync_events.rs +++ b/crates/matrix-sdk-crypto-js/src/sync_events.rs @@ -49,7 +49,7 @@ impl DeviceLists { self.inner .changed .iter() - .map(|user| identifiers::UserId { inner: user.clone() }) + .map(|user| identifiers::UserId::new_with(user.clone())) .map(JsValue::from) .collect() } @@ -60,7 +60,7 @@ impl DeviceLists { self.inner .left .iter() - .map(|user| identifiers::UserId { inner: user.clone() }) + .map(|user| identifiers::UserId::new_with(user.clone())) .map(JsValue::from) .collect() } diff --git a/crates/matrix-sdk-crypto-js/src/verifications.rs b/crates/matrix-sdk-crypto-js/src/verifications.rs new file mode 100644 index 000000000..1a22ccb40 --- /dev/null +++ b/crates/matrix-sdk-crypto-js/src/verifications.rs @@ -0,0 +1,354 @@ +use js_sys::{Array, JsString}; +use ruma::events::key::verification::cancel::CancelCode as RumaCancelCode; +use wasm_bindgen::prelude::*; + +use crate::identifiers::{DeviceId, RoomId, UserId}; + +#[wasm_bindgen] +#[derive(Debug)] +pub struct Sas { + inner: matrix_sdk_crypto::Sas, +} + +#[wasm_bindgen] +impl Sas { + /// Get our own user ID. + #[wasm_bindgen(js_name = "userId")] + pub fn user_id(&self) -> UserId { + UserId { inner: self.inner.user_id().to_owned() } + } + + /// Get our own device ID. + #[wasm_bindgen(js_name = "deviceId")] + pub fn device_id(&self) -> DeviceId { + DeviceId { inner: self.inner.device_id().to_owned() } + } + + /// Get the user id of the other side. + #[wasm_bindgen(js_name = "otherUserId")] + pub fn other_user_id(&self) -> UserId { + UserId { inner: self.inner.other_user_id().to_owned() } + } + + /// Get the device ID of the other side. + #[wasm_bindgen(js_name = "otherDeviceId")] + pub fn other_device_id(&self) -> DeviceId { + DeviceId { inner: self.inner.other_device_id().to_owned() } + } + + #[wasm_bindgen(js_name = "otherDevice")] + pub fn other_device(&self) { + todo!() + } + + #[wasm_bindgen(js_name = "flowId")] + pub fn flow_id(&self) { + todo!() + } + + /// Get the room ID if the verification is happening inside a + /// room. + #[wasm_bindgen(js_name = "roomId")] + pub fn room_id(&self) -> Option { + self.inner.room_id().map(ToOwned::to_owned).map(RoomId::new_with) + } + + /// Does this verification flow support displaying emoji for the + /// short authentication string. + #[wasm_bindgen(js_name = "supportsEmoji")] + pub fn supports_emoji(&self) -> bool { + self.inner.supports_emoji() + } + + /// Did this verification flow start from a verification request. + #[wasm_bindgen(js_name = "startedFromRequest")] + pub fn started_from_request(&self) -> bool { + self.inner.started_from_request() + } + + /// Is this a verification that is veryfying one of our own + /// devices. + #[wasm_bindgen(js_name = "isSelfVerification")] + pub fn is_self_verification(&self) -> bool { + self.inner.is_self_verification() + } + + /// Have we confirmed that the short auth string matches. + #[wasm_bindgen(js_name = "haveWeConfirmed")] + pub fn have_we_confirmed(&self) -> bool { + self.inner.have_we_confirmed() + } + + /// Has the verification been accepted by both parties. + #[wasm_bindgen(js_name = "hasBeenAccepted")] + pub fn has_been_accepted(&self) -> bool { + self.inner.has_been_accepted() + } + + /// Get info about the cancellation if the verification flow has + /// been cancelled. + #[wasm_bindgen(js_name = "cancelInfo")] + pub fn cancel_info(&self) -> Option { + self.inner.cancel_info().map(CancelInfo::new_with) + } + + /// Did we initiate the verification flow. + #[wasm_bindgen(js_name = "weStarted")] + pub fn we_started(&self) -> bool { + self.inner.we_started() + } + + pub fn accept(&self) { + todo!() + } + + #[wasm_bindgen(js_name = "acceptWithSetings")] + pub fn accept_with_settings(&self) { + todo!() + } + + pub fn confirm(&self) { + todo!() + } + + pub fn cancel(&self) { + todo!() + } + + #[wasm_bindgen(js_name = "cancelWithCode")] + pub fn cancel_with_code(&self) { + todo!() + } + + /// Has the SAS verification flow timed out. + #[wasm_bindgen(js_name = "timedOut")] + pub fn timed_out(&self) -> bool { + self.inner.timed_out() + } + + /// Are we in a state where we can show the short auth string. + #[wasm_bindgen(js_name = "canBePresented")] + pub fn can_be_presented(&self) -> bool { + self.inner.can_be_presented() + } + + /// Is the SAS flow done. + #[wasm_bindgen(js_name = "isDone")] + pub fn is_done(&self) -> bool { + self.inner.is_done() + } + + /// Is the SAS flow canceled. + #[wasm_bindgen(js_name = "isCancelled")] + pub fn is_cancelled(&self) -> bool { + self.inner.is_cancelled() + } + + /// Get the emoji version of the short auth string. + /// + /// Returns `undefined` if we can't yet present the short auth string, + /// otherwise seven tuples containing the emoji and description. + pub fn emoji(&self) -> Option { + Some( + self.inner + .emoji()? + .iter() + .map(|emoji| Emoji::new_with(emoji.clone())) + .map(JsValue::from) + .collect(), + ) + } + + /// Get the index of the emoji representing the short auth string + /// + /// Returns `undefined` if we can’t yet present the short auth + /// string, otherwise seven u8 numbers in the range from 0 to 63 + /// inclusive which can be converted to an emoji using [the + /// relevant specification + /// entry](https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji). + #[wasm_bindgen(js_name = "emoji_index")] + pub fn emoji_index(&self) -> Option { + Some(self.inner.emoji_index()?.iter().map(|emoji| *emoji).map(JsValue::from).collect()) + } + + /// Get the decimal version of the short auth string. + /// + /// Returns None if we can’t yet present the short auth string, + /// otherwise a tuple containing three 4-digit integers that + /// represent the short auth string. + pub fn decimals(&self) -> Option { + let decimals = self.inner.decimals()?; + + let out = Array::new_with_length(3); + out.set(0, JsValue::from(decimals.0)); + out.set(1, JsValue::from(decimals.1)); + out.set(2, JsValue::from(decimals.2)); + + Some(out) + } +} + +#[cfg(feature = "qrcode")] +#[wasm_bindgen] +#[derive(Debug)] +pub struct Qr { + inner: matrix_sdk_crypto::QrVerification, +} + +#[cfg(feature = "qrcode")] +#[wasm_bindgen] +impl Qr { + #[wasm_bindgen(js_name = "hasBeenScanned")] + pub fn has_been_scanned(&self) -> bool { + self.inner.has_been_scanned() + } +} + +pub(crate) struct Verification(pub(crate) matrix_sdk_crypto::Verification); + +impl TryFrom for JsValue { + type Error = JsError; + + fn try_from(verification: Verification) -> Result { + use matrix_sdk_crypto::Verification::*; + + Ok(match verification.0 { + SasV1(sas) => JsValue::from(Sas { inner: sas }), + + #[cfg(feature = "qrcode")] + QrV1(qr) => JsValue::from(Qr { inner: qr }), + + _ => { + return Err(JsError::new( + "Unknown verification type, expect `m.sas.v1` only for now", + )) + } + }) + } +} + +#[wasm_bindgen] +pub struct CancelInfo { + inner: matrix_sdk_crypto::CancelInfo, +} + +impl CancelInfo { + pub(crate) fn new_with(inner: matrix_sdk_crypto::CancelInfo) -> Self { + Self { inner } + } +} + +#[wasm_bindgen] +impl CancelInfo { + pub fn reason(&self) -> JsString { + self.inner.reason().into() + } + + #[wasm_bindgen(js_name = "cancelCode")] + pub fn cancel_code(&self) -> CancelCode { + self.inner.cancel_code().into() + } + + #[wasm_bindgen(js_name = "cancelledbyUs")] + pub fn cancelled_by_us(&self) -> bool { + self.inner.cancelled_by_us() + } +} + +#[wasm_bindgen] +pub enum CancelCode { + /// Unknown cancel code. + Other, + + /// The user cancelled the verification. + User, + + /// The verification process timed out. + /// + /// Verification processes can define their own timeout + /// parameters. + Timeout, + + /// The device does not know about the given transaction ID. + UnknownTransaction, + + /// The device does not know how to handle the requested method. + /// + /// Should be sent for `m.key.verification.start` messages and + /// messages defined by individual verification processes. + UnknownMethod, + + /// The device received an unexpected message. + /// + /// Typically raised when one of the parties is handling the + /// verification out of order. + UnexpectedMessage, + + /// The key was not verified. + KeyMismatch, + + /// The expected user did not match the user verified. + UserMismatch, + + /// The message received was invalid. + InvalidMessage, + + /// An `m.key.verification.request` was accepted by a different + /// device. + /// + /// The device receiving this error can ignore the verification + /// request. + Accepted, + + /// The device receiving this error can ignore the verification + /// request. + MismatchedCommitment, + + /// The SAS did not match. + MismatchedSas, +} + +impl From<&RumaCancelCode> for CancelCode { + fn from(code: &RumaCancelCode) -> Self { + use RumaCancelCode::*; + + match code { + User => Self::User, + Timeout => Self::Timeout, + UnknownTransaction => Self::UnknownTransaction, + UnknownMethod => Self::UnknownMethod, + UnexpectedMessage => Self::UnexpectedMessage, + KeyMismatch => Self::KeyMismatch, + UserMismatch => Self::UserMismatch, + InvalidMessage => Self::InvalidMessage, + Accepted => Self::Accepted, + MismatchedCommitment => Self::MismatchedCommitment, + MismatchedSas => Self::MismatchedSas, + _ => Self::Other, + } + } +} + +#[wasm_bindgen] +pub struct Emoji { + inner: matrix_sdk_crypto::Emoji, +} + +impl Emoji { + pub(crate) fn new_with(inner: matrix_sdk_crypto::Emoji) -> Self { + Self { inner } + } +} + +#[wasm_bindgen] +impl Emoji { + #[wasm_bindgen(getter)] + pub fn symbol(&self) -> JsString { + self.inner.symbol.into() + } + + #[wasm_bindgen(getter)] + pub fn description(&self) -> JsString { + self.inner.description.into() + } +} From b6a637893e42acea37fcdb6337915be7b70eb865 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 08:48:55 +0200 Subject: [PATCH 46/58] chore(crypto-js): Fix `cargo fmt`. --- crates/matrix-sdk-crypto-js/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index f93cde86f..5ec5e1ed9 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -23,10 +23,10 @@ pub mod events; mod future; pub mod identifiers; pub mod machine; -pub mod verifications; pub mod requests; pub mod responses; pub mod sync_events; +pub mod verifications; use js_sys::{Object, Reflect}; use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*}; From 51488b40d030fa48f2a92a93f9029fcb7dafbefe Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 09:35:21 +0200 Subject: [PATCH 47/58] doc(crypto-js) Add missing documentation. --- crates/matrix-sdk-crypto-js/src/requests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/matrix-sdk-crypto-js/src/requests.rs b/crates/matrix-sdk-crypto-js/src/requests.rs index 06b1366be..ac7942582 100644 --- a/crates/matrix-sdk-crypto-js/src/requests.rs +++ b/crates/matrix-sdk-crypto-js/src/requests.rs @@ -1,3 +1,5 @@ +//! Types to handle requests. + use js_sys::JsString; use matrix_sdk_crypto::{ requests::{ From bef1dfbf7925d5687cc200e8af47ce35def5a8a9 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 09:37:30 +0200 Subject: [PATCH 48/58] chore(crypto-js): Fix typos. --- crates/matrix-sdk-crypto-js/src/lib.rs | 2 +- crates/matrix-sdk-crypto-js/src/machine.rs | 2 +- crates/matrix-sdk-crypto-js/src/requests.rs | 2 +- crates/matrix-sdk-crypto-js/src/verifications.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index 5ec5e1ed9..3f3a4de6a 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -35,7 +35,7 @@ use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*}; /// RefFromWasmAbi`, inspired by /// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288. /// -/// The returned value is a likely to be `wasm_bindgen::__ref::Ref`. +/// The returned value is likely to be a `wasm_bindgen::__ref::Ref`. fn downcast(value: &JsValue, classname: &str) -> Result where T: RefFromWasmAbi, diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index 8ffade45c..d848f51e7 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -461,7 +461,7 @@ impl Into for ruma::EventEncryptionAlgorithm { #[wasm_bindgen(getter_with_clone)] #[derive(Debug, Clone)] pub struct EncryptionSettings { - /// The algorith, see `EncryptionAlgorithm`. + /// The algorithm, see `EncryptionAlgorithm`. pub algorithm: EncryptionAlgorithm, /// A duration expressed in microseconds. diff --git a/crates/matrix-sdk-crypto-js/src/requests.rs b/crates/matrix-sdk-crypto-js/src/requests.rs index ac7942582..468159947 100644 --- a/crates/matrix-sdk-crypto-js/src/requests.rs +++ b/crates/matrix-sdk-crypto-js/src/requests.rs @@ -176,7 +176,7 @@ request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn request!(KeysBackupRequest from RumaKeysBackupRequest maps fields version, rooms); // JavaScript has no complex enums like Rust. To return structs of -// different types, we have no choice that hidding everything behind a +// different types, we have no choice that hiding everything behind a // `JsValue`. pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest); diff --git a/crates/matrix-sdk-crypto-js/src/verifications.rs b/crates/matrix-sdk-crypto-js/src/verifications.rs index 1a22ccb40..d4a77728e 100644 --- a/crates/matrix-sdk-crypto-js/src/verifications.rs +++ b/crates/matrix-sdk-crypto-js/src/verifications.rs @@ -102,7 +102,7 @@ impl Sas { todo!() } - #[wasm_bindgen(js_name = "acceptWithSetings")] + #[wasm_bindgen(js_name = "acceptWithSettings")] pub fn accept_with_settings(&self) { todo!() } From 6afeeea56ce0bea0bd34f2d9a4ed2897a923116a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 09:42:09 +0200 Subject: [PATCH 49/58] test(ci): Do not compile `matrix-sdk-crypto` to Wasm, but `-crypto-js` instead. --- .github/workflows/wasm.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 53b9d7fac..11e293676 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -27,7 +27,7 @@ jobs: - matrix-sdk-qrcode - matrix-sdk-base - matrix-sdk-common - - matrix-sdk-crypto + - matrix-sdk-crypto-js - indexeddb-no-crypto - indexeddb-with-crypto @@ -54,7 +54,7 @@ jobs: profile: minimal override: true - - name: Install WasmPack + - name: Install wasm-pack uses: jetli/wasm-pack-action@v0.3.0 with: version: 'latest' From efe5ea6a9ccab9d2c035bcb827fe7fe46bed4f45 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 09:43:07 +0200 Subject: [PATCH 50/58] test(ci): Exclude `matrix-sdk-crypto-(js|nodejs)` from code coverage. --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b3b5a2330..5b3e72c3f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -38,7 +38,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: tarpaulin - args: --ignore-config --exclude-files "crates/matrix-sdk/examples/*,crates/matrix-sdk-common,crates/matrix-sdk-test" --out Xml + args: --ignore-config --exclude-files "crates/matrix-sdk/examples/*,crates/matrix-sdk-common,crates/matrix-sdk-test,crates/matrix-sdk-crypto-js,crates/matrix-sdk-crypto-nodejs" --out Xml - name: Upload to codecov.io uses: codecov/codecov-action@v3 From 3f8e3b61ffd179d1b2625df6ec400dbc7d74fd5d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 09:56:08 +0200 Subject: [PATCH 51/58] test(xtask) Replace `WasmFeatureSet::MatrixSdkCrypto` by `*Js`. --- xtask/src/ci.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 08c340cbb..fec35730c 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -66,7 +66,7 @@ enum WasmFeatureSet { MatrixSdkNoDefault, MatrixSdkBase, MatrixSdkCommon, - MatrixSdkCrypto, + MatrixSdkCryptoJs, MatrixSdkIndexeddbStoresNoCrypto, MatrixSdkIndexeddbStores, IndexeddbNoCrypto, @@ -208,7 +208,7 @@ fn run_wasm_checks(cmd: Option) -> Result<()> { ), (WasmFeatureSet::MatrixSdkBase, "-p matrix-sdk-base"), (WasmFeatureSet::MatrixSdkCommon, "-p matrix-sdk-common"), - (WasmFeatureSet::MatrixSdkCrypto, "-p matrix-sdk-crypto"), + (WasmFeatureSet::MatrixSdkCryptoJs, "-p matrix-sdk-crypto-js"), ( WasmFeatureSet::MatrixSdkIndexeddbStoresNoCrypto, "-p matrix-sdk --no-default-features --features indexeddb,rustls-tls", @@ -269,7 +269,7 @@ fn run_wasm_pack_tests(cmd: Option) -> Result<()> { ), (WasmFeatureSet::MatrixSdkBase, ("matrix-sdk-base", "")), (WasmFeatureSet::MatrixSdkCommon, ("matrix-sdk-common", "")), - (WasmFeatureSet::MatrixSdkCrypto, ("matrix-sdk-crypto", "")), + (WasmFeatureSet::MatrixSdkCryptoJs, ("matrix-sdk-crypto-js", "")), ( WasmFeatureSet::MatrixSdkIndexeddbStoresNoCrypto, ("matrix-sdk", "--no-default-features --features indexeddb,rustls-tls --lib"), From 0335fdd07f6c5689738e6e63b4a67fce3eda0a1b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 10:00:54 +0200 Subject: [PATCH 52/58] doc(crypto): Add missing module documentation. --- crates/matrix-sdk-crypto/src/requests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/requests.rs b/crates/matrix-sdk-crypto/src/requests.rs index 8084f15d7..67ca3f9b3 100644 --- a/crates/matrix-sdk-crypto/src/requests.rs +++ b/crates/matrix-sdk-crypto/src/requests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Modules containing customized request types. + use std::{collections::BTreeMap, iter, sync::Arc, time::Duration}; use ruma::{ From 7fa89f76aa7e4e8dbce8ed5a2ff6e5f8000659a0 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 10:08:29 +0200 Subject: [PATCH 53/58] chore(crypto-js): Update `vodozemac`'s version to match `matrix-sdk-crypto`'s. --- crates/matrix-sdk-crypto-js/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml index f7d69aa01..8e618da0c 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -29,7 +29,7 @@ docsrs = [] [dependencies] matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } -vodozemac = { version = "0.2.0", features = ["js"] } +vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } wasm-bindgen = "0.2.80" wasm-bindgen-futures = "0.4.30" js-sys = "0.3.49" From 6ff5c8e9189ae17552269e373404f349715601fc Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 10:44:16 +0200 Subject: [PATCH 54/58] chore(crypto-js): Thanks Clippy. --- crates/matrix-sdk-crypto-js/src/events.rs | 19 ++-- .../matrix-sdk-crypto-js/src/identifiers.rs | 4 + crates/matrix-sdk-crypto-js/src/lib.rs | 3 +- crates/matrix-sdk-crypto-js/src/machine.rs | 92 ++++++++++++++----- crates/matrix-sdk-crypto-js/src/requests.rs | 13 +++ .../matrix-sdk-crypto-js/src/verifications.rs | 42 ++++++++- 6 files changed, 139 insertions(+), 34 deletions(-) diff --git a/crates/matrix-sdk-crypto-js/src/events.rs b/crates/matrix-sdk-crypto-js/src/events.rs index 32c2af186..6372ad552 100644 --- a/crates/matrix-sdk-crypto-js/src/events.rs +++ b/crates/matrix-sdk-crypto-js/src/events.rs @@ -1,5 +1,6 @@ //! Types related to events. +use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility; use wasm_bindgen::prelude::*; /// Who can see a room's history. @@ -32,7 +33,7 @@ pub enum HistoryVisibility { WorldReadable, } -impl From for ruma::events::room::history_visibility::HistoryVisibility { +impl From for RumaHistoryVisibility { fn from(value: HistoryVisibility) -> Self { use HistoryVisibility::*; @@ -45,15 +46,15 @@ impl From for ruma::events::room::history_visibility::History } } -impl Into for ruma::events::room::history_visibility::HistoryVisibility { - fn into(self) -> HistoryVisibility { - use HistoryVisibility::*; +impl From for HistoryVisibility { + fn from(value: RumaHistoryVisibility) -> Self { + use RumaHistoryVisibility::*; - match self { - Self::Invited => Invited, - Self::Joined => Joined, - Self::Shared => Shared, - Self::WorldReadable => WorldReadable, + match value { + Invited => Self::Invited, + Joined => Self::Joined, + Shared => Self::Shared, + WorldReadable => Self::WorldReadable, _ => unreachable!("Unknown variant"), } } diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 8742cbc64..9b018ddd1 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -7,6 +7,7 @@ use wasm_bindgen::prelude::*; /// /// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers #[wasm_bindgen] +#[derive(Debug, Clone)] pub struct UserId { pub(crate) inner: ruma::OwnedUserId, } @@ -48,6 +49,7 @@ impl UserId { /// Return the user ID as a string. #[wasm_bindgen(js_name = "toString")] + #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } @@ -79,6 +81,7 @@ impl DeviceId { /// Return the device ID as a string. #[wasm_bindgen(js_name = "toString")] + #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } @@ -120,6 +123,7 @@ impl RoomId { /// Return the room ID as a string. #[wasm_bindgen(js_name = "toString")] + #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index 3f3a4de6a..f316b17ba 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -15,6 +15,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] +#![allow(clippy::drop_non_drop)] // `wasm-bindgen` generates probably useless `std::mem::drop` calls. #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] compile_error!("This crate is designed to only be compiled to `wasm32-unknown-unknown`."); @@ -40,7 +41,7 @@ fn downcast(value: &JsValue, classname: &str) -> Result where T: RefFromWasmAbi, { - let constructor_name = Object::get_prototype_of(&value).constructor().name(); + let constructor_name = Object::get_prototype_of(value).constructor().name(); if constructor_name == classname { let pointer = Reflect::get(value, &JsValue::from_str("ptr")) diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index d848f51e7..73e036d15 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -1,3 +1,5 @@ +//! The crypto specific Olm objects. + use std::{collections::BTreeMap, sync::Arc, time::Duration}; use js_sys::{Array, Map, Promise, Set}; @@ -17,6 +19,8 @@ use crate::{ sync_events, verifications, }; +/// State machine implementation of the Olm/Megolm encryption protocol +/// used for Matrix end to end encryption. #[wasm_bindgen] #[derive(Debug)] pub struct OlmMachine { @@ -25,7 +29,16 @@ pub struct OlmMachine { #[wasm_bindgen] impl OlmMachine { + /// Create a new memory based `OlmMachine`. + /// + /// The created machine will keep the encryption keys only in + /// memory and once the objects is dropped, the keys will be lost. + /// + /// `user_id` represents the unique ID of the user that owns this + /// machine. `device_id` represents the unique ID of the device + /// that owns this machine. #[wasm_bindgen(constructor)] + #[allow(clippy::new_ret_no_self)] pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise { let user_id = user_id.inner.clone(); let device_id = device_id.inner.clone(); @@ -42,16 +55,16 @@ impl OlmMachine { /// The unique user ID that owns this `OlmMachine` instance. #[wasm_bindgen(js_name = "userId")] pub fn user_id(&self) -> identifiers::UserId { - identifiers::UserId { inner: self.inner.user_id().to_owned() } + identifiers::UserId::new_with(self.inner.user_id().to_owned()) } /// The unique device ID that identifies this `OlmMachine`. #[wasm_bindgen(js_name = "deviceId")] pub fn device_id(&self) -> identifiers::DeviceId { - identifiers::DeviceId { inner: self.inner.device_id().to_owned() } + identifiers::DeviceId::new_with(self.inner.device_id().to_owned()) } - ///// Get the public parts of our Olm identity keys. + /// Get the public parts of our Olm identity keys. #[wasm_bindgen(js_name = "identityKeys")] pub fn identity_keys(&self) -> IdentityKeys { self.inner.identity_keys().into() @@ -72,13 +85,11 @@ impl OlmMachine { pub fn tracked_users(&self) -> Set { let set = Set::new(&JsValue::UNDEFINED); - self.inner - .tracked_users() - .into_iter() - .map(|user| identifiers::UserId { inner: user }) - .for_each(|user| { + self.inner.tracked_users().into_iter().map(identifiers::UserId::new_with).for_each( + |user| { set.add(&user.into()); - }); + }, + ); set } @@ -108,6 +119,13 @@ impl OlmMachine { })) } + /// Handle a to-device and one-time key counts from a sync response. + /// + /// This will decrypt and handle to-device events returning the + /// decrypted versions of them. + /// + /// To decrypt an event from the room timeline call + /// `decrypt_room_event`. #[wasm_bindgen(js_name = "receiveSyncChanges")] pub fn receive_sync_changes( &self, @@ -367,10 +385,11 @@ impl OlmMachine { .map(verifications::Verification) .map(JsValue::try_from) .transpose() - .map(|r| r.unwrap_or_else(|| JsValue::UNDEFINED)) + .map(|r| r.unwrap_or(JsValue::UNDEFINED)) } } +/// An Ed25519 public key, used to verify digital signatures. #[wasm_bindgen] #[derive(Debug, Clone)] pub struct Ed25519PublicKey { @@ -379,17 +398,21 @@ pub struct Ed25519PublicKey { #[wasm_bindgen] impl Ed25519PublicKey { + /// The number of bytes an Ed25519 public key has. #[wasm_bindgen(getter)] pub fn length(&self) -> usize { vodozemac::Ed25519PublicKey::LENGTH } + /// Serialize an Ed25519 public key to an unpadded base64 + /// representation. #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> String { self.inner.to_base64() } } +/// A Curve25519 public key. #[wasm_bindgen] #[derive(Debug, Clone)] pub struct Curve25519PublicKey { @@ -398,21 +421,28 @@ pub struct Curve25519PublicKey { #[wasm_bindgen] impl Curve25519PublicKey { + /// The number of bytes a Curve25519 public key has. #[wasm_bindgen(getter)] pub fn length(&self) -> usize { vodozemac::Curve25519PublicKey::LENGTH } + /// Serialize an Curve25519 public key to an unpadded base64 + /// representation. #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> String { self.inner.to_base64() } } +/// Struct holding the two public identity keys of an account. #[wasm_bindgen(getter_with_clone)] #[derive(Debug)] pub struct IdentityKeys { + /// The Ed25519 public key, used for signing. pub ed25519: Ed25519PublicKey, + + /// The Curve25519 public key, used for establish shared secrets. pub curve25519: Curve25519PublicKey, } @@ -425,6 +455,8 @@ impl From for IdentityKeys { } } +/// An encryption algorithm to be used to encrypt messages sent to a +/// room. #[wasm_bindgen] #[derive(Debug, Clone)] pub enum EncryptionAlgorithm { @@ -446,40 +478,45 @@ impl From for ruma::EventEncryptionAlgorithm { } } -impl Into for ruma::EventEncryptionAlgorithm { - fn into(self) -> EncryptionAlgorithm { - use EncryptionAlgorithm::*; +impl From for EncryptionAlgorithm { + fn from(value: ruma::EventEncryptionAlgorithm) -> Self { + use ruma::EventEncryptionAlgorithm::*; - match self { - Self::OlmV1Curve25519AesSha2 => OlmV1Curve25519AesSha2, - Self::MegolmV1AesSha2 => MegolmV1AesSha2, + match value { + OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, + MegolmV1AesSha2 => Self::MegolmV1AesSha2, _ => unreachable!("Unknown variant"), } } } +/// Settings for an encrypted room. +/// +/// This determines the algorithm and rotation periods of a group +/// session. #[wasm_bindgen(getter_with_clone)] #[derive(Debug, Clone)] pub struct EncryptionSettings { - /// The algorithm, see `EncryptionAlgorithm`. + /// The encryption algorithm that should be used in the room. pub algorithm: EncryptionAlgorithm, - /// A duration expressed in microseconds. + /// How long the session should be used before changing it, + /// expressed in microseconds. #[wasm_bindgen(js_name = "rotationPeriod")] pub rotation_period: u64, + /// How many messages should be sent before changing the session. #[wasm_bindgen(js_name = "rotationPeriodMessages")] pub rotation_period_messages: u64, + /// The history visibility of the room when the session was + /// created. #[wasm_bindgen(js_name = "historyVisibility")] pub history_visibility: events::HistoryVisibility, } -#[wasm_bindgen] -impl EncryptionSettings { - /// Create a new `EncryptionSettings` with default values. - #[wasm_bindgen(constructor)] - pub fn new() -> EncryptionSettings { +impl Default for EncryptionSettings { + fn default() -> Self { let default = matrix_sdk_crypto::olm::EncryptionSettings::default(); Self { @@ -491,6 +528,15 @@ impl EncryptionSettings { } } +#[wasm_bindgen] +impl EncryptionSettings { + /// Create a new `EncryptionSettings` with default values. + #[wasm_bindgen(constructor)] + pub fn new() -> EncryptionSettings { + Self::default() + } +} + impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings { fn from(value: &EncryptionSettings) -> Self { Self { diff --git a/crates/matrix-sdk-crypto-js/src/requests.rs b/crates/matrix-sdk-crypto-js/src/requests.rs index 468159947..df9e93035 100644 --- a/crates/matrix-sdk-crypto-js/src/requests.rs +++ b/crates/matrix-sdk-crypto-js/src/requests.rs @@ -222,11 +222,24 @@ impl TryFrom for JsValue { #[wasm_bindgen] #[derive(Debug)] pub enum RequestType { + /// Represents a `KeysUploadRequest`. KeysUpload, + + /// Represents a `KeysQueryRequest`. KeysQuery, + + /// Represents a `KeysClaimRequest`. KeysClaim, + + /// Represents a `ToDeviceRequest`. ToDevice, + + /// Represents a `SignatureUploadRequest`. SignatureUpload, + + /// Represents a `RoomMessageRequest`. RoomMessage, + + /// Represents a `KeysBackupRequest`. KeysBackup, } diff --git a/crates/matrix-sdk-crypto-js/src/verifications.rs b/crates/matrix-sdk-crypto-js/src/verifications.rs index d4a77728e..1112bf854 100644 --- a/crates/matrix-sdk-crypto-js/src/verifications.rs +++ b/crates/matrix-sdk-crypto-js/src/verifications.rs @@ -1,9 +1,12 @@ +//! Different verification types. + use js_sys::{Array, JsString}; use ruma::events::key::verification::cancel::CancelCode as RumaCancelCode; use wasm_bindgen::prelude::*; use crate::identifiers::{DeviceId, RoomId, UserId}; +/// Short Authentification String (SAS) verification. #[wasm_bindgen] #[derive(Debug)] pub struct Sas { @@ -36,15 +39,21 @@ impl Sas { DeviceId { inner: self.inner.other_device_id().to_owned() } } + /* + /// Get the device of the other user. #[wasm_bindgen(js_name = "otherDevice")] pub fn other_device(&self) { todo!() } + */ + /* + /// Get the unique ID that identifies this SAS verification flow. #[wasm_bindgen(js_name = "flowId")] pub fn flow_id(&self) { todo!() } + */ /// Get the room ID if the verification is happening inside a /// room. @@ -98,27 +107,37 @@ impl Sas { self.inner.we_started() } + /* pub fn accept(&self) { todo!() } + */ + /* #[wasm_bindgen(js_name = "acceptWithSettings")] pub fn accept_with_settings(&self) { todo!() } + */ + /* pub fn confirm(&self) { todo!() } + */ + /* pub fn cancel(&self) { todo!() } + */ + /* #[wasm_bindgen(js_name = "cancelWithCode")] pub fn cancel_with_code(&self) { todo!() } + */ /// Has the SAS verification flow timed out. #[wasm_bindgen(js_name = "timedOut")] @@ -168,7 +187,7 @@ impl Sas { /// entry](https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji). #[wasm_bindgen(js_name = "emoji_index")] pub fn emoji_index(&self) -> Option { - Some(self.inner.emoji_index()?.iter().map(|emoji| *emoji).map(JsValue::from).collect()) + Some(self.inner.emoji_index()?.iter().copied().map(JsValue::from).collect()) } /// Get the decimal version of the short auth string. @@ -188,6 +207,7 @@ impl Sas { } } +/// QR code based verification. #[cfg(feature = "qrcode")] #[wasm_bindgen] #[derive(Debug)] @@ -227,7 +247,10 @@ impl TryFrom for JsValue { } } +/// Information about the cancellation of a verification request or +/// verification flow. #[wasm_bindgen] +#[derive(Debug)] pub struct CancelInfo { inner: matrix_sdk_crypto::CancelInfo, } @@ -240,22 +263,28 @@ impl CancelInfo { #[wasm_bindgen] impl CancelInfo { + /// Get the human readable reason of the cancellation. pub fn reason(&self) -> JsString { self.inner.reason().into() } + /// Get the `CancelCode` that cancelled this verification. #[wasm_bindgen(js_name = "cancelCode")] pub fn cancel_code(&self) -> CancelCode { self.inner.cancel_code().into() } + /// Was the verification cancelled by us? #[wasm_bindgen(js_name = "cancelledbyUs")] pub fn cancelled_by_us(&self) -> bool { self.inner.cancelled_by_us() } } +/// An error code for why the process/request was cancelled by the +/// user. #[wasm_bindgen] +#[derive(Debug)] pub enum CancelCode { /// Unknown cancel code. Other, @@ -329,7 +358,15 @@ impl From<&RumaCancelCode> for CancelCode { } } +/// An emoji that is used for interactive verification using a short +/// auth string. +/// +/// This will contain a single emoji and description from the list of +/// emojis from [the specification]. +/// +/// [the specification]: https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji #[wasm_bindgen] +#[derive(Debug)] pub struct Emoji { inner: matrix_sdk_crypto::Emoji, } @@ -342,11 +379,14 @@ impl Emoji { #[wasm_bindgen] impl Emoji { + /// The emoji symbol that represents a part of the short auth + /// string, for example: 🐶 #[wasm_bindgen(getter)] pub fn symbol(&self) -> JsString { self.inner.symbol.into() } + /// The description of the emoji, for example ‘Dog’. #[wasm_bindgen(getter)] pub fn description(&self) -> JsString { self.inner.description.into() From bb8217d10f0a0bd1b50fa4c77ff3b847a2585906 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 10:55:24 +0200 Subject: [PATCH 55/58] doc(crypto-nodejs) Disable missing docs for now. --- crates/matrix-sdk-crypto-nodejs/src/errors.rs | 1 + crates/matrix-sdk-crypto-nodejs/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto-nodejs/src/errors.rs b/crates/matrix-sdk-crypto-nodejs/src/errors.rs index a22312994..6055cb730 100644 --- a/crates/matrix-sdk-crypto-nodejs/src/errors.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/errors.rs @@ -1,3 +1,4 @@ +/// Generic error wrapping `napi::Error`. #[derive(Debug)] pub struct Error(napi::Error); diff --git a/crates/matrix-sdk-crypto-nodejs/src/lib.rs b/crates/matrix-sdk-crypto-nodejs/src/lib.rs index f4ce1f1f2..b9874666e 100644 --- a/crates/matrix-sdk-crypto-nodejs/src/lib.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/lib.rs @@ -14,7 +14,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![warn(missing_docs, missing_debug_implementations)] +//#![warn(missing_docs, missing_debug_implementations)] mod errors; pub mod events; From 0debbf24d773b16177c869299d73487a4ecba956 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 10:58:30 +0200 Subject: [PATCH 56/58] chore(crypto-js): Remove an unknown Clippy lint for now. --- crates/matrix-sdk-crypto-js/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index f316b17ba..ec492fd2a 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -15,7 +15,6 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] -#![allow(clippy::drop_non_drop)] // `wasm-bindgen` generates probably useless `std::mem::drop` calls. #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] compile_error!("This crate is designed to only be compiled to `wasm32-unknown-unknown`."); From 4db1ad350b28419758b9ae83e3226fd42e6fff13 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 11:00:35 +0200 Subject: [PATCH 57/58] feat(crypto-nodejs): Derive `Debug` for `UserId`. --- crates/matrix-sdk-crypto-nodejs/src/identifiers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs b/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs index c44e2779c..a7e87f8cd 100644 --- a/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs @@ -9,6 +9,7 @@ use crate::errors::*; /// /// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers #[napi] +#[derive(Debug, Clone)] pub struct UserId { pub(crate) inner: ruma::OwnedUserId, } From 7931c4a589efdf14eeb52a9fe43190868ff7a3d0 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 30 May 2022 11:02:24 +0200 Subject: [PATCH 58/58] chore(crypto-js): Clean up code and make CI happy. --- .github/workflows/coverage.yml | 2 +- .github/workflows/docs.yml | 2 +- crates/matrix-sdk-crypto-js/.cargo/config | 2 +- crates/matrix-sdk-crypto-js/Cargo.toml | 2 +- .../matrix-sdk-crypto-js/src/identifiers.rs | 6 +- crates/matrix-sdk-crypto-js/src/lib.rs | 6 +- crates/matrix-sdk-crypto-js/src/machine.rs | 80 ++-- crates/matrix-sdk-crypto-js/src/requests.rs | 44 +- .../matrix-sdk-crypto-js/src/verifications.rs | 394 ------------------ crates/matrix-sdk-crypto-nodejs/src/errors.rs | 6 +- crates/matrix-sdk-crypto-nodejs/src/events.rs | 19 +- .../src/identifiers.rs | 3 + crates/matrix-sdk-crypto-nodejs/src/lib.rs | 2 +- crates/matrix-sdk-crypto/Cargo.toml | 21 +- 14 files changed, 89 insertions(+), 500 deletions(-) delete mode 100644 crates/matrix-sdk-crypto-js/src/verifications.rs diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5b3e72c3f..1371700e4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -38,7 +38,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: tarpaulin - args: --ignore-config --exclude-files "crates/matrix-sdk/examples/*,crates/matrix-sdk-common,crates/matrix-sdk-test,crates/matrix-sdk-crypto-js,crates/matrix-sdk-crypto-nodejs" --out Xml + args: --workspace --ignore-config --exclude-files "crates/matrix-sdk/examples/*,crates/matrix-sdk-common,crates/matrix-sdk-test" --exclude matrix-sdk-crypto-js --exclude matrix-sdk-crypto-nodejs --out Xml - name: Upload to codecov.io uses: codecov/codecov-action@v3 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1f0a6da2a..645892910 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings" with: command: doc - args: --no-deps --workspace --features docsrs -Zrustdoc-map + args: --no-deps --workspace --exclude matrix-sdk-crypto-js --exclude matrix-sdk-crypto-nodejs --features docsrs -Zrustdoc-map - name: Deploy docs if: github.event_name == 'push' && github.ref == 'refs/heads/main' diff --git a/crates/matrix-sdk-crypto-js/.cargo/config b/crates/matrix-sdk-crypto-js/.cargo/config index 435ed755e..f4e8c002f 100644 --- a/crates/matrix-sdk-crypto-js/.cargo/config +++ b/crates/matrix-sdk-crypto-js/.cargo/config @@ -1,2 +1,2 @@ [build] -target = "wasm32-unknown-unknown" \ No newline at end of file +target = "wasm32-unknown-unknown" diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml index 8e618da0c..8a918b105 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -35,4 +35,4 @@ wasm-bindgen-futures = "0.4.30" js-sys = "0.3.49" serde_json = "1.0.79" http = "0.2.6" -anyhow = "1.0" \ No newline at end of file +anyhow = "1.0" diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 9b018ddd1..3ff2f9068 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -23,7 +23,7 @@ impl UserId { /// Parse/validate and create a new `UserId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> Result { - Ok(Self { inner: ruma::UserId::parse(id)? }) + Ok(Self::new_with(ruma::UserId::parse(id)?)) } /// Returns the user's localpart. @@ -76,7 +76,7 @@ impl DeviceId { /// Create a new `DeviceId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> DeviceId { - Self { inner: id.into() } + Self::new_with(id.into()) } /// Return the device ID as a string. @@ -107,7 +107,7 @@ impl RoomId { /// Parse/validate and create a new `RoomId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> Result { - Ok(Self { inner: ruma::RoomId::parse(id)? }) + Ok(Self::new_with(ruma::RoomId::parse(id)?)) } /// Returns the user's localpart. diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index ec492fd2a..03a1fc8fe 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2020 The Matrix.org Foundation C.I.C. +// Copyright 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,9 +16,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] -#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] -compile_error!("This crate is designed to only be compiled to `wasm32-unknown-unknown`."); - pub mod events; mod future; pub mod identifiers; @@ -26,7 +23,6 @@ pub mod machine; pub mod requests; pub mod responses; pub mod sync_events; -pub mod verifications; use js_sys::{Object, Reflect}; use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*}; diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index 73e036d15..88202337b 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -1,13 +1,10 @@ //! The crypto specific Olm objects. -use std::{collections::BTreeMap, sync::Arc, time::Duration}; +use std::{collections::BTreeMap, time::Duration}; use js_sys::{Array, Map, Promise, Set}; -use ruma::{ - events::{AnyMessageLikeEventContent, EventContent}, - DeviceKeyAlgorithm, OwnedTransactionId, UInt, -}; -use serde_json::value::RawValue as RawJsonValue; +use ruma::{DeviceKeyAlgorithm, OwnedTransactionId, UInt}; +use serde_json::Value as JsonValue; use wasm_bindgen::prelude::*; use crate::{ @@ -16,15 +13,15 @@ use crate::{ identifiers, requests, requests::OutgoingRequest, responses::{self, response_from_string}, - sync_events, verifications, + sync_events, }; /// State machine implementation of the Olm/Megolm encryption protocol /// used for Matrix end to end encryption. #[wasm_bindgen] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct OlmMachine { - inner: Arc, + inner: matrix_sdk_crypto::OlmMachine, } #[wasm_bindgen] @@ -45,9 +42,8 @@ impl OlmMachine { future_to_promise(async move { Ok(OlmMachine { - inner: Arc::new( - matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref()).await, - ), + inner: matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref()) + .await, }) }) } @@ -119,7 +115,8 @@ impl OlmMachine { })) } - /// Handle a to-device and one-time key counts from a sync response. + /// Handle to-device events and one-time key counts from a sync + /// response. /// /// This will decrypt and handle to-device events returning the /// decrypted versions of them. @@ -206,12 +203,14 @@ impl OlmMachine { /// Mark the request with the given request ID as sent (see /// `outgoing_requests`). /// - /// `request_id` represents the unique ID of the request that was - /// sent out. This is needed to couple the response with the now - /// sent out request. `response_type` represents the type of the - /// request that was sent out. `response` represents the response - /// that was received from the server after the outgoing request - /// was sent out. ` + /// Arguments are: + /// + /// * `request_id` represents the unique ID of the request that was sent + /// out. This is needed to couple the response with the now sent out + /// request. + /// * `response_type` represents the type of the request that was sent out. + /// * `response` represents the response that was received from the server + /// after the outgoing request was sent out. #[wasm_bindgen(js_name = "markRequestAsSent")] pub fn mark_request_as_sent( &self, @@ -235,11 +234,6 @@ impl OlmMachine { /// Beware that a group session needs to be shared before this /// method can be called using the `share_group_session` method. /// - /// Since group sessions can expire or become invalid if the room - /// membership changes, client authors should check with the - /// `should_share_group_session` method if a new group session - /// needs to be shared. - /// /// `room_id` is the ID of the room for which the message should /// be encrypted. `event_type` is the type of the event. `content` /// is the plaintext content of the message that should be @@ -247,22 +241,23 @@ impl OlmMachine { /// /// # Panics /// - /// Panics if a group session for the given room wasn't shared beforehand. + /// Panics if a group session for the given room wasn't shared + /// beforehand. #[wasm_bindgen(js_name = "encryptRoomEvent")] pub fn encrypt_room_event( &self, room_id: &identifiers::RoomId, - event_type: &str, + event_type: String, content: &str, ) -> Result { let room_id = room_id.inner.clone(); - let content: Box = serde_json::from_str(content)?; - let content = AnyMessageLikeEventContent::from_parts(event_type, &content)?; - + let content: JsonValue = serde_json::from_str(content)?; let me = self.inner.clone(); Ok(future_to_promise(async move { - Ok(serde_json::to_string(&me.encrypt_room_event(&room_id, content).await?)?) + Ok(serde_json::to_string( + &me.encrypt_room_event_raw(&room_id, content, event_type.as_ref()).await?, + )?) })) } @@ -362,31 +357,6 @@ impl OlmMachine { } })) } - - /// Get a verification object for the given user ID with the given flow ID. - /// - /// Returns a list of `JsValue` to represent either (depending on - /// how the Wasm module has been compiled): - /// * `Sas` (enabled), - /// * `Qr` - #[cfg_attr(feature = "qrcode", doc = "(enabled).")] - #[cfg_attr(not(feature = "qrcode"), doc = "(disabled).")] - /// - /// If a verification mode is missing, please try to compile the - /// Wasm module with different features. - #[wasm_bindgen(js_name = "getVerification")] - pub fn get_verification( - &self, - user_id: &identifiers::UserId, - flow_id: &str, - ) -> Result { - self.inner - .get_verification(user_id.inner.as_ref(), flow_id) - .map(verifications::Verification) - .map(JsValue::try_from) - .transpose() - .map(|r| r.unwrap_or(JsValue::UNDEFINED)) - } } /// An Ed25519 public key, used to verify digital signatures. diff --git a/crates/matrix-sdk-crypto-js/src/requests.rs b/crates/matrix-sdk-crypto-js/src/requests.rs index df9e93035..f6b0a96a4 100644 --- a/crates/matrix-sdk-crypto-js/src/requests.rs +++ b/crates/matrix-sdk-crypto-js/src/requests.rs @@ -15,9 +15,12 @@ use ruma::api::client::keys::{ }; use wasm_bindgen::prelude::*; -/// Data for a request to the `upload_keys` API endpoint. +/// Data for a request to the `/keys/upload` API endpoint +/// ([specification]). /// /// Publishes end-to-end encryption keys for the device. +/// +/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysupload #[derive(Debug)] #[wasm_bindgen(getter_with_clone)] pub struct KeysUploadRequest { @@ -34,9 +37,12 @@ pub struct KeysUploadRequest { pub body: JsString, } -/// Data for a request to the `get_keys` API endpoint. +/// Data for a request to the `/keys/query` API endpoint +/// ([specification]). /// /// Returns the current devices and identity keys for the given users. +/// +/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysquery #[derive(Debug)] #[wasm_bindgen(getter_with_clone)] pub struct KeysQueryRequest { @@ -53,9 +59,13 @@ pub struct KeysQueryRequest { pub body: JsString, } -/// Data for a request to the `claim_keys` API endpoint. +/// Data for a request to the `/keys/claim` API endpoint +/// ([specification]). /// -/// Claims one-time keys for use in pre-key messages. +/// Claims one-time keys that can be used to establish 1-to-1 E2EE +/// sessions. +/// +/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysclaim #[derive(Debug)] #[wasm_bindgen(getter_with_clone)] pub struct KeysClaimRequest { @@ -72,9 +82,12 @@ pub struct KeysClaimRequest { pub body: JsString, } -/// Data for a request to the `send_event_to_device` API endpoint. +/// Data for a request to the `/sendToDevice` API endpoint +/// ([specification]). /// -/// Send an event to a device or devices. +/// Send an event to a single device or to a group of devices. +/// +/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid #[derive(Debug)] #[wasm_bindgen(getter_with_clone)] pub struct ToDeviceRequest { @@ -91,9 +104,12 @@ pub struct ToDeviceRequest { pub body: JsString, } -/// Data for a request to the `upload_signatures` API endpoint. +/// Data for a request to the `/keys/signatures/upload` API endpoint +/// ([specification]). /// /// Publishes cross-signing signatures for the user. +/// +/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keyssignaturesupload #[derive(Debug)] #[wasm_bindgen(getter_with_clone)] pub struct SignatureUploadRequest { @@ -110,7 +126,10 @@ pub struct SignatureUploadRequest { pub body: JsString, } -/// A customized owned request type for sending out room messages. +/// A customized owned request type for sending out room messages +/// ([specification]). +/// +/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid #[derive(Debug)] #[wasm_bindgen(getter_with_clone)] pub struct RoomMessageRequest { @@ -127,7 +146,10 @@ pub struct RoomMessageRequest { pub body: JsString, } -/// A request that will back up a batch of room keys to the server. +/// A request that will back up a batch of room keys to the server +/// ([specification]). +/// +/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3room_keyskeys #[derive(Debug)] #[wasm_bindgen(getter_with_clone)] pub struct KeysBackupRequest { @@ -138,7 +160,7 @@ pub struct KeysBackupRequest { /// A JSON-encoded object of form: /// /// ``` - /// {"version": …, "rooms": …} + /// {"rooms": …} /// ``` #[wasm_bindgen(readonly)] pub body: JsString, @@ -173,7 +195,7 @@ request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_tim request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages); request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys); request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content); -request!(KeysBackupRequest from RumaKeysBackupRequest maps fields version, rooms); +request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms); // JavaScript has no complex enums like Rust. To return structs of // different types, we have no choice that hiding everything behind a diff --git a/crates/matrix-sdk-crypto-js/src/verifications.rs b/crates/matrix-sdk-crypto-js/src/verifications.rs deleted file mode 100644 index 1112bf854..000000000 --- a/crates/matrix-sdk-crypto-js/src/verifications.rs +++ /dev/null @@ -1,394 +0,0 @@ -//! Different verification types. - -use js_sys::{Array, JsString}; -use ruma::events::key::verification::cancel::CancelCode as RumaCancelCode; -use wasm_bindgen::prelude::*; - -use crate::identifiers::{DeviceId, RoomId, UserId}; - -/// Short Authentification String (SAS) verification. -#[wasm_bindgen] -#[derive(Debug)] -pub struct Sas { - inner: matrix_sdk_crypto::Sas, -} - -#[wasm_bindgen] -impl Sas { - /// Get our own user ID. - #[wasm_bindgen(js_name = "userId")] - pub fn user_id(&self) -> UserId { - UserId { inner: self.inner.user_id().to_owned() } - } - - /// Get our own device ID. - #[wasm_bindgen(js_name = "deviceId")] - pub fn device_id(&self) -> DeviceId { - DeviceId { inner: self.inner.device_id().to_owned() } - } - - /// Get the user id of the other side. - #[wasm_bindgen(js_name = "otherUserId")] - pub fn other_user_id(&self) -> UserId { - UserId { inner: self.inner.other_user_id().to_owned() } - } - - /// Get the device ID of the other side. - #[wasm_bindgen(js_name = "otherDeviceId")] - pub fn other_device_id(&self) -> DeviceId { - DeviceId { inner: self.inner.other_device_id().to_owned() } - } - - /* - /// Get the device of the other user. - #[wasm_bindgen(js_name = "otherDevice")] - pub fn other_device(&self) { - todo!() - } - */ - - /* - /// Get the unique ID that identifies this SAS verification flow. - #[wasm_bindgen(js_name = "flowId")] - pub fn flow_id(&self) { - todo!() - } - */ - - /// Get the room ID if the verification is happening inside a - /// room. - #[wasm_bindgen(js_name = "roomId")] - pub fn room_id(&self) -> Option { - self.inner.room_id().map(ToOwned::to_owned).map(RoomId::new_with) - } - - /// Does this verification flow support displaying emoji for the - /// short authentication string. - #[wasm_bindgen(js_name = "supportsEmoji")] - pub fn supports_emoji(&self) -> bool { - self.inner.supports_emoji() - } - - /// Did this verification flow start from a verification request. - #[wasm_bindgen(js_name = "startedFromRequest")] - pub fn started_from_request(&self) -> bool { - self.inner.started_from_request() - } - - /// Is this a verification that is veryfying one of our own - /// devices. - #[wasm_bindgen(js_name = "isSelfVerification")] - pub fn is_self_verification(&self) -> bool { - self.inner.is_self_verification() - } - - /// Have we confirmed that the short auth string matches. - #[wasm_bindgen(js_name = "haveWeConfirmed")] - pub fn have_we_confirmed(&self) -> bool { - self.inner.have_we_confirmed() - } - - /// Has the verification been accepted by both parties. - #[wasm_bindgen(js_name = "hasBeenAccepted")] - pub fn has_been_accepted(&self) -> bool { - self.inner.has_been_accepted() - } - - /// Get info about the cancellation if the verification flow has - /// been cancelled. - #[wasm_bindgen(js_name = "cancelInfo")] - pub fn cancel_info(&self) -> Option { - self.inner.cancel_info().map(CancelInfo::new_with) - } - - /// Did we initiate the verification flow. - #[wasm_bindgen(js_name = "weStarted")] - pub fn we_started(&self) -> bool { - self.inner.we_started() - } - - /* - pub fn accept(&self) { - todo!() - } - */ - - /* - #[wasm_bindgen(js_name = "acceptWithSettings")] - pub fn accept_with_settings(&self) { - todo!() - } - */ - - /* - pub fn confirm(&self) { - todo!() - } - */ - - /* - pub fn cancel(&self) { - todo!() - } - */ - - /* - #[wasm_bindgen(js_name = "cancelWithCode")] - pub fn cancel_with_code(&self) { - todo!() - } - */ - - /// Has the SAS verification flow timed out. - #[wasm_bindgen(js_name = "timedOut")] - pub fn timed_out(&self) -> bool { - self.inner.timed_out() - } - - /// Are we in a state where we can show the short auth string. - #[wasm_bindgen(js_name = "canBePresented")] - pub fn can_be_presented(&self) -> bool { - self.inner.can_be_presented() - } - - /// Is the SAS flow done. - #[wasm_bindgen(js_name = "isDone")] - pub fn is_done(&self) -> bool { - self.inner.is_done() - } - - /// Is the SAS flow canceled. - #[wasm_bindgen(js_name = "isCancelled")] - pub fn is_cancelled(&self) -> bool { - self.inner.is_cancelled() - } - - /// Get the emoji version of the short auth string. - /// - /// Returns `undefined` if we can't yet present the short auth string, - /// otherwise seven tuples containing the emoji and description. - pub fn emoji(&self) -> Option { - Some( - self.inner - .emoji()? - .iter() - .map(|emoji| Emoji::new_with(emoji.clone())) - .map(JsValue::from) - .collect(), - ) - } - - /// Get the index of the emoji representing the short auth string - /// - /// Returns `undefined` if we can’t yet present the short auth - /// string, otherwise seven u8 numbers in the range from 0 to 63 - /// inclusive which can be converted to an emoji using [the - /// relevant specification - /// entry](https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji). - #[wasm_bindgen(js_name = "emoji_index")] - pub fn emoji_index(&self) -> Option { - Some(self.inner.emoji_index()?.iter().copied().map(JsValue::from).collect()) - } - - /// Get the decimal version of the short auth string. - /// - /// Returns None if we can’t yet present the short auth string, - /// otherwise a tuple containing three 4-digit integers that - /// represent the short auth string. - pub fn decimals(&self) -> Option { - let decimals = self.inner.decimals()?; - - let out = Array::new_with_length(3); - out.set(0, JsValue::from(decimals.0)); - out.set(1, JsValue::from(decimals.1)); - out.set(2, JsValue::from(decimals.2)); - - Some(out) - } -} - -/// QR code based verification. -#[cfg(feature = "qrcode")] -#[wasm_bindgen] -#[derive(Debug)] -pub struct Qr { - inner: matrix_sdk_crypto::QrVerification, -} - -#[cfg(feature = "qrcode")] -#[wasm_bindgen] -impl Qr { - #[wasm_bindgen(js_name = "hasBeenScanned")] - pub fn has_been_scanned(&self) -> bool { - self.inner.has_been_scanned() - } -} - -pub(crate) struct Verification(pub(crate) matrix_sdk_crypto::Verification); - -impl TryFrom for JsValue { - type Error = JsError; - - fn try_from(verification: Verification) -> Result { - use matrix_sdk_crypto::Verification::*; - - Ok(match verification.0 { - SasV1(sas) => JsValue::from(Sas { inner: sas }), - - #[cfg(feature = "qrcode")] - QrV1(qr) => JsValue::from(Qr { inner: qr }), - - _ => { - return Err(JsError::new( - "Unknown verification type, expect `m.sas.v1` only for now", - )) - } - }) - } -} - -/// Information about the cancellation of a verification request or -/// verification flow. -#[wasm_bindgen] -#[derive(Debug)] -pub struct CancelInfo { - inner: matrix_sdk_crypto::CancelInfo, -} - -impl CancelInfo { - pub(crate) fn new_with(inner: matrix_sdk_crypto::CancelInfo) -> Self { - Self { inner } - } -} - -#[wasm_bindgen] -impl CancelInfo { - /// Get the human readable reason of the cancellation. - pub fn reason(&self) -> JsString { - self.inner.reason().into() - } - - /// Get the `CancelCode` that cancelled this verification. - #[wasm_bindgen(js_name = "cancelCode")] - pub fn cancel_code(&self) -> CancelCode { - self.inner.cancel_code().into() - } - - /// Was the verification cancelled by us? - #[wasm_bindgen(js_name = "cancelledbyUs")] - pub fn cancelled_by_us(&self) -> bool { - self.inner.cancelled_by_us() - } -} - -/// An error code for why the process/request was cancelled by the -/// user. -#[wasm_bindgen] -#[derive(Debug)] -pub enum CancelCode { - /// Unknown cancel code. - Other, - - /// The user cancelled the verification. - User, - - /// The verification process timed out. - /// - /// Verification processes can define their own timeout - /// parameters. - Timeout, - - /// The device does not know about the given transaction ID. - UnknownTransaction, - - /// The device does not know how to handle the requested method. - /// - /// Should be sent for `m.key.verification.start` messages and - /// messages defined by individual verification processes. - UnknownMethod, - - /// The device received an unexpected message. - /// - /// Typically raised when one of the parties is handling the - /// verification out of order. - UnexpectedMessage, - - /// The key was not verified. - KeyMismatch, - - /// The expected user did not match the user verified. - UserMismatch, - - /// The message received was invalid. - InvalidMessage, - - /// An `m.key.verification.request` was accepted by a different - /// device. - /// - /// The device receiving this error can ignore the verification - /// request. - Accepted, - - /// The device receiving this error can ignore the verification - /// request. - MismatchedCommitment, - - /// The SAS did not match. - MismatchedSas, -} - -impl From<&RumaCancelCode> for CancelCode { - fn from(code: &RumaCancelCode) -> Self { - use RumaCancelCode::*; - - match code { - User => Self::User, - Timeout => Self::Timeout, - UnknownTransaction => Self::UnknownTransaction, - UnknownMethod => Self::UnknownMethod, - UnexpectedMessage => Self::UnexpectedMessage, - KeyMismatch => Self::KeyMismatch, - UserMismatch => Self::UserMismatch, - InvalidMessage => Self::InvalidMessage, - Accepted => Self::Accepted, - MismatchedCommitment => Self::MismatchedCommitment, - MismatchedSas => Self::MismatchedSas, - _ => Self::Other, - } - } -} - -/// An emoji that is used for interactive verification using a short -/// auth string. -/// -/// This will contain a single emoji and description from the list of -/// emojis from [the specification]. -/// -/// [the specification]: https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji -#[wasm_bindgen] -#[derive(Debug)] -pub struct Emoji { - inner: matrix_sdk_crypto::Emoji, -} - -impl Emoji { - pub(crate) fn new_with(inner: matrix_sdk_crypto::Emoji) -> Self { - Self { inner } - } -} - -#[wasm_bindgen] -impl Emoji { - /// The emoji symbol that represents a part of the short auth - /// string, for example: 🐶 - #[wasm_bindgen(getter)] - pub fn symbol(&self) -> JsString { - self.inner.symbol.into() - } - - /// The description of the emoji, for example ‘Dog’. - #[wasm_bindgen(getter)] - pub fn description(&self) -> JsString { - self.inner.description.into() - } -} diff --git a/crates/matrix-sdk-crypto-nodejs/src/errors.rs b/crates/matrix-sdk-crypto-nodejs/src/errors.rs index 6055cb730..3b35a55ce 100644 --- a/crates/matrix-sdk-crypto-nodejs/src/errors.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/errors.rs @@ -11,8 +11,8 @@ where } } -impl Into for Error { - fn into(self) -> napi::Error { - self.0 +impl From for napi::Error { + fn from(value: Error) -> Self { + value.0 } } diff --git a/crates/matrix-sdk-crypto-nodejs/src/events.rs b/crates/matrix-sdk-crypto-nodejs/src/events.rs index eed3c7459..ce6916d55 100644 --- a/crates/matrix-sdk-crypto-nodejs/src/events.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/events.rs @@ -2,6 +2,7 @@ use napi::bindgen_prelude::ToNapiValue; use napi_derive::*; +use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility; /// Who can see a room's history. #[napi] @@ -33,7 +34,7 @@ pub enum HistoryVisibility { WorldReadable, } -impl From for ruma::events::room::history_visibility::HistoryVisibility { +impl From for RumaHistoryVisibility { fn from(value: HistoryVisibility) -> Self { use HistoryVisibility::*; @@ -46,15 +47,15 @@ impl From for ruma::events::room::history_visibility::History } } -impl Into for ruma::events::room::history_visibility::HistoryVisibility { - fn into(self) -> HistoryVisibility { - use HistoryVisibility::*; +impl From for HistoryVisibility { + fn from(value: RumaHistoryVisibility) -> Self { + use RumaHistoryVisibility::*; - match self { - Self::Invited => Invited, - Self::Joined => Joined, - Self::Shared => Shared, - Self::WorldReadable => WorldReadable, + match value { + Invited => Self::Invited, + Joined => Self::Joined, + Shared => Self::Shared, + WorldReadable => Self::WorldReadable, _ => unreachable!("Unknown variant"), } } diff --git a/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs b/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs index a7e87f8cd..2a1f9bf8e 100644 --- a/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs @@ -50,6 +50,7 @@ impl UserId { /// Return the user ID as a string. #[napi(js_name = "toString")] + #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } @@ -75,6 +76,7 @@ impl DeviceId { /// Return the device ID as a string. #[napi(js_name = "toString")] + #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } @@ -115,6 +117,7 @@ impl RoomId { /// Return the room ID as a string. #[napi(js_name = "toString")] + #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.inner.as_str().to_owned() } diff --git a/crates/matrix-sdk-crypto-nodejs/src/lib.rs b/crates/matrix-sdk-crypto-nodejs/src/lib.rs index b9874666e..527863a64 100644 --- a/crates/matrix-sdk-crypto-nodejs/src/lib.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2020 The Matrix.org Foundation C.I.C. +// Copyright 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 11f65b333..e3c75bfac 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -48,22 +48,13 @@ thiserror = "1.0.30" tracing = "0.1.34" zeroize = { version = "1.3.0", features = ["zeroize_derive"] } -[target.'cfg(target_arch = "wasm32")'.dependencies.ruma] -version = "0.6.1" -features = ["client-api-c", "js", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +ruma = { version = "0.6.2", features = ["client-api-c", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"] } +vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36" } -[target.'cfg(target_arch = "wasm32")'.dependencies.vodozemac] -git = "https://github.com/matrix-org/vodozemac/" -rev = "d0e744287a14319c2a9148fef3747548c740fc36" -features = ["js"] - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.ruma] -version = "0.6.1" -features = ["client-api-c", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"] - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.vodozemac] -git = "https://github.com/matrix-org/vodozemac/" -rev = "d0e744287a14319c2a9148fef3747548c740fc36" +[target.'cfg(target_arch = "wasm32")'.dependencies] +ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"] } +vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] }