22
33use std:: { collections:: BTreeMap , ops:: Deref } ;
44
5+ use futures_util:: StreamExt ;
56use js_sys:: { Array , Function , Map , Promise , Set } ;
67use ruma:: { serde:: Raw , DeviceKeyAlgorithm , OwnedTransactionId , UInt } ;
78use serde_json:: { json, Value as JsonValue } ;
9+ use tracing:: warn;
810use wasm_bindgen:: prelude:: * ;
11+ use wasm_bindgen_futures:: { spawn_local, JsFuture } ;
912
1013use crate :: {
1114 device, encryption,
@@ -15,7 +18,9 @@ use crate::{
1518 olm, requests,
1619 requests:: { OutgoingRequest , ToDeviceRequest } ,
1720 responses:: { self , response_from_string} ,
18- store, sync_events, types, verification, vodozemac,
21+ store,
22+ store:: RoomKeyInfo ,
23+ sync_events, types, verification, vodozemac,
1924} ;
2025
2126/// State machine implementation of the Olm/Megolm encryption protocol
@@ -768,6 +773,25 @@ impl OlmMachine {
768773 ) ?) ?)
769774 }
770775
776+ /// Register a callback which will be called whenever there is an update to
777+ /// a room key.
778+ ///
779+ /// `callback` should be a function that takes a single argument (an array
780+ /// of {@link RoomKeyInfo}) and returns a Promise.
781+ #[ wasm_bindgen( js_name = "registerRoomKeyUpdatedCallback" ) ]
782+ pub async fn register_room_key_updated_callback ( & self , callback : Function ) {
783+ let stream = self . inner . store ( ) . room_keys_received_stream ( ) ;
784+
785+ // fire up a promise chain which will call `cb` on each result from the stream
786+ spawn_local ( async move {
787+ // take a reference to `callback` (which we then pass into the closure), to stop
788+ // the callback being moved into the closure (which would mean we could only
789+ // call the closure once)
790+ let callback_ref = & callback;
791+ stream. for_each ( move |item| send_room_key_info_to_callback ( callback_ref, item) ) . await ;
792+ } ) ;
793+ }
794+
771795 /// Shut down the `OlmMachine`.
772796 ///
773797 /// The `OlmMachine` cannot be used after this method has been called.
@@ -776,3 +800,41 @@ impl OlmMachine {
776800 /// connections.
777801 pub fn close ( self ) { }
778802}
803+
804+ // helper for register_room_key_received_callback: wraps the key info
805+ // into our own RoomKeyInfo struct, and passes it into the javascript
806+ // function
807+ async fn send_room_key_info_to_callback (
808+ callback : & Function ,
809+ room_key_info : Vec < matrix_sdk_crypto:: store:: RoomKeyInfo > ,
810+ ) {
811+ let rki: Array = room_key_info. into_iter ( ) . map ( RoomKeyInfo :: from) . map ( JsValue :: from) . collect ( ) ;
812+ match promise_result_to_future ( callback. call1 ( & JsValue :: NULL , & rki) ) . await {
813+ Ok ( _) => ( ) ,
814+ Err ( e) => {
815+ warn ! ( "Error calling room-key-received callback: {:?}" , e) ;
816+ }
817+ }
818+ }
819+
820+ /// Given a result from a javascript function which returns a Promise (or throws
821+ /// an exception before returning one), convert the result to a rust Future
822+ /// which completes with the result of the promise
823+ async fn promise_result_to_future ( res : Result < JsValue , JsValue > ) -> Result < JsValue , JsValue > {
824+ match res {
825+ Ok ( retval) => {
826+ if !retval. has_type :: < Promise > ( ) {
827+ panic ! ( "not a promise" ) ;
828+ }
829+ let prom: Promise = retval. dyn_into ( ) . map_err ( |v| {
830+ JsError :: new ( & format ! ( "function returned a non-Promise value {v:?}" ) )
831+ } ) ?;
832+ JsFuture :: from ( prom) . await
833+ }
834+ Err ( e) => {
835+ // the function threw an exception before it returned the promise. We can just
836+ // return the error as an error result.
837+ Err ( e)
838+ }
839+ }
840+ }
0 commit comments