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();