From 54f00dc05a7033ebc257d592ed9674462539d208 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 25 Jan 2023 09:56:21 +0100 Subject: [PATCH 01/16] feat(crypto-nodejs) Implement `OlmMachine.close`. This patch fixes https://github.com/matrix-org/matrix-rust-sdk/issues/1379/. This is similar to https://github.com/matrix-org/matrix-rust-sdk/pull/1197/. We need to close the `OlmMachine`. Problem, `napi-rs` doesn't allow methods to take ownership of `self`, so the following code: ```rs fn close(self) {} ```` isn't allowed. There an [`ObjectFinalize`](https://docs.rs/napi/latest/napi/bindgen_prelude/trait.ObjectFinalize.html) trait, but it's only called when the JS value is being collected by the GC. It's not possible to force call `finalize` here. This patch then introduces a new type: ```rs enum OlmMachineInner { Opened(matrix_sdk_crypto::OlmMachine), Closed } ``` that derefs to `matrix_sdk_crypto::OlmMachine` when its variant is `Opened`, otherwise it panics. Calling the new `OlmMachine::close` method will change the inner state from `Opened` to `Closed`. Ideally, I wanted to throw an error instead of panicking, but `napi_env` (used to build an `Env`, used to throw an error) is neither `Sync` nor `Send`. Honestly, my knowledge of NodeJS and NAPI is too weak to implement `Sync` and `Send` for `*mut napi_env__` safely. Especially because it can be executed from various threads within `async fn` functions, that are driven by Tokio in `napi-rs`. So, yeah, for the moment, it panics! --- .../matrix-sdk-crypto-nodejs/src/machine.rs | 46 +++++++++++++++++-- .../tests/machine.test.js | 14 +++++- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs index ec01b769d..d531ce1ab 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs @@ -2,6 +2,7 @@ use std::{ collections::{BTreeMap, HashMap}, + ops::Deref, sync::Arc, }; @@ -16,11 +17,38 @@ use crate::{ sync_events, types, vodozemac, }; +/// The value used by the `OlmMachine` JS class. +/// +/// It has 2 states: `Opened` and `Closed`. Why maintaining the state here? +/// Because NodeJS has no way to drop an object explicitely, and we want to be +/// able to “close” the `OlmMachine` to free all associated data. More over, +/// `napi-rs` doesn't allow a function to take the ownership of the type itself +/// (`fn close(self) { … }`). So we manage the state ourselves. +/// +/// Using the `OlmMachine` when its state is `Closed` will panic. +enum OlmMachineInner { + Opened(matrix_sdk_crypto::OlmMachine), + Closed, +} + +impl Deref for OlmMachineInner { + type Target = matrix_sdk_crypto::OlmMachine; + + #[inline] + fn deref(&self) -> &Self::Target { + match self { + Self::Opened(machine) => machine, + Self::Closed => panic!("The `OlmMachine` has been closed, cannot use it anymore"), + } + } +} + /// State machine implementation of the Olm/Megolm encryption protocol /// used for Matrix end to end encryption. +// #[napi(custom_finalize)] #[napi] pub struct OlmMachine { - inner: matrix_sdk_crypto::OlmMachine, + inner: OlmMachineInner, } #[napi] @@ -77,7 +105,7 @@ impl OlmMachine { store_passphrase.zeroize(); Ok(OlmMachine { - inner: match store { + inner: OlmMachineInner::Opened(match store { Some(store) => matrix_sdk_crypto::OlmMachine::with_store( user_id.inner.as_ref(), device_id.inner.as_ref(), @@ -92,7 +120,7 @@ impl OlmMachine { ) .await } - }, + }), }) } @@ -407,4 +435,16 @@ impl OlmMachine { pub async fn sign(&self, message: String) -> types::Signatures { self.inner.sign(message.as_str()).await.into() } + + /// Shut down the `OlmMachine`. + /// + /// The `OlmMachine` cannot be used after this method has been called, + /// otherwise it will panic. + /// + /// All associated resources will be closed too, like the crypto storage + /// connections. + #[napi(strict)] + pub fn close(&mut self) { + self.inner = OlmMachineInner::Closed; + } } diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js index a3b424683..92dc5f8a4 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js @@ -25,7 +25,7 @@ describe(OlmMachine.name, () => { expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory, 'hello')).toBeInstanceOf(OlmMachine); }); }); - + const user = new UserId('@alice:example.org'); const device = new DeviceId('foobar'); const room = new RoomId('!baz:matrix.org'); @@ -34,6 +34,16 @@ describe(OlmMachine.name, () => { return OlmMachine.initialize(new_user || user, new_device || device); } + test('can drop/close, and then re-open', async () => { + const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--')); + + let m1 = await OlmMachine.initialize(new UserId('@test:bar.org'), new DeviceId('device'), temp_directory, 'hello') + m1.close(); + + let m2 = await OlmMachine.initialize(new UserId('@test:bar.org'), new DeviceId('device'), temp_directory, 'hello') + m2.close(); + }); + test('can read user ID', async () => { expect((await machine()).userId.toString()).toStrictEqual(user.toString()); }); @@ -179,7 +189,7 @@ describe(OlmMachine.name, () => { beforeAll(async () => { m = await machine(user, device); }); - + test('can pass keysquery and keysclaim requests directly', async () => { { // derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json From bac105a9aa31610f095a746ebd7c19fe48a93950 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Jan 2023 09:59:15 +0100 Subject: [PATCH 02/16] doc(crypto-nodejs): Fix a typo. --- bindings/matrix-sdk-crypto-nodejs/src/machine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs index d531ce1ab..288e57e64 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs @@ -20,7 +20,7 @@ use crate::{ /// The value used by the `OlmMachine` JS class. /// /// It has 2 states: `Opened` and `Closed`. Why maintaining the state here? -/// Because NodeJS has no way to drop an object explicitely, and we want to be +/// Because NodeJS has no way to drop an object explicitly, and we want to be /// able to “close” the `OlmMachine` to free all associated data. More over, /// `napi-rs` doesn't allow a function to take the ownership of the type itself /// (`fn close(self) { … }`). So we manage the state ourselves. From 6593f615569d7f29a418f83cb7ff4419651f1394 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 15 Feb 2023 09:15:31 +0100 Subject: [PATCH 03/16] doc(crypto-nodejs): Add safety comment for `OlmMachine.close`. --- bindings/matrix-sdk-crypto-nodejs/src/machine.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs index 288e57e64..daf3bec4b 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs @@ -443,6 +443,11 @@ impl OlmMachine { /// /// All associated resources will be closed too, like the crypto storage /// connections. + /// + /// # Safety + /// + /// The caller is responsible to **not** use any objects that came from this + /// `OlmMachine` after this `close` method has been called. #[napi(strict)] pub fn close(&mut self) { self.inner = OlmMachineInner::Closed; From 4f14728c918f31a244f2562c5ff43d1a6a4d16b7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Feb 2023 10:55:11 +0100 Subject: [PATCH 04/16] chore(sdk): Add more spans and trace events to the timeline --- .../src/room/timeline/event_handler.rs | 14 ++++++++++++++ crates/matrix-sdk/src/room/timeline/inner.rs | 17 ++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 172f0f7eb..8e9912750 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -259,6 +259,8 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { } } + trace!("Handling event"); + match event_kind { TimelineEventKind::Message { content } => match content { AnyMessageLikeEventContent::Reaction(c) => { @@ -312,9 +314,11 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { } if !self.result.item_added { + trace!("No new item added"); if let Flow::Remote { position: TimelineItemPosition::Update(idx), .. } = self.flow { // If add was not called, that means the UTD event is one that // wouldn't normally be visible. Remove it. + trace!("Removing UTD that was successfully retried"); self.timeline_items.remove(idx); } @@ -562,6 +566,8 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { match &self.flow { Flow::Local { timestamp, .. } => { + trace!("Adding new local timeline item"); + // Check if the latest event has the same date as this event. if let Some(latest_event) = self.timeline_items.iter().rev().find_map(|item| item.as_event()) @@ -583,6 +589,8 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { } Flow::Remote { position: TimelineItemPosition::Start, origin_server_ts, .. } => { + trace!("Adding new remote timeline item at the start"); + // If there is a loading indicator at the top, check for / insert the day // divider at position 1 and the new event at 2 rather than 0 and 1. let offset = match self.timeline_items.first().and_then(|item| item.as_virtual()) { @@ -619,6 +627,8 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { origin_server_ts, .. } => { + trace!("Adding new remote timeline item at the end"); + let result = rfind_event_item(self.timeline_items, |it| { txn_id.is_some() && it.transaction_id() == txn_id.as_deref() || it.event_id() == Some(event_id) @@ -763,6 +773,8 @@ pub(crate) fn update_read_marker( fully_read_event_in_timeline: &mut bool, ) { let Some(fully_read_event) = fully_read_event else { return }; + trace!(?fully_read_event, "Updating read marker"); + let read_marker_idx = find_read_marker(items_lock); let fully_read_event_idx = rfind_event_by_id(items_lock, fully_read_event).map(|(idx, _)| idx); @@ -796,7 +808,9 @@ fn _update_timeline_item( update: impl FnOnce(&EventTimelineItem) -> Option, ) { if let Some((idx, item)) = rfind_event_by_id(timeline_items, event_id) { + trace!("Found timeline item to update"); if let Some(new_item) = update(item) { + trace!("Updating item"); timeline_items.set_cloned(idx, Arc::new(TimelineItem::Event(new_item))); *items_updated += 1; } diff --git a/crates/matrix-sdk/src/room/timeline/inner.rs b/crates/matrix-sdk/src/room/timeline/inner.rs index a098e8934..b161b47d9 100644 --- a/crates/matrix-sdk/src/room/timeline/inner.rs +++ b/crates/matrix-sdk/src/room/timeline/inner.rs @@ -84,6 +84,7 @@ impl TimelineInner

{ } pub(super) fn items_signal(&self) -> impl SignalVec> { + trace!("Creating timeline items signal"); self.items.signal_vec_cloned() } @@ -111,6 +112,8 @@ impl TimelineInner

{ #[cfg(feature = "experimental-sliding-sync")] pub(super) async fn clear(&self) { + trace!("Clearing timeline"); + let mut timeline_meta = self.metadata.lock().await; let mut timeline_items = self.items.lock_mut(); @@ -121,6 +124,7 @@ impl TimelineInner

{ timeline_items.clear(); } + #[instrument(skip_all)] pub(super) async fn handle_live_event( &self, raw: Raw, @@ -139,6 +143,7 @@ impl TimelineInner

{ } /// Handle the creation of a new local event. + #[instrument(skip_all)] pub(super) async fn handle_local_event( &self, txn_id: OwnedTransactionId, @@ -167,6 +172,7 @@ impl TimelineInner

{ /// Update the send state of a local event represented by a transaction ID. /// /// If no local event is found, a warning is raised. + #[instrument(skip_all, fields(txn_id))] pub(super) fn update_event_send_state( &self, txn_id: &TransactionId, @@ -187,13 +193,13 @@ impl TimelineInner

{ let Some((idx, item)) = result else { // Event isn't found at all. - warn!(?txn_id, "Timeline item not found, can't add event ID"); + warn!("Timeline item not found, can't add event ID"); return; }; let EventTimelineItem::Local(item) = item else { // Remote echo already received. This is very unlikely. - trace!(?txn_id, "Remote echo received before send-event response"); + trace!("Remote echo received before send-event response"); return; }; @@ -201,7 +207,7 @@ impl TimelineInner

{ // emit an error but also override to the given sent state. if let EventSendState::Sent { event_id: existing_event_id } = &item.send_state { let new_event_id = new_event_id.map(debug); - error!(?existing_event_id, ?new_event_id, ?txn_id, "Local echo already marked as sent"); + error!(?existing_event_id, ?new_event_id, "Local echo already marked as sent"); } let new_item = TimelineItem::Event(item.with_send_state(send_state).into()); @@ -211,6 +217,7 @@ impl TimelineInner

{ /// Handle a back-paginated event. /// /// Returns the number of timeline updates that were made. + #[instrument(skip_all)] pub(super) async fn handle_back_paginated_event( &self, event: TimelineEvent, @@ -261,6 +268,7 @@ impl TimelineInner

{ } } + #[instrument(skip_all)] pub(super) async fn handle_fully_read(&self, raw: Raw) { let fully_read_event_id = match raw.deserialize() { Ok(ev) => ev.content.event_id, @@ -273,6 +281,7 @@ impl TimelineInner

{ self.set_fully_read_event(fully_read_event_id).await; } + #[instrument(skip_all)] pub(super) async fn set_fully_read_event(&self, fully_read_event_id: OwnedEventId) { let mut metadata_lock = self.metadata.lock().await; @@ -413,6 +422,8 @@ impl TimelineInner

{ } pub(super) async fn update_sender_profiles(&self) { + trace!("Updating sender profiles"); + // Can't lock the timeline items across .await points without making the // resulting future `!Send`. As a (brittle) hack around that, lock the // timeline items in each loop iteration but keep a lock of the metadata From 74056de8d70a51d184c36d5deaecec3807e47e8c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Feb 2023 12:13:09 +0100 Subject: [PATCH 05/16] fix(sdk): Deduplicate events coming from back-pagination --- .../src/room/timeline/event_handler.rs | 17 ++++++++++++++++- .../matrix-sdk/src/room/timeline/tests/basic.rs | 16 +++++++++++++++- .../matrix-sdk/src/room/timeline/tests/mod.rs | 8 +++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 8e9912750..ee8cad83f 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -588,7 +588,22 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { self.timeline_items.push_cloned(item); } - Flow::Remote { position: TimelineItemPosition::Start, origin_server_ts, .. } => { + Flow::Remote { + position: TimelineItemPosition::Start, + event_id, + origin_server_ts, + .. + } => { + if self + .timeline_items + .iter() + .filter_map(|ev| ev.as_event()?.event_id()) + .any(|id| id == event_id) + { + trace!("Skipping back-paginated event that has already been seen"); + return; + } + trace!("Adding new remote timeline item at the start"); // If there is a loading indicator at the top, check for / insert the day diff --git a/crates/matrix-sdk/src/room/timeline/tests/basic.rs b/crates/matrix-sdk/src/room/timeline/tests/basic.rs index 4581f2d0d..25256cfe5 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/basic.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/basic.rs @@ -22,7 +22,7 @@ use serde_json::json; use super::{TestTimeline, ALICE, BOB}; use crate::room::timeline::{ - event_item::AnyOtherFullStateEventContent, MembershipChange, TimelineItemContent, + event_item::AnyOtherFullStateEventContent, MembershipChange, TimelineItem, TimelineItemContent, VirtualTimelineItem, }; @@ -227,3 +227,17 @@ async fn other_state() { assert_matches!(ev.content(), AnyOtherFullStateEventContent::RoomTopic(c) => c); assert_matches!(full_content, FullStateEventContent::Redacted(_)); } + +#[async_test] +async fn dedup_pagination() { + let timeline = TestTimeline::new(); + + let event = timeline.make_message_event(*ALICE, RoomMessageEventContent::text_plain("o/")); + timeline.handle_live_custom_event(event.clone()).await; + timeline.handle_back_paginated_custom_event(event).await; + + let timeline_items = timeline.inner.items(); + assert_eq!(timeline_items.len(), 2); + assert_matches!(*timeline_items[0], TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_))); + assert_matches!(*timeline_items[1], TimelineItem::Event(_)); +} diff --git a/crates/matrix-sdk/src/room/timeline/tests/mod.rs b/crates/matrix-sdk/src/room/timeline/tests/mod.rs index 5febdd5a1..4ca7a4969 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/mod.rs @@ -22,7 +22,7 @@ use std::sync::{ use async_trait::async_trait; use futures_core::Stream; use futures_signals::signal_vec::{SignalVecExt, VecDiff}; -use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_base::deserialized_responses::{SyncTimelineEvent, TimelineEvent}; use once_cell::sync::Lazy; use ruma::{ events::{ @@ -171,6 +171,12 @@ impl TestTimeline { txn_id } + async fn handle_back_paginated_custom_event(&self, event: JsonValue) { + let timeline_event = + TimelineEvent { event: Raw::new(&event).unwrap().cast(), encryption_info: None }; + self.inner.handle_back_paginated_event(timeline_event).await; + } + /// Set the next server timestamp. /// /// Timestamps will continue to increase by 1 (millisecond) from that value. From 3f8590f86d25e5cb665bfc4d1fa40807c6df8362 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 15 Feb 2023 12:06:20 +0000 Subject: [PATCH 06/16] crypto-nodejs, crypto-js: Run `prettier` on source code --- bindings/matrix-sdk-crypto-js/README.md | 2 - .../matrix-sdk-crypto-js/scripts/epilogue.js | 81 +-- .../tests/asyncload.test.js | 8 +- .../tests/attachment.test.js | 52 +- .../matrix-sdk-crypto-js/tests/device.test.js | 426 +++++++----- .../tests/encryption.test.js | 23 +- .../matrix-sdk-crypto-js/tests/events.test.js | 6 +- bindings/matrix-sdk-crypto-js/tests/helper.js | 28 +- .../tests/identifiers.test.js | 172 ++--- .../tests/machine.test.js | 618 +++++++++--------- .../tests/requests.test.js | 15 +- .../tests/sync_events.test.js | 14 +- .../tests/tracing.test.js | 51 +- bindings/matrix-sdk-crypto-js/tsconfig.json | 2 +- .../matrix-sdk-crypto-nodejs/CHANGELOG.md | 2 +- bindings/matrix-sdk-crypto-nodejs/README.md | 22 +- .../matrix-sdk-crypto-nodejs/download-lib.js | 141 ++-- .../tests/attachment.test.js | 54 +- .../tests/encryption.test.js | 18 +- .../tests/events.test.js | 6 +- .../tests/identifiers.test.js | 130 ++-- .../tests/machine.test.js | 347 +++++----- .../tests/requests.test.js | 23 +- .../tests/responses.test.js | 8 +- .../tests/sync_events.test.js | 14 +- .../matrix-sdk-crypto-nodejs/tsconfig.json | 2 +- 26 files changed, 1252 insertions(+), 1013 deletions(-) diff --git a/bindings/matrix-sdk-crypto-js/README.md b/bindings/matrix-sdk-crypto-js/README.md index 640055c4a..33ea75ee5 100644 --- a/bindings/matrix-sdk-crypto-js/README.md +++ b/bindings/matrix-sdk-crypto-js/README.md @@ -49,8 +49,6 @@ $ npm run doc The documentation is generated in the `./docs` directory. - - [WebAssembly]: https://webassembly.org/ [`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto [`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk diff --git a/bindings/matrix-sdk-crypto-js/scripts/epilogue.js b/bindings/matrix-sdk-crypto-js/scripts/epilogue.js index 8db0d3f09..f294804b2 100644 --- a/bindings/matrix-sdk-crypto-js/scripts/epilogue.js +++ b/bindings/matrix-sdk-crypto-js/scripts/epilogue.js @@ -2,12 +2,15 @@ // replace 'wasm' with a reference to the exports from the wasm module. // // Ideally this will never get used because the application will call initAsync instead. -wasm = new Proxy({}, { - get: (target, prop, receiver) => __initSync()[prop], -}); +wasm = new Proxy( + {}, + { + get: (target, prop, receiver) => __initSync()[prop], + }, +); let inited = false; -__initSync = function() { +__initSync = function () { if (inited) { return; } @@ -21,7 +24,7 @@ __initSync = function() { wasm.__wbindgen_start(); inited = true; return wasm; -} +}; let initPromise = null; @@ -39,43 +42,47 @@ module.exports.initAsync = function () { if (!initPromise) { initPromise = Promise.resolve() .then(() => require("./matrix_sdk_crypto_js_bg.wasm.js")) - .then(b64 => WebAssembly.instantiate(unbase64(b64), imports)) - .then(result => { + .then((b64) => WebAssembly.instantiate(unbase64(b64), imports)) + .then((result) => { wasm = result.instance.exports; wasm.__wbindgen_start(); inited = true; }); } return initPromise; -} - -const b64lookup = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]); - -// base64 decoder, based on the code at https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_%E2%80%93_rewriting_atob_and_btoa_using_typedarrays_and_utf-8 -function unbase64(sBase64) { - const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, ""); - const nInLen = sB64Enc.length; - const nOutLen = (nInLen * 3 + 1) >> 2; - const taBytes = new Uint8Array(nOutLen); - - let nMod3; - let nMod4; - let nUint24 = 0; - let nOutIdx = 0; - for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) { - nMod4 = nInIdx & 3; - nUint24 |= b64lookup[sB64Enc.charCodeAt(nInIdx)] << (6 * (3 - nMod4)); - if (nMod4 === 3 || nInLen - nInIdx === 1) { - nMod3 = 0; - while (nMod3 < 3 && nOutIdx < nOutLen) { - taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255; - nMod3++; - nOutIdx++; - } - nUint24 = 0; - } - } - - return taBytes; }; +const b64lookup = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 62, 0, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, +]); + +// base64 decoder, based on the code at https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_%E2%80%93_rewriting_atob_and_btoa_using_typedarrays_and_utf-8 +function unbase64(sBase64) { + const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, ""); + const nInLen = sB64Enc.length; + const nOutLen = (nInLen * 3 + 1) >> 2; + const taBytes = new Uint8Array(nOutLen); + + let nMod3; + let nMod4; + let nUint24 = 0; + let nOutIdx = 0; + for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= b64lookup[sB64Enc.charCodeAt(nInIdx)] << (6 * (3 - nMod4)); + if (nMod4 === 3 || nInLen - nInIdx === 1) { + nMod3 = 0; + while (nMod3 < 3 && nOutIdx < nOutLen) { + taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255; + nMod3++; + nOutIdx++; + } + nUint24 = 0; + } + } + + return taBytes; +} diff --git a/bindings/matrix-sdk-crypto-js/tests/asyncload.test.js b/bindings/matrix-sdk-crypto-js/tests/asyncload.test.js index 6e6d08797..fe0dea526 100644 --- a/bindings/matrix-sdk-crypto-js/tests/asyncload.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/asyncload.test.js @@ -1,10 +1,12 @@ const { UserId, initAsync } = require("../pkg/matrix_sdk_crypto_js"); -test('can instantiate rust objects with async initialiser', async () => { - initUserId = () => new UserId('@foo:bar.org'); +test("can instantiate rust objects with async initialiser", async () => { + initUserId = () => new UserId("@foo:bar.org"); // stub out the synchronous WebAssembly loader with one that raises an error - jest.spyOn(WebAssembly, 'Module').mockImplementation(() => { throw new Error('synchronous WebAssembly.Module() not allowed')}); + jest.spyOn(WebAssembly, "Module").mockImplementation(() => { + throw new Error("synchronous WebAssembly.Module() not allowed"); + }); // this should fail expect(initUserId).toThrow(/synchronous/); diff --git a/bindings/matrix-sdk-crypto-js/tests/attachment.test.js b/bindings/matrix-sdk-crypto-js/tests/attachment.test.js index a614ef0dc..20b4df8b5 100644 --- a/bindings/matrix-sdk-crypto-js/tests/attachment.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/attachment.test.js @@ -1,37 +1,41 @@ -const { Attachment, EncryptedAttachment } = require('../pkg/matrix_sdk_crypto_js'); +const { Attachment, EncryptedAttachment } = require("../pkg/matrix_sdk_crypto_js"); describe(Attachment.name, () => { - const originalData = 'hello'; + const originalData = "hello"; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); let encryptedAttachment; - test('can encrypt data', () => { + test("can encrypt data", () => { encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData)); const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo); expect(mediaEncryptionInfo).toMatchObject({ - v: 'v2', + v: "v2", key: { kty: expect.any(String), - key_ops: expect.arrayContaining(['encrypt', 'decrypt']), + key_ops: expect.arrayContaining(["encrypt", "decrypt"]), alg: expect.any(String), k: expect.any(String), ext: expect.any(Boolean), }, iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/), hashes: { - sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/) - } + sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/), + }, }); const encryptedData = encryptedAttachment.encryptedData; - expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false); + expect( + encryptedData.every((i) => { + i != 0; + }), + ).toStrictEqual(false); }); - test('can decrypt data', () => { + test("can decrypt data", () => { expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false); const decryptedAttachment = Attachment.decrypt(encryptedAttachment); @@ -40,34 +44,36 @@ describe(Attachment.name, () => { expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true); }); - test('can only decrypt once', () => { + test("can only decrypt once", () => { expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true); - expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow() + expect(() => { + textDecoder.decode(decryptedAttachment); + }).toThrow(); }); }); describe(EncryptedAttachment.name, () => { - const originalData = 'hello'; + const originalData = "hello"; const textDecoder = new TextDecoder(); - test('can be created manually', () => { + test("can be created manually", () => { const encryptedAttachment = new EncryptedAttachment( new Uint8Array([24, 150, 67, 37, 144]), JSON.stringify({ - v: 'v2', + v: "v2", key: { - kty: 'oct', - key_ops: [ 'encrypt', 'decrypt' ], - alg: 'A256CTR', - k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM', - ext: true + kty: "oct", + key_ops: ["encrypt", "decrypt"], + alg: "A256CTR", + k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM", + ext: true, }, - iv: 'xk2AcWkomiYAAAAAAAAAAA', + iv: "xk2AcWkomiYAAAAAAAAAAA", hashes: { - sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok' - } - }) + sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok", + }, + }), ); expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false); diff --git a/bindings/matrix-sdk-crypto-js/tests/device.test.js b/bindings/matrix-sdk-crypto-js/tests/device.test.js index ea6d3d6e9..2d19d910c 100644 --- a/bindings/matrix-sdk-crypto-js/tests/device.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/device.test.js @@ -27,11 +27,11 @@ const { Qr, QrCode, QrCodeScan, -} = require('../pkg/matrix_sdk_crypto_js'); -const { zip, addMachineToMachine } = require('./helper'); +} = require("../pkg/matrix_sdk_crypto_js"); +const { zip, addMachineToMachine } = require("./helper"); -describe('LocalTrust', () => { - test('has the correct variant values', () => { +describe("LocalTrust", () => { + test("has the correct variant values", () => { expect(LocalTrust.Verified).toStrictEqual(0); expect(LocalTrust.BlackListed).toStrictEqual(1); expect(LocalTrust.Ignored).toStrictEqual(2); @@ -39,8 +39,8 @@ describe('LocalTrust', () => { }); }); -describe('DeviceKeyName', () => { - test('has the correct variant values', () => { +describe("DeviceKeyName", () => { + test("has the correct variant values", () => { expect(DeviceKeyName.Curve25519).toStrictEqual(0); expect(DeviceKeyName.Ed25519).toStrictEqual(1); expect(DeviceKeyName.Unknown).toStrictEqual(2); @@ -48,54 +48,52 @@ describe('DeviceKeyName', () => { }); describe(OlmMachine.name, () => { - const user = new UserId('@alice:example.org'); - const device = new DeviceId('foobar'); - const room = new RoomId('!baz:matrix.org'); + const user = new UserId("@alice:example.org"); + const device = new DeviceId("foobar"); + const room = new RoomId("!baz:matrix.org"); function machine(new_user, new_device) { return OlmMachine.initialize(new_user || user, new_device || device); } - test('can read user devices', async () => { + test("can read user devices", async () => { const m = await machine(); const userDevices = await m.getUserDevices(user); expect(userDevices).toBeInstanceOf(UserDevices); expect(userDevices.get(device)).toBeInstanceOf(Device); expect(userDevices.isAnyVerified()).toStrictEqual(false); - expect(userDevices.keys().map(device_id => device_id.toString())).toStrictEqual([device.toString()]); - expect(userDevices.devices().map(device => device.deviceId.toString())).toStrictEqual([device.toString()]); + expect(userDevices.keys().map((device_id) => device_id.toString())).toStrictEqual([device.toString()]); + expect(userDevices.devices().map((device) => device.deviceId.toString())).toStrictEqual([device.toString()]); }); - test('can read a user device', async () => { + test("can read a user device", async () => { const m = await machine(); const hypothetical_response = JSON.stringify({ - "device_keys": { + device_keys: { "@alice:example.org": { - "JLAFKJWSCS": { - "algorithms": [ - "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" - ], - "device_id": "JLAFKJWSCS", - "keys": { + JLAFKJWSCS: { + algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + device_id: "JLAFKJWSCS", + keys: { "curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4", - "ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM" + "ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM", }, - "signatures": { + signatures: { "@alice:example.org": { - "ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA" - } + "ed25519:JLAFKJWSCS": + "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA", + }, }, - "unsigned": { - "device_display_name": "Alice's mobile phone" + unsigned: { + device_display_name: "Alice's mobile phone", }, - "user_id": "@alice:example.org" - } - } + user_id: "@alice:example.org", + }, + }, }, - "failures": {} + failures: {}, }); // Insert another device into the store await m.markRequestAsSent("ID", RequestType.KeysQuery, hypothetical_response); @@ -140,18 +138,18 @@ describe(OlmMachine.name, () => { }); }); -describe('Key Verification', () => { - const userId1 = new UserId('@alice:example.org'); - const deviceId1 = new DeviceId('alice_device'); +describe("Key Verification", () => { + const userId1 = new UserId("@alice:example.org"); + const deviceId1 = new DeviceId("alice_device"); - const userId2 = new UserId('@bob:example.org'); - const deviceId2 = new DeviceId('bob_device'); + const userId2 = new UserId("@bob:example.org"); + const deviceId2 = new DeviceId("bob_device"); function machine(new_user, new_device) { return OlmMachine.initialize(new_user || userId1, new_device || deviceId1); } - describe('SAS', () => { + describe("SAS", () => { // First Olm machine. let m1; @@ -169,7 +167,7 @@ describe('Key Verification', () => { // The flow ID. let flowId; - test('can request verification (`m.key.verification.request`)', async () => { + test("can request verification (`m.key.verification.request`)", async () => { // Make `m1` and `m2` be aware of each other. { await addMachineToMachine(m2, m1); @@ -196,7 +194,9 @@ describe('Key Verification', () => { expect(verificationRequest1.isReady()).toStrictEqual(false); expect(verificationRequest1.timedOut()).toStrictEqual(false); expect(verificationRequest1.theirSupportedMethods).toBeUndefined(); - expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1])); + expect(verificationRequest1.ourSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]), + ); expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/); expect(verificationRequest1.isSelfVerification()).toStrictEqual(false); expect(verificationRequest1.weStarted()).toStrictEqual(true); @@ -204,13 +204,17 @@ describe('Key Verification', () => { expect(verificationRequest1.isCancelled()).toStrictEqual(false); expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.request"); - const toDeviceEvents = [{ - sender: userId1.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId1.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][ + deviceId2.toString() + ], + }, + ]; // Let's send the verification request to `m2`. await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); @@ -221,7 +225,7 @@ describe('Key Verification', () => { // Verification request for `m2`. let verificationRequest2; - test('can fetch received request verification', async () => { + test("can fetch received request verification", async () => { // Oh, a new verification request. verificationRequest2 = m2.getVerificationRequest(userId1, flowId); @@ -235,7 +239,9 @@ describe('Key Verification', () => { expect(verificationRequest2.isPassive()).toStrictEqual(false); expect(verificationRequest2.isReady()).toStrictEqual(false); expect(verificationRequest2.timedOut()).toStrictEqual(false); - expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1])); + expect(verificationRequest2.theirSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]), + ); expect(verificationRequest2.ourSupportedMethods).toBeUndefined(); expect(verificationRequest2.flowId).toStrictEqual(flowId); expect(verificationRequest2.isSelfVerification()).toStrictEqual(false); @@ -248,40 +254,52 @@ describe('Key Verification', () => { expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same }); - test('can accept a verification request (`m.key.verification.ready`)', async () => { + test("can accept a verification request (`m.key.verification.ready`)", async () => { // Accept the verification request. let outgoingVerificationRequest = verificationRequest2.accept(); expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); // The request verification is ready. - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.ready"); - const toDeviceEvents = [{ - sender: userId2.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId2.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][ + deviceId1.toString() + ], + }, + ]; // Let's send the verification ready to `m1`. await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); }); - test('verification requests are synchronized and automatically updated', () => { + test("verification requests are synchronized and automatically updated", () => { expect(verificationRequest1.isReady()).toStrictEqual(true); expect(verificationRequest2.isReady()).toStrictEqual(true); - expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1])); - expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1])); + expect(verificationRequest1.theirSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]), + ); + expect(verificationRequest1.ourSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]), + ); - expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1])); - expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1])); + expect(verificationRequest2.theirSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]), + ); + expect(verificationRequest2.ourSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]), + ); }); // SAS verification for the second machine. let sas2; - test('can start a SAS verification (`m.key.verification.start`)', async () => { + test("can start a SAS verification (`m.key.verification.start`)", async () => { // Let's start a SAS verification, from `m2` for example. [sas2, outgoingVerificationRequest] = await verificationRequest2.startSas(); expect(sas2).toBeInstanceOf(Sas); @@ -308,13 +326,17 @@ describe('Key Verification', () => { expect(sas2.decimals()).toBeUndefined(); expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start"); - const toDeviceEvents = [{ - sender: userId2.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId2.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][ + deviceId1.toString() + ], + }, + ]; // Let's send the SAS start to `m1`. await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); @@ -323,7 +345,7 @@ describe('Key Verification', () => { // SAS verification for the second machine. let sas1; - test('can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)', async () => { + test("can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)", async () => { // Let's fetch the ongoing SAS verification. sas1 = await m1.getVerification(userId2, flowId); @@ -353,64 +375,72 @@ describe('Key Verification', () => { let outgoingVerificationRequest = sas1.accept(); expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.accept'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.accept"); - const toDeviceEvents = [{ - sender: userId1.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId1.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][ + deviceId2.toString() + ], + }, + ]; // Let's send the SAS accept to `m2`. await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); }); - test('emojis are supported by both sides', () => { + test("emojis are supported by both sides", () => { expect(sas1.supportsEmoji()).toStrictEqual(true); expect(sas2.supportsEmoji()).toStrictEqual(true); }); - test('one side sends verification key (`m.key.verification.key`)', async () => { + test("one side sends verification key (`m.key.verification.key`)", async () => { // Let's send the verification keys from `m2` to `m1`. const outgoingRequests = await m2.outgoingRequests(); let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice); expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest); - expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key'); + expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.key"); - const toDeviceEvents = [{ - sender: userId2.toString(), - type: toDeviceRequest.event_type, - content: JSON.parse(toDeviceRequest.body).messages[userId1.toString()][deviceId1.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId2.toString(), + type: toDeviceRequest.event_type, + content: JSON.parse(toDeviceRequest.body).messages[userId1.toString()][deviceId1.toString()], + }, + ]; // Let's send te SAS key to `m1`. await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); - m2.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, '{}'); + m2.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}"); }); - test('other side sends back verification key (`m.key.verification.key`)', async () => { + test("other side sends back verification key (`m.key.verification.key`)", async () => { // Let's send the verification keys from `m1` to `m2`. const outgoingRequests = await m1.outgoingRequests(); let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice); expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest); - expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key'); + expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.key"); - const toDeviceEvents = [{ - sender: userId1.toString(), - type: toDeviceRequest.event_type, - content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId1.toString(), + type: toDeviceRequest.event_type, + content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()], + }, + ]; // Let's send te SAS key to `m2`. await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); - m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, '{}'); + m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}"); }); - test('emojis match from both sides', () => { + test("emojis match from both sides", () => { const emojis1 = sas1.emoji(); const emojiIndexes1 = sas1.emojiIndex(); const emojis2 = sas2.emoji(); @@ -421,9 +451,15 @@ describe('Key Verification', () => { expect(emojis2).toHaveLength(emojis1.length); expect(emojiIndexes2).toHaveLength(emojis1.length); - const isEmoji = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/; + const isEmoji = + /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/; - for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(emojis1, emojiIndexes1, emojis2, emojiIndexes2)) { + for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip( + emojis1, + emojiIndexes1, + emojis2, + emojiIndexes2, + )) { expect(emoji1).toBeInstanceOf(Emoji); expect(emoji1.symbol).toMatch(isEmoji); expect(emoji1.description).toBeTruthy(); @@ -439,7 +475,7 @@ describe('Key Verification', () => { } }); - test('decimals match from both sides', () => { + test("decimals match from both sides", () => { const decimals1 = sas1.decimals(); const decimals2 = sas2.decimals(); @@ -455,7 +491,7 @@ describe('Key Verification', () => { } }); - test('can confirm keys match (`m.key.verification.mac`)', async () => { + test("can confirm keys match (`m.key.verification.mac`)", async () => { // `m1` confirms. const [outgoingVerificationRequests, signatureUploadRequest] = await sas1.confirm(); @@ -465,19 +501,23 @@ describe('Key Verification', () => { let outgoingVerificationRequest = outgoingVerificationRequests[0]; expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.mac"); - const toDeviceEvents = [{ - sender: userId1.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId1.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][ + deviceId2.toString() + ], + }, + ]; // Let's send te SAS confirmation to `m2`. await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); }); - test('can confirm back keys match (`m.key.verification.done`)', async () => { + test("can confirm back keys match (`m.key.verification.done`)", async () => { // `m2` confirms. const [outgoingVerificationRequests, signatureUploadRequest] = await sas2.confirm(); @@ -489,13 +529,17 @@ describe('Key Verification', () => { let outgoingVerificationRequest = outgoingVerificationRequests[0]; expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.mac"); - const toDeviceEvents = [{ - sender: userId2.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId2.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][ + deviceId1.toString() + ], + }, + ]; // Let's send te SAS confirmation to `m1`. await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); @@ -506,41 +550,47 @@ describe('Key Verification', () => { let outgoingVerificationRequest = outgoingVerificationRequests[1]; expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.done"); - const toDeviceEvents = [{ - sender: userId2.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId2.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][ + deviceId1.toString() + ], + }, + ]; // Let's send te SAS done to `m1`. await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); } }); - test('can send final done (`m.key.verification.done`)', async () => { + test("can send final done (`m.key.verification.done`)", async () => { const outgoingRequests = await m1.outgoingRequests(); expect(outgoingRequests).toHaveLength(4); let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice); expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest); - expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.done'); + expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.done"); - const toDeviceEvents = [{ - sender: userId1.toString(), - type: toDeviceRequest.event_type, - content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId1.toString(), + type: toDeviceRequest.event_type, + content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()], + }, + ]; // Let's send te SAS key to `m2`. await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); - m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, '{}'); + m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}"); }); - test('can see if verification is done', () => { + test("can see if verification is done", () => { expect(verificationRequest1.isDone()).toStrictEqual(true); expect(verificationRequest2.isDone()).toStrictEqual(true); @@ -549,10 +599,10 @@ describe('Key Verification', () => { }); }); - describe('QR Code', () => { + describe("QR Code", () => { if (undefined === Qr) { // qrcode supports is not enabled - console.info('qrcode support is disabled, skip the associated test suite'); + console.info("qrcode support is disabled, skip the associated test suite"); return; } @@ -574,7 +624,7 @@ describe('Key Verification', () => { // The flow ID. let flowId; - test('can request verification (`m.key.verification.request`)', async () => { + test("can request verification (`m.key.verification.request`)", async () => { // Make `m1` and `m2` be aware of each other. { await addMachineToMachine(m2, m1); @@ -604,7 +654,9 @@ describe('Key Verification', () => { expect(verificationRequest1.isReady()).toStrictEqual(false); expect(verificationRequest1.timedOut()).toStrictEqual(false); expect(verificationRequest1.theirSupportedMethods).toBeUndefined(); - expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeShowV1])); + expect(verificationRequest1.ourSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.QrCodeShowV1]), + ); expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/); expect(verificationRequest1.isSelfVerification()).toStrictEqual(false); expect(verificationRequest1.weStarted()).toStrictEqual(true); @@ -612,13 +664,17 @@ describe('Key Verification', () => { expect(verificationRequest1.isCancelled()).toStrictEqual(false); expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.request"); - const toDeviceEvents = [{ - sender: userId1.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId1.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][ + deviceId2.toString() + ], + }, + ]; // Let's send the verification request to `m2`. await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); @@ -629,7 +685,7 @@ describe('Key Verification', () => { // Verification request for `m2`. let verificationRequest2; - test('can fetch received request verification', async () => { + test("can fetch received request verification", async () => { // Oh, a new verification request. verificationRequest2 = m2.getVerificationRequest(userId1, flowId); @@ -643,7 +699,9 @@ describe('Key Verification', () => { expect(verificationRequest2.isPassive()).toStrictEqual(false); expect(verificationRequest2.isReady()).toStrictEqual(false); expect(verificationRequest2.timedOut()).toStrictEqual(false); - expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1])); + expect(verificationRequest2.theirSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]), + ); expect(verificationRequest2.ourSupportedMethods).toBeUndefined(); expect(verificationRequest2.flowId).toStrictEqual(flowId); expect(verificationRequest2.isSelfVerification()).toStrictEqual(false); @@ -656,7 +714,7 @@ describe('Key Verification', () => { expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same }); - test('can accept a verification request with methods (`m.key.verification.ready`)', async () => { + test("can accept a verification request with methods (`m.key.verification.ready`)", async () => { // Accept the verification request. let outgoingVerificationRequest = verificationRequest2.acceptWithMethods([ VerificationMethod.QrCodeScanV1, // by default @@ -666,33 +724,45 @@ describe('Key Verification', () => { expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); // The request verification is ready. - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.ready"); - const toDeviceEvents = [{ - sender: userId2.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId2.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][ + deviceId1.toString() + ], + }, + ]; // Let's send the verification ready to `m1`. await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); }); - test('verification requests are synchronized and automatically updated', () => { + test("verification requests are synchronized and automatically updated", () => { expect(verificationRequest1.isReady()).toStrictEqual(true); expect(verificationRequest2.isReady()).toStrictEqual(true); - expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1])); - expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1])); + expect(verificationRequest1.theirSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]), + ); + expect(verificationRequest1.ourSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]), + ); - expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1])); - expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1])); + expect(verificationRequest2.theirSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]), + ); + expect(verificationRequest2.ourSupportedMethods).toEqual( + expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]), + ); }); // QR verification for the second machine. let qr2; - test('can generate a QR code', async () => { + test("can generate a QR code", async () => { qr2 = await verificationRequest2.generateQrCode(); expect(qr2).toBeInstanceOf(Qr); @@ -712,17 +782,19 @@ describe('Key Verification', () => { expect(qr2.roomId).toBeUndefined(); }); - test('can read QR code\'s bytes', async () => { - const qrCodeHeader = 'MATRIX'; - const qrCodeVersion = '\x02'; + test("can read QR code's bytes", async () => { + const qrCodeHeader = "MATRIX"; + const qrCodeVersion = "\x02"; const qrCodeBytes = qr2.toBytes(); expect(qrCodeBytes).toHaveLength(122); - expect(Array.from(qrCodeBytes.slice(0, 7))).toEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0))); + expect(Array.from(qrCodeBytes.slice(0, 7))).toEqual( + [...qrCodeHeader, ...qrCodeVersion].map((char) => char.charCodeAt(0)), + ); }); - test('can render QR code', async () => { + test("can render QR code", async () => { const qrCode = qr2.toQrCode(); expect(qrCode).toBeInstanceOf(QrCode); @@ -738,7 +810,7 @@ describe('Key Verification', () => { // 45px ⨉ 45px expect(buffer).toHaveLength(45 * 45); // 0 for a white pixel, 1 for a black pixel. - expect(buffer.every(p => p == 0 || p == 1)).toStrictEqual(true); + expect(buffer.every((p) => p == 0 || p == 1)).toStrictEqual(true); /* const { Canvas } = require('canvas'); @@ -795,7 +867,7 @@ describe('Key Verification', () => { let qr1; - test('can scan a QR code from bytes', async () => { + test("can scan a QR code from bytes", async () => { const scan = QrCodeScan.fromBytes(qr2.toBytes()); expect(scan).toBeInstanceOf(QrCodeScan); @@ -819,50 +891,58 @@ describe('Key Verification', () => { expect(qr1.roomId).toBeUndefined(); }); - test('can start a QR verification/reciprocate (`m.key.verification.start`)', async () => { + test("can start a QR verification/reciprocate (`m.key.verification.start`)", async () => { let outgoingVerificationRequest = qr1.reciprocate(); expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start"); - const toDeviceEvents = [{ - sender: userId1.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId1.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][ + deviceId2.toString() + ], + }, + ]; // Let's send the verification request to `m2`. await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); }); - test('can confirm QR code has been scanned', () => { + test("can confirm QR code has been scanned", () => { expect(qr2.hasBeenScanned()).toStrictEqual(true); }); - test('can confirm scanning (`m.key.verification.done`)', async () => { + test("can confirm scanning (`m.key.verification.done`)", async () => { let outgoingVerificationRequest = qr2.confirmScanning(); expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.done"); - const toDeviceEvents = [{ - sender: userId2.toString(), - type: outgoingVerificationRequest.event_type, - content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()], - }]; + const toDeviceEvents = [ + { + sender: userId2.toString(), + type: outgoingVerificationRequest.event_type, + content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][ + deviceId1.toString() + ], + }, + ]; // Let's send the verification request to `m2`. await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set()); }); - test('can confirm QR code has been confirmed', () => { + test("can confirm QR code has been confirmed", () => { expect(qr2.hasBeenConfirmed()).toStrictEqual(true); }); }); }); -describe('VerificationMethod', () => { - test('has the correct variant values', () => { +describe("VerificationMethod", () => { + test("has the correct variant values", () => { expect(VerificationMethod.SasV1).toStrictEqual(0); expect(VerificationMethod.QrCodeScanV1).toStrictEqual(1); expect(VerificationMethod.QrCodeShowV1).toStrictEqual(2); diff --git a/bindings/matrix-sdk-crypto-js/tests/encryption.test.js b/bindings/matrix-sdk-crypto-js/tests/encryption.test.js index 4307deaf3..e564e6d26 100644 --- a/bindings/matrix-sdk-crypto-js/tests/encryption.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/encryption.test.js @@ -1,14 +1,19 @@ -const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto_js'); +const { + EncryptionAlgorithm, + EncryptionSettings, + HistoryVisibility, + VerificationState, +} = require("../pkg/matrix_sdk_crypto_js"); -describe('EncryptionAlgorithm', () => { - test('has the correct variant values', () => { +describe("EncryptionAlgorithm", () => { + test("has the correct variant values", () => { expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0); expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1); }); }); describe(EncryptionSettings.name, () => { - test('can be instantiated with default values', () => { + test("can be instantiated with default values", () => { const es = new EncryptionSettings(); expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2); @@ -17,18 +22,20 @@ describe(EncryptionSettings.name, () => { expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared); }); - test('checks the history visibility values', () => { + test("checks the history visibility values", () => { const es = new EncryptionSettings(); es.historyVisibility = HistoryVisibility.Invited; expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited); - expect(() => { es.historyVisibility = 42 }).toThrow(); + expect(() => { + es.historyVisibility = 42; + }).toThrow(); }); }); -describe('VerificationState', () => { - test('has the correct variant values', () => { +describe("VerificationState", () => { + test("has the correct variant values", () => { expect(VerificationState.Trusted).toStrictEqual(0); expect(VerificationState.Untrusted).toStrictEqual(1); expect(VerificationState.UnknownDevice).toStrictEqual(2); diff --git a/bindings/matrix-sdk-crypto-js/tests/events.test.js b/bindings/matrix-sdk-crypto-js/tests/events.test.js index 75ed2b610..4c7fdc893 100644 --- a/bindings/matrix-sdk-crypto-js/tests/events.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/events.test.js @@ -1,7 +1,7 @@ -const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto_js'); +const { HistoryVisibility } = require("../pkg/matrix_sdk_crypto_js"); -describe('HistoryVisibility', () => { - test('has the correct variant values', () => { +describe("HistoryVisibility", () => { + test("has the correct variant values", () => { expect(HistoryVisibility.Invited).toStrictEqual(0); expect(HistoryVisibility.Joined).toStrictEqual(1); expect(HistoryVisibility.Shared).toStrictEqual(2); diff --git a/bindings/matrix-sdk-crypto-js/tests/helper.js b/bindings/matrix-sdk-crypto-js/tests/helper.js index 0d3136bed..eaf58d5b8 100644 --- a/bindings/matrix-sdk-crypto-js/tests/helper.js +++ b/bindings/matrix-sdk-crypto-js/tests/helper.js @@ -1,10 +1,10 @@ -const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require('../pkg/matrix_sdk_crypto_js'); +const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require("../pkg/matrix_sdk_crypto_js"); function* zip(...arrays) { const len = Math.min(...arrays.map((array) => array.length)); for (let nth = 0; nth < len; ++nth) { - yield [...arrays.map((array) => array.at(nth))] + yield [...arrays.map((array) => array.at(nth))]; } } @@ -16,7 +16,9 @@ async function addMachineToMachine(machineToAdd, machine) { const oneTimeKeyCounts = new Map(); const unusedFallbackKeys = new Set(); - const receiveSyncChanges = JSON.parse(await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys)); + const receiveSyncChanges = JSON.parse( + await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys), + ); expect(receiveSyncChanges).toEqual([]); @@ -38,12 +40,16 @@ async function addMachineToMachine(machineToAdd, machine) { // https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload const hypothetical_response = JSON.stringify({ - "one_time_key_counts": { - "curve25519": 10, - "signed_curve25519": 20 - } + one_time_key_counts: { + curve25519: 10, + signed_curve25519: 20, + }, }); - const marked = await machineToAdd.markRequestAsSent(outgoingRequests[0].id, outgoingRequests[0].type, hypothetical_response); + const marked = await machineToAdd.markRequestAsSent( + outgoingRequests[0].id, + outgoingRequests[0].type, + hypothetical_response, + ); expect(marked).toStrictEqual(true); keysUploadRequest = outgoingRequests[0]; @@ -71,7 +77,11 @@ async function addMachineToMachine(machineToAdd, machine) { keyQueryResponse.self_signing_keys[userId] = keys.self_signing_key; keyQueryResponse.user_signing_keys[userId] = keys.user_signing_key; - const marked = await machine.markRequestAsSent(outgoingRequests[1].id, outgoingRequests[1].type, JSON.stringify(keyQueryResponse)); + const marked = await machine.markRequestAsSent( + outgoingRequests[1].id, + outgoingRequests[1].type, + JSON.stringify(keyQueryResponse), + ); expect(marked).toStrictEqual(true); } } diff --git a/bindings/matrix-sdk-crypto-js/tests/identifiers.test.js b/bindings/matrix-sdk-crypto-js/tests/identifiers.test.js index 714dc64a6..5fe37f2d6 100644 --- a/bindings/matrix-sdk-crypto-js/tests/identifiers.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/identifiers.test.js @@ -7,65 +7,75 @@ const { RoomId, ServerName, UserId, -} = require('../pkg/matrix_sdk_crypto_js'); +} = require("../pkg/matrix_sdk_crypto_js"); describe(UserId.name, () => { - test('cannot be invalid', () => { - expect(() => { new UserId('@foobar') }).toThrow(); + test("cannot be invalid", () => { + expect(() => { + new UserId("@foobar"); + }).toThrow(); }); - const user = new UserId('@foo:bar.org'); + const user = new UserId("@foo:bar.org"); - test('localpart is present', () => { - expect(user.localpart).toStrictEqual('foo'); + test("localpart is present", () => { + expect(user.localpart).toStrictEqual("foo"); }); - test('server name is present', () => { + test("server name is present", () => { expect(user.serverName).toBeInstanceOf(ServerName); }); - test('user ID is not historical', () => { + test("user ID is not historical", () => { expect(user.isHistorical()).toStrictEqual(false); }); - test('can read the user ID as a string', () => { - expect(user.toString()).toStrictEqual('@foo:bar.org'); - }) + test("can read the user ID as a string", () => { + expect(user.toString()).toStrictEqual("@foo:bar.org"); + }); }); describe(DeviceId.name, () => { - const device = new DeviceId('foo'); + const device = new DeviceId("foo"); - test('can read the device ID as a string', () => { - expect(device.toString()).toStrictEqual('foo'); - }) + test("can read the device ID as a string", () => { + expect(device.toString()).toStrictEqual("foo"); + }); }); describe(DeviceKeyId.name, () => { for (const deviceKey of [ - { name: 'ed25519', - id: 'ed25519:foobar', - algorithmName: DeviceKeyAlgorithmName.Ed25519, - algorithm: 'ed25519', - deviceId: 'foobar' }, + { + name: "ed25519", + id: "ed25519:foobar", + algorithmName: DeviceKeyAlgorithmName.Ed25519, + algorithm: "ed25519", + deviceId: "foobar", + }, - { name: 'curve25519', - id: 'curve25519:foobar', - algorithmName: DeviceKeyAlgorithmName.Curve25519, - algorithm: 'curve25519', - deviceId: 'foobar' }, + { + name: "curve25519", + id: "curve25519:foobar", + algorithmName: DeviceKeyAlgorithmName.Curve25519, + algorithm: "curve25519", + deviceId: "foobar", + }, - { name: 'signed curve25519', - id: 'signed_curve25519:foobar', - algorithmName: DeviceKeyAlgorithmName.SignedCurve25519, - algorithm: 'signed_curve25519', - deviceId: 'foobar' }, + { + name: "signed curve25519", + id: "signed_curve25519:foobar", + algorithmName: DeviceKeyAlgorithmName.SignedCurve25519, + algorithm: "signed_curve25519", + deviceId: "foobar", + }, - { name: 'unknown', - id: 'hello:foobar', - algorithmName: DeviceKeyAlgorithmName.Unknown, - algorithm: 'hello', - deviceId: 'foobar' }, + { + name: "unknown", + id: "hello:foobar", + algorithmName: DeviceKeyAlgorithmName.Unknown, + algorithm: "hello", + deviceId: "foobar", + }, ]) { test(`${deviceKey.name} algorithm`, () => { const dk = new DeviceKeyId(deviceKey.id); @@ -78,8 +88,8 @@ describe(DeviceKeyId.name, () => { } }); -describe('DeviceKeyAlgorithmName', () => { - test('has the correct variants', () => { +describe("DeviceKeyAlgorithmName", () => { + test("has the correct variants", () => { expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0); expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1); expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2); @@ -88,94 +98,100 @@ describe('DeviceKeyAlgorithmName', () => { }); describe(RoomId.name, () => { - test('cannot be invalid', () => { - expect(() => { new RoomId('!foo') }).toThrow(); + test("cannot be invalid", () => { + expect(() => { + new RoomId("!foo"); + }).toThrow(); }); - const room = new RoomId('!foo:bar.org'); + const room = new RoomId("!foo:bar.org"); - test('localpart is present', () => { - expect(room.localpart).toStrictEqual('foo'); + test("localpart is present", () => { + expect(room.localpart).toStrictEqual("foo"); }); - test('server name is present', () => { + test("server name is present", () => { expect(room.serverName).toBeInstanceOf(ServerName); }); - test('can read the room ID as string', () => { - expect(room.toString()).toStrictEqual('!foo:bar.org'); + test("can read the room ID as string", () => { + expect(room.toString()).toStrictEqual("!foo:bar.org"); }); }); describe(ServerName.name, () => { - test('cannot be invalid', () => { - expect(() => { new ServerName('@foobar') }).toThrow() + test("cannot be invalid", () => { + expect(() => { + new ServerName("@foobar"); + }).toThrow(); }); - test('host is present', () => { - expect(new ServerName('foo.org').host).toStrictEqual('foo.org'); + test("host is present", () => { + expect(new ServerName("foo.org").host).toStrictEqual("foo.org"); }); - test('port can be optional', () => { - expect(new ServerName('foo.org').port).toStrictEqual(undefined); - expect(new ServerName('foo.org:1234').port).toStrictEqual(1234); + test("port can be optional", () => { + expect(new ServerName("foo.org").port).toStrictEqual(undefined); + expect(new ServerName("foo.org:1234").port).toStrictEqual(1234); }); - test('server is not an IP literal', () => { - expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false); + test("server is not an IP literal", () => { + expect(new ServerName("foo.org").isIpLiteral()).toStrictEqual(false); }); }); describe(EventId.name, () => { - test('cannot be invalid', () => { - expect(() => { new EventId('%foo') }).toThrow(); + test("cannot be invalid", () => { + expect(() => { + new EventId("%foo"); + }).toThrow(); }); - describe('Versions 1 & 2', () => { - const room = new EventId('$h29iv0s8:foo.org'); + describe("Versions 1 & 2", () => { + const room = new EventId("$h29iv0s8:foo.org"); - test('localpart is present', () => { - expect(room.localpart).toStrictEqual('h29iv0s8'); + test("localpart is present", () => { + expect(room.localpart).toStrictEqual("h29iv0s8"); }); - test('server name is present', () => { + test("server name is present", () => { expect(room.serverName).toBeInstanceOf(ServerName); }); - test('can read the room ID as string', () => { - expect(room.toString()).toStrictEqual('$h29iv0s8:foo.org'); + test("can read the room ID as string", () => { + expect(room.toString()).toStrictEqual("$h29iv0s8:foo.org"); }); }); - describe('Version 3', () => { - const room = new EventId('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk'); + describe("Version 3", () => { + const room = new EventId("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk"); - test('localpart is present', () => { - expect(room.localpart).toStrictEqual('acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk'); + test("localpart is present", () => { + expect(room.localpart).toStrictEqual("acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk"); }); - test('server name is present', () => { + test("server name is present", () => { expect(room.serverName).toBeUndefined(); }); - test('can read the room ID as string', () => { - expect(room.toString()).toStrictEqual('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk'); + test("can read the room ID as string", () => { + expect(room.toString()).toStrictEqual("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk"); }); }); - describe('Version 4', () => { - const room = new EventId('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg'); + describe("Version 4", () => { + const room = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg"); - test('localpart is present', () => { - expect(room.localpart).toStrictEqual('Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg'); + test("localpart is present", () => { + expect(room.localpart).toStrictEqual("Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg"); }); - test('server name is present', () => { + test("server name is present", () => { expect(room.serverName).toBeUndefined(); }); - test('can read the room ID as string', () => { - expect(room.toString()).toStrictEqual('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg'); + test("can read the room ID as string", () => { + expect(room.toString()).toStrictEqual("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg"); }); }); -}) +}); diff --git a/bindings/matrix-sdk-crypto-js/tests/machine.test.js b/bindings/matrix-sdk-crypto-js/tests/machine.test.js index 1d11eafd4..22ac71c63 100644 --- a/bindings/matrix-sdk-crypto-js/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/machine.test.js @@ -22,26 +22,28 @@ const { UserIdentity, VerificationRequest, VerificationState, -} = require('../pkg/matrix_sdk_crypto_js'); -const { addMachineToMachine } = require('./helper'); -require('fake-indexeddb/auto'); +} = require("../pkg/matrix_sdk_crypto_js"); +const { addMachineToMachine } = require("./helper"); +require("fake-indexeddb/auto"); describe(OlmMachine.name, () => { - test('can be instantiated with the async initializer', async () => { - expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine); + test("can be instantiated with the async initializer", async () => { + expect(await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"))).toBeInstanceOf(OlmMachine); }); - test('can be instantiated with a store', async () => { - let store_name = 'hello'; - let store_passphrase = 'world'; + test("can be instantiated with a store", async () => { + let store_name = "hello"; + let store_passphrase = "world"; - const by_store_name = db => db.name.startsWith(store_name); + const by_store_name = (db) => db.name.startsWith(store_name); // No databases. expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0); // Creating a new Olm machine. - expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine); + expect( + await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase), + ).toBeInstanceOf(OlmMachine); // Oh, there is 2 databases now, prefixed by `store_name`. let databases = (await indexedDB.databases()).filter(by_store_name); @@ -53,21 +55,28 @@ describe(OlmMachine.name, () => { ]); // Creating a new Olm machine, with the stored state. - expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine); + expect( + await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase), + ).toBeInstanceOf(OlmMachine); // Same number of databases. expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(2); }); - describe('cannot be instantiated with a store', () => { - test('store name is missing', async () => { + describe("cannot be instantiated with a store", () => { + test("store name is missing", async () => { let store_name = null; - let store_passphrase = 'world'; + let store_passphrase = "world"; let err = null; try { - await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase); + await OlmMachine.initialize( + new UserId("@foo:bar.org"), + new DeviceId("baz"), + store_name, + store_passphrase, + ); } catch (error) { err = error; } @@ -75,14 +84,19 @@ describe(OlmMachine.name, () => { expect(err).toBeDefined(); }); - test('store passphrase is missing', async () => { - let store_name = 'hello'; + test("store passphrase is missing", async () => { + let store_name = "hello"; let store_passphrase = null; let err = null; try { - await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase); + await OlmMachine.initialize( + new UserId("@foo:bar.org"), + new DeviceId("baz"), + store_name, + store_passphrase, + ); } catch (error) { err = error; } @@ -91,30 +105,35 @@ describe(OlmMachine.name, () => { }); }); - const user = new UserId('@alice:example.org'); - const device = new DeviceId('foobar'); - const room = new RoomId('!baz:matrix.org'); + const user = new UserId("@alice:example.org"); + const device = new DeviceId("foobar"); + const room = new RoomId("!baz:matrix.org"); function machine(new_user, new_device) { return OlmMachine.initialize(new_user || user, new_device || device); } - test('can drop/close', async () => { + test("can drop/close", async () => { m = await machine(); m.close(); }); - test('can drop/close with a store', async () => { - let store_name = 'temporary'; - let store_passphrase = 'temporary'; + test("can drop/close with a store", async () => { + let store_name = "temporary"; + let store_passphrase = "temporary"; - const by_store_name = db => db.name.startsWith(store_name); + const by_store_name = (db) => db.name.startsWith(store_name); // No databases. expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0); // Creating a new Olm machine. - const m = await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase); + const m = await OlmMachine.initialize( + new UserId("@foo:bar.org"), + new DeviceId("baz"), + store_name, + store_passphrase, + ); expect(m).toBeInstanceOf(OlmMachine); // Oh, there is 2 databases now, prefixed by `store_name`. @@ -133,31 +152,35 @@ describe(OlmMachine.name, () => { for (const database_name of [`${store_name}::matrix-sdk-crypto`, `${store_name}::matrix-sdk-crypto-meta`]) { const deleting = indexedDB.deleteDatabase(database_name); deleting.onsuccess = () => {}; - deleting.onerror = () => { throw new Error('failed to remove the database (error)') }; - deleting.onblocked = () => { throw new Error('failed to remove the database (blocked)') }; + deleting.onerror = () => { + throw new Error("failed to remove the database (error)"); + }; + deleting.onblocked = () => { + throw new Error("failed to remove the database (blocked)"); + }; } }); - test('can read user ID', async () => { + test("can read user ID", async () => { expect((await machine()).userId.toString()).toStrictEqual(user.toString()); }); - test('can read device ID', async () => { + test("can read device ID", async () => { expect((await machine()).deviceId.toString()).toStrictEqual(device.toString()); }); - test('can read identity keys', async () => { + test("can read identity keys", async () => { const identityKeys = (await machine()).identityKeys; expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/); expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/); }); - test('can read display name', async () => { + test("can read display name", async () => { expect(await machine().displayName).toBeUndefined(); }); - test('can read tracked users', async () => { + test("can read tracked users", async () => { const m = await machine(); const trackedUsers = await m.trackedUsers(); @@ -165,32 +188,36 @@ describe(OlmMachine.name, () => { expect(trackedUsers.size).toStrictEqual(0); }); - test('can update tracked users', async () => { + test("can update tracked users", async () => { const m = await machine(); expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined); }); - test('can receive sync changes', async () => { + test("can receive sync changes", async () => { const m = await machine(); const toDeviceEvents = JSON.stringify([]); const changedDevices = new DeviceLists(); const oneTimeKeyCounts = new Map(); const unusedFallbackKeys = new Set(); - const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys)); + const receiveSyncChanges = JSON.parse( + await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys), + ); expect(receiveSyncChanges).toEqual([]); }); - test('can get the outgoing requests that need to be send out', async () => { + test("can get the outgoing requests that need to be send out", async () => { const m = await machine(); const toDeviceEvents = JSON.stringify([]); const changedDevices = new DeviceLists(); const oneTimeKeyCounts = new Map(); const unusedFallbackKeys = new Set(); - const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys)); + const receiveSyncChanges = JSON.parse( + await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys), + ); expect(receiveSyncChanges).toEqual([]); @@ -222,35 +249,40 @@ describe(OlmMachine.name, () => { } }); - describe('setup workflow to mark requests as sent', () => { + describe("setup workflow to mark requests as sent", () => { let m; let ougoingRequests; beforeAll(async () => { - m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID')); + m = await machine(new UserId("@alice:example.org"), new DeviceId("DEVICEID")); const toDeviceEvents = JSON.stringify([]); const changedDevices = new DeviceLists(); const oneTimeKeyCounts = new Map(); const unusedFallbackKeys = new Set(); - const receiveSyncChanges = await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys); + const receiveSyncChanges = await m.receiveSyncChanges( + toDeviceEvents, + changedDevices, + oneTimeKeyCounts, + unusedFallbackKeys, + ); outgoingRequests = await m.outgoingRequests(); expect(outgoingRequests).toHaveLength(2); }); - test('can mark requests as sent', async () => { + test("can mark requests as sent", async () => { { const request = outgoingRequests[0]; expect(request).toBeInstanceOf(KeysUploadRequest); // https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload const hypothetical_response = JSON.stringify({ - "one_time_key_counts": { - "curve25519": 10, - "signed_curve25519": 20 - } + one_time_key_counts: { + curve25519: 10, + signed_curve25519: 20, + }, }); const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response); expect(marked).toStrictEqual(true); @@ -262,31 +294,29 @@ describe(OlmMachine.name, () => { // https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery const hypothetical_response = JSON.stringify({ - "device_keys": { + device_keys: { "@alice:example.org": { - "JLAFKJWSCS": { - "algorithms": [ - "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" - ], - "device_id": "JLAFKJWSCS", - "keys": { + JLAFKJWSCS: { + algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + device_id: "JLAFKJWSCS", + keys: { "curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4", - "ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM" + "ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM", }, - "signatures": { + signatures: { "@alice:example.org": { - "ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA" - } + "ed25519:JLAFKJWSCS": + "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA", + }, }, - "unsigned": { - "device_display_name": "Alice's mobile phone" + unsigned: { + device_display_name: "Alice's mobile phone", }, - "user_id": "@alice:example.org" - } - } + user_id: "@alice:example.org", + }, + }, }, - "failures": {} + failures: {}, }); const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response); expect(marked).toStrictEqual(true); @@ -294,122 +324,121 @@ describe(OlmMachine.name, () => { }); }); - describe('setup workflow to encrypt/decrypt events', () => { + describe("setup workflow to encrypt/decrypt events", () => { let m; - const user = new UserId('@alice:example.org'); - const device = new DeviceId('JLAFKJWSCS'); - const room = new RoomId('!test:localhost'); + const user = new UserId("@alice:example.org"); + const device = new DeviceId("JLAFKJWSCS"); + const room = new RoomId("!test:localhost"); beforeAll(async () => { m = await machine(user, device); }); - test('can pass keysquery and keysclaim requests directly', async () => { + test("can pass keysquery and keysclaim requests directly", async () => { { // derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json const hypothetical_response = JSON.stringify({ - "device_keys": { + device_keys: { "@example:localhost": { - "AFGUOBTZWM": { - "algorithms": [ - "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" - ], - "device_id": "AFGUOBTZWM", - "keys": { + AFGUOBTZWM: { + algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + device_id: "AFGUOBTZWM", + keys: { "curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo", - "ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec" + "ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec", }, - "signatures": { + signatures: { "@example:localhost": { - "ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA" - } + "ed25519:AFGUOBTZWM": + "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA", + }, + }, + user_id: "@example:localhost", + unsigned: { + device_display_name: "rust-sdk", }, - "user_id": "@example:localhost", - "unsigned": { - "device_display_name": "rust-sdk" - } }, - } + }, }, - "failures": {}, - "master_keys": { + failures: {}, + master_keys: { "@example:localhost": { - "user_id": "@example:localhost", - "usage": [ - "master" - ], - "keys": { - "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU" + user_id: "@example:localhost", + usage: ["master"], + keys: { + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": + "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU", }, - "signatures": { + signatures: { "@example:localhost": { - "ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg" - } - } - } + "ed25519:TCSJXPWGVS": + "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg", + }, + }, + }, }, - "self_signing_keys": { + self_signing_keys: { "@example:localhost": { - "user_id": "@example:localhost", - "usage": [ - "self_signing" - ], - "keys": { - "ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI" + user_id: "@example:localhost", + usage: ["self_signing"], + keys: { + "ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": + "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI", }, - "signatures": { + signatures: { "@example:localhost": { - "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA" - } - } - } + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": + "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA", + }, + }, + }, }, - "user_signing_keys": { + user_signing_keys: { "@example:localhost": { - "user_id": "@example:localhost", - "usage": [ - "user_signing" - ], - "keys": { - "ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s" + user_id: "@example:localhost", + usage: ["user_signing"], + keys: { + "ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": + "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s", }, - "signatures": { + signatures: { "@example:localhost": { - "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg" - } - } - } - } + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": + "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg", + }, + }, + }, + }, }); - const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response); + const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response); } { // derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json const hypothetical_response = JSON.stringify({ - "one_time_keys": { + one_time_keys: { "@example:localhost": { - "AFGUOBTZWM": { + AFGUOBTZWM: { "signed_curve25519:AAAABQ": { - "key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws", - "signatures": { + key: "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws", + signatures: { "@example:localhost": { - "ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg" - } - } - } + "ed25519:AFGUOBTZWM": + "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg", + }, + }, + }, }, - } + }, }, - "failures": {} + failures: {}, }); - const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response); + const marked = await m.markRequestAsSent("bar", RequestType.KeysClaim, hypothetical_response); } }); - test('can share a room key', async () => { - const other_users = [new UserId('@example:localhost')]; + test("can share a room key", async () => { + const other_users = [new UserId("@example:localhost")]; const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings())); @@ -417,20 +446,22 @@ describe(OlmMachine.name, () => { expect(requests[0].event_type).toBeDefined(); expect(requests[0].txn_id).toBeDefined(); expect(requests[0].messages).toBeDefined(); - expect(requests[0].messages['@example:localhost']).toBeDefined(); + expect(requests[0].messages["@example:localhost"]).toBeDefined(); }); let encrypted; - test('can encrypt an event', async () => { - encrypted = JSON.parse(await m.encryptRoomEvent( - room, - 'm.room.message', - JSON.stringify({ - "msgtype": "m.text", - "body": "Hello, World!" - }), - )); + test("can encrypt an event", async () => { + encrypted = JSON.parse( + await m.encryptRoomEvent( + room, + "m.room.message", + JSON.stringify({ + msgtype: "m.text", + body: "Hello, World!", + }), + ), + ); expect(encrypted.algorithm).toBeDefined(); expect(encrypted.ciphertext).toBeDefined(); @@ -439,17 +470,17 @@ describe(OlmMachine.name, () => { expect(encrypted.session_id).toBeDefined(); }); - test('can decrypt an event', async () => { + test("can decrypt an event", async () => { const decrypted = await m.decryptRoomEvent( JSON.stringify({ - "type": "m.room.encrypted", - "event_id": "$xxxxx:example.org", - "origin_server_ts": Date.now(), - "sender": user.toString(), + type: "m.room.encrypted", + event_id: "$xxxxx:example.org", + origin_server_ts: Date.now(), + sender: user.toString(), content: encrypted, unsigned: { - "age": 1234 - } + age: 1234, + }, }), room, ); @@ -484,7 +515,7 @@ describe(OlmMachine.name, () => { await expect(() => m.decryptRoomEvent(JSON.stringify(evt), room)).rejects.toThrowError(); }); - test('can read cross-signing status', async () => { + test("can read cross-signing status", async () => { const m = await machine(); const crossSigningStatus = await m.crossSigningStatus(); @@ -494,9 +525,9 @@ describe(OlmMachine.name, () => { expect(crossSigningStatus.hasUserSigning).toStrictEqual(false); }); - test('can sign a message', async () => { + test("can sign a message", async () => { const m = await machine(); - const signatures = await m.sign('foo'); + const signatures = await m.sign("foo"); expect(signatures.isEmpty()).toStrictEqual(false); expect(signatures.count).toStrictEqual(1); @@ -507,9 +538,9 @@ describe(OlmMachine.name, () => { { const signature = signatures.get(user); - expect(signature.has('ed25519:foobar')).toStrictEqual(true); + expect(signature.has("ed25519:foobar")).toStrictEqual(true); - const s = signature.get('ed25519:foobar'); + const s = signature.get("ed25519:foobar"); expect(s).toBeInstanceOf(MaybeSignature); @@ -525,18 +556,18 @@ describe(OlmMachine.name, () => { // `getSignature` { - const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar')); + const signature = signatures.getSignature(user, new DeviceKeyId("ed25519:foobar")); expect(signature.toBase64()).toStrictEqual(base64); } // Unknown signatures. { - expect(signatures.get(new UserId('@hello:example.org'))).toBeUndefined(); - expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeUndefined(); + expect(signatures.get(new UserId("@hello:example.org"))).toBeUndefined(); + expect(signatures.getSignature(user, new DeviceKeyId("world:foobar"))).toBeUndefined(); } }); - test('can get a user identities', async () => { + test("can get a user identities", async () => { const m = await machine(); let _ = m.bootstrapCrossSigning(true); @@ -556,15 +587,15 @@ describe(OlmMachine.name, () => { expect(isTrusted).toStrictEqual(false); }); - describe('can export/import room keys', () => { + describe("can export/import room keys", () => { let m; let exportedRoomKeys; - test('can export room keys', async () => { + test("can export room keys", async () => { m = await machine(); - await m.shareRoomKey(room, [new UserId('@bob:example.org')], new EncryptionSettings()); + await m.shareRoomKey(room, [new UserId("@bob:example.org")], new EncryptionSettings()); - exportedRoomKeys = await m.exportRoomKeys(session => { + exportedRoomKeys = await m.exportRoomKeys((session) => { expect(session).toBeInstanceOf(InboundGroupSession); expect(session.roomId.toString()).toStrictEqual(room.toString()); expect(session.sessionId).toBeDefined(); @@ -589,9 +620,9 @@ describe(OlmMachine.name, () => { }); let encryptedExportedRoomKeys; - let encryptionPassphrase = 'Hello, Matrix!'; + let encryptionPassphrase = "Hello, Matrix!"; - test('can encrypt the exported room keys', () => { + test("can encrypt the exported room keys", () => { encryptedExportedRoomKeys = OlmMachine.encryptExportedRoomKeys( exportedRoomKeys, encryptionPassphrase, @@ -601,7 +632,7 @@ describe(OlmMachine.name, () => { expect(encryptedExportedRoomKeys).toMatch(/^-----BEGIN MEGOLM SESSION DATA-----/); }); - test('can decrypt the exported room keys', () => { + test("can decrypt the exported room keys", () => { const decryptedExportedRoomKeys = OlmMachine.decryptExportedRoomKeys( encryptedExportedRoomKeys, encryptionPassphrase, @@ -610,7 +641,7 @@ describe(OlmMachine.name, () => { expect(decryptedExportedRoomKeys).toStrictEqual(exportedRoomKeys); }); - test('can import room keys', async () => { + test("can import room keys", async () => { const progressListener = (progress, total) => { expect(progress).toBeLessThan(total); @@ -629,169 +660,160 @@ describe(OlmMachine.name, () => { }); }); - describe('can do in-room verification', () => { + describe("can do in-room verification", () => { let m; - const user = new UserId('@alice:example.org'); - const device = new DeviceId('JLAFKJWSCS'); - const room = new RoomId('!test:localhost'); + const user = new UserId("@alice:example.org"); + const device = new DeviceId("JLAFKJWSCS"); + const room = new RoomId("!test:localhost"); beforeAll(async () => { m = await machine(user, device); }); - test('can inject devices from someone else', async () => { + test("can inject devices from someone else", async () => { { const hypothetical_response = JSON.stringify({ - "device_keys": { + device_keys: { "@example:morpheus.localhost": { - "ATRLDCRXAC": { - "algorithms": [ - "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" - ], - "device_id": "ATRLDCRXAC", - "keys": { + ATRLDCRXAC: { + algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + device_id: "ATRLDCRXAC", + keys: { "curve25519:ATRLDCRXAC": "cAVT5Es3Z3F5pFD+2w3HT7O9+R3PstzYVkzD51X/FWQ", - "ed25519:ATRLDCRXAC": "V2w/T/x7i7AXiCCtS6JldrpbvRliRoef3CqTUNqMRHA" + "ed25519:ATRLDCRXAC": "V2w/T/x7i7AXiCCtS6JldrpbvRliRoef3CqTUNqMRHA", }, - "signatures": { + signatures: { "@example:morpheus.localhost": { - "ed25519:ATRLDCRXAC": "ro2BjO5J6089B/JOANHnFmGrogrC2TIdMlgJbJO00DjOOcGxXfvOezCFIORTwZNHvkHU617YIGl/4keTDIWvBQ" - } + "ed25519:ATRLDCRXAC": + "ro2BjO5J6089B/JOANHnFmGrogrC2TIdMlgJbJO00DjOOcGxXfvOezCFIORTwZNHvkHU617YIGl/4keTDIWvBQ", + }, + }, + user_id: "@example:morpheus.localhost", + unsigned: { + device_display_name: "Element Desktop: Linux", }, - "user_id": "@example:morpheus.localhost", - "unsigned": { - "device_display_name": "Element Desktop: Linux" - } }, - "EYYGYTCTNC": { - "algorithms": [ - "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" - ], - "device_id": "EYYGYTCTNC", - "keys": { + EYYGYTCTNC: { + algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + device_id: "EYYGYTCTNC", + keys: { "curve25519:EYYGYTCTNC": "Pqu50fo472wgb6NjKkaUxjuqoAIEAmhln2gw/zSQ7Ek", - "ed25519:EYYGYTCTNC": "Pf/2QPvui8lDty6TCTglVPRVM+irNHYavNNkyv5yFpU" + "ed25519:EYYGYTCTNC": "Pf/2QPvui8lDty6TCTglVPRVM+irNHYavNNkyv5yFpU", }, - "signatures": { + signatures: { "@example:morpheus.localhost": { - "ed25519:EYYGYTCTNC": "pnP5BYLEUUaxDgrvdzCznkjNDbvY1/MFBr1JejdnLiXlcmxRULQpIWZUCO7QTbULsCwMsYQNGn50nfmjBQX3CQ" - } + "ed25519:EYYGYTCTNC": + "pnP5BYLEUUaxDgrvdzCznkjNDbvY1/MFBr1JejdnLiXlcmxRULQpIWZUCO7QTbULsCwMsYQNGn50nfmjBQX3CQ", + }, + }, + user_id: "@example:morpheus.localhost", + unsigned: { + device_display_name: "WeeChat-Matrix-rs", }, - "user_id": "@example:morpheus.localhost", - "unsigned": { - "device_display_name": "WeeChat-Matrix-rs" - } }, - "SUMODVLSIU": { - "algorithms": [ - "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" - ], - "device_id": "SUMODVLSIU", - "keys": { + SUMODVLSIU: { + algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + device_id: "SUMODVLSIU", + keys: { "curve25519:SUMODVLSIU": "geQXWGWc++gcUHk0JcFmEVSjyzDOnk2mjVsUQwbNqQU", - "ed25519:SUMODVLSIU": "ccktaQ3g+B18E6FwVhTBYie26OlHbvDUzDEtxOQ4Qcs" + "ed25519:SUMODVLSIU": "ccktaQ3g+B18E6FwVhTBYie26OlHbvDUzDEtxOQ4Qcs", }, - "signatures": { + signatures: { "@example:morpheus.localhost": { - "ed25519:SUMODVLSIU": "Yn+AOxHRt1GQpY2xT2Jcqqn8jh5+Vw23ctA7NXyDiWPsLPLNTpjGWHMjZdpUqflQvpiKfhODPICoIa7Pu0iSAg", - "ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM": "Cio6k/sq289XNTOvTCWre7Q6zg+A3euzMUe7Uy1T3gPqYFzX+kt7EAxrhbPqx1HyXAEz9zD0D/uw9VEXFCvWBQ" - } + "ed25519:SUMODVLSIU": + "Yn+AOxHRt1GQpY2xT2Jcqqn8jh5+Vw23ctA7NXyDiWPsLPLNTpjGWHMjZdpUqflQvpiKfhODPICoIa7Pu0iSAg", + "ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM": + "Cio6k/sq289XNTOvTCWre7Q6zg+A3euzMUe7Uy1T3gPqYFzX+kt7EAxrhbPqx1HyXAEz9zD0D/uw9VEXFCvWBQ", + }, + }, + user_id: "@example:morpheus.localhost", + unsigned: { + device_display_name: "Element Desktop (Linux)", }, - "user_id": "@example:morpheus.localhost", - "unsigned": { - "device_display_name": "Element Desktop (Linux)" - } - } - } - }, - "failures": {}, - "master_keys": { - "@example:morpheus.localhost": { - "user_id": "@example:morpheus.localhost", - "usage": [ - "master" - ], - "keys": { - "ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": "ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A" }, - "signatures": { - "@example:morpheus.localhost": { - "ed25519:SUMODVLSIU": "RL6WOuuzB/mZ+edfUFG/KeEcmKh+NaWpM6m2bUYmDnJrtTCYyoU+pgHJuL2/6nynemmONo18JEHBuqtNcMq2AQ" - } - } - } + }, }, - "self_signing_keys": { + failures: {}, + master_keys: { "@example:morpheus.localhost": { - "user_id": "@example:morpheus.localhost", - "usage": [ - "self_signing" - ], - "keys": { - "ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM": "rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM" + user_id: "@example:morpheus.localhost", + usage: ["master"], + keys: { + "ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": + "ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A", }, - "signatures": { + signatures: { "@example:morpheus.localhost": { - "ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": "uCBn9rpeg6umY8H97ejN26UMp6QDwNL98869t1DoVGL50J8adLN05OZd8lYk9QzwTr2d56ZTGYSYX8kv28SDDA" - } - } - } + "ed25519:SUMODVLSIU": + "RL6WOuuzB/mZ+edfUFG/KeEcmKh+NaWpM6m2bUYmDnJrtTCYyoU+pgHJuL2/6nynemmONo18JEHBuqtNcMq2AQ", + }, + }, + }, }, - "user_signing_keys": { + self_signing_keys: { "@example:morpheus.localhost": { - "user_id": "@example:morpheus.localhost", - "usage": [ - "user_signing" - ], - "keys": { - "ed25519:GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw": "GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw" + user_id: "@example:morpheus.localhost", + usage: ["self_signing"], + keys: { + "ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM": + "rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM", }, - "signatures": { + signatures: { "@example:morpheus.localhost": { - "ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": "4fIyWlVzuz1pgoegNLZASycORXqKycVS0dNq5vmmwsVEudp1yrPhndnaIJ3fjF8LDHvwzXTvohOid7DiU1j0AA" - } - } - } - } + "ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": + "uCBn9rpeg6umY8H97ejN26UMp6QDwNL98869t1DoVGL50J8adLN05OZd8lYk9QzwTr2d56ZTGYSYX8kv28SDDA", + }, + }, + }, + }, + user_signing_keys: { + "@example:morpheus.localhost": { + user_id: "@example:morpheus.localhost", + usage: ["user_signing"], + keys: { + "ed25519:GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw": + "GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw", + }, + signatures: { + "@example:morpheus.localhost": { + "ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": + "4fIyWlVzuz1pgoegNLZASycORXqKycVS0dNq5vmmwsVEudp1yrPhndnaIJ3fjF8LDHvwzXTvohOid7DiU1j0AA", + }, + }, + }, + }, }); - const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response); + const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response); } }); - test('can start an in-room SAS verification', async () => { + test("can start an in-room SAS verification", async () => { let _ = m.bootstrapCrossSigning(true); - const identity = await m.getIdentity(new UserId('@example:morpheus.localhost')); + const identity = await m.getIdentity(new UserId("@example:morpheus.localhost")); expect(identity).toBeInstanceOf(UserIdentity); expect(identity.isVerified()).toStrictEqual(false); - const eventId = new EventId('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg'); + const eventId = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg"); const verificationRequest = await identity.requestVerification(room, eventId); expect(verificationRequest).toBeInstanceOf(VerificationRequest); await m.receiveVerificationEvent( JSON.stringify({ - "sender": "@example:morpheus.localhost", - "type": "m.key.verification.ready", - "event_id": "$QguWmaeMt6Hao7Ea6XHDInvr8ndknev79t9a2eBxlz0", - "origin_server_ts": 1674037263075, - "content": { - "methods": [ - "m.sas.v1", - "m.qr_code.show.v1", - "m.reciprocate.v1" - ], + sender: "@example:morpheus.localhost", + type: "m.key.verification.ready", + event_id: "$QguWmaeMt6Hao7Ea6XHDInvr8ndknev79t9a2eBxlz0", + origin_server_ts: 1674037263075, + content: { + "methods": ["m.sas.v1", "m.qr_code.show.v1", "m.reciprocate.v1"], "from_device": "SUMODVLSIU", "m.relates_to": { - "rel_type": "m.reference", - "event_id": eventId.toString(), - } - } + rel_type: "m.reference", + event_id: eventId.toString(), + }, + }, }), - room + room, ); expect(verificationRequest.roomId.toString()).toStrictEqual(room.toString()); @@ -802,22 +824,22 @@ describe(OlmMachine.name, () => { expect(outgoingVerificationRequest.id).toBeDefined(); expect(outgoingVerificationRequest.room_id).toStrictEqual(room.toString()); expect(outgoingVerificationRequest.txn_id).toBeDefined(); - expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start'); + expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start"); expect(outgoingVerificationRequest.body).toBeDefined(); const body = JSON.parse(outgoingVerificationRequest.body); expect(body).toMatchObject({ - from_device: expect.any(String), - method: 'm.sas.v1', - key_agreement_protocols: [expect.any(String)], - hashes: [expect.any(String)], - message_authentication_codes: [expect.any(String), expect.any(String)], - short_authentication_string: ['decimal', 'emoji'], - 'm.relates_to': { - rel_type: 'm.reference', + "from_device": expect.any(String), + "method": "m.sas.v1", + "key_agreement_protocols": [expect.any(String)], + "hashes": [expect.any(String)], + "message_authentication_codes": [expect.any(String), expect.any(String)], + "short_authentication_string": ["decimal", "emoji"], + "m.relates_to": { + rel_type: "m.reference", event_id: eventId.toString(), - } + }, }); - }) + }); }); }); diff --git a/bindings/matrix-sdk-crypto-js/tests/requests.test.js b/bindings/matrix-sdk-crypto-js/tests/requests.test.js index a69b7b392..bb216550e 100644 --- a/bindings/matrix-sdk-crypto-js/tests/requests.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/requests.test.js @@ -1,7 +1,16 @@ -const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto_js'); +const { + RequestType, + KeysUploadRequest, + KeysQueryRequest, + KeysClaimRequest, + ToDeviceRequest, + SignatureUploadRequest, + RoomMessageRequest, + KeysBackupRequest, +} = require("../pkg/matrix_sdk_crypto_js"); -describe('RequestType', () => { - test('has the correct variant values', () => { +describe("RequestType", () => { + test("has the correct variant values", () => { expect(RequestType.KeysUpload).toStrictEqual(0); expect(RequestType.KeysQuery).toStrictEqual(1); expect(RequestType.KeysClaim).toStrictEqual(2); diff --git a/bindings/matrix-sdk-crypto-js/tests/sync_events.test.js b/bindings/matrix-sdk-crypto-js/tests/sync_events.test.js index 305d22556..f3acd127c 100644 --- a/bindings/matrix-sdk-crypto-js/tests/sync_events.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/sync_events.test.js @@ -1,7 +1,7 @@ -const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto_js'); +const { DeviceLists, UserId } = require("../pkg/matrix_sdk_crypto_js"); describe(DeviceLists.name, () => { - test('can be empty', () => { + test("can be empty", () => { const empty = new DeviceLists(); expect(empty.isEmpty()).toStrictEqual(true); @@ -9,7 +9,7 @@ describe(DeviceLists.name, () => { expect(empty.left).toHaveLength(0); }); - test('can be coerced empty', () => { + test("can be coerced empty", () => { const empty = new DeviceLists([], []); expect(empty.isEmpty()).toStrictEqual(true); @@ -17,15 +17,15 @@ describe(DeviceLists.name, () => { expect(empty.left).toHaveLength(0); }); - test('returns the correct `changed` and `left`', () => { - const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]); + test("returns the correct `changed` and `left`", () => { + const list = new DeviceLists([new UserId("@foo:bar.org")], [new UserId("@baz:qux.org")]); expect(list.isEmpty()).toStrictEqual(false); expect(list.changed).toHaveLength(1); - expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org'); + expect(list.changed[0].toString()).toStrictEqual("@foo:bar.org"); expect(list.left).toHaveLength(1); - expect(list.left[0].toString()).toStrictEqual('@baz:qux.org'); + expect(list.left[0].toString()).toStrictEqual("@baz:qux.org"); }); }); diff --git a/bindings/matrix-sdk-crypto-js/tests/tracing.test.js b/bindings/matrix-sdk-crypto-js/tests/tracing.test.js index a37383f24..9c36dfa40 100644 --- a/bindings/matrix-sdk-crypto-js/tests/tracing.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/tracing.test.js @@ -1,7 +1,7 @@ -const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require('../pkg/matrix_sdk_crypto_js'); +const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require("../pkg/matrix_sdk_crypto_js"); -describe('LoggerLevel', () => { - test('has the correct variant values', () => { +describe("LoggerLevel", () => { + test("has the correct variant values", () => { expect(LoggerLevel.Trace).toStrictEqual(0); expect(LoggerLevel.Debug).toStrictEqual(1); expect(LoggerLevel.Info).toStrictEqual(2); @@ -14,7 +14,7 @@ describe(Tracing.name, () => { if (Tracing.isAvailable()) { let tracing = new Tracing(LoggerLevel.Debug); - test('can installed several times', () => { + test("can installed several times", () => { new Tracing(LoggerLevel.Debug); new Tracing(LoggerLevel.Warn); new Tracing(LoggerLevel.Debug); @@ -23,27 +23,30 @@ describe(Tracing.name, () => { const originalConsoleDebug = console.debug; for (const [testName, testPreState, testPostState, expectedGotcha] of [ + ["can log something", () => {}, () => {}, true], [ - 'can log something', - () => {}, - () => {}, - true, - ], - [ - 'can change the logger level', - () => { tracing.minLevel = LoggerLevel.Warn }, - () => { tracing.minLevel = LoggerLevel.Debug }, + "can change the logger level", + () => { + tracing.minLevel = LoggerLevel.Warn; + }, + () => { + tracing.minLevel = LoggerLevel.Debug; + }, false, ], [ - 'can be turned off', - () => { tracing.turnOff() }, + "can be turned off", + () => { + tracing.turnOff(); + }, () => {}, false, ], [ - 'can be turned on', - () => { tracing.turnOn() }, + "can be turned on", + () => { + tracing.turnOn(); + }, () => {}, true, ], @@ -51,8 +54,10 @@ describe(Tracing.name, () => { // This one *must* be the last. We are turning tracing off // again for the other tests. [ - 'can be turned off', - () => { tracing.turnOff() }, + "can be turned off", + () => { + tracing.turnOff(); + }, () => {}, false, ], @@ -68,7 +73,7 @@ describe(Tracing.name, () => { }; // Do something that emits a `DEBUG` log. - await OlmMachine.initialize(new UserId('@alice:example.org'), new DeviceId('foo')); + await OlmMachine.initialize(new UserId("@alice:example.org"), new DeviceId("foo")); console.debug = originalConsoleDebug; testPostState(); @@ -77,8 +82,10 @@ describe(Tracing.name, () => { }); } } else { - test('cannot be constructed', () => { - expect(() => { new Tracing(LoggerLevel.Error) }).toThrow(); + test("cannot be constructed", () => { + expect(() => { + new Tracing(LoggerLevel.Error); + }).toThrow(); }); } }); diff --git a/bindings/matrix-sdk-crypto-js/tsconfig.json b/bindings/matrix-sdk-crypto-js/tsconfig.json index cca9bfa19..2bcd35324 100644 --- a/bindings/matrix-sdk-crypto-js/tsconfig.json +++ b/bindings/matrix-sdk-crypto-js/tsconfig.json @@ -5,6 +5,6 @@ "typedocOptions": { "entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"], "out": "docs", - "readme": "README.md", + "readme": "README.md" } } diff --git a/bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md b/bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md index 2d83a8260..ad372b52f 100644 --- a/bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md +++ b/bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.1.0-beta.1 - 2022-07-14 -- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842) +- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842) ## 0.1.0-beta.0 - 2022-07-12 diff --git a/bindings/matrix-sdk-crypto-nodejs/README.md b/bindings/matrix-sdk-crypto-nodejs/README.md index 51bed6f2e..7e7b80ff1 100644 --- a/bindings/matrix-sdk-crypto-nodejs/README.md +++ b/bindings/matrix-sdk-crypto-nodejs/README.md @@ -12,6 +12,7 @@ Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for ## Usage Just add the latest release to your `package.json`: + ```sh $ npm install --save @matrix-org/matrix-sdk-crypto-nodejs ``` @@ -112,28 +113,27 @@ generated. At the same level of those files, you can edit a file and try this: ```javascript -const { OlmMachine } = require('./index.js'); +const { OlmMachine } = require("./index.js"); // Let's see what we can do. ``` The `OlmMachine` state machine works in a push/pull manner: -* You push state changes and events retrieved from a Matrix homeserver - `/sync` response, into the state machine, - -* You pull requests that you will need to send back to the homeserver - out of the state machine. - +- You push state changes and events retrieved from a Matrix homeserver + `/sync` response, into the state machine, +- You pull requests that you will need to send back to the homeserver + out of the state machine. + ```javascript -const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require('./index.js'); +const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require("./index.js"); async function main() { // Define a user ID. - const alice = new UserId('@alice:example.org'); + const alice = new UserId("@alice:example.org"); // Define a device ID. - const device = new DeviceId('DEVICEID'); + const device = new DeviceId("DEVICEID"); // Let's create the `OlmMachine` state machine. const machine = await OlmMachine.initialize(alice, device); @@ -198,8 +198,6 @@ $ npm run doc The documentation is generated in the `./docs` directory. - - [Node.js]: https://nodejs.org/ [`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto [`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk diff --git a/bindings/matrix-sdk-crypto-nodejs/download-lib.js b/bindings/matrix-sdk-crypto-nodejs/download-lib.js index 6e360db13..c23719e30 100644 --- a/bindings/matrix-sdk-crypto-nodejs/download-lib.js +++ b/bindings/matrix-sdk-crypto-nodejs/download-lib.js @@ -1,19 +1,18 @@ -const { HttpsProxyAgent } = require('https-proxy-agent'); -const { DownloaderHelper } = require('node-downloader-helper'); +const { HttpsProxyAgent } = require("https-proxy-agent"); +const { DownloaderHelper } = require("node-downloader-helper"); const { version } = require("./package.json"); -const { platform, arch } = process +const { platform, arch } = process; const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download"; const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`; const byteHelper = function (value) { if (value === 0) { - return '0 b'; + return "0 b"; } - const units = ['b', 'kB', 'MB', 'GB', 'TB']; + const units = ["b", "kB", "MB", "GB", "TB"]; const number = Math.floor(Math.log(value) / Math.log(1024)); - return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + ' ' + - units[number]; + return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + " " + units[number]; }; function download_lib(libname) { @@ -33,9 +32,9 @@ function download_lib(libname) { }); } - dl.on('end', () => console.info('Download Completed')); - dl.on('error', (err) => console.info('Download Failed', err)); - dl.on('progress', stats => { + dl.on("end", () => console.info("Download Completed")); + dl.on("error", (err) => console.info("Download Failed", err)); + dl.on("progress", (stats) => { const progress = stats.progress.toFixed(1); const speed = byteHelper(stats.speed); const downloaded = byteHelper(stats.downloaded); @@ -49,74 +48,74 @@ function download_lib(libname) { console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`); } }); - dl.start().catch(err => console.error(err)); + dl.start().catch((err) => console.error(err)); } function isMusl() { - // For Node 10 - if (!process.report || typeof process.report.getReport !== 'function') { - try { - return readFileSync('/usr/bin/ldd', 'utf8').includes('musl') - } catch (e) { - return true + // For Node 10 + if (!process.report || typeof process.report.getReport !== "function") { + try { + return readFileSync("/usr/bin/ldd", "utf8").includes("musl"); + } catch (e) { + return true; + } + } else { + const { glibcVersionRuntime } = process.report.getReport().header; + return !glibcVersionRuntime; } - } else { - const { glibcVersionRuntime } = process.report.getReport().header - return !glibcVersionRuntime - } } switch (platform) { - case 'win32': - switch (arch) { - case 'x64': - download_lib('matrix-sdk-crypto.win32-x64-msvc.node') - break - case 'ia32': - download_lib('matrix-sdk-crypto.win32-ia32-msvc.node') - break - case 'arm64': - download_lib('matrix-sdk-crypto.win32-arm64-msvc.node') - break - default: - throw new Error(`Unsupported architecture on Windows: ${arch}`) - } - break - case 'darwin': - switch (arch) { - case 'x64': - download_lib('matrix-sdk-crypto.darwin-x64.node') - break - case 'arm64': - download_lib('matrix-sdk-crypto.darwin-arm64.node') - break - default: - throw new Error(`Unsupported architecture on macOS: ${arch}`) - } - break - case 'linux': - switch (arch) { - case 'x64': - if (isMusl()) { - download_lib('matrix-sdk-crypto.linux-x64-musl.node') - } else { - download_lib('matrix-sdk-crypto.linux-x64-gnu.node') + case "win32": + switch (arch) { + case "x64": + download_lib("matrix-sdk-crypto.win32-x64-msvc.node"); + break; + case "ia32": + download_lib("matrix-sdk-crypto.win32-ia32-msvc.node"); + break; + case "arm64": + download_lib("matrix-sdk-crypto.win32-arm64-msvc.node"); + break; + default: + throw new Error(`Unsupported architecture on Windows: ${arch}`); } - break - case 'arm64': - if (isMusl()) { - throw new Error('Linux for arm64 musl isn\'t support at the moment') - } else { - download_lib('matrix-sdk-crypto.linux-arm64-gnu.node') + break; + case "darwin": + switch (arch) { + case "x64": + download_lib("matrix-sdk-crypto.darwin-x64.node"); + break; + case "arm64": + download_lib("matrix-sdk-crypto.darwin-arm64.node"); + break; + default: + throw new Error(`Unsupported architecture on macOS: ${arch}`); } - break - case 'arm': - download_lib('matrix-sdk-crypto.linux-arm-gnueabihf.node') - break - default: - throw new Error(`Unsupported architecture on Linux: ${arch}`) - } - break - default: - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) + break; + case "linux": + switch (arch) { + case "x64": + if (isMusl()) { + download_lib("matrix-sdk-crypto.linux-x64-musl.node"); + } else { + download_lib("matrix-sdk-crypto.linux-x64-gnu.node"); + } + break; + case "arm64": + if (isMusl()) { + throw new Error("Linux for arm64 musl isn't support at the moment"); + } else { + download_lib("matrix-sdk-crypto.linux-arm64-gnu.node"); + } + break; + case "arm": + download_lib("matrix-sdk-crypto.linux-arm-gnueabihf.node"); + break; + default: + throw new Error(`Unsupported architecture on Linux: ${arch}`); + } + break; + default: + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`); } diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js index 86e3eaf8b..99dc632c1 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js @@ -1,37 +1,41 @@ -const { Attachment, EncryptedAttachment } = require('../'); +const { Attachment, EncryptedAttachment } = require("../"); describe(Attachment.name, () => { - const originalData = 'hello'; + const originalData = "hello"; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); let encryptedAttachment; - - test('can encrypt data', () => { + + test("can encrypt data", () => { encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData)); const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo); expect(mediaEncryptionInfo).toMatchObject({ - v: 'v2', + v: "v2", key: { kty: expect.any(String), - key_ops: expect.arrayContaining(['encrypt', 'decrypt']), + key_ops: expect.arrayContaining(["encrypt", "decrypt"]), alg: expect.any(String), k: expect.any(String), ext: expect.any(Boolean), }, iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/), hashes: { - sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/) - } + sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/), + }, }); const encryptedData = encryptedAttachment.encryptedData; - expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false); + expect( + encryptedData.every((i) => { + i != 0; + }), + ).toStrictEqual(false); }); - test('can decrypt data', () => { + test("can decrypt data", () => { expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false); const decryptedAttachment = Attachment.decrypt(encryptedAttachment); @@ -40,34 +44,36 @@ describe(Attachment.name, () => { expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true); }); - test('can only decrypt once', () => { + test("can only decrypt once", () => { expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true); - expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow() + expect(() => { + textDecoder.decode(decryptedAttachment); + }).toThrow(); }); }); describe(EncryptedAttachment.name, () => { - const originalData = 'hello'; + const originalData = "hello"; const textDecoder = new TextDecoder(); - test('can be created manually', () => { + test("can be created manually", () => { const encryptedAttachment = new EncryptedAttachment( new Uint8Array([24, 150, 67, 37, 144]), JSON.stringify({ - v: 'v2', + v: "v2", key: { - kty: 'oct', - key_ops: [ 'encrypt', 'decrypt' ], - alg: 'A256CTR', - k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM', - ext: true + kty: "oct", + key_ops: ["encrypt", "decrypt"], + alg: "A256CTR", + k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM", + ext: true, }, - iv: 'xk2AcWkomiYAAAAAAAAAAA', + iv: "xk2AcWkomiYAAAAAAAAAAA", hashes: { - sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok' - } - }) + sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok", + }, + }), ); expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/encryption.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/encryption.test.js index 83241d6b1..14a7f15d1 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/encryption.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/encryption.test.js @@ -1,14 +1,14 @@ -const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../'); +const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require("../"); -describe('EncryptionAlgorithm', () => { - test('has the correct variant values', () => { +describe("EncryptionAlgorithm", () => { + test("has the correct variant values", () => { expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0); expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1); }); }); describe(EncryptionSettings.name, () => { - test('can be instantiated with default values', () => { + test("can be instantiated with default values", () => { const es = new EncryptionSettings(); expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2); @@ -17,18 +17,20 @@ describe(EncryptionSettings.name, () => { expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared); }); - test('checks the history visibility values', () => { + test("checks the history visibility values", () => { const es = new EncryptionSettings(); es.historyVisibility = HistoryVisibility.Invited; expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited); - expect(() => { es.historyVisibility = 42 }).toThrow(); + expect(() => { + es.historyVisibility = 42; + }).toThrow(); }); }); -describe('VerificationState', () => { - test('has the correct variant values', () => { +describe("VerificationState", () => { + test("has the correct variant values", () => { expect(VerificationState.Trusted).toStrictEqual(0); expect(VerificationState.Untrusted).toStrictEqual(1); expect(VerificationState.UnknownDevice).toStrictEqual(2); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/events.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/events.test.js index 8318c9244..cec4d57e0 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/events.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/events.test.js @@ -1,7 +1,7 @@ -const { HistoryVisibility } = require('../'); +const { HistoryVisibility } = require("../"); -describe('HistoryVisibility', () => { - test('has the correct variant values', () => { +describe("HistoryVisibility", () => { + test("has the correct variant values", () => { expect(HistoryVisibility.Invited).toStrictEqual(0); expect(HistoryVisibility.Joined).toStrictEqual(1); expect(HistoryVisibility.Shared).toStrictEqual(2); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js index ef8a17943..f08e67d00 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js @@ -1,62 +1,80 @@ -const { UserId, DeviceId, DeviceKeyId, DeviceKeyAlgorithm, DeviceKeyAlgorithmName, RoomId, ServerName } = require('../'); +const { + UserId, + DeviceId, + DeviceKeyId, + DeviceKeyAlgorithm, + DeviceKeyAlgorithmName, + RoomId, + ServerName, +} = require("../"); describe(UserId.name, () => { - test('cannot be invalid', () => { - expect(() => { new UserId('@foobar') }).toThrow(); + test("cannot be invalid", () => { + expect(() => { + new UserId("@foobar"); + }).toThrow(); }); - const user = new UserId('@foo:bar.org'); + const user = new UserId("@foo:bar.org"); - test('localpart is present', () => { - expect(user.localpart).toStrictEqual('foo'); + test("localpart is present", () => { + expect(user.localpart).toStrictEqual("foo"); }); - test('server name is present', () => { + test("server name is present", () => { expect(user.serverName).toBeInstanceOf(ServerName); }); - test('user ID is not historical', () => { + test("user ID is not historical", () => { expect(user.isHistorical()).toStrictEqual(false); }); - test('can read the user ID as a string', () => { - expect(user.toString()).toStrictEqual('@foo:bar.org'); - }) + test("can read the user ID as a string", () => { + expect(user.toString()).toStrictEqual("@foo:bar.org"); + }); }); describe(DeviceId.name, () => { - const device = new DeviceId('foo'); + const device = new DeviceId("foo"); - test('can read the device ID as a string', () => { - expect(device.toString()).toStrictEqual('foo'); - }) + test("can read the device ID as a string", () => { + expect(device.toString()).toStrictEqual("foo"); + }); }); describe(DeviceKeyId.name, () => { for (const deviceKey of [ - { name: 'ed25519', - id: 'ed25519:foobar', - algorithmName: DeviceKeyAlgorithmName.Ed25519, - algorithm: 'ed25519', - deviceId: 'foobar' }, + { + name: "ed25519", + id: "ed25519:foobar", + algorithmName: DeviceKeyAlgorithmName.Ed25519, + algorithm: "ed25519", + deviceId: "foobar", + }, - { name: 'curve25519', - id: 'curve25519:foobar', - algorithmName: DeviceKeyAlgorithmName.Curve25519, - algorithm: 'curve25519', - deviceId: 'foobar' }, + { + name: "curve25519", + id: "curve25519:foobar", + algorithmName: DeviceKeyAlgorithmName.Curve25519, + algorithm: "curve25519", + deviceId: "foobar", + }, - { name: 'signed curve25519', - id: 'signed_curve25519:foobar', - algorithmName: DeviceKeyAlgorithmName.SignedCurve25519, - algorithm: 'signed_curve25519', - deviceId: 'foobar' }, + { + name: "signed curve25519", + id: "signed_curve25519:foobar", + algorithmName: DeviceKeyAlgorithmName.SignedCurve25519, + algorithm: "signed_curve25519", + deviceId: "foobar", + }, - { name: 'unknown', - id: 'hello:foobar', - algorithmName: DeviceKeyAlgorithmName.Unknown, - algorithm: 'hello', - deviceId: 'foobar' }, + { + name: "unknown", + id: "hello:foobar", + algorithmName: DeviceKeyAlgorithmName.Unknown, + algorithm: "hello", + deviceId: "foobar", + }, ]) { test(`${deviceKey.name} algorithm`, () => { const dk = new DeviceKeyId(deviceKey.id); @@ -69,8 +87,8 @@ describe(DeviceKeyId.name, () => { } }); -describe('DeviceKeyAlgorithmName', () => { - test('has the correct variants', () => { +describe("DeviceKeyAlgorithmName", () => { + test("has the correct variants", () => { expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0); expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1); expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2); @@ -79,40 +97,44 @@ describe('DeviceKeyAlgorithmName', () => { }); describe(RoomId.name, () => { - test('cannot be invalid', () => { - expect(() => { new RoomId('!foo') }).toThrow(); + test("cannot be invalid", () => { + expect(() => { + new RoomId("!foo"); + }).toThrow(); }); - const room = new RoomId('!foo:bar.org'); + const room = new RoomId("!foo:bar.org"); - test('localpart is present', () => { - expect(room.localpart).toStrictEqual('foo'); + test("localpart is present", () => { + expect(room.localpart).toStrictEqual("foo"); }); - test('server name is present', () => { + test("server name is present", () => { expect(room.serverName).toBeInstanceOf(ServerName); }); - test('can read the room ID as string', () => { - expect(room.toString()).toStrictEqual('!foo:bar.org'); + test("can read the room ID as string", () => { + expect(room.toString()).toStrictEqual("!foo:bar.org"); }); }); describe(ServerName.name, () => { - test('cannot be invalid', () => { - expect(() => { new ServerName('@foobar') }).toThrow() + test("cannot be invalid", () => { + expect(() => { + new ServerName("@foobar"); + }).toThrow(); }); - test('host is present', () => { - expect(new ServerName('foo.org').host).toStrictEqual('foo.org'); + test("host is present", () => { + expect(new ServerName("foo.org").host).toStrictEqual("foo.org"); }); - test('port can be optional', () => { - expect(new ServerName('foo.org').port).toStrictEqual(null); - expect(new ServerName('foo.org:1234').port).toStrictEqual(1234); + test("port can be optional", () => { + expect(new ServerName("foo.org").port).toStrictEqual(null); + expect(new ServerName("foo.org:1234").port).toStrictEqual(1234); }); - test('server is not an IP literal', () => { - expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false); + test("server is not an IP literal", () => { + expect(new ServerName("foo.org").isIpLiteral()).toStrictEqual(false); }); }); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js index 92dc5f8a4..5e00930ab 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js @@ -1,84 +1,120 @@ -const { OlmMachine, UserId, DeviceId, DeviceKeyId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus, MaybeSignature } = require('../'); -const path = require('path'); -const os = require('os'); -const fs = require('fs/promises'); +const { + OlmMachine, + UserId, + DeviceId, + DeviceKeyId, + RoomId, + DeviceLists, + RequestType, + KeysUploadRequest, + KeysQueryRequest, + KeysClaimRequest, + EncryptionSettings, + DecryptedRoomEvent, + VerificationState, + CrossSigningStatus, + MaybeSignature, +} = require("../"); +const path = require("path"); +const os = require("os"); +const fs = require("fs/promises"); describe(OlmMachine.name, () => { - test('cannot be instantiated with the constructor', () => { - expect(() => { new OlmMachine() }).toThrow(); + test("cannot be instantiated with the constructor", () => { + expect(() => { + new OlmMachine(); + }).toThrow(); }); - test('can be instantiated with the async initializer', async () => { - expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine); + test("can be instantiated with the async initializer", async () => { + expect(await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"))).toBeInstanceOf(OlmMachine); }); - describe('can be instantiated with a store', () => { - test('with no passphrase', async () => { - const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--')); + describe("can be instantiated with a store", () => { + test("with no passphrase", async () => { + const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--")); - expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory)).toBeInstanceOf(OlmMachine); + expect( + await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), temp_directory), + ).toBeInstanceOf(OlmMachine); }); - test('with a passphrase', async () => { - const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--')); + test("with a passphrase", async () => { + const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--")); - expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory, 'hello')).toBeInstanceOf(OlmMachine); + expect( + await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), temp_directory, "hello"), + ).toBeInstanceOf(OlmMachine); }); }); - const user = new UserId('@alice:example.org'); - const device = new DeviceId('foobar'); - const room = new RoomId('!baz:matrix.org'); + const user = new UserId("@alice:example.org"); + const device = new DeviceId("foobar"); + const room = new RoomId("!baz:matrix.org"); function machine(new_user, new_device) { return OlmMachine.initialize(new_user || user, new_device || device); } - test('can drop/close, and then re-open', async () => { - const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--')); + test("can drop/close, and then re-open", async () => { + const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--")); - let m1 = await OlmMachine.initialize(new UserId('@test:bar.org'), new DeviceId('device'), temp_directory, 'hello') + let m1 = await OlmMachine.initialize( + new UserId("@test:bar.org"), + new DeviceId("device"), + temp_directory, + "hello", + ); m1.close(); - let m2 = await OlmMachine.initialize(new UserId('@test:bar.org'), new DeviceId('device'), temp_directory, 'hello') + let m2 = await OlmMachine.initialize( + new UserId("@test:bar.org"), + new DeviceId("device"), + temp_directory, + "hello", + ); m2.close(); }); - test('can read user ID', async () => { + test("can read user ID", async () => { expect((await machine()).userId.toString()).toStrictEqual(user.toString()); }); - test('can read device ID', async () => { + test("can read device ID", async () => { expect((await machine()).deviceId.toString()).toStrictEqual(device.toString()); }); - test('can read identity keys', async () => { + test("can read identity keys", async () => { const identityKeys = (await machine()).identityKeys; expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/); expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/); }); - test('can receive sync changes', async () => { + test("can receive sync changes", async () => { const m = await machine(); const toDeviceEvents = JSON.stringify([]); const changedDevices = new DeviceLists(); const oneTimeKeyCounts = {}; const unusedFallbackKeys = []; - const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys)); + const receiveSyncChanges = JSON.parse( + await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys), + ); expect(receiveSyncChanges).toEqual([]); }); - test('can get the outgoing requests that need to be send out', async () => { + test("can get the outgoing requests that need to be send out", async () => { const m = await machine(); const toDeviceEvents = JSON.stringify([]); const changedDevices = new DeviceLists(); const oneTimeKeyCounts = {}; const unusedFallbackKeys = []; - const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys)); + const receiveSyncChanges = JSON.parse( + await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys), + ); expect(receiveSyncChanges).toEqual([]); @@ -108,12 +144,12 @@ describe(OlmMachine.name, () => { } }); - describe('setup workflow to mark requests as sent', () => { + describe("setup workflow to mark requests as sent", () => { let m; let ougoingRequests; beforeAll(async () => { - m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID')); + m = await machine(new UserId("@alice:example.org"), new DeviceId("DEVICEID")); const toDeviceEvents = JSON.stringify([]); const changedDevices = new DeviceLists(); @@ -126,17 +162,17 @@ describe(OlmMachine.name, () => { expect(outgoingRequests).toHaveLength(2); }); - test('can mark requests as sent', async () => { + test("can mark requests as sent", async () => { { const request = outgoingRequests[0]; expect(request).toBeInstanceOf(KeysUploadRequest); // https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload const hypothetical_response = JSON.stringify({ - "one_time_key_counts": { - "curve25519": 10, - "signed_curve25519": 20 - } + one_time_key_counts: { + curve25519: 10, + signed_curve25519: 20, + }, }); const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response); expect(marked).toStrictEqual(true); @@ -148,31 +184,29 @@ describe(OlmMachine.name, () => { // https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery const hypothetical_response = JSON.stringify({ - "device_keys": { + device_keys: { "@alice:example.org": { - "JLAFKJWSCS": { - "algorithms": [ - "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" - ], - "device_id": "JLAFKJWSCS", - "keys": { + JLAFKJWSCS: { + algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + device_id: "JLAFKJWSCS", + keys: { "curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4", - "ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM" + "ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM", }, - "signatures": { + signatures: { "@alice:example.org": { - "ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA" - } + "ed25519:JLAFKJWSCS": + "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA", + }, }, - "unsigned": { - "device_display_name": "Alice's mobile phone" + unsigned: { + device_display_name: "Alice's mobile phone", }, - "user_id": "@alice:example.org" - } - } + user_id: "@alice:example.org", + }, + }, }, - "failures": {} + failures: {}, }); const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response); expect(marked).toStrictEqual(true); @@ -180,122 +214,121 @@ describe(OlmMachine.name, () => { }); }); - describe('setup workflow to encrypt/decrypt events', () => { + describe("setup workflow to encrypt/decrypt events", () => { let m; - const user = new UserId('@alice:example.org'); - const device = new DeviceId('JLAFKJWSCS'); - const room = new RoomId('!test:localhost'); + const user = new UserId("@alice:example.org"); + const device = new DeviceId("JLAFKJWSCS"); + const room = new RoomId("!test:localhost"); beforeAll(async () => { m = await machine(user, device); }); - test('can pass keysquery and keysclaim requests directly', async () => { + test("can pass keysquery and keysclaim requests directly", async () => { { // derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json const hypothetical_response = JSON.stringify({ - "device_keys": { + device_keys: { "@example:localhost": { - "AFGUOBTZWM": { - "algorithms": [ - "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" - ], - "device_id": "AFGUOBTZWM", - "keys": { + AFGUOBTZWM: { + algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + device_id: "AFGUOBTZWM", + keys: { "curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo", - "ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec" + "ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec", }, - "signatures": { + signatures: { "@example:localhost": { - "ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA" - } + "ed25519:AFGUOBTZWM": + "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA", + }, + }, + user_id: "@example:localhost", + unsigned: { + device_display_name: "rust-sdk", }, - "user_id": "@example:localhost", - "unsigned": { - "device_display_name": "rust-sdk" - } }, - } + }, }, - "failures": {}, - "master_keys": { + failures: {}, + master_keys: { "@example:localhost": { - "user_id": "@example:localhost", - "usage": [ - "master" - ], - "keys": { - "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU" + user_id: "@example:localhost", + usage: ["master"], + keys: { + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": + "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU", }, - "signatures": { + signatures: { "@example:localhost": { - "ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg" - } - } - } + "ed25519:TCSJXPWGVS": + "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg", + }, + }, + }, }, - "self_signing_keys": { + self_signing_keys: { "@example:localhost": { - "user_id": "@example:localhost", - "usage": [ - "self_signing" - ], - "keys": { - "ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI" + user_id: "@example:localhost", + usage: ["self_signing"], + keys: { + "ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": + "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI", }, - "signatures": { + signatures: { "@example:localhost": { - "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA" - } - } - } + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": + "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA", + }, + }, + }, }, - "user_signing_keys": { + user_signing_keys: { "@example:localhost": { - "user_id": "@example:localhost", - "usage": [ - "user_signing" - ], - "keys": { - "ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s" + user_id: "@example:localhost", + usage: ["user_signing"], + keys: { + "ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": + "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s", }, - "signatures": { + signatures: { "@example:localhost": { - "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg" - } - } - } - } + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": + "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg", + }, + }, + }, + }, }); - const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response); + const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response); } { // derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json const hypothetical_response = JSON.stringify({ - "one_time_keys": { + one_time_keys: { "@example:localhost": { - "AFGUOBTZWM": { + AFGUOBTZWM: { "signed_curve25519:AAAABQ": { - "key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws", - "signatures": { + key: "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws", + signatures: { "@example:localhost": { - "ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg" - } - } - } + "ed25519:AFGUOBTZWM": + "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg", + }, + }, + }, }, - } + }, }, - "failures": {} + failures: {}, }); - const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response); + const marked = await m.markRequestAsSent("bar", RequestType.KeysClaim, hypothetical_response); } }); - test('can share a room key', async () => { - const other_users = [new UserId('@example:localhost')]; + test("can share a room key", async () => { + const other_users = [new UserId("@example:localhost")]; const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings())); @@ -303,19 +336,21 @@ describe(OlmMachine.name, () => { expect(requests[0].event_type).toBeDefined(); expect(requests[0].txn_id).toBeDefined(); expect(requests[0].messages).toBeDefined(); - expect(requests[0].messages['@example:localhost']).toBeDefined(); + expect(requests[0].messages["@example:localhost"]).toBeDefined(); }); let encrypted; - test('can encrypt an event', async () => { - encrypted = JSON.parse(await m.encryptRoomEvent( - room, - 'm.room.message', - JSON.stringify({ - "hello": "world" - }), - )); + test("can encrypt an event", async () => { + encrypted = JSON.parse( + await m.encryptRoomEvent( + room, + "m.room.message", + JSON.stringify({ + hello: "world", + }), + ), + ); expect(encrypted.algorithm).toBeDefined(); expect(encrypted.ciphertext).toBeDefined(); @@ -324,17 +359,17 @@ describe(OlmMachine.name, () => { expect(encrypted.session_id).toBeDefined(); }); - test('can decrypt an event', async () => { + test("can decrypt an event", async () => { const decrypted = await m.decryptRoomEvent( JSON.stringify({ - "type": "m.room.encrypted", - "event_id": "$xxxxx:example.org", - "origin_server_ts": Date.now(), - "sender": user.toString(), + type: "m.room.encrypted", + event_id: "$xxxxx:example.org", + origin_server_ts: Date.now(), + sender: user.toString(), content: encrypted, unsigned: { - "age": 1234 - } + age: 1234, + }, }), room, ); @@ -353,13 +388,13 @@ describe(OlmMachine.name, () => { }); }); - test('can update tracked users', async () => { + test("can update tracked users", async () => { const m = await machine(); expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined); }); - test('can read cross-signing status', async () => { + test("can read cross-signing status", async () => { const m = await machine(); const crossSigningStatus = await m.crossSigningStatus(); @@ -369,9 +404,9 @@ describe(OlmMachine.name, () => { expect(crossSigningStatus.hasUserSigning).toStrictEqual(false); }); - test('can sign a message', async () => { + test("can sign a message", async () => { const m = await machine(); - const signatures = await m.sign('foo'); + const signatures = await m.sign("foo"); expect(signatures.isEmpty).toStrictEqual(false); expect(signatures.count).toStrictEqual(1n); @@ -385,26 +420,26 @@ describe(OlmMachine.name, () => { expect(signature).toMatchObject({ "ed25519:foobar": expect.any(MaybeSignature), }); - expect(signature['ed25519:foobar'].isValid).toStrictEqual(true); - expect(signature['ed25519:foobar'].isInvalid).toStrictEqual(false); - expect(signature['ed25519:foobar'].invalidSignatureSource).toBeNull(); + expect(signature["ed25519:foobar"].isValid).toStrictEqual(true); + expect(signature["ed25519:foobar"].isInvalid).toStrictEqual(false); + expect(signature["ed25519:foobar"].invalidSignatureSource).toBeNull(); - base64 = signature['ed25519:foobar'].signature.toBase64(); + base64 = signature["ed25519:foobar"].signature.toBase64(); expect(base64).toMatch(/^[A-Za-z0-9\+/]+$/); - expect(signature['ed25519:foobar'].signature.ed25519.toBase64()).toStrictEqual(base64); + expect(signature["ed25519:foobar"].signature.ed25519.toBase64()).toStrictEqual(base64); } // `getSignature` { - const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar')); + const signature = signatures.getSignature(user, new DeviceKeyId("ed25519:foobar")); expect(signature.toBase64()).toStrictEqual(base64); } // Unknown signatures. { - expect(signatures.get(new UserId('@hello:example.org'))).toBeNull(); - expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeNull(); + expect(signatures.get(new UserId("@hello:example.org"))).toBeNull(); + expect(signatures.getSignature(user, new DeviceKeyId("world:foobar"))).toBeNull(); } }); }); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js index 96cf946b3..51aeb3944 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js @@ -1,7 +1,16 @@ -const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../'); +const { + RequestType, + KeysUploadRequest, + KeysQueryRequest, + KeysClaimRequest, + ToDeviceRequest, + SignatureUploadRequest, + RoomMessageRequest, + KeysBackupRequest, +} = require("../"); -describe('RequestType', () => { - test('has the correct variant values', () => { +describe("RequestType", () => { + test("has the correct variant values", () => { expect(RequestType.KeysUpload).toStrictEqual(0); expect(RequestType.KeysQuery).toStrictEqual(1); expect(RequestType.KeysClaim).toStrictEqual(2); @@ -22,8 +31,10 @@ for (const request of [ KeysBackupRequest, ]) { describe(request.name, () => { - test('cannot be instantiated', () => { - expect(() => { new (request)() }).toThrow(); + test("cannot be instantiated", () => { + expect(() => { + new request(); + }).toThrow(); }); - }) + }); } diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/responses.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/responses.test.js index ddd68c828..924f00d7b 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/responses.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/responses.test.js @@ -1,7 +1,9 @@ -const { DecryptedRoomEvent } = require('../'); +const { DecryptedRoomEvent } = require("../"); describe(DecryptedRoomEvent.name, () => { - test('cannot be instantiated', () => { - expect(() => { new DecryptedRoomEvent() }).toThrow(); + test("cannot be instantiated", () => { + expect(() => { + new DecryptedRoomEvent(); + }).toThrow(); }); }); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/sync_events.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/sync_events.test.js index b6db25e70..c52e180ac 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/sync_events.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/sync_events.test.js @@ -1,7 +1,7 @@ -const { DeviceLists, UserId } = require('../'); +const { DeviceLists, UserId } = require("../"); describe(DeviceLists.name, () => { - test('can be empty', () => { + test("can be empty", () => { const empty = new DeviceLists(); expect(empty.isEmpty()).toStrictEqual(true); @@ -9,7 +9,7 @@ describe(DeviceLists.name, () => { expect(empty.left).toHaveLength(0); }); - test('can be coerced empty', () => { + test("can be coerced empty", () => { const empty = new DeviceLists([], []); expect(empty.isEmpty()).toStrictEqual(true); @@ -17,15 +17,15 @@ describe(DeviceLists.name, () => { expect(empty.left).toHaveLength(0); }); - test('returns the correct `changed` and `left`', () => { - const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]); + test("returns the correct `changed` and `left`", () => { + const list = new DeviceLists([new UserId("@foo:bar.org")], [new UserId("@baz:qux.org")]); expect(list.isEmpty()).toStrictEqual(false); expect(list.changed).toHaveLength(1); - expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org'); + expect(list.changed[0].toString()).toStrictEqual("@foo:bar.org"); expect(list.left).toHaveLength(1); - expect(list.left[0].toString()).toStrictEqual('@baz:qux.org'); + expect(list.left[0].toString()).toStrictEqual("@baz:qux.org"); }); }); diff --git a/bindings/matrix-sdk-crypto-nodejs/tsconfig.json b/bindings/matrix-sdk-crypto-nodejs/tsconfig.json index 584b1ddac..fbbcf4105 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tsconfig.json +++ b/bindings/matrix-sdk-crypto-nodejs/tsconfig.json @@ -5,6 +5,6 @@ "typedocOptions": { "entryPoints": ["index.d.ts"], "out": "docs", - "readme": "README.md", + "readme": "README.md" } } From e16c113db0f6af89f699dcd733c7cdcbdf5e780e Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 15 Feb 2023 12:02:29 +0000 Subject: [PATCH 07/16] crypto-js, crypto-nodejs: Run prettier in CI --- .github/workflows/bindings_ci.yml | 28 +++++++++++++++++++ bindings/matrix-sdk-crypto-js/.prettierignore | 1 + bindings/matrix-sdk-crypto-js/.prettierrc.js | 9 ++++++ bindings/matrix-sdk-crypto-js/package.json | 2 ++ .../matrix-sdk-crypto-nodejs/.prettierignore | 1 + .../matrix-sdk-crypto-nodejs/.prettierrc.js | 9 ++++++ .../matrix-sdk-crypto-nodejs/package.json | 2 ++ 7 files changed, 52 insertions(+) create mode 100644 bindings/matrix-sdk-crypto-js/.prettierignore create mode 100644 bindings/matrix-sdk-crypto-js/.prettierrc.js create mode 100644 bindings/matrix-sdk-crypto-nodejs/.prettierignore create mode 100644 bindings/matrix-sdk-crypto-nodejs/.prettierrc.js diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index 155f82439..c468b6755 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -75,6 +75,34 @@ jobs: - name: Build library & generate bindings run: target/debug/xtask ci bindings + lint-js-bindings: + strategy: + fail-fast: true + matrix: + include: + - name: "[m]-crypto-nodejs" + path: "bindings/matrix-sdk-crypto-nodejs" + - name: "[m]-crypto-js" + path: "bindings/matrix-sdk-crypto-js" + + name: lint ${{ matrix.name }} + runs-on: ubuntu-latest + + steps: + - name: Checkout the repo + uses: actions/checkout@v3 + + - name: Install Node.js + uses: actions/setup-node@v3 + + - name: Install NPM dependencies + working-directory: ${{ matrix.path }} + run: npm install + + - name: run lint + working-directory: ${{ matrix.path }} + run: npm run lint + test-matrix-sdk-crypto-nodejs: name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }} if: github.event_name == 'push' || !github.event.pull_request.draft diff --git a/bindings/matrix-sdk-crypto-js/.prettierignore b/bindings/matrix-sdk-crypto-js/.prettierignore new file mode 100644 index 000000000..fc5bd9083 --- /dev/null +++ b/bindings/matrix-sdk-crypto-js/.prettierignore @@ -0,0 +1 @@ +/pkg diff --git a/bindings/matrix-sdk-crypto-js/.prettierrc.js b/bindings/matrix-sdk-crypto-js/.prettierrc.js new file mode 100644 index 000000000..f739c10be --- /dev/null +++ b/bindings/matrix-sdk-crypto-js/.prettierrc.js @@ -0,0 +1,9 @@ +// prettier configuration: the same as the conventions used throughout Matrix.org +// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js + +module.exports = { + printWidth: 120, + tabWidth: 4, + quoteProps: "consistent", + trailingComma: "all", +}; diff --git a/bindings/matrix-sdk-crypto-js/package.json b/bindings/matrix-sdk-crypto-js/package.json index a31524259..b5724c0bc 100644 --- a/bindings/matrix-sdk-crypto-js/package.json +++ b/bindings/matrix-sdk-crypto-js/package.json @@ -30,6 +30,7 @@ "cross-env": "^7.0.3", "fake-indexeddb": "^4.0", "jest": "^28.1.0", + "prettier": "^2.8.3", "typedoc": "^0.22.17", "wasm-pack": "^0.10.2", "yargs-parser": "~21.0.1" @@ -38,6 +39,7 @@ "node": ">= 10" }, "scripts": { + "lint": "prettier --check .", "build": "./scripts/build.sh", "test": "jest --verbose", "doc": "typedoc --tsconfig .", diff --git a/bindings/matrix-sdk-crypto-nodejs/.prettierignore b/bindings/matrix-sdk-crypto-nodejs/.prettierignore new file mode 100644 index 000000000..fc5bd9083 --- /dev/null +++ b/bindings/matrix-sdk-crypto-nodejs/.prettierignore @@ -0,0 +1 @@ +/pkg diff --git a/bindings/matrix-sdk-crypto-nodejs/.prettierrc.js b/bindings/matrix-sdk-crypto-nodejs/.prettierrc.js new file mode 100644 index 000000000..f739c10be --- /dev/null +++ b/bindings/matrix-sdk-crypto-nodejs/.prettierrc.js @@ -0,0 +1,9 @@ +// prettier configuration: the same as the conventions used throughout Matrix.org +// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js + +module.exports = { + printWidth: 120, + tabWidth: 4, + quoteProps: "consistent", + trailingComma: "all", +}; diff --git a/bindings/matrix-sdk-crypto-nodejs/package.json b/bindings/matrix-sdk-crypto-nodejs/package.json index d858158e2..e7e3ed1fe 100644 --- a/bindings/matrix-sdk-crypto-nodejs/package.json +++ b/bindings/matrix-sdk-crypto-nodejs/package.json @@ -15,6 +15,7 @@ "devDependencies": { "@napi-rs/cli": "^2.9.0", "jest": "^28.1.0", + "prettier": "^2.8.3", "typedoc": "^0.22.17", "yargs-parser": "~21.0.1" }, @@ -22,6 +23,7 @@ "node": ">= 14" }, "scripts": { + "lint": "prettier --check .", "release-build": "napi build --platform --release --strip", "build": "napi build --platform", "postinstall": "node download-lib.js", From d41753ea178c4791aa13a7020927f1feeda532d0 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 15 Feb 2023 12:41:14 +0000 Subject: [PATCH 08/16] crypto-js: log trace messages at debug Javascript's `console.trace` is not a good equivalent for Rust's `Level::TRACE`. In particular: * `console.trace` causes a stacktrace to be written with each message * Under nodejs, `console.trace` writes the message to `stderr`, while `console.debug` writes to stdout `console.trace` is therefore somewhat analogous to `console.error`. Since we already include the log level in the message, we might as well just send trace messages to `console.debug` rather than `console.trace`. --- bindings/matrix-sdk-crypto-js/src/tracing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-crypto-js/src/tracing.rs b/bindings/matrix-sdk-crypto-js/src/tracing.rs index 3d5ca7f9f..16ddfdb7f 100644 --- a/bindings/matrix-sdk-crypto-js/src/tracing.rs +++ b/bindings/matrix-sdk-crypto-js/src/tracing.rs @@ -213,7 +213,7 @@ mod inner { let message = format!("{level} {origin}{recorder}"); match *level { - Level::TRACE => log_trace(message), + Level::TRACE => log_debug(message), Level::DEBUG => log_debug(message), Level::INFO => log_info(message), Level::WARN => log_warn(message), From 188aaecde30ac3a0d27fd8148e61d72709a69785 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Feb 2023 15:10:37 +0100 Subject: [PATCH 09/16] feat(sdk): Keep Timeline event handlers around longer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … if the Timeline object itself is dropped, but something is still subscribed to its changes. --- Cargo.lock | 1 + crates/matrix-sdk/Cargo.toml | 1 + .../matrix-sdk/src/room/timeline/builder.rs | 9 +-- crates/matrix-sdk/src/room/timeline/inner.rs | 4 +- crates/matrix-sdk/src/room/timeline/mod.rs | 61 +++++++++++++++---- 5 files changed, 57 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7ef28b44..4a574fce5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2705,6 +2705,7 @@ dependencies = [ "matrix-sdk-test", "mime", "once_cell", + "pin-project-lite", "rand 0.8.5", "reqwest", "ruma", diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index d8f865adc..7491e5831 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -83,6 +83,7 @@ matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" } matrix-sdk-indexeddb = { version = "0.2.0", path = "../matrix-sdk-indexeddb", default-features = false, optional = true } matrix-sdk-sled = { version = "0.2.0", path = "../matrix-sdk-sled", default-features = false, optional = true } mime = "0.3.16" +pin-project-lite = "0.2.9" rand = { version = "0.8.5", optional = true } reqwest = { version = "0.11.10", default_features = false } ruma = { workspace = true, features = ["compat", "rand", "unstable-msc2448", "unstable-msc2965"] } diff --git a/crates/matrix-sdk/src/room/timeline/builder.rs b/crates/matrix-sdk/src/room/timeline/builder.rs index 385f53c84..f0e0be4f9 100644 --- a/crates/matrix-sdk/src/room/timeline/builder.rs +++ b/crates/matrix-sdk/src/room/timeline/builder.rs @@ -24,7 +24,7 @@ use tracing::error; use super::{ inner::TimelineInner, to_device::{handle_forwarded_room_key_event, handle_room_key_event}, - Timeline, + Timeline, TimelineEventHandlerHandles, }; use crate::room; @@ -102,7 +102,7 @@ impl TimelineBuilder { handle_forwarded_room_key_event(inner.clone(), room.room_id().to_owned()), ); - let mut event_handler_handles = vec![ + let mut handles = vec![ timeline_event_handle, #[cfg(feature = "e2e-encryption")] room_key_handle, @@ -135,14 +135,15 @@ impl TimelineBuilder { } } }); - event_handler_handles.push(fully_read_handle); + handles.push(fully_read_handle); } + let client = room.client.clone(); let timeline = Timeline { inner, start_token: Mutex::new(prev_token), _end_token: Mutex::new(None), - event_handler_handles, + event_handler_handles: Arc::new(TimelineEventHandlerHandles { client, handles }), }; #[cfg(feature = "e2e-encryption")] diff --git a/crates/matrix-sdk/src/room/timeline/inner.rs b/crates/matrix-sdk/src/room/timeline/inner.rs index b161b47d9..22f614581 100644 --- a/crates/matrix-sdk/src/room/timeline/inner.rs +++ b/crates/matrix-sdk/src/room/timeline/inner.rs @@ -18,7 +18,7 @@ use std::{ }; use async_trait::async_trait; -use futures_signals::signal_vec::{MutableVec, MutableVecLockRef, SignalVec}; +use futures_signals::signal_vec::{MutableSignalVec, MutableVec, MutableVecLockRef}; use indexmap::IndexSet; use matrix_sdk_base::{ crypto::OlmMachine, @@ -83,7 +83,7 @@ impl TimelineInner

{ self.items.lock_ref() } - pub(super) fn items_signal(&self) -> impl SignalVec> { + pub(super) fn items_signal(&self) -> MutableSignalVec> { trace!("Creating timeline items signal"); self.items.signal_vec_cloned() } diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index c5b4d0229..672a137ef 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -16,13 +16,14 @@ //! //! See [`Timeline`] for details. -use std::sync::Arc; +use std::{pin::Pin, sync::Arc, task::Poll}; use futures_core::Stream; #[cfg(feature = "testing")] use futures_signals::signal_vec::MutableVecLockRef; -use futures_signals::signal_vec::{SignalVec, SignalVecExt, VecDiff}; +use futures_signals::signal_vec::{MutableSignalVec, SignalVec, SignalVecExt, VecDiff}; use matrix_sdk_base::locks::Mutex; +use pin_project_lite::pin_project; use ruma::{ assign, events::AnyMessageLikeEventContent, EventId, MilliSecondsSinceUnixEpoch, TransactionId, }; @@ -33,7 +34,7 @@ use super::Joined; use crate::{ event_handler::EventHandlerHandle, room::{self, MessagesOptions}, - Result, + Client, Result, }; mod builder; @@ -70,15 +71,7 @@ pub struct Timeline { inner: Arc>, start_token: Mutex>, _end_token: Mutex>, - event_handler_handles: Vec, -} - -impl Drop for Timeline { - fn drop(&mut self) { - for handle in self.event_handler_handles.drain(..) { - self.inner.room().client().remove_event_handler(handle); - } - } + event_handler_handles: Arc, } impl Timeline { @@ -245,7 +238,7 @@ impl Timeline { /// See [`SignalVecExt`](futures_signals::signal_vec::SignalVecExt) for a /// high-level API on top of [`SignalVec`]. pub fn signal(&self) -> impl SignalVec> { - self.inner.items_signal() + TimelineSignal::new(self.inner.items_signal(), self.event_handler_handles.clone()) } /// Get a stream of timeline changes. @@ -353,6 +346,48 @@ impl Timeline { } } +#[derive(Debug)] +struct TimelineEventHandlerHandles { + client: Client, + handles: Vec, +} + +impl Drop for TimelineEventHandlerHandles { + fn drop(&mut self) { + for handle in self.handles.drain(..) { + self.client.remove_event_handler(handle); + } + } +} + +pin_project! { + struct TimelineSignal { + #[pin] + inner: MutableSignalVec>, + event_handler_handles: Arc, + } +} + +impl TimelineSignal { + fn new( + inner: MutableSignalVec>, + event_handler_handles: Arc, + ) -> Self { + Self { inner, event_handler_handles } + } +} + +impl SignalVec for TimelineSignal { + type Item = Arc; + + fn poll_vec_change( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll>> { + self.project().inner.poll_vec_change(cx) + } +} + /// A single entry in timeline. #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] From 88376e85cd1dc92d2d8564839a235f2ca795093d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Feb 2023 13:52:31 +0100 Subject: [PATCH 10/16] test(sdk): Remove TestTimeline::with_initial_events It's not general enough for further inital event tests. --- .../src/room/timeline/tests/basic.rs | 26 ++++++++++++++----- .../matrix-sdk/src/room/timeline/tests/mod.rs | 25 +----------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/tests/basic.rs b/crates/matrix-sdk/src/room/timeline/tests/basic.rs index 25256cfe5..9b76b27ff 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/basic.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/basic.rs @@ -1,6 +1,7 @@ use assert_matches::assert_matches; use futures_signals::signal_vec::VecDiff; use futures_util::StreamExt; +use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; use matrix_sdk_test::async_test; use ruma::{ assign, @@ -18,7 +19,7 @@ use ruma::{ FullStateEventContent, }, }; -use serde_json::json; +use serde_json::{json, Value as JsonValue}; use super::{TestTimeline, ALICE, BOB}; use crate::room::timeline::{ @@ -26,13 +27,26 @@ use crate::room::timeline::{ VirtualTimelineItem, }; +fn sync_timeline_event(event: JsonValue) -> SyncTimelineEvent { + let event = serde_json::from_value(event).unwrap(); + SyncTimelineEvent { event, encryption_info: None } +} + #[async_test] async fn initial_events() { - let timeline = TestTimeline::with_initial_events([ - (*ALICE, RoomMessageEventContent::text_plain("A").into()), - (*BOB, RoomMessageEventContent::text_plain("B").into()), - ]) - .await; + let mut timeline = TestTimeline::new(); + timeline + .inner + .add_initial_events(vec![ + sync_timeline_event( + timeline.make_message_event(*ALICE, RoomMessageEventContent::text_plain("A")), + ), + sync_timeline_event( + timeline.make_message_event(*BOB, RoomMessageEventContent::text_plain("B")), + ), + ]) + .await; + let mut stream = timeline.stream(); let items = assert_matches!(stream.next().await, Some(VecDiff::Replace { values }) => values); diff --git a/crates/matrix-sdk/src/room/timeline/tests/mod.rs b/crates/matrix-sdk/src/room/timeline/tests/mod.rs index 4ca7a4969..1b0a20909 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/mod.rs @@ -22,7 +22,7 @@ use std::sync::{ use async_trait::async_trait; use futures_core::Stream; use futures_signals::signal_vec::{SignalVecExt, VecDiff}; -use matrix_sdk_base::deserialized_responses::{SyncTimelineEvent, TimelineEvent}; +use matrix_sdk_base::deserialized_responses::TimelineEvent; use once_cell::sync::Lazy; use ruma::{ events::{ @@ -57,29 +57,6 @@ impl TestTimeline { Self { inner: TimelineInner::new(TestProfileProvider), next_ts: AtomicU64::new(0) } } - async fn with_initial_events<'a>( - events: impl IntoIterator, - ) -> Self { - let mut this = - Self { inner: TimelineInner::new(TestProfileProvider), next_ts: AtomicU64::new(0) }; - - this.inner - .add_initial_events( - events - .into_iter() - .map(|(sender, content)| { - let event = - serde_json::from_value(this.make_message_event(sender, content)) - .unwrap(); - SyncTimelineEvent { event, encryption_info: None } - }) - .collect(), - ) - .await; - - this - } - fn stream(&self) -> impl Stream>> { self.inner.items_signal().to_stream() } From e8d5d29d2c96cef30f62fee9c2f80aed627aae56 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Feb 2023 15:47:43 +0100 Subject: [PATCH 11/16] chore(sdk): Improve tracing in TimelineEventHandler::add --- crates/matrix-sdk/src/room/timeline/event_handler.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index ee8cad83f..dde2c222f 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -577,10 +577,12 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { if let Some(day_divider_item) = maybe_create_day_divider_from_timestamps(old_ts, *timestamp) { + trace!("Adding day divider"); self.timeline_items.push_cloned(Arc::new(day_divider_item)); } } else { // If there is no event item, there is no day divider yet. + trace!("Adding first day divider"); self.timeline_items .push_cloned(Arc::new(TimelineItem::day_divider(*timestamp))); } @@ -642,8 +644,6 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { origin_server_ts, .. } => { - trace!("Adding new remote timeline item at the end"); - let result = rfind_event_item(self.timeline_items, |it| { txn_id.is_some() && it.transaction_id() == txn_id.as_deref() || it.event_id() == Some(event_id) @@ -682,11 +682,13 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { { // If the old item is the last one and no day divider // changes need to happen, replace and return early. + trace!(idx, "Replacing existing event"); self.timeline_items.set_cloned(idx, item); return; } else { // In more complex cases, remove the item and day // divider (if necessary) before re-adding the item. + trace!("Removing local echo or duplicate timeline item"); self.timeline_items.remove(idx); assert_ne!( @@ -706,6 +708,7 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { .get(idx + 1) .map_or(true, |item| item.is_virtual()) { + trace!("Removing day divider"); self.timeline_items.remove(idx - 1); } @@ -728,14 +731,17 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { if let Some(day_divider_item) = maybe_create_day_divider_from_timestamps(old_ts, *origin_server_ts) { + trace!("Adding day divider"); self.timeline_items.push_cloned(Arc::new(day_divider_item)); } } else { - // If there is not event item, there is no day divider yet. + // If there is no event item, there is no day divider yet. + trace!("Adding first day divider"); self.timeline_items .push_cloned(Arc::new(TimelineItem::day_divider(*origin_server_ts))); } + trace!("Adding new remote timeline item at the end"); self.timeline_items.push_cloned(item); } From 38e2d0bdcd7ab5c56ace392b36dc60f2e03d64b8 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Feb 2023 16:15:16 +0100 Subject: [PATCH 12/16] fix(sdk): Fix day divider removal logic in timeline --- crates/matrix-sdk/src/room/timeline/event_handler.rs | 4 ++-- crates/matrix-sdk/src/room/timeline/tests/echo.rs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index dde2c222f..022fbb1ae 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -700,12 +700,12 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { // Pre-requisites for removing the day divider: // 1. there is one preceding the old item at all if self.timeline_items[idx - 1].is_day_divider() - // 2. the next item after the old one being removed + // 2. the item after the old one that was removed // is virtual (it should be impossible for this // to be a read marker) && self .timeline_items - .get(idx + 1) + .get(idx) .map_or(true, |item| item.is_virtual()) { trace!("Removing day divider"); diff --git a/crates/matrix-sdk/src/room/timeline/tests/echo.rs b/crates/matrix-sdk/src/room/timeline/tests/echo.rs index 677f26acd..06e24281a 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/echo.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/echo.rs @@ -136,8 +136,11 @@ async fn remote_echo_new_position() { // … the local echo should be removed assert_matches!(stream.next().await, Some(VecDiff::RemoveAt { index: 1 })); + // … along with its day divider + assert_matches!(stream.next().await, Some(VecDiff::RemoveAt { index: 0 })); - // … and the remote echo added + // … and the remote echo added (no new day divider because both bob's and + // alice's message are from the same day according to server timestamps) let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); assert_matches!(item.as_event().unwrap(), EventTimelineItem::Remote(_)); } From 9e2fa13b9c60a3b9536fce9c681f029db9ca81f4 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Feb 2023 16:16:13 +0100 Subject: [PATCH 13/16] fix(sdk): Respect new server ordering when receiving duplicate events --- .../src/room/timeline/event_handler.rs | 15 ++++----------- .../src/room/timeline/tests/basic.rs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 022fbb1ae..1bab264d8 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -651,17 +651,10 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { if let Some((idx, old_item)) = result { if let EventTimelineItem::Remote(old_item) = old_item { - // Item was previously received by the server. Until we - // implement forwards pagination, this indicates a bug - // somewhere. - warn!(?item, ?old_item, "Received duplicate event"); - - // With /messages and /sync sometimes disagreeing on - // order of messages, we might want to change the - // position in some circumstances, but for now this - // should be good enough. - self.timeline_items.set_cloned(idx, item); - return; + // Item was previously received from the server. This + // should be very rare normally, but with the sliding- + // sync proxy, it is actually very common. + trace!(?item, ?old_item, "Received duplicate event"); }; if txn_id.is_none() { diff --git a/crates/matrix-sdk/src/room/timeline/tests/basic.rs b/crates/matrix-sdk/src/room/timeline/tests/basic.rs index 9b76b27ff..c104d1cb8 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/basic.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/basic.rs @@ -255,3 +255,22 @@ async fn dedup_pagination() { assert_matches!(*timeline_items[0], TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_))); assert_matches!(*timeline_items[1], TimelineItem::Event(_)); } + +#[async_test] +async fn dedup_initial() { + let mut timeline = TestTimeline::new(); + + let event_a = sync_timeline_event( + timeline.make_message_event(*ALICE, RoomMessageEventContent::text_plain("A")), + ); + let event_b = sync_timeline_event( + timeline.make_message_event(*BOB, RoomMessageEventContent::text_plain("B")), + ); + + timeline.inner.add_initial_events(vec![event_a.clone(), event_b, event_a]).await; + + let timeline_items = dbg!(timeline.inner.items()); + assert_eq!(timeline_items.len(), 3); + assert_eq!(timeline_items[1].as_event().unwrap().sender(), *BOB); + assert_eq!(timeline_items[2].as_event().unwrap().sender(), *ALICE); +} From fb467b75e0f9f80f60a423d4203a0ba5eed0a7d0 Mon Sep 17 00:00:00 2001 From: kegsay Date: Wed, 15 Feb 2023 18:03:29 +0100 Subject: [PATCH 14/16] ci: Update sliding sync integration tests job --- .github/workflows/ci.yml | 55 +++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0176b8b4e..06db21d8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -412,14 +412,13 @@ jobs: sliding-sync-integration-tests: name: Sliding Sync Integration test - # disabled until we can figure out the weird docker-not-starting-situation - if: false - # if: github.event_name == 'push' || !github.event.pull_request.draft + if: github.event_name == 'push' || !github.event.pull_request.draft runs-on: ubuntu-latest - # Service containers to run with `runner-job` + # run several docker containers with the same networking stack so the hostname 'postgres' + # maps to the postgres container, etc. services: - # Label used to access the service container + # sliding sync needs a postgres container postgres: # Docker Hub image image: postgres @@ -437,7 +436,28 @@ jobs: ports: # Maps tcp port 5432 on service container to the host - 5432:5432 - + # run sliding sync and point it at the postgres container and synapse container. + # the postgres container needs to be above this to make sure it has started prior to this service. + slidingsync: + image: "ghcr.io/matrix-org/sliding-sync:v0.99.0" + env: + SYNCV3_SERVER: "http://synapse:8008" + SYNCV3_SECRET: "SUPER_CI_SECRET" + SYNCV3_BINDADDR: ":8118" + SYNCV3_DB: "user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres" + ports: + - 8118:8118 + # tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the + # latter does not provide networking for services to communicate with it. + synapse: + # Custom image built from https://github.com/matrix-org/synapse/tree/v1.72.0/docker/complement + # with a dummy /complement/ca set + image: ghcr.io/matrix-org/synapse-service:v1.72.0 + env: + SYNAPSE_COMPLEMENT_DATABASE: sqlite + SERVER_NAME: synapse + ports: + - 8008:8008 steps: - name: Checkout the repo uses: actions/checkout@v3 @@ -455,22 +475,11 @@ jobs: with: python-version: 3.8 - # local synapse - - uses: michaelkaye/setup-matrix-synapse@main - with: - uploadLogs: true - httpPort: 8228 - disableRateLimiting: true - - # latest sliding sync proxy - - - uses: addnab/docker-run-action@v3 - with: - registry: gcr.io - image: "matrix-org/sliding-sync:v0.98.0" - docker_network: "host" - options: '-e "SYNCV3_SERVER=http://locahost:8228" -e "SYNCV3_SECRET=SUPER_CI_SECRET" -e "SYNCV3_BINDADDR=:8118" -e "SYNCV3_DB=user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres" -p 8118:8118' - - name: Test + env: + RUST_LOG: "hyper=trace" + HOMESERVER_URL: "http://localhost:8008" + HOMESERVER_DOMAIN: "synapse" + SLIDING_SYNC_PROXY_URL: "http://localhost:8118" run: | - cargo nextest run -p sliding-sync-integration-tests + cargo nextest run -p sliding-sync-integration-test From 99b61ef660ad02891e46ae2fb2d5869a14503cf6 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 15 Feb 2023 17:06:46 +0000 Subject: [PATCH 15/16] Remove redundant `log_trace` --- bindings/matrix-sdk-crypto-js/src/tracing.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/bindings/matrix-sdk-crypto-js/src/tracing.rs b/bindings/matrix-sdk-crypto-js/src/tracing.rs index 16ddfdb7f..b7a516ac1 100644 --- a/bindings/matrix-sdk-crypto-js/src/tracing.rs +++ b/bindings/matrix-sdk-crypto-js/src/tracing.rs @@ -145,9 +145,6 @@ mod inner { #[wasm_bindgen] extern "C" { - #[wasm_bindgen(js_namespace = console, js_name = "trace")] - fn log_trace(message: String); - #[wasm_bindgen(js_namespace = console, js_name = "debug")] fn log_debug(message: String); From 19b8ab409dc30316236369b78c6d4cd163b98f1b Mon Sep 17 00:00:00 2001 From: kegsay Date: Wed, 15 Feb 2023 18:32:57 +0100 Subject: [PATCH 16/16] test: Add depends_on block for the sliding sync proxy This prevents the proxy starting before postgres is ready, which causes the proxy to panic. --- .../sliding-sync-integration-test/assets/docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testing/sliding-sync-integration-test/assets/docker-compose.yml b/testing/sliding-sync-integration-test/assets/docker-compose.yml index 7ff1b383e..bafb49392 100644 --- a/testing/sliding-sync-integration-test/assets/docker-compose.yml +++ b/testing/sliding-sync-integration-test/assets/docker-compose.yml @@ -29,6 +29,9 @@ services: sliding-sync-proxy: image: ghcr.io/matrix-org/sliding-sync:v0.99.0-rc1 + depends_on: + postgres: + condition: service_healthy # image: ghcr.io/matrix-org/sliding-sync:latest links: - synapse