Merge pull request #675 from Hywan/feat-crypto-wasm
feat(crypto): Port to Wasm (in a JS host) and to NodeJS
This commit is contained in:
@@ -38,7 +38,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: tarpaulin
|
||||
args: --ignore-config --exclude-files "crates/matrix-sdk/examples/*,crates/matrix-sdk-common,crates/matrix-sdk-test" --out Xml
|
||||
args: --workspace --ignore-config --exclude-files "crates/matrix-sdk/examples/*,crates/matrix-sdk-common,crates/matrix-sdk-test" --exclude matrix-sdk-crypto-js --exclude matrix-sdk-crypto-nodejs --out Xml
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
|
||||
with:
|
||||
command: doc
|
||||
args: --no-deps --workspace --features docsrs -Zrustdoc-map
|
||||
args: --no-deps --workspace --exclude matrix-sdk-crypto-js --exclude matrix-sdk-crypto-nodejs --features docsrs -Zrustdoc-map
|
||||
|
||||
- name: Deploy docs
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- matrix-sdk-qrcode
|
||||
- matrix-sdk-base
|
||||
- matrix-sdk-common
|
||||
- matrix-sdk-crypto
|
||||
- matrix-sdk-crypto-js
|
||||
- indexeddb-no-crypto
|
||||
- indexeddb-with-crypto
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install WasmPack
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: 'latest'
|
||||
|
||||
+3
-1
@@ -2,5 +2,7 @@
|
||||
members = ["benchmarks", "crates/*", "labs/*", "xtask"]
|
||||
# xtask and labs should only be compiled when invoked explicitly
|
||||
default-members = ["benchmarks", "crates/*"]
|
||||
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
authors = ["Ivan Enderlin <ivane@element.io>"]
|
||||
description = "Matrix encryption library, for JavaScript"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "Apache-2.0"
|
||||
name = "matrix-sdk-crypto-js"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
version = "0.5.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ['-Oz']
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
docsrs = []
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" }
|
||||
ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] }
|
||||
wasm-bindgen = "0.2.80"
|
||||
wasm-bindgen-futures = "0.4.30"
|
||||
js-sys = "0.3.49"
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
anyhow = "1.0"
|
||||
@@ -0,0 +1,5 @@
|
||||
build:
|
||||
RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./js/pkg ../
|
||||
|
||||
test:
|
||||
node --test ../tests/js/**.js
|
||||
@@ -0,0 +1,61 @@
|
||||
//! Types related to events.
|
||||
|
||||
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Who can see a room's history.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HistoryVisibility {
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they were invited onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *invite* or *join*.
|
||||
Invited,
|
||||
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they joined the room onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *join*.
|
||||
Joined,
|
||||
|
||||
/// Previous events are always accessible to newly joined members.
|
||||
///
|
||||
/// All events in the room are accessible, even those sent when
|
||||
/// the member was not a part of the room.
|
||||
Shared,
|
||||
|
||||
/// All events while this is the `HistoryVisibility` value may be
|
||||
/// shared by any participating homeserver with anyone, regardless
|
||||
/// of whether they have ever joined the room.
|
||||
WorldReadable,
|
||||
}
|
||||
|
||||
impl From<HistoryVisibility> for RumaHistoryVisibility {
|
||||
fn from(value: HistoryVisibility) -> Self {
|
||||
use HistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RumaHistoryVisibility> for HistoryVisibility {
|
||||
fn from(value: RumaHistoryVisibility) -> Self {
|
||||
use RumaHistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::future::Future;
|
||||
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::{JsValue, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
pub(crate) fn future_to_promise<F, T>(future: F) -> Promise
|
||||
where
|
||||
F: Future<Output = Result<T, anyhow::Error>> + 'static,
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
let mut future = Some(future);
|
||||
|
||||
Promise::new(&mut |resolve, reject| {
|
||||
let future = future.take().unwrap_throw();
|
||||
|
||||
spawn_local(async move {
|
||||
match future.await {
|
||||
Ok(value) => resolve.call1(&JsValue::UNDEFINED, &value.into()).unwrap_throw(),
|
||||
Err(value) => {
|
||||
reject.call1(&JsValue::UNDEFINED, &value.to_string().into()).unwrap_throw()
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
|
||||
//! events, keys, rooms, servers, users and URIs.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// A Matrix [user ID].
|
||||
///
|
||||
/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserId {
|
||||
pub(crate) inner: ruma::OwnedUserId,
|
||||
}
|
||||
|
||||
impl UserId {
|
||||
pub(crate) fn new_with(inner: ruma::OwnedUserId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UserId {
|
||||
/// Parse/validate and create a new `UserId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> Result<UserId, JsError> {
|
||||
Ok(Self::new_with(ruma::UserId::parse(id)?))
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the user ID.
|
||||
#[wasm_bindgen(js_name = "serverName")]
|
||||
pub fn server_name(&self) -> ServerName {
|
||||
ServerName { inner: self.inner.server_name().to_owned() }
|
||||
}
|
||||
|
||||
/// Whether this user ID is a historical one.
|
||||
///
|
||||
/// A historical user ID is one that doesn't conform to the latest
|
||||
/// specification of the user ID grammar but is still accepted
|
||||
/// because it was previously allowed.
|
||||
#[wasm_bindgen(getter, js_name = "isHistorical")]
|
||||
pub fn is_historical(&self) -> bool {
|
||||
self.inner.is_historical()
|
||||
}
|
||||
|
||||
/// Return the user ID as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix key ID.
|
||||
///
|
||||
/// Device identifiers in Matrix are completely opaque character
|
||||
/// sequences. This type is provided simply for its semantic value.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeviceId {
|
||||
pub(crate) inner: ruma::OwnedDeviceId,
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub(crate) fn new_with(inner: ruma::OwnedDeviceId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceId {
|
||||
/// Create a new `DeviceId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> DeviceId {
|
||||
Self::new_with(id.into())
|
||||
}
|
||||
|
||||
/// Return the device ID as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix [room ID].
|
||||
///
|
||||
/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoomId {
|
||||
pub(crate) inner: ruma::OwnedRoomId,
|
||||
}
|
||||
|
||||
impl RoomId {
|
||||
pub(crate) fn new_with(inner: ruma::OwnedRoomId) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoomId {
|
||||
/// Parse/validate and create a new `RoomId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: &str) -> Result<RoomId, JsError> {
|
||||
Ok(Self::new_with(ruma::RoomId::parse(id)?))
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the room ID.
|
||||
#[wasm_bindgen(js_name = "serverName")]
|
||||
pub fn server_name(&self) -> ServerName {
|
||||
ServerName { inner: self.inner.server_name().to_owned() }
|
||||
}
|
||||
|
||||
/// Return the room ID as a string.
|
||||
#[wasm_bindgen(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix-spec compliant [server name].
|
||||
///
|
||||
/// It consists of a host and an optional port (separated by a colon if
|
||||
/// present).
|
||||
///
|
||||
/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct ServerName {
|
||||
inner: ruma::OwnedServerName,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ServerName {
|
||||
/// Parse/validate and create a new `ServerName`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(name: &str) -> Result<ServerName, JsError> {
|
||||
Ok(Self { inner: ruma::ServerName::parse(name)? })
|
||||
}
|
||||
|
||||
/// Returns the host of the server name.
|
||||
///
|
||||
/// That is: Return the part of the server before `:<port>` or the
|
||||
/// full server name if there is no port.
|
||||
pub fn host(&self) -> String {
|
||||
self.inner.host().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the port of the server name if any.
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
self.inner.port()
|
||||
}
|
||||
|
||||
/// Returns true if and only if the server name is an IPv4 or IPv6
|
||||
/// address.
|
||||
#[wasm_bindgen(js_name = "isIpLiteral")]
|
||||
pub fn is_ip_literal(&self) -> bool {
|
||||
self.inner.is_ip_literal()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
|
||||
pub mod events;
|
||||
mod future;
|
||||
pub mod identifiers;
|
||||
pub mod machine;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
pub mod sync_events;
|
||||
|
||||
use js_sys::{Object, Reflect};
|
||||
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
|
||||
|
||||
/// A really hacky and dirty code to downcast a `JsValue` to `T:
|
||||
/// RefFromWasmAbi`, inspired by
|
||||
/// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288.
|
||||
///
|
||||
/// The returned value is likely to be a `wasm_bindgen::__ref::Ref<T>`.
|
||||
fn downcast<T>(value: &JsValue, classname: &str) -> Result<T::Anchor, JsError>
|
||||
where
|
||||
T: RefFromWasmAbi<Abi = u32>,
|
||||
{
|
||||
let constructor_name = Object::get_prototype_of(value).constructor().name();
|
||||
|
||||
if constructor_name == classname {
|
||||
let pointer = Reflect::get(value, &JsValue::from_str("ptr"))
|
||||
.map_err(|_| JsError::new("Failed to read the `JsValue` pointer"))?;
|
||||
let pointer = pointer
|
||||
.as_f64()
|
||||
.ok_or_else(|| JsError::new("Failed to read the `JsValue` pointer as a `f64`"))?
|
||||
as u32;
|
||||
|
||||
Ok(unsafe { T::ref_from_abi(pointer) })
|
||||
} else {
|
||||
Err(JsError::new(&format!(
|
||||
"Expect an `{}` instance, received `{}` instead",
|
||||
classname, constructor_name,
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
//! The crypto specific Olm objects.
|
||||
|
||||
use std::{collections::BTreeMap, time::Duration};
|
||||
|
||||
use js_sys::{Array, Map, Promise, Set};
|
||||
use ruma::{DeviceKeyAlgorithm, OwnedTransactionId, UInt};
|
||||
use serde_json::Value as JsonValue;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
downcast, events,
|
||||
future::future_to_promise,
|
||||
identifiers, requests,
|
||||
requests::OutgoingRequest,
|
||||
responses::{self, response_from_string},
|
||||
sync_events,
|
||||
};
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
/// used for Matrix end to end encryption.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OlmMachine {
|
||||
inner: matrix_sdk_crypto::OlmMachine,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl OlmMachine {
|
||||
/// Create a new memory based `OlmMachine`.
|
||||
///
|
||||
/// The created machine will keep the encryption keys only in
|
||||
/// memory and once the objects is dropped, the keys will be lost.
|
||||
///
|
||||
/// `user_id` represents the unique ID of the user that owns this
|
||||
/// machine. `device_id` represents the unique ID of the device
|
||||
/// that owns this machine.
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise {
|
||||
let user_id = user_id.inner.clone();
|
||||
let device_id = device_id.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(OlmMachine {
|
||||
inner: matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
|
||||
.await,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The unique user ID that owns this `OlmMachine` instance.
|
||||
#[wasm_bindgen(js_name = "userId")]
|
||||
pub fn user_id(&self) -> identifiers::UserId {
|
||||
identifiers::UserId::new_with(self.inner.user_id().to_owned())
|
||||
}
|
||||
|
||||
/// The unique device ID that identifies this `OlmMachine`.
|
||||
#[wasm_bindgen(js_name = "deviceId")]
|
||||
pub fn device_id(&self) -> identifiers::DeviceId {
|
||||
identifiers::DeviceId::new_with(self.inner.device_id().to_owned())
|
||||
}
|
||||
|
||||
/// Get the public parts of our Olm identity keys.
|
||||
#[wasm_bindgen(js_name = "identityKeys")]
|
||||
pub fn identity_keys(&self) -> IdentityKeys {
|
||||
self.inner.identity_keys().into()
|
||||
}
|
||||
|
||||
/// Get the display name of our own device.
|
||||
#[wasm_bindgen(js_name = "displayName")]
|
||||
pub fn display_name(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move { Ok(me.display_name().await?) })
|
||||
}
|
||||
|
||||
/// Get all the tracked users of our own device.
|
||||
///
|
||||
/// Returns a `Set<UserId>`.
|
||||
#[wasm_bindgen(js_name = "trackedUsers")]
|
||||
pub fn tracked_users(&self) -> Set {
|
||||
let set = Set::new(&JsValue::UNDEFINED);
|
||||
|
||||
self.inner.tracked_users().into_iter().map(identifiers::UserId::new_with).for_each(
|
||||
|user| {
|
||||
set.add(&user.into());
|
||||
},
|
||||
);
|
||||
|
||||
set
|
||||
}
|
||||
|
||||
/// Update the tracked users.
|
||||
///
|
||||
/// `users` is an iterator over user IDs that should be marked for
|
||||
/// tracking.
|
||||
///
|
||||
/// This will mark users that weren't seen before for a key query
|
||||
/// and tracking.
|
||||
///
|
||||
/// If the user is already known to the Olm machine, it will not
|
||||
/// be considered for a key query.
|
||||
#[wasm_bindgen(js_name = "updateTrackedUsers")]
|
||||
pub fn update_tracked_users(&self, users: &Array) -> Result<Promise, JsError> {
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
|
||||
Ok(JsValue::UNDEFINED)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Handle to-device events and one-time key counts from a sync
|
||||
/// response.
|
||||
///
|
||||
/// This will decrypt and handle to-device events returning the
|
||||
/// decrypted versions of them.
|
||||
///
|
||||
/// To decrypt an event from the room timeline call
|
||||
/// `decrypt_room_event`.
|
||||
#[wasm_bindgen(js_name = "receiveSyncChanges")]
|
||||
pub fn receive_sync_changes(
|
||||
&self,
|
||||
to_device_events: &str,
|
||||
changed_devices: &sync_events::DeviceLists,
|
||||
one_time_key_counts: &Map,
|
||||
unused_fallback_keys: &Set,
|
||||
) -> Result<Promise, JsError> {
|
||||
let to_device_events = serde_json::from_str(to_device_events)?;
|
||||
let changed_devices = changed_devices.inner.clone();
|
||||
let one_time_key_counts: BTreeMap<DeviceKeyAlgorithm, UInt> = one_time_key_counts
|
||||
.entries()
|
||||
.into_iter()
|
||||
.filter_map(|js_value| {
|
||||
let pair = Array::from(&js_value.ok()?);
|
||||
let (key, value) = (
|
||||
DeviceKeyAlgorithm::from(pair.at(0).as_string()?),
|
||||
UInt::new(pair.at(1).as_f64()? as u64)?,
|
||||
);
|
||||
|
||||
Some((key, value))
|
||||
})
|
||||
.collect();
|
||||
let unused_fallback_keys: Option<Vec<DeviceKeyAlgorithm>> = Some(
|
||||
unused_fallback_keys
|
||||
.values()
|
||||
.into_iter()
|
||||
.filter_map(|js_value| Some(DeviceKeyAlgorithm::from(js_value.ok()?.as_string()?)))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.receive_sync_changes(
|
||||
to_device_events,
|
||||
&changed_devices,
|
||||
&one_time_key_counts,
|
||||
unused_fallback_keys.as_deref(),
|
||||
)
|
||||
.await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the outgoing requests that need to be sent out.
|
||||
///
|
||||
/// This returns a list of `JsValue` to represent either:
|
||||
/// * `KeysUploadRequest`,
|
||||
/// * `KeysQueryRequest`,
|
||||
/// * `KeysClaimRequest`,
|
||||
/// * `ToDeviceRequest`,
|
||||
/// * `SignatureUploadRequest`,
|
||||
/// * `RoomMessageRequest` or
|
||||
/// * `KeysBackupRequest`.
|
||||
///
|
||||
/// Those requests need to be sent out to the server and the
|
||||
/// responses need to be passed back to the state machine using
|
||||
/// `mark_request_as_sent`.
|
||||
#[wasm_bindgen(js_name = "outgoingRequests")]
|
||||
pub fn outgoing_requests(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(me
|
||||
.outgoing_requests()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(OutgoingRequest)
|
||||
.map(TryFrom::try_from)
|
||||
.collect::<Result<Vec<JsValue>, _>>()?
|
||||
.into_iter()
|
||||
.collect::<Array>())
|
||||
})
|
||||
}
|
||||
|
||||
/// Mark the request with the given request ID as sent (see
|
||||
/// `outgoing_requests`).
|
||||
///
|
||||
/// Arguments are:
|
||||
///
|
||||
/// * `request_id` represents the unique ID of the request that was sent
|
||||
/// out. This is needed to couple the response with the now sent out
|
||||
/// request.
|
||||
/// * `response_type` represents the type of the request that was sent out.
|
||||
/// * `response` represents the response that was received from the server
|
||||
/// after the outgoing request was sent out.
|
||||
#[wasm_bindgen(js_name = "markRequestAsSent")]
|
||||
pub fn mark_request_as_sent(
|
||||
&self,
|
||||
request_id: &str,
|
||||
request_type: requests::RequestType,
|
||||
response: &str,
|
||||
) -> Result<Promise, JsError> {
|
||||
let transaction_id = OwnedTransactionId::from(request_id);
|
||||
let response = response_from_string(response)?;
|
||||
let incoming_response = responses::OwnedResponse::try_from((request_type, response))?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(me.mark_request_as_sent(&transaction_id, &incoming_response).await.map(|_| true)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Encrypt a room message for the given room.
|
||||
///
|
||||
/// Beware that a group session needs to be shared before this
|
||||
/// method can be called using the `share_group_session` method.
|
||||
///
|
||||
/// `room_id` is the ID of the room for which the message should
|
||||
/// be encrypted. `event_type` is the type of the event. `content`
|
||||
/// is the plaintext content of the message that should be
|
||||
/// encrypted.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a group session for the given room wasn't shared
|
||||
/// beforehand.
|
||||
#[wasm_bindgen(js_name = "encryptRoomEvent")]
|
||||
pub fn encrypt_room_event(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
event_type: String,
|
||||
content: &str,
|
||||
) -> Result<Promise, JsError> {
|
||||
let room_id = room_id.inner.clone();
|
||||
let content: JsonValue = serde_json::from_str(content)?;
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.encrypt_room_event_raw(&room_id, content, event_type.as_ref()).await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Invalidate the currently active outbound group session for the
|
||||
/// given room.
|
||||
///
|
||||
/// Returns true if a session was invalidated, false if there was
|
||||
/// no session to invalidate.
|
||||
#[wasm_bindgen(js_name = "invalidateGroupSession")]
|
||||
pub fn invalidate_group_session(&self, room_id: &identifiers::RoomId) -> Promise {
|
||||
let room_id = room_id.inner.clone();
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move { Ok(me.invalidate_group_session(&room_id).await?) })
|
||||
}
|
||||
|
||||
/// Get to-device requests to share a group session with users in a room.
|
||||
///
|
||||
/// `room_id` is the room ID. `users` is an array of `UserId`
|
||||
/// objects. `encryption_settings` are an `EncryptionSettings`
|
||||
/// object.
|
||||
#[wasm_bindgen(js_name = "shareGroupSession")]
|
||||
pub fn share_group_session(
|
||||
&self,
|
||||
room_id: &identifiers::RoomId,
|
||||
users: &Array,
|
||||
encryption_settings: &EncryptionSettings,
|
||||
) -> Result<Promise, JsError> {
|
||||
let room_id = room_id.inner.clone();
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
let encryption_settings =
|
||||
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.share_group_session(
|
||||
&room_id,
|
||||
users.iter().map(AsRef::as_ref),
|
||||
encryption_settings,
|
||||
)
|
||||
.await?,
|
||||
)?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the a key claiming request for the user/device pairs that
|
||||
/// we are missing Olm sessions for.
|
||||
///
|
||||
/// Returns `NULL` if no key claiming request needs to be sent
|
||||
/// out, otherwise it returns an `Array` where the first key is
|
||||
/// the transaction ID as a string, and the second key is the keys
|
||||
/// claim request serialized to JSON.
|
||||
///
|
||||
/// Sessions need to be established between devices so group
|
||||
/// sessions for a room can be shared with them.
|
||||
///
|
||||
/// This should be called every time a group session needs to be
|
||||
/// shared as well as between sync calls. After a sync some
|
||||
/// devices may request room keys without us having a valid Olm
|
||||
/// session with them, making it impossible to server the room key
|
||||
/// request, thus it’s necessary to check for missing sessions
|
||||
/// between sync as well.
|
||||
///
|
||||
/// Note: Care should be taken that only one such request at a
|
||||
/// time is in flight, e.g. using a lock.
|
||||
///
|
||||
/// The response of a successful key claiming requests needs to be
|
||||
/// passed to the `OlmMachine` with the `mark_request_as_sent`.
|
||||
///
|
||||
/// `users` represents the list of users that we should check if
|
||||
/// we lack a session with one of their devices. This can be an
|
||||
/// empty iterator when calling this method between sync requests.
|
||||
#[wasm_bindgen(js_name = "getMissingSessions")]
|
||||
pub fn get_missing_sessions(&self, users: &Array) -> Result<Promise, JsError> {
|
||||
let users = users
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
match me.get_missing_sessions(users.iter().map(AsRef::as_ref)).await? {
|
||||
Some((transaction_id, keys_claim_request)) => {
|
||||
Ok(JsValue::from(requests::KeysClaimRequest::try_from((
|
||||
transaction_id.to_string(),
|
||||
&keys_claim_request,
|
||||
))?))
|
||||
}
|
||||
|
||||
None => Ok(JsValue::NULL),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// An Ed25519 public key, used to verify digital signatures.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ed25519PublicKey {
|
||||
inner: vodozemac::Ed25519PublicKey,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Ed25519PublicKey {
|
||||
/// The number of bytes an Ed25519 public key has.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn length(&self) -> usize {
|
||||
vodozemac::Ed25519PublicKey::LENGTH
|
||||
}
|
||||
|
||||
/// Serialize an Ed25519 public key to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Curve25519 public key.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Curve25519PublicKey {
|
||||
inner: vodozemac::Curve25519PublicKey,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Curve25519PublicKey {
|
||||
/// The number of bytes a Curve25519 public key has.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn length(&self) -> usize {
|
||||
vodozemac::Curve25519PublicKey::LENGTH
|
||||
}
|
||||
|
||||
/// Serialize an Curve25519 public key to an unpadded base64
|
||||
/// representation.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct holding the two public identity keys of an account.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct IdentityKeys {
|
||||
/// The Ed25519 public key, used for signing.
|
||||
pub ed25519: Ed25519PublicKey,
|
||||
|
||||
/// The Curve25519 public key, used for establish shared secrets.
|
||||
pub curve25519: Curve25519PublicKey,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
|
||||
fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self {
|
||||
Self {
|
||||
ed25519: Ed25519PublicKey { inner: value.ed25519 },
|
||||
curve25519: Curve25519PublicKey { inner: value.curve25519 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An encryption algorithm to be used to encrypt messages sent to a
|
||||
/// room.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EncryptionAlgorithm {
|
||||
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
|
||||
OlmV1Curve25519AesSha2,
|
||||
|
||||
/// Megolm version 1 using AES-256 and SHA-256.
|
||||
MegolmV1AesSha2,
|
||||
}
|
||||
|
||||
impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
|
||||
fn from(value: EncryptionAlgorithm) -> Self {
|
||||
use EncryptionAlgorithm::*;
|
||||
|
||||
match value {
|
||||
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ruma::EventEncryptionAlgorithm> for EncryptionAlgorithm {
|
||||
fn from(value: ruma::EventEncryptionAlgorithm) -> Self {
|
||||
use ruma::EventEncryptionAlgorithm::*;
|
||||
|
||||
match value {
|
||||
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings for an encrypted room.
|
||||
///
|
||||
/// This determines the algorithm and rotation periods of a group
|
||||
/// session.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EncryptionSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EncryptionAlgorithm,
|
||||
|
||||
/// How long the session should be used before changing it,
|
||||
/// expressed in microseconds.
|
||||
#[wasm_bindgen(js_name = "rotationPeriod")]
|
||||
pub rotation_period: u64,
|
||||
|
||||
/// How many messages should be sent before changing the session.
|
||||
#[wasm_bindgen(js_name = "rotationPeriodMessages")]
|
||||
pub rotation_period_messages: u64,
|
||||
|
||||
/// The history visibility of the room when the session was
|
||||
/// created.
|
||||
#[wasm_bindgen(js_name = "historyVisibility")]
|
||||
pub history_visibility: events::HistoryVisibility,
|
||||
}
|
||||
|
||||
impl Default for EncryptionSettings {
|
||||
fn default() -> Self {
|
||||
let default = matrix_sdk_crypto::olm::EncryptionSettings::default();
|
||||
|
||||
Self {
|
||||
algorithm: default.algorithm.into(),
|
||||
rotation_period: default.rotation_period.as_micros().try_into().unwrap(),
|
||||
rotation_period_messages: default.rotation_period_msgs,
|
||||
history_visibility: default.history_visibility.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl EncryptionSettings {
|
||||
/// Create a new `EncryptionSettings` with default values.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> EncryptionSettings {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
|
||||
fn from(value: &EncryptionSettings) -> Self {
|
||||
Self {
|
||||
algorithm: value.algorithm.clone().into(),
|
||||
rotation_period: Duration::from_micros(value.rotation_period),
|
||||
rotation_period_msgs: value.rotation_period_messages,
|
||||
history_visibility: value.history_visibility.clone().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
//! Types to handle requests.
|
||||
|
||||
use js_sys::JsString;
|
||||
use matrix_sdk_crypto::{
|
||||
requests::{
|
||||
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
|
||||
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
|
||||
},
|
||||
OutgoingRequests,
|
||||
};
|
||||
use ruma::api::client::keys::{
|
||||
claim_keys::v3::Request as RumaKeysClaimRequest,
|
||||
upload_keys::v3::Request as RumaKeysUploadRequest,
|
||||
upload_signatures::v3::Request as RumaSignatureUploadRequest,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Data for a request to the `/keys/upload` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Publishes end-to-end encryption keys for the device.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysupload
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub request_id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"device_keys": …, "one_time_keys": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/query` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Returns the current devices and identity keys for the given users.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysquery
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysQueryRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub request_id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```
|
||||
/// {"timeout": …, "device_keys": …, "token": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/claim` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Claims one-time keys that can be used to establish 1-to-1 E2EE
|
||||
/// sessions.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysclaim
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysClaimRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub request_id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```
|
||||
/// {"timeout": …, "one_time_keys": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/// Data for a request to the `/sendToDevice` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Send an event to a single device or to a group of devices.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct ToDeviceRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub request_id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```
|
||||
/// {"event_type": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/// Data for a request to the `/keys/signatures/upload` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
/// Publishes cross-signing signatures for the user.
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keyssignaturesupload
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct SignatureUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub request_id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```
|
||||
/// {"signed_keys": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/// A customized owned request type for sending out room messages
|
||||
/// ([specification]).
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct RoomMessageRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub request_id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```
|
||||
/// {"room_id": …, "txn_id": …, "content": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/// A request that will back up a batch of room keys to the server
|
||||
/// ([specification]).
|
||||
///
|
||||
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3room_keyskeys
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct KeysBackupRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub request_id: JsString,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```
|
||||
/// {"rooms": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
macro_rules! request {
|
||||
($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
|
||||
impl TryFrom<(String, &$ruma_request)> for $request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(
|
||||
(request_id, request): (String, &$ruma_request),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let mut map = serde_json::Map::new();
|
||||
$(
|
||||
map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap());
|
||||
)+
|
||||
let value = serde_json::Value::Object(map);
|
||||
|
||||
Ok($request {
|
||||
request_id: request_id.into(),
|
||||
body: serde_json::to_string(&value)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys);
|
||||
request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token);
|
||||
request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys);
|
||||
request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages);
|
||||
request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys);
|
||||
request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content);
|
||||
request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms);
|
||||
|
||||
// JavaScript has no complex enums like Rust. To return structs of
|
||||
// different types, we have no choice that hiding everything behind a
|
||||
// `JsValue`.
|
||||
pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest);
|
||||
|
||||
impl TryFrom<OutgoingRequest> for JsValue {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(outgoing_request: OutgoingRequest) -> Result<Self, Self::Error> {
|
||||
let request_id = outgoing_request.0.request_id().to_string();
|
||||
|
||||
Ok(match outgoing_request.0.request() {
|
||||
OutgoingRequests::KeysUpload(request) => {
|
||||
JsValue::from(KeysUploadRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysQuery(request) => {
|
||||
JsValue::from(KeysQueryRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysClaim(request) => {
|
||||
JsValue::from(KeysClaimRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::ToDeviceRequest(request) => {
|
||||
JsValue::from(ToDeviceRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::SignatureUpload(request) => {
|
||||
JsValue::from(SignatureUploadRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::RoomMessage(request) => {
|
||||
JsValue::from(RoomMessageRequest::try_from((request_id, request))?)
|
||||
}
|
||||
|
||||
OutgoingRequests::KeysBackup(request) => {
|
||||
JsValue::from(KeysBackupRequest::try_from((request_id, request))?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent the type of a request.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum RequestType {
|
||||
/// Represents a `KeysUploadRequest`.
|
||||
KeysUpload,
|
||||
|
||||
/// Represents a `KeysQueryRequest`.
|
||||
KeysQuery,
|
||||
|
||||
/// Represents a `KeysClaimRequest`.
|
||||
KeysClaim,
|
||||
|
||||
/// Represents a `ToDeviceRequest`.
|
||||
ToDevice,
|
||||
|
||||
/// Represents a `SignatureUploadRequest`.
|
||||
SignatureUpload,
|
||||
|
||||
/// Represents a `RoomMessageRequest`.
|
||||
RoomMessage,
|
||||
|
||||
/// Represents a `KeysBackupRequest`.
|
||||
KeysBackup,
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
//! Types related to responses.
|
||||
|
||||
use matrix_sdk_crypto::IncomingResponse;
|
||||
pub(crate) use ruma::api::client::{
|
||||
backup::add_backup_keys::v3::Response as KeysBackupResponse,
|
||||
keys::{
|
||||
claim_keys::v3::Response as KeysClaimResponse, get_keys::v3::Response as KeysQueryResponse,
|
||||
upload_keys::v3::Response as KeysUploadResponse,
|
||||
upload_signatures::v3::Response as SignatureUploadResponse,
|
||||
},
|
||||
message::send_message_event::v3::Response as RoomMessageResponse,
|
||||
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
|
||||
};
|
||||
use ruma::api::IncomingResponse as RumaIncomingResponse;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::requests::RequestType;
|
||||
|
||||
pub(crate) fn response_from_string(body: &str) -> http::Result<http::Response<Vec<u8>>> {
|
||||
http::Response::builder().status(200).body(body.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
/// Intermediate private type to store an incoming owned response,
|
||||
/// without the need to manage lifetime.
|
||||
pub(crate) enum OwnedResponse {
|
||||
KeysUpload(KeysUploadResponse),
|
||||
KeysQuery(KeysQueryResponse),
|
||||
KeysClaim(KeysClaimResponse),
|
||||
ToDevice(ToDeviceResponse),
|
||||
SignatureUpload(SignatureUploadResponse),
|
||||
RoomMessage(RoomMessageResponse),
|
||||
KeysBackup(KeysBackupResponse),
|
||||
}
|
||||
|
||||
impl From<KeysUploadResponse> for OwnedResponse {
|
||||
fn from(response: KeysUploadResponse) -> Self {
|
||||
OwnedResponse::KeysUpload(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysQueryResponse> for OwnedResponse {
|
||||
fn from(response: KeysQueryResponse) -> Self {
|
||||
OwnedResponse::KeysQuery(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysClaimResponse> for OwnedResponse {
|
||||
fn from(response: KeysClaimResponse) -> Self {
|
||||
OwnedResponse::KeysClaim(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToDeviceResponse> for OwnedResponse {
|
||||
fn from(response: ToDeviceResponse) -> Self {
|
||||
OwnedResponse::ToDevice(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignatureUploadResponse> for OwnedResponse {
|
||||
fn from(response: SignatureUploadResponse) -> Self {
|
||||
Self::SignatureUpload(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoomMessageResponse> for OwnedResponse {
|
||||
fn from(response: RoomMessageResponse) -> Self {
|
||||
OwnedResponse::RoomMessage(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysBackupResponse> for OwnedResponse {
|
||||
fn from(r: KeysBackupResponse) -> Self {
|
||||
Self::KeysBackup(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(RequestType, http::Response<Vec<u8>>)> for OwnedResponse {
|
||||
type Error = JsError;
|
||||
|
||||
fn try_from(
|
||||
(request_type, response): (RequestType, http::Response<Vec<u8>>),
|
||||
) -> Result<Self, Self::Error> {
|
||||
match request_type {
|
||||
RequestType::KeysUpload => {
|
||||
KeysUploadResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysQuery => {
|
||||
KeysQueryResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysClaim => {
|
||||
KeysClaimResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::ToDevice => {
|
||||
ToDeviceResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::SignatureUpload => {
|
||||
SignatureUploadResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::RoomMessage => {
|
||||
RoomMessageResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
|
||||
RequestType::KeysBackup => {
|
||||
KeysBackupResponse::try_from_http_response(response).map(Into::into)
|
||||
}
|
||||
}
|
||||
.map_err(JsError::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
|
||||
fn from(response: &'a OwnedResponse) -> Self {
|
||||
match response {
|
||||
OwnedResponse::KeysUpload(response) => IncomingResponse::KeysUpload(response),
|
||||
OwnedResponse::KeysQuery(response) => IncomingResponse::KeysQuery(response),
|
||||
OwnedResponse::KeysClaim(response) => IncomingResponse::KeysClaim(response),
|
||||
OwnedResponse::ToDevice(response) => IncomingResponse::ToDevice(response),
|
||||
OwnedResponse::SignatureUpload(response) => IncomingResponse::SignatureUpload(response),
|
||||
OwnedResponse::RoomMessage(response) => IncomingResponse::RoomMessage(response),
|
||||
OwnedResponse::KeysBackup(response) => IncomingResponse::KeysBackup(response),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//! `GET /_matrix/client/*/sync`
|
||||
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{downcast, identifiers};
|
||||
|
||||
/// Information on E2E device updates.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceLists {
|
||||
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceLists {
|
||||
/// Create an empty `DeviceLists`.
|
||||
///
|
||||
/// `changed` and `left` must be an array of strings representing
|
||||
/// a user ID. Ideally, we should pass a `UserId` object instance,
|
||||
/// but it's a limitation of `wasm-bindgen` (a workaround is
|
||||
/// possible but it will slow down performance).
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(changed: Array, left: Array) -> Result<DeviceLists, JsError> {
|
||||
let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default();
|
||||
|
||||
inner.changed = changed
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
inner.left = left
|
||||
.iter()
|
||||
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
|
||||
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/// Returns true if there are no device list updates.
|
||||
#[wasm_bindgen(js_name = "isEmpty")]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
/// List of users who have updated their device identity keys or who now
|
||||
/// share an encrypted room with the client since the previous sync
|
||||
pub fn changed(&self) -> Array {
|
||||
self.inner
|
||||
.changed
|
||||
.iter()
|
||||
.map(|user| identifiers::UserId::new_with(user.clone()))
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// List of users who no longer share encrypted rooms since the previous
|
||||
/// sync response.
|
||||
pub fn left(&self) -> Array {
|
||||
self.inner
|
||||
.left
|
||||
.iter()
|
||||
.map(|user| identifiers::UserId::new_with(user.clone()))
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
const { HistoryVisibility } = require('../../js/pkg/matrix_sdk_crypto');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
test('HistoryVisibility', (t) => {
|
||||
assert.equal(HistoryVisibility.Invited, 0);
|
||||
assert.equal(HistoryVisibility.Joined, 1);
|
||||
assert.equal(HistoryVisibility.Shared, 2);
|
||||
assert.equal(HistoryVisibility.WorldReadable, 3);
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
const { UserId, DeviceId, RoomId, ServerName } = require('../../js/pkg/matrix_sdk_crypto');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
test('UserId', (t) => {
|
||||
assert.throws(() => { new UserId('@foobar') }, Error, 'An invalid user ID must throw an error');
|
||||
|
||||
const user = new UserId('@foo:bar.org');
|
||||
|
||||
assert.equal(user.localpart(), 'foo', 'Localpart is present');
|
||||
assert.ok(user.serverName() instanceof ServerName, 'Server name is present');
|
||||
assert.equal(user.isHistorical, false, 'User ID is not historical');
|
||||
assert.equal(user.toString(), '@foo:bar.org', 'Can read the user ID as a string');
|
||||
});
|
||||
|
||||
test('DeviceId', (t) => {
|
||||
assert.equal(new DeviceId('foo').toString(), 'foo', 'Can read the device ID as a string');
|
||||
});
|
||||
|
||||
test('RoomId', (t) => {
|
||||
assert.throws(() => { new UserId('!foo') }, Error, 'An invalid room ID must throw an error');
|
||||
|
||||
const room = new RoomId('!foo:bar.org');
|
||||
|
||||
assert.equal(room.localpart(), 'foo', 'Localpart is present');
|
||||
assert.ok(room.serverName() instanceof ServerName, 'Server name is present');
|
||||
assert.equal(room.toString(), '!foo:bar.org', 'Can read the room ID as a string');
|
||||
});
|
||||
|
||||
test('ServerName', (t) => {
|
||||
assert.throws(() => { new ServerName('@foobar') }, Error, 'An invalid server name must throw an error');
|
||||
|
||||
assert.equal(new ServerName('foo.org').host(), 'foo.org', 'Host is present');
|
||||
assert.equal(new ServerName('foo.org').port(), undefined, 'Port is absent');
|
||||
assert.equal(new ServerName('foo.org:1234').port(), 1234, 'Port is present');
|
||||
assert.equal(new ServerName('foo.org').isIpLiteral(), false, 'Server name is not an IP literal');
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, UserId, DeviceId, OlmMachine, DeviceLists, KeysUploadRequest, KeysQueryRequest } = require('../../js/pkg/matrix_sdk_crypto');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
test('EncryptionAlgorithm', (t) => {
|
||||
assert.equal(EncryptionAlgorithm.OlmV1Curve25519AesSha2, 0);
|
||||
assert.equal(EncryptionAlgorithm.MegolmV1AesSha2, 1);
|
||||
});
|
||||
|
||||
test('EncryptionSettings', (t) => {
|
||||
let es = new EncryptionSettings();
|
||||
|
||||
assert.equal(es.algorithm, EncryptionAlgorithm.MegolmV1AesSha2, 'It has a default algorithm');
|
||||
assert.equal(es.rotationPeriod, 604800000000n, 'It has a default rotation period');
|
||||
assert.equal(es.rotationPeriodMessages, 100n, 'It has a default message rotation period');
|
||||
assert.equal(es.historyVisibility, HistoryVisibility.Shared, 'It has a default history visibility');
|
||||
|
||||
es.algorithm = EncryptionSettings.OlmV1Curve25519AesSha2;
|
||||
assert.equal(es.algorithm, EncryptionAlgorithm.OlmV1Curve25519AesSha2, 'It has a new algorithm');
|
||||
assert.throws(() => { es.algorithm = 42 }, Error, 'Enum values are validated');
|
||||
|
||||
es.rotationPeriod = 42n;
|
||||
assert.equal(es.rotationPeriod, 42n, 'It has a new rotation period');
|
||||
|
||||
es.rotationPeriodMessages = 153n;
|
||||
assert.equal(es.rotationPeriodMessages, 153n, 'It has a new message rotation period');
|
||||
|
||||
es.historyVisibility = HistoryVisibility.WorldReadable;
|
||||
assert.equal(es.historyVisibility, HistoryVisibility.WorldReadable, 'It has a new history visibility');
|
||||
assert.throws(() => { es.historyVisibility = 42 }, Error, 'Enum values are validated');
|
||||
});
|
||||
|
||||
test('OlmMachine', async (t) => {
|
||||
const user_id = new UserId('@foo:bar.org');
|
||||
const device_id = new DeviceId('baz');
|
||||
|
||||
await t.test('Construct', async (t) => {
|
||||
const machine = await new OlmMachine(user_id, device_id);
|
||||
|
||||
assert.ok(machine instanceof OlmMachine);
|
||||
assert.equal(machine.userId().toString(), '@foo:bar.org', 'User ID is present');
|
||||
assert.equal(machine.deviceId().toString(), 'baz', 'Device ID is present');
|
||||
});
|
||||
|
||||
await t.test('Identity keys', async (t) => {
|
||||
const machine = await new OlmMachine(user_id, device_id);
|
||||
const identity_keys = machine.identityKeys();
|
||||
|
||||
assert.match(identity_keys.ed25519.toBase64(), /^[A-Za-z0-9+/]+$/, 'Ed25519 can be base64-encoded');
|
||||
assert.match(identity_keys.curve25519.toBase64(), /^[A-Za-z0-9+/]+$/, 'Curve25519 can be base64-encoded');
|
||||
assert.ok(identity_keys.curve25519.length > 0, 'Curve25519\'s length is greater than zero');
|
||||
});
|
||||
|
||||
await t.test('Display name', async (t) => {
|
||||
const machine = await new OlmMachine(user_id, device_id);
|
||||
|
||||
assert.equal(await machine.displayName(), undefined, 'Display name is absent by default');
|
||||
});
|
||||
|
||||
await t.test('Tracked users', async (t) => {
|
||||
const machine = await new OlmMachine(user_id, device_id);
|
||||
const tracked_users = machine.trackedUsers();
|
||||
|
||||
assert.ok(tracked_users instanceof Set, 'Tracket users are stored in a `Set`');
|
||||
assert.equal(tracked_users.size, 0, 'No tracked users by default');
|
||||
});
|
||||
|
||||
await t.test('Update tracked users', async (t) => {
|
||||
const machine = await new OlmMachine(user_id, device_id);
|
||||
const update_tracked_users = await machine.updateTrackedUsers([new UserId('@foo:matrix.org'), new UserId('@bar:matrix.org')]);
|
||||
|
||||
assert.equal(update_tracked_users, undefined, 'Updating tracked users returns nothing');
|
||||
});
|
||||
|
||||
await t.test('Receive sync changes', async (t) => {
|
||||
const machine = await new OlmMachine(user_id, device_id);
|
||||
const to_device_events = JSON.stringify({});
|
||||
const changed_devices = new DeviceLists(
|
||||
[new UserId('@foo:matrix.org'), new UserId('@bar:matrix.org')],
|
||||
[new UserId('@baz:matrix.org'), new UserId('@qux:matrix.org')],
|
||||
);
|
||||
const one_time_key_counts = new Map();
|
||||
one_time_key_counts.set('foo', 42);
|
||||
one_time_key_counts.set('bar', 153);
|
||||
const unused_fallback_keys = new Set();
|
||||
unused_fallback_keys.add('baz');
|
||||
unused_fallback_keys.add('qux');
|
||||
|
||||
const decrypted_to_device = JSON.parse(
|
||||
await machine.receiveSyncChanges(
|
||||
to_device_events,
|
||||
changed_devices,
|
||||
one_time_key_counts,
|
||||
unused_fallback_keys,
|
||||
)
|
||||
);
|
||||
|
||||
assert.deepEqual(decrypted_to_device, {}, 'Nothing to do by default');
|
||||
});
|
||||
|
||||
await t.test('Outgoing requests', async (t) => {
|
||||
const machine = await new OlmMachine(user_id, device_id);
|
||||
const outgoing_requests = await machine.outgoingRequests();
|
||||
|
||||
assert.ok(outgoing_requests instanceof Array, 'Outgoing requests are stored in an `Array`');
|
||||
assert.equal(outgoing_requests.length, 2, 'There is 2 outgoing requests');
|
||||
|
||||
const request1 = outgoing_requests[0];
|
||||
const request2 = outgoing_requests[1];
|
||||
|
||||
assert.ok(request1 instanceof KeysUploadRequest, 'First request is `KeysUploadRequest');
|
||||
assert.ok(request1.request_id.length > 0, 'First request has an ID');
|
||||
assert.ok(JSON.parse(request1.body) instanceof Object, 'First request has a valid body');
|
||||
|
||||
assert.ok(request2 instanceof KeysQueryRequest, 'Second request is `KeysQueryRequest`');
|
||||
assert.ok(request2.request_id.length > 0, 'Second request has an ID');
|
||||
assert.ok(JSON.parse(request2.body) instanceof Object, 'Second request has a valid body');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../../js/pkg/matrix_sdk_crypto');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
test('RequestType', (t) => {
|
||||
assert.equal(RequestType.KeysUpload, 0);
|
||||
assert.equal(RequestType.KeysQuery, 1);
|
||||
assert.equal(RequestType.KeysClaim, 2);
|
||||
assert.equal(RequestType.ToDevice, 3);
|
||||
assert.equal(RequestType.SignatureUpload, 4);
|
||||
assert.equal(RequestType.RoomMessage, 5);
|
||||
assert.equal(RequestType.KeysBackup, 6);
|
||||
});
|
||||
|
||||
test('KeysUploadRequest', (t) => {
|
||||
assert.ok(new KeysUploadRequest());
|
||||
});
|
||||
|
||||
test('KeysQueryRequest', (t) => {
|
||||
assert.ok(new KeysQueryRequest());
|
||||
});
|
||||
|
||||
test('KeysClaimRequest', (t) => {
|
||||
assert.ok(new KeysClaimRequest());
|
||||
});
|
||||
|
||||
test('ToDeviceRequest', (t) => {
|
||||
assert.ok(new ToDeviceRequest());
|
||||
});
|
||||
|
||||
test('SignatureUploadRequest', (t) => {
|
||||
assert.ok(new SignatureUploadRequest());
|
||||
});
|
||||
|
||||
test('RoomMessageRequest', (t) => {
|
||||
assert.ok(new RoomMessageRequest());
|
||||
});
|
||||
|
||||
test('KeysBackupRequest', (t) => {
|
||||
assert.ok(new KeysBackupRequest());
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
const { DeviceLists, UserId } = require('../../js/pkg/matrix_sdk_crypto');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
test('DeviceLists', (t) => {
|
||||
const empty = new DeviceLists([], []);
|
||||
|
||||
assert.equal(empty.isEmpty(), true, 'List is empty');
|
||||
assert.equal(empty.changed().length, 0, 'No user ID changed');
|
||||
assert.equal(empty.left().length, 0, 'No user ID left');
|
||||
|
||||
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
|
||||
|
||||
assert.equal(list.isEmpty(), false, 'List is not empty');
|
||||
|
||||
const changed = list.changed();
|
||||
assert.equal(changed.length, 1, 'There is one user ID changed');
|
||||
assert.equal(changed[0].toString(), '@foo:bar.org', 'The user ID changed is correct');
|
||||
|
||||
const left = list.left();
|
||||
assert.equal(left.length, 1, 'There is one user ID left');
|
||||
assert.equal(left[0].toString(), '@baz:qux.org', 'The user ID left is correct');
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
authors = ["Ivan Enderlin <ivane@element.io>"]
|
||||
description = "Matrix encryption library, for NodeJS"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "Apache-2.0"
|
||||
name = "matrix-sdk-crypto-nodejs"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
version = "0.5.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
docsrs = []
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" }
|
||||
ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = "0.2.0"
|
||||
napi = { git = "https://github.com/Hywan/napi-rs", branch = "feat-tonapivalue-u16", default-features = false, features = ["napi4"] }
|
||||
napi-derive = "2.4.1"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.0.0"
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
build:
|
||||
cd .. && napi build --platform --release
|
||||
test -f ../index.js && mv ../index.js pkg/ || true
|
||||
test -f ../index.d.ts && mv ../index.d.ts pkg/ || true
|
||||
test -f ../matrix-sdk-crypto.*.node && mv ../matrix-sdk-crypto.*.node pkg/ || true
|
||||
echo '*' > pkg/.gitignore
|
||||
|
||||
test:
|
||||
echo 'nop'
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "matrix-sdk-crypto",
|
||||
"version": "0.5.0",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"name": "matrix-sdk-crypto",
|
||||
"triples": {
|
||||
"additional": [
|
||||
"aarch64-apple-darwin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.9.0",
|
||||
"ava": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"artifacts": "napi artifacts",
|
||||
"build": "napi build --platform --release",
|
||||
"test": "ava"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/// Generic error wrapping `napi::Error`.
|
||||
#[derive(Debug)]
|
||||
pub struct Error(napi::Error);
|
||||
|
||||
impl<E> From<E> for Error
|
||||
where
|
||||
E: std::error::Error,
|
||||
{
|
||||
fn from(error: E) -> Self {
|
||||
Self(napi::Error::from_reason(error.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for napi::Error {
|
||||
fn from(value: Error) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//! Types related to events.
|
||||
|
||||
use napi::bindgen_prelude::ToNapiValue;
|
||||
use napi_derive::*;
|
||||
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
|
||||
|
||||
/// Who can see a room's history.
|
||||
#[napi]
|
||||
#[derive(Debug)]
|
||||
pub enum HistoryVisibility {
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they were invited onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *invite* or *join*.
|
||||
Invited,
|
||||
|
||||
/// Previous events are accessible to newly joined members from
|
||||
/// the point they joined the room onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes
|
||||
/// to something other than *join*.
|
||||
Joined,
|
||||
|
||||
/// Previous events are always accessible to newly joined members.
|
||||
///
|
||||
/// All events in the room are accessible, even those sent when
|
||||
/// the member was not a part of the room.
|
||||
Shared,
|
||||
|
||||
/// All events while this is the `HistoryVisibility` value may be
|
||||
/// shared by any participating homeserver with anyone, regardless
|
||||
/// of whether they have ever joined the room.
|
||||
WorldReadable,
|
||||
}
|
||||
|
||||
impl From<HistoryVisibility> for RumaHistoryVisibility {
|
||||
fn from(value: HistoryVisibility) -> Self {
|
||||
use HistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RumaHistoryVisibility> for HistoryVisibility {
|
||||
fn from(value: RumaHistoryVisibility) -> Self {
|
||||
use RumaHistoryVisibility::*;
|
||||
|
||||
match value {
|
||||
Invited => Self::Invited,
|
||||
Joined => Self::Joined,
|
||||
Shared => Self::Shared,
|
||||
WorldReadable => Self::WorldReadable,
|
||||
_ => unreachable!("Unknown variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
|
||||
//! events, keys, rooms, servers, users and URIs.
|
||||
|
||||
use napi_derive::*;
|
||||
|
||||
use crate::errors::*;
|
||||
|
||||
/// A Matrix [user ID].
|
||||
///
|
||||
/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers
|
||||
#[napi]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserId {
|
||||
pub(crate) inner: ruma::OwnedUserId,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl UserId {
|
||||
/// Parse/validate and create a new `UserId`.
|
||||
#[napi(constructor)]
|
||||
pub fn new(id: String) -> Result<UserId, napi::Error> {
|
||||
Ok(Self {
|
||||
inner: ruma::UserId::parse(id.as_str())
|
||||
.map_err(Error::from)
|
||||
.map_err(Into::<napi::Error>::into)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
#[napi]
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the user ID.
|
||||
#[napi(js_name = "serverName")]
|
||||
pub fn server_name(&self) -> ServerName {
|
||||
ServerName { inner: self.inner.server_name().to_owned() }
|
||||
}
|
||||
|
||||
/// Whether this user ID is a historical one.
|
||||
///
|
||||
/// A historical user ID is one that doesn't conform to the latest
|
||||
/// specification of the user ID grammar but is still accepted
|
||||
/// because it was previously allowed.
|
||||
#[napi(getter, js_name = "isHistorical")]
|
||||
pub fn is_historical(&self) -> bool {
|
||||
self.inner.is_historical()
|
||||
}
|
||||
|
||||
/// Return the user ID as a string.
|
||||
#[napi(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix key ID.
|
||||
///
|
||||
/// Device identifiers in Matrix are completely opaque character
|
||||
/// sequences. This type is provided simply for its semantic value.
|
||||
#[napi]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeviceId {
|
||||
pub(crate) inner: ruma::OwnedDeviceId,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl DeviceId {
|
||||
/// Create a new `DeviceId`.
|
||||
#[napi(constructor)]
|
||||
pub fn new(id: String) -> DeviceId {
|
||||
Self { inner: id.into() }
|
||||
}
|
||||
|
||||
/// Return the device ID as a string.
|
||||
#[napi(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix [room ID].
|
||||
///
|
||||
/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
|
||||
#[napi]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoomId {
|
||||
pub(crate) inner: ruma::OwnedRoomId,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl RoomId {
|
||||
/// Parse/validate and create a new `RoomId`.
|
||||
#[napi(constructor)]
|
||||
pub fn new(id: String) -> Result<RoomId, napi::Error> {
|
||||
Ok(Self {
|
||||
inner: ruma::RoomId::parse(id)
|
||||
.map_err(Error::from)
|
||||
.map_err(Into::<napi::Error>::into)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the user's localpart.
|
||||
#[napi]
|
||||
pub fn localpart(&self) -> String {
|
||||
self.inner.localpart().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the server name of the room ID.
|
||||
#[napi(js_name = "serverName")]
|
||||
pub fn server_name(&self) -> ServerName {
|
||||
ServerName { inner: self.inner.server_name().to_owned() }
|
||||
}
|
||||
|
||||
/// Return the room ID as a string.
|
||||
#[napi(js_name = "toString")]
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.inner.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix-spec compliant [server name].
|
||||
///
|
||||
/// It consists of a host and an optional port (separated by a colon if
|
||||
/// present).
|
||||
///
|
||||
/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
|
||||
#[napi]
|
||||
#[derive(Debug)]
|
||||
pub struct ServerName {
|
||||
inner: ruma::OwnedServerName,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl ServerName {
|
||||
/// Parse/validate and create a new `ServerName`.
|
||||
#[napi(constructor)]
|
||||
pub fn new(name: String) -> Result<ServerName, napi::Error> {
|
||||
Ok(Self {
|
||||
inner: ruma::ServerName::parse(name)
|
||||
.map_err(Error::from)
|
||||
.map_err(Into::<napi::Error>::into)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the host of the server name.
|
||||
///
|
||||
/// That is: Return the part of the server before `:<port>` or the
|
||||
/// full server name if there is no port.
|
||||
#[napi]
|
||||
pub fn host(&self) -> String {
|
||||
self.inner.host().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the port of the server name if any.
|
||||
#[napi]
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
self.inner.port()
|
||||
}
|
||||
|
||||
/// Returns true if and only if the server name is an IPv4 or IPv6
|
||||
/// address.
|
||||
#[napi(js_name = "isIpLiteral")]
|
||||
pub fn is_ip_literal(&self) -> bool {
|
||||
self.inner.is_ip_literal()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
//#![warn(missing_docs, missing_debug_implementations)]
|
||||
|
||||
mod errors;
|
||||
pub mod events;
|
||||
//mod future;
|
||||
pub mod identifiers;
|
||||
//pub mod machine;
|
||||
//pub mod requests;
|
||||
//pub mod responses;
|
||||
//pub mod sync_events;
|
||||
|
||||
pub use crate::errors::Error;
|
||||
@@ -48,22 +48,13 @@ thiserror = "1.0.30"
|
||||
tracing = "0.1.34"
|
||||
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.ruma]
|
||||
version = "0.6.1"
|
||||
features = ["client-api-c", "js", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"]
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
ruma = { version = "0.6.2", features = ["client-api-c", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.vodozemac]
|
||||
git = "https://github.com/matrix-org/vodozemac/"
|
||||
rev = "d0e744287a14319c2a9148fef3747548c740fc36"
|
||||
features = ["js"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.ruma]
|
||||
version = "0.6.1"
|
||||
features = ["client-api-c", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.vodozemac]
|
||||
git = "https://github.com/matrix-org/vodozemac/"
|
||||
rev = "d0e744287a14319c2a9148fef3747548c740fc36"
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { version = "0.3.21", default-features = false, features = ["executor"] }
|
||||
@@ -73,8 +64,4 @@ matches = "0.1.9"
|
||||
matrix-sdk-test = { version = "0.5.0", path = "../matrix-sdk-test" }
|
||||
proptest = { version = "1.0.0", default-features = false, features = ["std"] }
|
||||
# required for async_test macro
|
||||
tokio = { version = "1.7.0", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
getrandom = { version = "0.2.6", features = ["js"] }
|
||||
wasm-bindgen-test = "0.3.24"
|
||||
tokio = { version = "1.7.0", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
@@ -24,7 +24,7 @@ mod gossiping;
|
||||
mod identities;
|
||||
mod machine;
|
||||
pub mod olm;
|
||||
mod requests;
|
||||
pub mod requests;
|
||||
mod session_manager;
|
||||
pub mod store;
|
||||
pub mod types;
|
||||
|
||||
@@ -1505,6 +1505,7 @@ impl OlmMachine {
|
||||
&self.backup_machine
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "testing", test))]
|
||||
pub(crate) mod testing {
|
||||
#![allow(dead_code)]
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Modules containing customized request types.
|
||||
|
||||
use std::{collections::BTreeMap, iter, sync::Arc, time::Duration};
|
||||
|
||||
use ruma::{
|
||||
|
||||
+3
-3
@@ -66,7 +66,7 @@ enum WasmFeatureSet {
|
||||
MatrixSdkNoDefault,
|
||||
MatrixSdkBase,
|
||||
MatrixSdkCommon,
|
||||
MatrixSdkCrypto,
|
||||
MatrixSdkCryptoJs,
|
||||
MatrixSdkIndexeddbStoresNoCrypto,
|
||||
MatrixSdkIndexeddbStores,
|
||||
IndexeddbNoCrypto,
|
||||
@@ -208,7 +208,7 @@ fn run_wasm_checks(cmd: Option<WasmFeatureSet>) -> Result<()> {
|
||||
),
|
||||
(WasmFeatureSet::MatrixSdkBase, "-p matrix-sdk-base"),
|
||||
(WasmFeatureSet::MatrixSdkCommon, "-p matrix-sdk-common"),
|
||||
(WasmFeatureSet::MatrixSdkCrypto, "-p matrix-sdk-crypto"),
|
||||
(WasmFeatureSet::MatrixSdkCryptoJs, "-p matrix-sdk-crypto-js"),
|
||||
(
|
||||
WasmFeatureSet::MatrixSdkIndexeddbStoresNoCrypto,
|
||||
"-p matrix-sdk --no-default-features --features indexeddb,rustls-tls",
|
||||
@@ -269,7 +269,7 @@ fn run_wasm_pack_tests(cmd: Option<WasmFeatureSet>) -> Result<()> {
|
||||
),
|
||||
(WasmFeatureSet::MatrixSdkBase, ("matrix-sdk-base", "")),
|
||||
(WasmFeatureSet::MatrixSdkCommon, ("matrix-sdk-common", "")),
|
||||
(WasmFeatureSet::MatrixSdkCrypto, ("matrix-sdk-crypto", "")),
|
||||
(WasmFeatureSet::MatrixSdkCryptoJs, ("matrix-sdk-crypto-js", "")),
|
||||
(
|
||||
WasmFeatureSet::MatrixSdkIndexeddbStoresNoCrypto,
|
||||
("matrix-sdk", "--no-default-features --features indexeddb,rustls-tls --lib"),
|
||||
|
||||
Reference in New Issue
Block a user