diff --git a/crates/matrix-sdk-crypto/src/js/machine.rs b/crates/matrix-sdk-crypto/src/js/machine.rs index 2cdb06be0..a0b9035d5 100644 --- a/crates/matrix-sdk-crypto/src/js/machine.rs +++ b/crates/matrix-sdk-crypto/src/js/machine.rs @@ -9,10 +9,9 @@ use serde_json::value::RawValue as RawJsonValue; use wasm_bindgen::prelude::*; use crate::js::{ - events, + downcast, events, future::future_to_promise, - identifiers, - requests::RequestType, + identifiers, requests, responses::{self, response_from_string}, sync_events, }; @@ -170,11 +169,11 @@ impl OlmMachine { pub fn mark_request_as_sent( &self, request_id: &str, - request_type: RequestType, + request_type: requests::RequestType, response: &str, ) -> Result { let transaction_id = OwnedTransactionId::from(request_id); - let response = response_from_string(response).map_err(JsError::from)?; + let response = response_from_string(response)?; let incoming_response = responses::OwnedResponse::try_from((request_type, response))?; let me = self.inner.clone(); @@ -209,9 +208,8 @@ impl OlmMachine { content: &str, ) -> Result { let room_id = room_id.inner.clone(); - let content: Box = serde_json::from_str(content).map_err(JsError::from)?; - let content = - AnyMessageLikeEventContent::from_parts(event_type, &content).map_err(JsError::from)?; + let content: Box = serde_json::from_str(content)?; + let content = AnyMessageLikeEventContent::from_parts(event_type, &content)?; let me = self.inner.clone(); @@ -235,9 +233,9 @@ impl OlmMachine { /// Get to-device requests to share a group session with users in a room. /// - /// `room_id` is the room ID. `users` is an array of strings - /// representing user IDs. `encryption_settings` are an - /// `EncryptionSettings` object. + /// `room_id` is the room ID. `users` is an array of `UserId` + /// objects. `encryption_settings` are an `EncryptionSettings` + /// object. #[wasm_bindgen(js_name = "shareGroupSession")] pub fn share_group_session( &self, @@ -248,19 +246,7 @@ impl OlmMachine { let room_id = room_id.inner.clone(); let users = users .iter() - .map(|user| { - let user = user - .as_string() - .ok_or_else(|| JsError::new("Given user ID is not a string"))?; - let user = ruma::UserId::parse(&user).map_err(|error| { - JsError::new(&format!( - "Given user ID `{}` has an invalid syntax: {}", - user, error - )) - })?; - - Ok(user) - }) + .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) .collect::, JsError>>()?; let encryption_settings = crate::olm::EncryptionSettings::from(encryption_settings); @@ -270,13 +256,63 @@ impl OlmMachine { Ok(serde_json::to_string( &me.share_group_session( &room_id, - users.iter().by_ref().map(AsRef::as_ref), + users.iter().map(AsRef::as_ref), encryption_settings, ) .await?, )?) })) } + + /// Get the a key claiming request for the user/device pairs that + /// we are missing Olm sessions for. + /// + /// Returns `NULL` if no key claiming request needs to be sent + /// out, otherwise it returns an `Array` where the first key is + /// the transaction ID as a string, and the second key is the keys + /// claim request serialized to JSON. + /// + /// Sessions need to be established between devices so group + /// sessions for a room can be shared with them. + /// + /// This should be called every time a group session needs to be + /// shared as well as between sync calls. After a sync some + /// devices may request room keys without us having a valid Olm + /// session with them, making it impossible to server the room key + /// request, thus it’s necessary to check for missing sessions + /// between sync as well. + /// + /// Note: Care should be taken that only one such request at a + /// time is in flight, e.g. using a lock. + /// + /// The response of a successful key claiming requests needs to be + /// passed to the `OlmMachine` with the `mark_request_as_sent`. + /// + /// `users` represents the list of users that we should check if + /// we lack a session with one of their devices. This can be an + /// empty iterator when calling this method between sync requests. + #[wasm_bindgen(js_name = "getMissingSessions")] + pub fn get_missing_sessions(&self, users: &Array) -> Result { + let users = users + .iter() + .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) + .collect::, JsError>>()?; + + let me = self.inner.clone(); + + Ok(future_to_promise(async move { + match me.get_missing_sessions(users.iter().map(AsRef::as_ref)).await? { + Some((transaction_id, keys_claim_request)) => { + Ok(JsValue::from(requests::KeysClaimRequest::try_from(( + transaction_id.to_string(), + &keys_claim_request, + ))?)) + } + + None => Ok(JsValue::NULL), + } + })) + } } #[wasm_bindgen] diff --git a/crates/matrix-sdk-crypto/src/js/requests.rs b/crates/matrix-sdk-crypto/src/js/requests.rs index 4d08356e0..ea871dd1c 100644 --- a/crates/matrix-sdk-crypto/src/js/requests.rs +++ b/crates/matrix-sdk-crypto/src/js/requests.rs @@ -1,8 +1,18 @@ use js_sys::JsString; -use serde_json::json; +use ruma::api::client::keys::{ + claim_keys::v3::Request as RumaKeysClaimRequest, + upload_keys::v3::Request as RumaKeysUploadRequest, + upload_signatures::v3::Request as RumaSignatureUploadRequest, +}; use wasm_bindgen::prelude::*; -use crate::{OutgoingRequest, OutgoingRequests}; +use crate::{ + requests::{ + KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest, + RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest, + }, + OutgoingRequest, OutgoingRequests, +}; /// Data for a request to the `upload_keys` API endpoint. /// @@ -133,6 +143,37 @@ pub struct KeysBackupRequest { pub body: JsString, } +macro_rules! request { + ($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => { + impl TryFrom<(String, &$ruma_request)> for $request { + type Error = serde_json::Error; + + fn try_from( + (request_id, request): (String, &$ruma_request), + ) -> Result { + let mut map = serde_json::Map::new(); + $( + map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap()); + )+ + let value = serde_json::Value::Object(map); + + Ok($request { + request_id: request_id.into(), + body: serde_json::to_string(&value)?.into(), + }) + } + } + }; +} + +request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys); +request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token); +request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys); +request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages); +request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys); +request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content); +request!(KeysBackupRequest from RumaKeysBackupRequest maps fields version, rooms); + // JavaScript has no complex enums like Rust. To return structs of // different types, we have no choice that hidding everything behind a // `JsValue`. @@ -140,93 +181,35 @@ impl TryFrom for JsValue { type Error = serde_json::Error; fn try_from(outgoing_request: OutgoingRequest) -> Result { - let request_id: JsString = outgoing_request.request_id().to_string().into(); + let request_id = outgoing_request.request_id().to_string(); Ok(match outgoing_request.request() { OutgoingRequests::KeysUpload(request) => { - let body = json!({ - "device_keys": request.device_keys, - "one_time_keys": request.one_time_keys, - }); - - JsValue::from(KeysUploadRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(KeysUploadRequest::try_from((request_id, request))?) } OutgoingRequests::KeysQuery(request) => { - let body = json!({ - "timeout": request.timeout, - "device_keys": request.device_keys, - "token": request.token, - }); - - JsValue::from(KeysQueryRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(KeysQueryRequest::try_from((request_id, request))?) } OutgoingRequests::KeysClaim(request) => { - let body = json!({ - "timeout": request.timeout, - "one_time_keys": request.one_time_keys, - }); - - JsValue::from(KeysClaimRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(KeysClaimRequest::try_from((request_id, request))?) } OutgoingRequests::ToDeviceRequest(request) => { - let body = json!({ - "event_type": request.event_type, - "txn_id": request.txn_id, - "messages": request.messages, - }); - - JsValue::from(ToDeviceRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(ToDeviceRequest::try_from((request_id, request))?) } OutgoingRequests::SignatureUpload(request) => { - let body = json!({ - "signed_keys": request.signed_keys, - }); - - JsValue::from(SignatureUploadRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(SignatureUploadRequest::try_from((request_id, request))?) } OutgoingRequests::RoomMessage(request) => { - let body = json!({ - "room_id": request.room_id, - "txn_id": request.txn_id, - "content": request.content, - }); - - JsValue::from(RoomMessageRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(RoomMessageRequest::try_from((request_id, request))?) } OutgoingRequests::KeysBackup(request) => { - let body = json!({ - "version": request.version, - "rooms": request.rooms, - }); - - JsValue::from(KeysBackupRequest { - request_id, - body: serde_json::to_string(&body)?.into(), - }) + JsValue::from(KeysBackupRequest::try_from((request_id, request))?) } }) }