-
Notifications
You must be signed in to change notification settings - Fork 7
/
machine.rs
1595 lines (1461 loc) · 60.7 KB
/
machine.rs
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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! The crypto specific Olm objects.
use std::{
collections::{BTreeMap, HashSet},
ops::Deref,
pin::{pin, Pin},
time::Duration,
};
use futures_util::{pin_mut, Stream, StreamExt};
use js_sys::{Array, Function, JsString, Map, Promise, Set};
use matrix_sdk_common::ruma::{
self, events::secret::request::SecretName, serde::Raw, DeviceKeyAlgorithm, OwnedDeviceId,
OwnedTransactionId, OwnedUserId, UInt,
};
use matrix_sdk_crypto::{
backups::MegolmV1BackupKey,
olm::BackedUpRoomKey,
store::{DeviceChanges, IdentityChanges},
types::RoomKeyBackupInfo,
CryptoStoreError, EncryptionSyncChanges, GossippedSecret,
};
use serde::{ser::SerializeSeq, Serialize, Serializer};
use serde_json::json;
use tracing::warn;
use wasm_bindgen::{convert::TryFromJsValue, prelude::*};
use wasm_bindgen_futures::{spawn_local, JsFuture};
use crate::{
backup::{BackupDecryptionKey, BackupKeys, RoomKeyCounts},
device, encryption,
error::MegolmDecryptionError,
future::{future_to_promise, future_to_promise_with_custom_error},
identifiers, identities, olm, requests,
requests::{outgoing_request_to_js_value, CrossSigningBootstrapRequests, ToDeviceRequest},
responses::{self, response_from_string},
store,
store::{RoomKeyInfo, StoreHandle},
sync_events,
types::{self, RoomKeyImportResult, RoomSettings, SignatureVerification},
verification, vodozemac,
};
/// 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 {
/// Constructor will always fail. To create a new `OlmMachine`, please use
/// the `initialize` method.
///
/// Why this pattern? `initialize` returns a `Promise`. Returning a
// `Promise` from a constructor is not idiomatic in JavaScript.
#[wasm_bindgen(constructor)]
pub fn new() -> Result<OlmMachine, JsError> {
Err(JsError::new("To build an `OlmMachine`, please use the `initialize` method"))
}
/// Create a new `OlmMachine`.
///
/// The created machine will keep the encryption keys either in a IndexedDB
/// based store, or in a memory store and once the objects is dropped,
/// the keys will be lost.
///
/// # Arguments
///
/// * `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.
///
/// * `store_name` - The name that should be used to open the IndexedDB
/// based database. If this isn't provided, a memory-only store will be
/// used. *Note* the memory-only store will lose your E2EE keys when the
/// `OlmMachine` gets dropped.
///
/// * `store_passphrase` - The passphrase that should be used to encrypt the
/// IndexedDB-based store.
pub async fn initialize(
user_id: &identifiers::UserId,
device_id: &identifiers::DeviceId,
store_name: Option<String>,
store_passphrase: Option<String>,
) -> Result<JsValue, JsValue> {
let user_id = user_id.inner.clone();
let device_id = device_id.inner.clone();
let store_handle = StoreHandle::open(store_name, store_passphrase)
.await
.map_err(|e| JsError::from(&*e))?;
Self::init_helper(user_id, device_id, store_handle).await
}
/// Create a new `OlmMachine` backed by an existing store.
///
/// # Arguments
///
/// * `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.
///
/// * `store_handle` - the connection to the crypto store to be used for
/// this machine.
#[wasm_bindgen(js_name = "initFromStore")]
pub async fn init_from_store(
user_id: &identifiers::UserId,
device_id: &identifiers::DeviceId,
store_handle: &StoreHandle,
) -> Result<JsValue, JsValue> {
let user_id = user_id.inner.clone();
let device_id = device_id.inner.clone();
Self::init_helper(user_id, device_id, store_handle.clone()).await
}
async fn init_helper(
user_id: OwnedUserId,
device_id: OwnedDeviceId,
store_handle: StoreHandle,
) -> Result<JsValue, JsValue> {
Ok(OlmMachine {
inner: matrix_sdk_crypto::OlmMachine::with_store(
user_id.as_ref(),
device_id.as_ref(),
store_handle,
)
.await
.map_err(JsError::from)?,
}
.into())
}
/// The unique user ID that owns this `OlmMachine` instance.
#[wasm_bindgen(getter, js_name = "userId")]
pub fn user_id(&self) -> identifiers::UserId {
identifiers::UserId::from(self.inner.user_id().to_owned())
}
/// The unique device ID that identifies this `OlmMachine`.
#[wasm_bindgen(getter, js_name = "deviceId")]
pub fn device_id(&self) -> identifiers::DeviceId {
identifiers::DeviceId::from(self.inner.device_id().to_owned())
}
/// Get the public parts of our Olm identity keys.
#[wasm_bindgen(getter, js_name = "identityKeys")]
pub fn identity_keys(&self) -> vodozemac::IdentityKeys {
self.inner.identity_keys().into()
}
/// Get the display name of our own device.
#[wasm_bindgen(getter, js_name = "displayName")]
pub fn display_name(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move { Ok(me.display_name().await?) })
}
/// Whether automatic transmission of room key requests is enabled.
///
/// Room key requests allow the device to request room keys that it might
/// have missed in the original share using `m.room_key_request`
/// events.
#[wasm_bindgen(getter, js_name = "roomKeyRequestsEnabled")]
pub fn are_room_key_requests_enabled(&self) -> bool {
self.inner.are_room_key_requests_enabled()
}
/// Enable or disable automatic transmission of room key requests.
#[wasm_bindgen(setter, js_name = "roomKeyRequestsEnabled")]
pub fn set_room_key_requests_enabled(&self, enabled: bool) {
self.inner.set_room_key_requests_enabled(enabled)
}
/// Whether room key forwarding is enabled.
///
/// If room key forwarding is enabled, we will automatically reply to
/// incoming `m.room_key_request` messages from verified devices by
/// forwarding the requested key (if we have it).
#[wasm_bindgen(getter, js_name = "roomKeyForwardingEnabled")]
pub fn is_room_key_forwarding_enabled(&self) -> bool {
self.inner.is_room_key_forwarding_enabled()
}
/// Enable or disable room key forwarding.
#[wasm_bindgen(setter, js_name = "roomKeyForwardingEnabled")]
pub fn set_room_key_forwarding_enabled(&self, enabled: bool) {
self.inner.set_room_key_forwarding_enabled(enabled)
}
/// Get the list of users whose devices we are currently tracking.
///
/// A user can be marked for tracking using the
/// [`update_tracked_users`](#method.update_tracked_users) method.
///
/// Returns a `Set<UserId>`.
#[wasm_bindgen(js_name = "trackedUsers")]
pub fn tracked_users(&self) -> Result<Promise, JsError> {
let set = Set::new(&JsValue::UNDEFINED);
let me = self.inner.clone();
Ok(future_to_promise(async move {
for user in me.tracked_users().await? {
set.add(&identifiers::UserId::from(user).into());
}
Ok(set)
}))
}
/// Update the list of tracked users.
///
/// The OlmMachine maintains a list of users whose devices we are keeping
/// track of: these are known as "tracked users". These must be users
/// that we share a room with, so that the server sends us updates for
/// their device lists.
///
/// # Arguments
///
/// * `users` - An array of user ids that should be added to the list of
/// tracked users
///
/// Any users that hadn't been seen before will be flagged for a key query
/// immediately, and whenever `receive_sync_changes` receives a
/// "changed" notification for that user in the future.
///
/// Users that were already in the list are unaffected.
///
/// Items inside `users` will be invalidated by this method. Be careful not
/// to use the `UserId`s after this method has been called.
#[wasm_bindgen(js_name = "updateTrackedUsers")]
pub fn update_tracked_users(&self, users: Vec<identifiers::UserId>) -> Promise {
let users = users.iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
let me = self.inner.clone();
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`.
///
/// # Arguments
///
/// * `to_device_events`: the JSON-encoded to-device evens from the `/sync`
/// response
/// * `changed_devices`: the mapping of changed and left devices, from the
/// `/sync` response
/// * `one_time_keys_counts`: The number of one-time keys on the server,
/// from the `/sync` response. A `Map` from string (encryption algorithm)
/// to number (number of keys).
/// * `unused_fallback_keys`: Optionally, a `Set` of unused fallback keys on
/// the server, from the `/sync` response. If this is set, it is used to
/// determine if new fallback keys should be uploaded.
///
/// # Returns
///
/// A list of JSON strings, containing the decrypted to-device events.
#[wasm_bindgen(js_name = "receiveSyncChanges")]
pub fn receive_sync_changes(
&self,
to_device_events: &str,
changed_devices: &sync_events::DeviceLists,
one_time_keys_counts: &Map,
unused_fallback_keys: Option<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_keys_counts: BTreeMap<DeviceKeyAlgorithm, UInt> = one_time_keys_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();
// Convert the unused_fallback_keys JS Set to a `Vec<DeviceKeyAlgorithm>`
let unused_fallback_keys: Option<Vec<DeviceKeyAlgorithm>> =
unused_fallback_keys.map(|fallback_keys| {
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 {
// we discard the list of updated room keys in the result; JS applications are
// expected to use register_room_key_updated_callback to receive updated room
// keys.
let (decrypted_to_device_events, _) = me
.receive_sync_changes(EncryptionSyncChanges {
to_device_events,
changed_devices: &changed_devices,
one_time_keys_counts: &one_time_keys_counts,
unused_fallback_keys: unused_fallback_keys.as_deref(),
// matrix-sdk-crypto does not (currently) use `next_batch_token`.
next_batch_token: None,
})
.await?;
Ok(serde_json::to_string(&decrypted_to_device_events)?)
}))
}
/// Get the outgoing requests that need to be sent out.
///
/// This returns a list of values, each of which can be any of:
/// * {@link KeysUploadRequest},
/// * {@link KeysQueryRequest},
/// * {@link KeysClaimRequest},
/// * {@link ToDeviceRequest},
/// * {@link SignatureUploadRequest},
/// * {@link RoomMessageRequest}, or
/// * {@link KeysBackupRequest}.
///
/// Those requests need to be sent out to the server and the
/// responses need to be passed back to the state machine
/// using {@link OlmMachine.markRequestAsSent}.
#[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(outgoing_request_to_js_value)
.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.
///
/// **Note**: A room key needs to be shared with the group of users that are
/// members in the given room. If this is not done this method will panic.
///
/// The usual flow to encrypt an event using this state machine is as
/// follows:
///
/// 1. Get the one-time key claim request to establish 1:1 Olm sessions for
/// the room members of the room we wish to participate in. This is done
/// using the [`get_missing_sessions()`](Self::get_missing_sessions)
/// method. This method call should be locked per call.
///
/// 2. Share a room key with all the room members using the
/// [`share_room_key()`](Self::share_room_key). This method call should
/// be locked per room.
///
/// 3. Encrypt the event using this method.
///
/// 4. Send the encrypted event to the server.
///
/// After the room key is shared steps 1 and 2 will become noops, unless
/// there's some changes in the room membership or in the list of devices a
/// member has.
///
///
/// `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 = 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, event_type.as_ref(), &content).await?,
)?)
}))
}
/// Decrypt an event from a room timeline.
///
/// # Arguments
///
/// * `event`, the event that should be decrypted.
/// * `room_id`, the ID of the room where the event was sent to.
///
/// # Returns
///
/// A `Promise` which resolves to a {@link DecryptedRoomEvent} instance, or
/// rejects with a {@link MegolmDecryptionError} instance.
#[wasm_bindgen(js_name = "decryptRoomEvent")]
pub fn decrypt_room_event(
&self,
event: &str,
room_id: &identifiers::RoomId,
) -> Result<Promise, JsError> {
let event: Raw<_> = serde_json::from_str(event)?;
let room_id = room_id.inner.clone();
let me = self.inner.clone();
Ok(future_to_promise_with_custom_error::<
_,
responses::DecryptedRoomEvent,
MegolmDecryptionError,
>(async move {
let room_event = me
.decrypt_room_event(&event, room_id.as_ref())
.await
.map_err(MegolmDecryptionError::from)?;
Ok(responses::DecryptedRoomEvent::from(room_event))
}))
}
/// Get encryption info for a decrypted timeline event.
///
/// This recalculates the `EncryptionInfo` data that is returned by
/// `decryptRoomEvent`, based on the current
/// verification status of the sender, etc.
///
/// Returns an error for an unencrypted event.
///
/// # Arguments
///
/// * `event` - The event to get information for.
/// * `room_id` - The ID of the room where the event was sent to.
///
/// # Returns
///
/// {@link EncryptionInfo}
#[wasm_bindgen(js_name = "getRoomEventEncryptionInfo")]
pub fn get_room_event_encryption_info(
&self,
event: &str,
room_id: &identifiers::RoomId,
) -> Result<Promise, JsError> {
let event: Raw<_> = serde_json::from_str(event)?;
let room_id = room_id.inner.clone();
let me = self.inner.clone();
Ok(future_to_promise(async move {
let encryption_info =
me.get_room_event_encryption_info(&event, room_id.as_ref()).await?;
Ok(responses::EncryptionInfo::from(encryption_info))
}))
}
/// Get the status of the private cross signing keys.
///
/// This can be used to check which private cross signing keys we
/// have stored locally.
#[wasm_bindgen(js_name = "crossSigningStatus")]
pub fn cross_signing_status(&self) -> Promise {
let me = self.inner.clone();
future_to_promise::<_, olm::CrossSigningStatus>(async move {
Ok(me.cross_signing_status().await.into())
})
}
/// Export all the private cross signing keys we have.
///
/// The export will contain the seeds for the ed25519 keys as
/// unpadded base64 encoded strings.
///
/// Returns `null` if we don’t have any private cross signing keys;
/// otherwise returns a `CrossSigningKeyExport`.
#[wasm_bindgen(js_name = "exportCrossSigningKeys")]
pub fn export_cross_signing_keys(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
Ok(me.export_cross_signing_keys().await?.map(store::CrossSigningKeyExport::from))
})
}
/// Import our private cross signing keys.
///
/// The keys should be provided as unpadded-base64-encoded strings.
///
/// Returns a `CrossSigningStatus`.
#[wasm_bindgen(js_name = "importCrossSigningKeys")]
pub fn import_cross_signing_keys(
&self,
master_key: Option<String>,
self_signing_key: Option<String>,
user_signing_key: Option<String>,
) -> Promise {
let me = self.inner.clone();
let export = matrix_sdk_crypto::store::CrossSigningKeyExport {
master_key,
self_signing_key,
user_signing_key,
};
future_to_promise(async move {
Ok(me.import_cross_signing_keys(export).await.map(olm::CrossSigningStatus::from)?)
})
}
/// Create a new cross signing identity and get the upload request
/// to push the new public keys to the server.
///
/// Warning: This will delete any existing cross signing keys that
/// might exist on the server and thus will reset the trust
/// between all the devices.
///
/// Uploading these keys will require user interactive auth.
///
/// # Arguments
///
/// * `reset`, whether the method should create a new identity or use the
/// existing one during the request. If set to true, the request will
/// attempt to upload a new identity. If set to false, the request will
/// attempt to upload the existing identity. Since the uploading process
/// requires user interactive authentication, which involves sending out
/// the same request multiple times, setting this argument to false
/// enables you to reuse the same request.
///
/// Returns a {@link CrossSigningBootstrapRequests}.
#[wasm_bindgen(js_name = "bootstrapCrossSigning")]
pub fn bootstrap_cross_signing(&self, reset: bool) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
let requests = me.bootstrap_cross_signing(reset).await?;
Ok(CrossSigningBootstrapRequests::try_from(requests)?)
})
}
/// Get the cross signing user identity of a user.
///
/// Returns a promise for an `OwnUserIdentity`, a `UserIdentity`, or
/// `undefined`.
#[wasm_bindgen(js_name = "getIdentity")]
pub fn get_identity(&self, user_id: &identifiers::UserId) -> Promise {
let me = self.inner.clone();
let user_id = user_id.inner.clone();
future_to_promise(async move {
// wait for up to a second for any in-flight device list requests to complete.
// The reason for this isn't so much to avoid races, but to make testing easier.
Ok(me
.get_identity(user_id.as_ref(), Some(Duration::from_secs(1)))
.await?
.map(identities::UserIdentities::from))
})
}
/// Sign the given message using our device key and if available
/// cross-signing master key.
pub fn sign(&self, message: String) -> Promise {
let me = self.inner.clone();
future_to_promise::<_, types::Signatures>(
async move { Ok(me.sign(&message).await?.into()) },
)
}
/// 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.discard_room_key(&room_id).await?) })
}
/// Get to-device requests to share a room key with users in a room.
///
/// `room_id` is the room ID. `users` is an array of `UserId`
/// objects. `encryption_settings` are an `EncryptionSettings`
/// object.
///
/// Note: Care should be taken that only one such request at a
/// time is in flight for the same room, e.g. using a lock.
///
/// Returns an array of `ToDeviceRequest`s.
///
/// Items inside `users` will be invalidated by this method. Be careful not
/// to use the `UserId`s after this method has been called.
#[wasm_bindgen(js_name = "shareRoomKey")]
pub fn share_room_key(
&self,
room_id: &identifiers::RoomId,
users: Vec<identifiers::UserId>,
encryption_settings: &encryption::EncryptionSettings,
) -> Promise {
let room_id = room_id.inner.clone();
let users = users.iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
let encryption_settings =
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
let me = self.inner.clone();
future_to_promise(async move {
let to_device_requests = me
.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
.await?;
// convert each request to our own ToDeviceRequest struct, and then wrap it in a
// JsValue.
//
// Then collect the results into a javascript Array, throwing any errors into
// the promise.
Ok(to_device_requests
.into_iter()
.map(|td| ToDeviceRequest::try_from(td.deref()).map(JsValue::from))
.collect::<Result<Array, _>>()?)
})
}
/// Generate an "out-of-band" key query request for the given set of users.
///
/// This can be useful if we need the results from `getIdentity` or
/// `getUserDevices` to be as up-to-date as possible.
///
/// Returns a `KeysQueryRequest` object. The response of the request should
/// be passed to the `OlmMachine` with the `mark_request_as_sent`.
///
/// Items inside `users` will be invalidated by this method. Be careful not
/// to use the `UserId`s after this method has been called.
#[wasm_bindgen(js_name = "queryKeysForUsers")]
pub fn query_keys_for_users(
&self,
users: Vec<identifiers::UserId>,
) -> Result<requests::KeysQueryRequest, JsError> {
let users = users.iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
let (request_id, request) =
self.inner.query_keys_for_users(users.iter().map(AsRef::as_ref));
Ok(requests::KeysQueryRequest::try_from((request_id.to_string(), &request))?)
}
/// 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 a `KeysClaimRequest` object.
///
/// 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.
///
/// Items inside `users` will be invalidated by this method. Be careful not
/// to use the `UserId`s after this method has been called.
#[wasm_bindgen(js_name = "getMissingSessions")]
pub fn get_missing_sessions(&self, users: Vec<identifiers::UserId>) -> Promise {
let users = users.iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
let me = self.inner.clone();
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),
}
})
}
/// Get a map holding all the devices of a user.
///
/// ### Parameters
///
/// * `user_id` - The unique ID of the user that the device belongs to.
///
/// * `timeout_secs` - The amount of time we should wait for a `/keys/query`
/// response before returning if the user's device list has been marked as
/// stale. **Note**, this assumes that the requests from {@link
/// OlmMachine.outgoingRequests} are being processed and sent out.
///
/// If unset, we will return immediately even if the device list is stale.
///
/// ### Returns
///
/// A {@link UserDevices} object.
#[wasm_bindgen(js_name = "getUserDevices")]
pub fn get_user_devices(
&self,
user_id: &identifiers::UserId,
timeout_secs: Option<f64>,
) -> Promise {
let user_id = user_id.inner.clone();
let timeout_duration = timeout_secs.map(Duration::from_secs_f64);
let me = self.inner.clone();
future_to_promise::<_, device::UserDevices>(async move {
Ok(me.get_user_devices(&user_id, timeout_duration).await.map(Into::into)?)
})
}
/// Get a specific device of a user.
///
/// ### Parameters
///
/// * `user_id` - The unique ID of the user that the device belongs to.
///
/// * `device_id` - The unique ID of the device.
///
/// * `timeout_secs` - The amount of time we should wait for a `/keys/query`
/// response before returning if the user's device list has been marked as
/// stale. **Note**, this assumes that the requests from {@link
/// OlmMachine.outgoingRequests} are being processed and sent out.
///
/// If unset, we will return immediately even if the device list is stale.
///
/// ### Returns
///
/// If the device is known, a {@link Device}. Otherwise, `undefined`.
#[wasm_bindgen(js_name = "getDevice")]
pub fn get_device(
&self,
user_id: &identifiers::UserId,
device_id: &identifiers::DeviceId,
timeout_secs: Option<f64>,
) -> Promise {
let user_id = user_id.inner.clone();
let device_id = device_id.inner.clone();
let timeout_duration = timeout_secs.map(Duration::from_secs_f64);
let me = self.inner.clone();
future_to_promise::<_, Option<device::Device>>(async move {
Ok(me.get_device(&user_id, &device_id, timeout_duration).await?.map(Into::into))
})
}
/// Get a verification object for the given user ID with the given
/// flow ID (a to-device request ID if the verification has been
/// requested by a to-device request, or a room event ID if the
/// verification has been requested by a room event).
///
/// It returns a “`Verification` object”, which is either a `Sas`
/// or `Qr` object.
#[wasm_bindgen(js_name = "getVerification")]
pub fn get_verification(
&self,
user_id: &identifiers::UserId,
flow_id: &str,
) -> Result<JsValue, JsError> {
self.inner
.get_verification(&user_id.inner, flow_id)
.map(verification::Verification)
.map(JsValue::try_from)
.transpose()
.map(JsValue::from)
}
/// Get a verification request object with the given flow ID.
#[wasm_bindgen(js_name = "getVerificationRequest")]
pub fn get_verification_request(
&self,
user_id: &identifiers::UserId,
flow_id: &str,
) -> Option<verification::VerificationRequest> {
self.inner.get_verification_request(&user_id.inner, flow_id).map(Into::into)
}
/// Get all the verification requests of a given user.
#[wasm_bindgen(js_name = "getVerificationRequests")]
pub fn get_verification_requests(&self, user_id: &identifiers::UserId) -> Array {
self.inner
.get_verification_requests(&user_id.inner)
.into_iter()
.map(verification::VerificationRequest::from)
.map(JsValue::from)
.collect()
}
/// Receive a verification event.
///
/// This method can be used to pass verification events that are happening
/// in rooms to the `OlmMachine`. The event should be in the decrypted form.
#[wasm_bindgen(js_name = "receiveVerificationEvent")]
pub fn receive_verification_event(
&self,
event: &str,
room_id: &identifiers::RoomId,
) -> Result<Promise, JsError> {
let room_id = room_id.inner.clone();
let event: ruma::events::AnySyncMessageLikeEvent = serde_json::from_str(event)?;
let event = event.into_full_event(room_id);
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(me.receive_verification_event(&event).await.map(|_| JsValue::UNDEFINED)?)
}))
}
/// Export the keys that match the given predicate.
///
/// `predicate` is a closure that will be called for every known
/// `InboundGroupSession`, which represents a room key. If the closure
/// returns `true`, the `InboundGroupSession` will be included in the
/// export; otherwise it won't.
///
/// Returns a Promise containing a Result containing a String which is a
/// JSON-encoded array of ExportedRoomKey objects.
#[wasm_bindgen(js_name = "exportRoomKeys")]
pub fn export_room_keys(&self, predicate: Function) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
stream_to_json_array(pin!(
me.store()
.export_room_keys_stream(|session| {
let session = session.clone();
predicate
.call1(&JsValue::NULL, &olm::InboundGroupSession::from(session).into())
.expect("Predicate function passed to `export_room_keys` failed")
.as_bool()
.unwrap_or(false)
})
.await?,
))
.await
})
}
/// Import the given room keys into our store.
///
/// Mostly, a deprecated alias for `importExportedRoomKeys`, though the
/// return type is different.
///
/// Returns a String containing a JSON-encoded object, holding three
/// properties:
/// * `total_count` (the total number of keys found in the export data).
/// * `imported_count` (the number of keys that were imported).
/// * `keys` (the keys that were imported; a map from room id to a map of
/// the sender key to a list of session ids).
///
/// @deprecated Use `importExportedRoomKeys` or `importBackedUpRoomKeys`.
#[wasm_bindgen(js_name = "importRoomKeys")]
pub fn import_room_keys(
&self,
exported_room_keys: &str,
progress_listener: Function,
) -> Result<Promise, JsError> {
let me = self.inner.clone();
let exported_room_keys = serde_json::from_str(exported_room_keys)?;
Ok(future_to_promise(async move {
let matrix_sdk_crypto::RoomKeyImportResult { imported_count, total_count, keys } =
Self::import_exported_room_keys_helper(&me, exported_room_keys, progress_listener)
.await?;
Ok(serde_json::to_string(&json!({
"imported_count": imported_count,
"total_count": total_count,
"keys": keys,
}))?)
}))
}
/// Import the given room keys into our store.
///
/// `exported_keys` is a JSON-encoded list of previously exported keys that
/// should be imported into our store. If we already have a better
/// version of a key, the key will _not_ be imported.
///
/// `progress_listener` is a closure that takes 2 `BigInt` arguments:
/// `progress` and `total`, and returns nothing.
///
/// Returns a {@link RoomKeyImportResult}.
#[wasm_bindgen(js_name = "importExportedRoomKeys")]
pub fn import_exported_room_keys(
&self,
exported_room_keys: &str,
progress_listener: Function,
) -> Result<Promise, JsError> {
let me = self.inner.clone();
let exported_room_keys = serde_json::from_str(exported_room_keys)?;
Ok(future_to_promise(async move {
let result: RoomKeyImportResult =
Self::import_exported_room_keys_helper(&me, exported_room_keys, progress_listener)
.await?
.into();
Ok(result)
}))
}
/// Import the given room keys into our store.
///
/// # Arguments
///
/// * `backed_up_room_keys`: keys that were retrieved from backup and that
/// should be added to our store (provided they are better than our
/// current versions of those keys). Specifically, it should be a Map from
/// {@link RoomId}, to a Map from session ID to a (decrypted) session data
/// structure.
///
/// * `progress_listener`: an optional callback that takes 3 arguments:
/// `progress` (the number of keys that have successfully been imported),
/// `total` (the total number of keys), and `failures` (the number of keys
/// that failed to import), and returns nothing.
///
/// # Returns
///
/// A {@link RoomKeyImportResult}.
#[wasm_bindgen(js_name = "importBackedUpRoomKeys")]
pub fn import_backed_up_room_keys(
&self,
backed_up_room_keys: &Map,
progress_listener: Option<Function>,
) -> Result<Promise, JsValue> {
let me = self.inner.clone();