1use std::{
18 collections::{BTreeMap, BTreeSet, btree_map},
19 fmt::{self, Debug},
20 future::{Future, ready},
21 pin::Pin,
22 sync::{Arc, Mutex as StdMutex, RwLock as StdRwLock, Weak},
23 time::Duration,
24};
25
26use eyeball::{SharedObservable, Subscriber};
27use eyeball_im::{Vector, VectorDiff};
28use futures_core::Stream;
29use futures_util::{StreamExt, join};
30#[cfg(feature = "e2e-encryption")]
31use matrix_sdk_base::crypto::{
32 DecryptionSettings, store::LockableCryptoStore, store::types::RoomPendingKeyBundleDetails,
33};
34use matrix_sdk_base::{
35 BaseClient, DmRoomDefinition, RoomInfoNotableUpdate, RoomState, RoomStateFilter,
36 SendOutsideWasm, SessionMeta, StateStoreDataKey, StateStoreDataValue, StoreError,
37 SyncOutsideWasm, ThreadingSupport,
38 event_cache::store::EventCacheStoreLock,
39 media::store::MediaStoreLock,
40 store::{DynStateStore, RoomLoadSettings, SupportedVersionsResponse, WellKnownResponse},
41 sync::{Notification, RoomUpdates},
42 task_monitor::TaskMonitor,
43};
44use matrix_sdk_common::{cross_process_lock::CrossProcessLockConfig, ttl::TtlValue};
45#[cfg(feature = "e2e-encryption")]
46use ruma::events::{InitialStateEvent, room::encryption::RoomEncryptionEventContent};
47use ruma::{
48 DeviceId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
49 RoomAliasId, RoomId, RoomOrAliasId, ServerName, UInt, UserId,
50 api::{
51 FeatureFlag, MatrixVersion, Metadata, OutgoingRequest, SupportedVersions,
52 client::{
53 account::whoami,
54 alias::{create_alias, delete_alias, get_alias},
55 authenticated_media,
56 device::{self, delete_devices, get_devices, update_device},
57 directory::{get_public_rooms, get_public_rooms_filtered},
58 discovery::{discover_homeserver, get_supported_versions},
59 filter::{FilterDefinition, create_filter::v3::Request as FilterUploadRequest},
60 knock::knock_room,
61 media,
62 membership::{join_room_by_id, join_room_by_id_or_alias},
63 presence::set_presence as set_presence_status,
64 retention::get_retention_configuration,
65 room::create_room,
66 rtc::{RtcTransport, transports},
67 session::login::v3::DiscoveryInfo,
68 sync::sync_events,
69 threads::get_thread_subscriptions_changes,
70 uiaa,
71 user_directory::search_users,
72 },
73 error::{ErrorKind, FromHttpResponseError, UnknownTokenErrorData},
74 path_builder::PathBuilder,
75 },
76 assign,
77 events::{beacon_info::OriginalSyncBeaconInfoEvent, direct::DirectUserIdentifier},
78 presence::PresenceState,
79 push::Ruleset,
80 time::Instant,
81};
82use serde::de::DeserializeOwned;
83use tokio::sync::{Mutex, OnceCell, RwLock, RwLockReadGuard, broadcast};
84use tracing::{Instrument, Span, debug, error, info, instrument, trace, warn};
85use url::Url;
86
87use self::{
88 caches::{Cache, CachedValue, ClientCaches},
89 futures::SendRequest,
90};
91use crate::{
92 Account, AuthApi, AuthSession, Error, HttpError, Media, Pusher, RefreshTokenError, Result,
93 Room, SessionTokens, TransmissionProgress,
94 authentication::{
95 AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback, matrix::MatrixAuth,
96 oauth::OAuth,
97 },
98 client::{
99 homeserver_capabilities::HomeserverCapabilities,
100 thread_subscriptions::ThreadSubscriptionCatchup,
101 },
102 config::{RequestConfig, SyncToken},
103 deduplicating_handler::DeduplicatingHandler,
104 error::HttpResult,
105 event_cache::EventCache,
106 event_handler::{
107 EventHandler, EventHandlerContext, EventHandlerDropGuard, EventHandlerHandle,
108 EventHandlerStore, ObservableEventHandler, SyncEvent,
109 },
110 http_client::{HttpClient, SupportedAuthScheme, SupportedPathBuilder},
111 latest_events::LatestEvents,
112 live_locations_observer::BeaconInfoUpdate,
113 media::{MediaError, MediaFetcher},
114 notification_settings::NotificationSettings,
115 room::RoomMember,
116 room_preview::RoomPreview,
117 send_queue::{SendQueue, SendQueueData},
118 sliding_sync::Version as SlidingSyncVersion,
119 sync::{RoomUpdate, SyncResponse},
120};
121#[cfg(feature = "e2e-encryption")]
122use crate::{
123 cross_process_lock::CrossProcessLock,
124 encryption::{
125 DuplicateOneTimeKeyErrorMessage, Encryption, EncryptionData, EncryptionSettings,
126 VerificationState,
127 },
128};
129
130mod builder;
131pub(crate) mod caches;
132pub(crate) mod futures;
133pub(crate) mod homeserver_capabilities;
134pub(crate) mod thread_subscriptions;
135
136pub use self::builder::{ClientBuildError, ClientBuilder, sanitize_server_name};
137#[cfg(feature = "experimental-search")]
138use crate::search_index::SearchIndex;
139
140#[cfg(not(target_family = "wasm"))]
141type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()> + Send>>;
142#[cfg(target_family = "wasm")]
143type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()>>>;
144
145#[cfg(not(target_family = "wasm"))]
146type NotificationHandlerFn =
147 Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut + Send + Sync>;
148#[cfg(target_family = "wasm")]
149type NotificationHandlerFn = Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut>;
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum LoopCtrl {
159 Continue,
161 Break,
163}
164
165#[derive(Debug, Clone, PartialEq)]
167pub enum SessionChange {
168 UnknownToken(UnknownTokenErrorData),
170 TokensRefreshed,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
176#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
177pub struct ServerVendorInfo {
178 pub server_name: String,
180 pub version: String,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Hash)]
187#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
188pub struct TileServerInfo {
189 pub map_style_url: String,
193}
194
195impl From<discover_homeserver::TileServerInfo> for TileServerInfo {
196 fn from(value: discover_homeserver::TileServerInfo) -> Self {
197 Self { map_style_url: value.map_style_url }
198 }
199}
200
201#[derive(Clone)]
205pub struct Client {
206 pub(crate) inner: Arc<ClientInner>,
207}
208
209#[derive(Default)]
210pub(crate) struct ClientLocks {
211 pub(crate) mark_as_dm_lock: Mutex<()>,
215
216 #[cfg(feature = "e2e-encryption")]
222 pub(crate) open_secret_store_lock: Mutex<()>,
223
224 #[cfg(feature = "e2e-encryption")]
231 pub(crate) store_secret_lock: Mutex<()>,
232
233 #[cfg(feature = "e2e-encryption")]
235 pub(crate) backup_modify_lock: Mutex<()>,
236
237 #[cfg(feature = "e2e-encryption")]
240 pub(crate) backup_upload_lock: Mutex<()>,
241
242 #[cfg(feature = "e2e-encryption")]
245 pub(crate) group_session_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
246
247 #[cfg(feature = "e2e-encryption")]
249 pub(crate) key_claim_lock: Mutex<()>,
250
251 pub(crate) members_request_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
254
255 pub(crate) encryption_state_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
258
259 pub(crate) read_receipt_deduplicated_handler: DeduplicatingHandler<(String, OwnedEventId)>,
262
263 #[cfg(feature = "e2e-encryption")]
264 pub(crate) cross_process_crypto_store_lock: OnceCell<CrossProcessLock<LockableCryptoStore>>,
265
266 #[cfg(feature = "e2e-encryption")]
285 pub(crate) crypto_store_generation: Arc<Mutex<Option<u64>>>,
286}
287
288pub(crate) struct ClientInner {
289 pub(crate) auth_ctx: Arc<AuthCtx>,
291
292 server: StdRwLock<Option<Url>>,
304
305 homeserver: StdRwLock<Url>,
309
310 sliding_sync_version: StdRwLock<SlidingSyncVersion>,
312
313 sync_presence: Arc<StdRwLock<PresenceState>>,
318
319 pub(crate) http_client: HttpClient,
321
322 pub(super) base_client: BaseClient,
324
325 pub(crate) caches: ClientCaches,
327
328 pub(crate) locks: ClientLocks,
332
333 cross_process_lock_config: CrossProcessLockConfig,
342
343 pub(crate) typing_notice_times: StdRwLock<BTreeMap<OwnedRoomId, Instant>>,
346
347 pub(crate) event_handlers: EventHandlerStore,
349
350 notification_handlers: RwLock<Vec<NotificationHandlerFn>>,
352
353 pub(crate) room_update_channels: StdMutex<BTreeMap<OwnedRoomId, broadcast::Sender<RoomUpdate>>>,
355
356 pub(crate) room_updates_sender: broadcast::Sender<RoomUpdates>,
359
360 respect_login_well_known: bool,
363
364 well_known_lookup_disabled: StdRwLock<bool>,
368
369 pub(crate) sync_beat: event_listener::Event,
375
376 pub(crate) event_cache: OnceCell<EventCache>,
380
381 #[cfg(feature = "e2e-encryption")]
383 pub(crate) e2ee: EncryptionData,
384
385 #[cfg(feature = "e2e-encryption")]
387 pub(crate) verification_state: SharedObservable<VerificationState>,
388
389 #[cfg(feature = "e2e-encryption")]
394 pub(crate) enable_share_history_on_invite: bool,
395
396 pub(crate) send_queue_data: Arc<SendQueueData>,
400
401 pub(crate) server_max_upload_size: Mutex<OnceCell<UInt>>,
404
405 latest_events: OnceCell<LatestEvents>,
409
410 thread_subscription_catchup: OnceCell<Arc<ThreadSubscriptionCatchup>>,
413
414 #[cfg(feature = "experimental-search")]
415 search_index: SearchIndex,
417
418 pub(crate) task_monitor: TaskMonitor,
420
421 #[cfg(feature = "e2e-encryption")]
424 pub(crate) duplicate_key_upload_error_sender:
425 broadcast::Sender<Option<DuplicateOneTimeKeyErrorMessage>>,
426
427 pub(crate) media_fetcher: RwLock<Arc<dyn MediaFetcher>>,
428
429 #[cfg(feature = "unstable-msc4426")]
438 pub(crate) automatic_call_status:
439 StdMutex<Option<crate::automatic_call_status::AutomaticCallStatus>>,
440}
441
442impl ClientInner {
443 #[allow(clippy::too_many_arguments)]
449 async fn new(
450 auth_ctx: Arc<AuthCtx>,
451 server: Option<Url>,
452 homeserver: Url,
453 sliding_sync_version: SlidingSyncVersion,
454 sync_presence: Arc<StdRwLock<PresenceState>>,
455 http_client: HttpClient,
456 base_client: BaseClient,
457 supported_versions: CachedValue<TtlValue<SupportedVersions>>,
458 well_known: CachedValue<TtlValue<Option<WellKnownResponse>>>,
459 respect_login_well_known: bool,
460 well_known_lookup_disabled: bool,
461 event_cache: OnceCell<EventCache>,
462 enable_automatic_back_pagination: bool,
463 send_queue: Arc<SendQueueData>,
464 latest_events: OnceCell<LatestEvents>,
465 #[cfg(feature = "e2e-encryption")] encryption_settings: EncryptionSettings,
466 #[cfg(feature = "e2e-encryption")] enable_share_history_on_invite: bool,
467 cross_process_lock_config: CrossProcessLockConfig,
468 #[cfg(feature = "experimental-search")] search_index_handler: SearchIndex,
469 thread_subscription_catchup: OnceCell<Arc<ThreadSubscriptionCatchup>>,
470 media_fetcher: Arc<dyn MediaFetcher>,
471 ) -> Arc<Self> {
472 let caches = ClientCaches {
473 supported_versions: Cache::with_value(supported_versions),
474 well_known: Cache::with_value(well_known),
475 server_metadata: Cache::new(),
476 homeserver_capabilities: Cache::new(),
477 rtc_transports: Cache::new(),
478 };
479
480 let client = Self {
481 server: StdRwLock::new(server),
482 homeserver: StdRwLock::new(homeserver),
483 auth_ctx,
484 sliding_sync_version: StdRwLock::new(sliding_sync_version),
485 sync_presence,
486 http_client,
487 base_client,
488 caches,
489 locks: Default::default(),
490 cross_process_lock_config,
491 typing_notice_times: Default::default(),
492 event_handlers: Default::default(),
493 notification_handlers: Default::default(),
494 room_update_channels: Default::default(),
495 room_updates_sender: broadcast::Sender::new(32),
498 respect_login_well_known,
499 well_known_lookup_disabled: StdRwLock::new(well_known_lookup_disabled),
500 sync_beat: event_listener::Event::new(),
501 event_cache,
502 send_queue_data: send_queue,
503 latest_events,
504 #[cfg(feature = "e2e-encryption")]
505 e2ee: EncryptionData::new(encryption_settings),
506 #[cfg(feature = "e2e-encryption")]
507 verification_state: SharedObservable::new(VerificationState::Unknown),
508 #[cfg(feature = "e2e-encryption")]
509 enable_share_history_on_invite,
510 server_max_upload_size: Mutex::new(OnceCell::new()),
511 #[cfg(feature = "experimental-search")]
512 search_index: search_index_handler,
513 thread_subscription_catchup,
514 task_monitor: TaskMonitor::new(),
515 #[cfg(feature = "e2e-encryption")]
516 duplicate_key_upload_error_sender: broadcast::channel(1).0,
517 media_fetcher: RwLock::new(media_fetcher),
518 #[cfg(feature = "unstable-msc4426")]
519 automatic_call_status: StdMutex::new(None),
520 };
521
522 #[allow(clippy::let_and_return)]
523 let client = Arc::new(client);
524
525 #[cfg(feature = "e2e-encryption")]
526 client.e2ee.initialize_tasks(&client);
527
528 let init_event_cache = client.event_cache.get_or_init(|| async {
529 EventCache::new(
530 &client,
531 client.base_client.event_cache_store().clone(),
532 enable_automatic_back_pagination,
533 )
534 });
535
536 let init_thread_subscription_catchup = client
537 .thread_subscription_catchup
538 .get_or_init(|| ThreadSubscriptionCatchup::new(Client { inner: client.clone() }));
539
540 let _ = join!(init_event_cache, init_thread_subscription_catchup);
541
542 client
543 }
544}
545
546#[cfg(not(tarpaulin_include))]
547impl Debug for Client {
548 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
549 write!(fmt, "Client")
550 }
551}
552
553impl Client {
554 pub async fn new(homeserver_url: Url) -> Result<Self, ClientBuildError> {
560 Self::builder().homeserver_url(homeserver_url).build().await
561 }
562
563 pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<Vec<String>> {
566 self.inner.base_client.subscribe_to_ignore_user_list_changes()
567 }
568
569 pub fn builder() -> ClientBuilder {
571 ClientBuilder::new()
572 }
573
574 pub(crate) fn base_client(&self) -> &BaseClient {
575 &self.inner.base_client
576 }
577
578 pub fn http_client(&self) -> &reqwest::Client {
580 &self.inner.http_client.inner
581 }
582
583 pub(crate) fn locks(&self) -> &ClientLocks {
584 &self.inner.locks
585 }
586
587 pub(crate) fn auth_ctx(&self) -> &AuthCtx {
588 &self.inner.auth_ctx
589 }
590
591 pub fn cross_process_lock_config(&self) -> &CrossProcessLockConfig {
599 &self.inner.cross_process_lock_config
600 }
601
602 fn set_homeserver(&self, homeserver_url: Url) {
610 let mut homeserver = self.inner.homeserver.write().unwrap();
611 let mut server = self.inner.server.write().unwrap();
612
613 *homeserver = homeserver_url;
614 *server = None;
615 }
616
617 #[cfg(feature = "e2e-encryption")]
619 pub(crate) async fn switch_homeserver_and_re_resolve_well_known(
620 &self,
621 homeserver_url: Url,
622 ) -> Result<()> {
623 self.set_homeserver(homeserver_url);
624 self.reset_well_known().await?;
625 if let Some(well_known) = self.well_known().await {
626 self.set_homeserver(Url::parse(&well_known.homeserver.base_url)?);
627 }
628 Ok(())
629 }
630
631 pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
634 HomeserverCapabilities::new(self.clone())
635 }
636
637 #[cfg(feature = "federation-api")]
659 pub async fn server_vendor_info(
660 &self,
661 request_config: Option<RequestConfig>,
662 ) -> HttpResult<ServerVendorInfo> {
663 use ruma::api::federation::discovery::get_server_version;
664
665 let res = self
666 .send_inner(get_server_version::v1::Request::new(), request_config, Default::default())
667 .await?;
668
669 let server = res.server.unwrap_or_default();
671 let server_name_str = server.name.unwrap_or_else(|| "unknown".to_owned());
672 let version = server.version.unwrap_or_else(|| "unknown".to_owned());
673
674 Ok(ServerVendorInfo { server_name: server_name_str, version })
675 }
676
677 pub fn request_config(&self) -> RequestConfig {
687 self.inner.http_client.request_config
688 }
689
690 pub fn is_active(&self) -> bool {
700 self.inner.base_client.is_active()
701 }
702
703 pub fn server(&self) -> Option<Url> {
707 self.inner.server.read().unwrap().clone()
708 }
709
710 pub fn homeserver(&self) -> Url {
712 self.inner.homeserver.read().unwrap().clone()
713 }
714
715 pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
717 self.inner.sliding_sync_version.read().unwrap().clone()
718 }
719
720 pub fn set_sliding_sync_version(&self, version: SlidingSyncVersion) {
722 let mut lock = self.inner.sliding_sync_version.write().unwrap();
723 *lock = version;
724 }
725
726 pub(crate) fn sync_presence(&self) -> PresenceState {
728 self.inner.sync_presence.read().unwrap().clone()
729 }
730
731 pub fn session_meta(&self) -> Option<&SessionMeta> {
737 self.base_client().session_meta()
738 }
739
740 pub fn room_info_notable_update_receiver(&self) -> broadcast::Receiver<RoomInfoNotableUpdate> {
743 self.base_client().room_info_notable_update_receiver()
744 }
745
746 pub fn subscribe_to_global_profile_updates(
752 &self,
753 ) -> broadcast::Receiver<BTreeSet<ruma::OwnedUserId>> {
754 self.base_client().subscribe_to_global_profile_updates()
755 }
756
757 pub fn subscribe_to_own_profile(
768 &self,
769 ) -> Result<impl Stream<Item = ruma::profile::UserProfile> + use<>> {
770 let own_user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
771 let mut updates = self.subscribe_to_global_profile_updates();
772 let client = self.clone();
773
774 Ok(async_stream::stream! {
775 match client.state_store().get_global_profile(&own_user_id).await {
777 Ok(profile) => yield profile.unwrap_or_default(),
778 Err(error) => error!(?error, "Failed to load the stored global profile"),
779 }
780
781 while let Ok(updated_user_ids) = updates.recv().await {
782 if !updated_user_ids.contains(&own_user_id) {
783 continue;
784 }
785
786 match client.state_store().get_global_profile(&own_user_id).await {
787 Ok(profile) => yield profile.unwrap_or_default(),
788 Err(error) => error!(?error, "Failed to load the updated global profile"),
789 }
790 }
791 })
792 }
793
794 pub async fn search_users(
804 &self,
805 search_term: &str,
806 limit: u64,
807 ) -> HttpResult<search_users::v3::Response> {
808 let mut request = search_users::v3::Request::new(search_term.to_owned());
809
810 if let Some(limit) = UInt::new(limit) {
811 request.limit = limit;
812 }
813
814 self.send(request).await
815 }
816
817 pub fn user_id(&self) -> Option<&UserId> {
819 self.session_meta().map(|s| s.user_id.as_ref())
820 }
821
822 pub fn device_id(&self) -> Option<&DeviceId> {
824 self.session_meta().map(|s| s.device_id.as_ref())
825 }
826
827 pub fn access_token(&self) -> Option<String> {
831 self.auth_ctx().access_token()
832 }
833
834 pub async fn set_presence(
842 &self,
843 presence: PresenceState,
844 status_msg: Option<String>,
845 immediate: bool,
846 ) -> Result<()> {
847 *self.inner.sync_presence.write().unwrap() = presence.clone();
848
849 if !immediate {
850 return Ok(());
851 }
852
853 let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
854 let mut request = set_presence_status::v3::Request::new(user_id, presence);
855 request.status_msg = status_msg;
856
857 self.send(request).await?;
858
859 Ok(())
860 }
861
862 pub fn session_tokens(&self) -> Option<SessionTokens> {
870 self.auth_ctx().session_tokens()
871 }
872
873 pub fn auth_api(&self) -> Option<AuthApi> {
877 match self.auth_ctx().auth_data.get()? {
878 AuthData::Matrix => Some(AuthApi::Matrix(self.matrix_auth())),
879 AuthData::OAuth(_) => Some(AuthApi::OAuth(self.oauth())),
880 }
881 }
882
883 pub fn session(&self) -> Option<AuthSession> {
890 match self.auth_api()? {
891 AuthApi::Matrix(api) => api.session().map(Into::into),
892 AuthApi::OAuth(api) => api.full_session().map(Into::into),
893 }
894 }
895
896 pub fn state_store(&self) -> &DynStateStore {
898 self.base_client().state_store()
899 }
900
901 pub fn event_cache_store(&self) -> &EventCacheStoreLock {
903 self.base_client().event_cache_store()
904 }
905
906 pub fn media_store(&self) -> &MediaStoreLock {
908 self.base_client().media_store()
909 }
910
911 pub fn matrix_auth(&self) -> MatrixAuth {
913 MatrixAuth::new(self.clone())
914 }
915
916 pub fn account(&self) -> Account {
918 Account::new(self.clone())
919 }
920
921 #[cfg(feature = "e2e-encryption")]
923 pub fn encryption(&self) -> Encryption {
924 Encryption::new(self.clone())
925 }
926
927 pub fn media(&self) -> Media {
929 Media::new(self.clone())
930 }
931
932 pub fn pusher(&self) -> Pusher {
934 Pusher::new(self.clone())
935 }
936
937 pub fn oauth(&self) -> OAuth {
939 OAuth::new(self.clone())
940 }
941
942 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
1074 where
1075 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
1076 H: EventHandler<Ev, Ctx>,
1077 {
1078 self.add_event_handler_impl(handler, None)
1079 }
1080
1081 pub fn add_room_event_handler<Ev, Ctx, H>(
1092 &self,
1093 room_id: &RoomId,
1094 handler: H,
1095 ) -> EventHandlerHandle
1096 where
1097 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
1098 H: EventHandler<Ev, Ctx>,
1099 {
1100 self.add_event_handler_impl(handler, Some(room_id.to_owned()))
1101 }
1102
1103 pub fn observe_events<Ev, Ctx>(&self) -> ObservableEventHandler<(Ev, Ctx)>
1178 where
1179 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1180 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1181 {
1182 self.observe_room_events_impl(None)
1183 }
1184
1185 pub fn observe_room_events<Ev, Ctx>(
1195 &self,
1196 room_id: &RoomId,
1197 ) -> ObservableEventHandler<(Ev, Ctx)>
1198 where
1199 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1200 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1201 {
1202 self.observe_room_events_impl(Some(room_id.to_owned()))
1203 }
1204
1205 fn observe_room_events_impl<Ev, Ctx>(
1208 &self,
1209 room_id: Option<OwnedRoomId>,
1210 ) -> ObservableEventHandler<(Ev, Ctx)>
1211 where
1212 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1213 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1214 {
1215 let shared_observable = SharedObservable::new(None);
1218
1219 ObservableEventHandler::new(
1220 shared_observable.clone(),
1221 self.event_handler_drop_guard(self.add_event_handler_impl(
1222 move |event: Ev, context: Ctx| {
1223 shared_observable.set(Some((event, context)));
1224
1225 ready(())
1226 },
1227 room_id,
1228 )),
1229 )
1230 }
1231
1232 pub fn observe_own_beacon_info_updates(
1238 &self,
1239 ) -> Result<impl Stream<Item = BeaconInfoUpdate> + use<>> {
1240 let observer = self.observe_events::<OriginalSyncBeaconInfoEvent, Room>();
1241 let mut stream = observer.subscribe();
1242 let own_user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
1243 Ok(async_stream::stream! {
1244 let _observer = observer;
1245
1246 while let Some((event, room)) = stream.next().await {
1247 if event.state_key != own_user_id {
1248 continue;
1249 }
1250 yield BeaconInfoUpdate {
1251 room_id: room.room_id().to_owned(),
1252 event_id: event.event_id,
1253 content: event.content,
1254 };
1255 }
1256 })
1257 }
1258
1259 pub fn remove_event_handler(&self, handle: EventHandlerHandle) {
1317 self.inner.event_handlers.remove(handle);
1318 }
1319
1320 pub fn event_handler_drop_guard(&self, handle: EventHandlerHandle) -> EventHandlerDropGuard {
1325 EventHandlerDropGuard::new(handle, self.clone())
1326 }
1327
1328 pub fn add_event_handler_context<T>(&self, ctx: T)
1369 where
1370 T: Clone + Send + Sync + 'static,
1371 {
1372 self.inner.event_handlers.add_context(ctx);
1373 }
1374
1375 pub async fn register_notification_handler<H, Fut>(&self, handler: H) -> &Self
1381 where
1382 H: Fn(Notification, Room, Client) -> Fut + SendOutsideWasm + SyncOutsideWasm + 'static,
1383 Fut: Future<Output = ()> + SendOutsideWasm + 'static,
1384 {
1385 self.inner.notification_handlers.write().await.push(Box::new(
1386 move |notification, room, client| Box::pin((handler)(notification, room, client)),
1387 ));
1388
1389 self
1390 }
1391
1392 pub fn subscribe_to_room_updates(&self, room_id: &RoomId) -> broadcast::Receiver<RoomUpdate> {
1397 match self.inner.room_update_channels.lock().unwrap().entry(room_id.to_owned()) {
1398 btree_map::Entry::Vacant(entry) => {
1399 let (tx, rx) = broadcast::channel(8);
1400 entry.insert(tx);
1401 rx
1402 }
1403 btree_map::Entry::Occupied(entry) => entry.get().subscribe(),
1404 }
1405 }
1406
1407 pub fn subscribe_to_all_room_updates(&self) -> broadcast::Receiver<RoomUpdates> {
1410 self.inner.room_updates_sender.subscribe()
1411 }
1412
1413 pub(crate) async fn notification_handlers(
1414 &self,
1415 ) -> RwLockReadGuard<'_, Vec<NotificationHandlerFn>> {
1416 self.inner.notification_handlers.read().await
1417 }
1418
1419 pub fn rooms(&self) -> Vec<Room> {
1423 self.base_client().rooms().into_iter().map(|room| Room::new(self.clone(), room)).collect()
1424 }
1425
1426 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
1428 self.base_client()
1429 .rooms_filtered(filter)
1430 .into_iter()
1431 .map(|room| Room::new(self.clone(), room))
1432 .collect()
1433 }
1434
1435 pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + '_) {
1437 let (rooms, stream) = self.base_client().rooms_stream();
1438
1439 let map_room = |room| Room::new(self.clone(), room);
1440
1441 (
1442 rooms.into_iter().map(map_room).collect(),
1443 stream.map(move |diffs| diffs.into_iter().map(|diff| diff.map(map_room)).collect()),
1444 )
1445 }
1446
1447 pub fn joined_rooms(&self) -> Vec<Room> {
1449 self.rooms_filtered(RoomStateFilter::JOINED)
1450 }
1451
1452 pub fn invited_rooms(&self) -> Vec<Room> {
1454 self.rooms_filtered(RoomStateFilter::INVITED)
1455 }
1456
1457 pub fn left_rooms(&self) -> Vec<Room> {
1459 self.rooms_filtered(RoomStateFilter::LEFT)
1460 }
1461
1462 pub fn joined_space_rooms(&self) -> Vec<Room> {
1464 self.base_client()
1465 .rooms_filtered(RoomStateFilter::JOINED)
1466 .into_iter()
1467 .flat_map(|room| room.is_space().then_some(Room::new(self.clone(), room)))
1468 .collect()
1469 }
1470
1471 pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1477 self.base_client().get_room(room_id).map(|room| Room::new(self.clone(), room))
1478 }
1479
1480 pub async fn get_room_preview(
1483 &self,
1484 room_or_alias_id: &RoomOrAliasId,
1485 via: Vec<OwnedServerName>,
1486 ) -> Result<RoomPreview> {
1487 let room_id = match <&RoomId>::try_from(room_or_alias_id) {
1488 Ok(room_id) => room_id.to_owned(),
1489 Err(alias) => self.resolve_room_alias(alias).await?.room_id,
1490 };
1491
1492 if let Some(room) = self.get_room(&room_id) {
1493 match room.state() {
1499 RoomState::Joined | RoomState::Banned => {
1500 return Ok(RoomPreview::from_known_room(&room).await);
1501 }
1502 RoomState::Left | RoomState::Invited | RoomState::Knocked => {}
1503 }
1504 }
1505
1506 RoomPreview::from_remote_room(self, room_id, room_or_alias_id, via).await
1507 }
1508
1509 pub async fn resolve_room_alias(
1516 &self,
1517 room_alias: &RoomAliasId,
1518 ) -> HttpResult<get_alias::v3::Response> {
1519 let request = get_alias::v3::Request::new(room_alias.to_owned());
1520 self.send(request).await
1521 }
1522
1523 pub async fn is_room_alias_available(&self, alias: &RoomAliasId) -> HttpResult<bool> {
1531 match self.resolve_room_alias(alias).await {
1532 Ok(_) => Ok(false),
1534 Err(error) => {
1535 match error.client_api_error_kind() {
1536 Some(ErrorKind::NotFound) => Ok(true),
1538 _ => Err(error),
1539 }
1540 }
1541 }
1542 }
1543
1544 pub async fn create_room_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> HttpResult<()> {
1546 let request = create_alias::v3::Request::new(alias.to_owned(), room_id.to_owned());
1547 self.send(request).await?;
1548 Ok(())
1549 }
1550
1551 pub async fn remove_room_alias(&self, alias: &RoomAliasId) -> HttpResult<()> {
1553 let request = delete_alias::v3::Request::new(alias.to_owned());
1554 self.send(request).await?;
1555 Ok(())
1556 }
1557
1558 pub(crate) fn maybe_update_login_well_known(&self, login_well_known: Option<&DiscoveryInfo>) {
1565 if self.inner.respect_login_well_known
1566 && let Some(well_known) = login_well_known
1567 && let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url)
1568 {
1569 self.set_homeserver(homeserver);
1570 }
1571 }
1572
1573 #[instrument(skip_all)]
1580 pub async fn restore_session(&self, session: impl Into<AuthSession>) -> Result<()> {
1581 self.restore_session_with(session, RoomLoadSettings::default()).await
1582 }
1583
1584 #[instrument(skip_all)]
1595 pub async fn restore_session_with(
1596 &self,
1597 session: impl Into<AuthSession>,
1598 room_load_settings: RoomLoadSettings,
1599 ) -> Result<()> {
1600 let session = session.into();
1601 match session {
1602 AuthSession::Matrix(session) => {
1603 Box::pin(self.matrix_auth().restore_session(session, room_load_settings)).await
1604 }
1605 AuthSession::OAuth(session) => {
1606 Box::pin(self.oauth().restore_session(*session, room_load_settings)).await
1607 }
1608 }
1609 }
1610
1611 pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1617 let Some(auth_api) = self.auth_api() else {
1618 return Err(RefreshTokenError::RefreshTokenRequired);
1619 };
1620
1621 match auth_api {
1622 AuthApi::Matrix(api) => {
1623 trace!("Token refresh: Using the homeserver.");
1624 Box::pin(api.refresh_access_token()).await?;
1625 }
1626 AuthApi::OAuth(api) => {
1627 trace!("Token refresh: Using OAuth 2.0.");
1628 Box::pin(api.refresh_access_token()).await?;
1629 }
1630 }
1631
1632 Ok(())
1633 }
1634
1635 pub async fn logout(&self) -> Result<(), Error> {
1642 let auth_api = self.auth_api().ok_or(Error::AuthenticationRequired)?;
1643 match auth_api {
1644 AuthApi::Matrix(matrix_auth) => {
1645 matrix_auth.logout().await?;
1646 Ok(())
1647 }
1648 AuthApi::OAuth(oauth) => Ok(oauth.logout().await?),
1649 }
1650 }
1651
1652 #[instrument(skip(self, definition))]
1698 pub async fn get_or_upload_filter(
1699 &self,
1700 filter_name: &str,
1701 definition: FilterDefinition,
1702 ) -> Result<String> {
1703 if let Some(filter) = self.inner.base_client.get_filter(filter_name).await? {
1704 debug!("Found filter locally");
1705 Ok(filter)
1706 } else {
1707 debug!("Didn't find filter locally");
1708 let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?;
1709 let request = FilterUploadRequest::new(user_id.to_owned(), definition);
1710 let response = self.send(request).await?;
1711
1712 self.inner.base_client.receive_filter_upload(filter_name, &response).await?;
1713
1714 Ok(response.filter_id)
1715 }
1716 }
1717
1718 async fn prepare_join_room_by_id(&self, room_id: &RoomId) -> Option<PreJoinRoomInfo> {
1720 let room = self.get_room(room_id)?;
1721
1722 let inviter = match room.invite_details().await {
1723 Ok(details) => details.inviter,
1724 Err(Error::WrongRoomState(_)) => None,
1725 Err(e) => {
1726 warn!("Error fetching invite details for room: {e:?}");
1727 None
1728 }
1729 };
1730
1731 Some(PreJoinRoomInfo { inviter })
1732 }
1733
1734 async fn finish_join_room(
1747 &self,
1748 room_id: &RoomId,
1749 pre_join_room_info: Option<PreJoinRoomInfo>,
1750 ) -> Result<Room> {
1751 info!(?room_id, ?pre_join_room_info, "Completing room join");
1752 let mark_as_dm = if let Some(room) = self.get_room(room_id) {
1753 room.state() == RoomState::Invited
1754 && room.is_direct().await.unwrap_or_else(|e| {
1755 warn!(%room_id, "is_direct() failed: {e}");
1756 false
1757 })
1758 } else {
1759 false
1760 };
1761
1762 let base_room = self
1763 .base_client()
1764 .room_joined(
1765 room_id,
1766 pre_join_room_info
1767 .as_ref()
1768 .and_then(|info| info.inviter.as_ref())
1769 .map(|i| i.user_id().to_owned()),
1770 )
1771 .await?;
1772 let room = Room::new(self.clone(), base_room);
1773
1774 if mark_as_dm {
1775 room.set_is_direct(true).await?;
1776 }
1777
1778 #[cfg(feature = "e2e-encryption")]
1785 if self.inner.enable_share_history_on_invite
1786 && let Some(inviter) =
1787 pre_join_room_info.as_ref().and_then(|info| info.inviter.as_ref())
1788 {
1789 crate::room::shared_room_history::maybe_accept_key_bundle(&room, inviter.user_id())
1790 .await?;
1791 }
1792
1793 #[cfg(not(feature = "e2e-encryption"))]
1795 let _ = pre_join_room_info.map(|i| i.inviter);
1796
1797 Ok(room)
1798 }
1799
1800 #[instrument(skip(self))]
1808 pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result<Room> {
1809 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1812
1813 let request = join_room_by_id::v3::Request::new(room_id.to_owned());
1814 let response = self.send(request).await?;
1815 self.finish_join_room(&response.room_id, pre_join_info).await
1816 }
1817
1818 #[instrument(skip(self))]
1829 pub async fn join_room_by_id_or_alias(
1830 &self,
1831 alias: &RoomOrAliasId,
1832 server_names: &[OwnedServerName],
1833 ) -> Result<Room> {
1834 let room_id = match <&RoomId>::try_from(alias) {
1835 Ok(room_id) => room_id,
1836 Err(room_alias) => &self.resolve_room_alias(room_alias).await?.room_id,
1837 };
1838 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1839 let request = assign!(join_room_by_id_or_alias::v3::Request::new(alias.to_owned()), {
1840 via: server_names.to_owned(),
1841 });
1842 let response = self.send(request).await?;
1843 self.finish_join_room(&response.room_id, pre_join_info).await
1844 }
1845
1846 #[cfg_attr(not(target_family = "wasm"), deny(clippy::future_not_send))]
1875 pub async fn public_rooms(
1876 &self,
1877 limit: Option<u32>,
1878 since: Option<&str>,
1879 server: Option<&ServerName>,
1880 ) -> HttpResult<get_public_rooms::v3::Response> {
1881 let limit = limit.map(UInt::from);
1882
1883 let request = assign!(get_public_rooms::v3::Request::new(), {
1884 limit,
1885 since: since.map(ToOwned::to_owned),
1886 server: server.map(ToOwned::to_owned),
1887 });
1888 self.send(request).await
1889 }
1890
1891 pub async fn create_room(&self, request: create_room::v3::Request) -> Result<Room> {
1921 let invite = request.invite.clone();
1922 let is_direct_room = request.is_direct;
1923 let response = self.send(request).await?;
1924
1925 let base_room = self.base_client().get_or_create_room(&response.room_id, RoomState::Joined);
1926
1927 let joined_room = Room::new(self.clone(), base_room);
1928
1929 if is_direct_room
1930 && !invite.is_empty()
1931 && let Err(error) =
1932 self.account().mark_as_dm(joined_room.room_id(), invite.as_slice()).await
1933 {
1934 error!("Failed to mark room as DM: {error}");
1936 }
1937
1938 Ok(joined_room)
1939 }
1940
1941 pub async fn create_dm(&self, user_id: &UserId) -> Result<Room> {
1954 #[cfg(feature = "e2e-encryption")]
1955 let initial_state = vec![
1956 InitialStateEvent::with_empty_state_key(
1957 RoomEncryptionEventContent::with_recommended_defaults(),
1958 )
1959 .to_raw_any(),
1960 ];
1961
1962 #[cfg(not(feature = "e2e-encryption"))]
1963 let initial_state = vec![];
1964
1965 let request = assign!(create_room::v3::Request::new(), {
1966 invite: vec![user_id.to_owned()],
1967 is_direct: true,
1968 preset: Some(create_room::v3::RoomPreset::TrustedPrivateChat),
1969 initial_state,
1970 });
1971
1972 self.create_room(request).await
1973 }
1974
1975 pub fn get_dm_room(&self, user_id: &UserId) -> Option<Room> {
1977 self.get_dm_rooms(user_id).next()
1978 }
1979
1980 pub fn get_dm_rooms(&self, user_id: &UserId) -> impl Iterator<Item = Room> {
1982 let rooms = self.joined_rooms();
1983
1984 let dm_definition = &self.base_client().dm_room_definition;
1985
1986 let rooms = rooms.into_iter().filter(move |r| {
1988 let targets = r.direct_targets();
1989 let targets_match =
1990 targets.len() == 1 && targets.contains(<&DirectUserIdentifier>::from(user_id));
1991 match dm_definition {
1992 DmRoomDefinition::MatrixSpec => targets_match,
1993 DmRoomDefinition::TwoMembers => {
1994 let service_members_count =
1995 r.service_members().map(|s| s.len()).unwrap_or_default() as u64;
1996 let active_non_service_members =
1997 r.active_members_count().saturating_sub(service_members_count);
1998 targets_match && active_non_service_members <= 2
1999 }
2000 }
2001 });
2002
2003 trace!(?user_id, ?rooms, "Found DM rooms with user");
2004 rooms
2005 }
2006
2007 pub async fn public_rooms_filtered(
2039 &self,
2040 request: get_public_rooms_filtered::v3::Request,
2041 ) -> HttpResult<get_public_rooms_filtered::v3::Response> {
2042 self.send(request).await
2043 }
2044
2045 pub fn send<Request>(&self, request: Request) -> SendRequest<Request>
2084 where
2085 Request: OutgoingRequest + Clone + Debug,
2086 Request::Authentication: SupportedAuthScheme,
2087 Request::PathBuilder: SupportedPathBuilder,
2088 for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2089 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2090 {
2091 SendRequest {
2092 client: self.clone(),
2093 request,
2094 config: None,
2095 send_progress: Default::default(),
2096 }
2097 }
2098
2099 pub(crate) async fn send_inner<Request>(
2100 &self,
2101 request: Request,
2102 config: Option<RequestConfig>,
2103 send_progress: SharedObservable<TransmissionProgress>,
2104 ) -> HttpResult<Request::IncomingResponse>
2105 where
2106 Request: OutgoingRequest + Debug,
2107 Request::Authentication: SupportedAuthScheme,
2108 Request::PathBuilder: SupportedPathBuilder,
2109 for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2110 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2111 {
2112 let homeserver = self.homeserver().to_string();
2113 let access_token = self.access_token();
2114 let skip_auth = config.map(|c| c.skip_auth).unwrap_or(self.request_config().skip_auth);
2115
2116 let path_builder_input =
2117 Request::PathBuilder::get_path_builder_input(self, skip_auth).await?;
2118
2119 let result = self
2120 .inner
2121 .http_client
2122 .send(
2123 request,
2124 config,
2125 homeserver,
2126 access_token.as_deref(),
2127 path_builder_input,
2128 send_progress,
2129 )
2130 .await;
2131
2132 if let Err(Some(ErrorKind::UnknownToken { .. })) =
2133 result.as_ref().map_err(HttpError::client_api_error_kind)
2134 && let Some(access_token) = &access_token
2135 {
2136 self.auth_ctx().set_access_token_expired(access_token);
2138 }
2139
2140 result
2141 }
2142
2143 fn broadcast_unknown_token(&self, unknown_token_data: &UnknownTokenErrorData) {
2144 _ = self
2145 .inner
2146 .auth_ctx
2147 .session_change_sender
2148 .send(SessionChange::UnknownToken(unknown_token_data.clone()));
2149 }
2150
2151 pub async fn fetch_server_versions(
2153 &self,
2154 request_config: Option<RequestConfig>,
2155 ) -> HttpResult<get_supported_versions::Response> {
2156 self.fetch_server_versions_inner(false, request_config).await
2159 }
2160
2161 pub(crate) async fn fetch_server_versions_inner(
2167 &self,
2168 failsafe: bool,
2169 request_config: Option<RequestConfig>,
2170 ) -> HttpResult<get_supported_versions::Response> {
2171 if !failsafe {
2172 return self
2174 .send(get_supported_versions::Request::new())
2175 .with_request_config(request_config)
2176 .await;
2177 }
2178
2179 let homeserver = self.homeserver().to_string();
2180
2181 if !request_config.as_ref().is_some_and(|config| config.skip_auth && !config.force_auth)
2183 && self.auth_ctx().has_valid_access_token()
2184 && let Some(access_token) = self.access_token()
2185 {
2186 let result = self
2187 .inner
2188 .http_client
2189 .send(
2190 get_supported_versions::Request::new(),
2191 request_config,
2192 homeserver.clone(),
2193 Some(&access_token),
2194 (),
2195 Default::default(),
2196 )
2197 .await;
2198
2199 if let Err(Some(ErrorKind::UnknownToken { .. })) =
2200 result.as_ref().map_err(HttpError::client_api_error_kind)
2201 {
2202 self.auth_ctx().set_access_token_expired(&access_token);
2205 } else {
2206 return result;
2208 }
2209 }
2210
2211 self.inner
2213 .http_client
2214 .send(
2215 get_supported_versions::Request::new(),
2216 request_config,
2217 homeserver.clone(),
2218 None,
2219 (),
2220 Default::default(),
2221 )
2222 .await
2223 }
2224
2225 pub async fn fetch_client_well_known(&self) -> Option<discover_homeserver::Response> {
2238 if self.well_known_lookup_disabled() {
2239 return None;
2240 }
2241
2242 let homeserver = self.homeserver();
2243 let scheme = homeserver.scheme();
2244
2245 let server_url = self
2249 .server()
2250 .map(|server| server.to_string())
2251 .or_else(|| self.user_id().map(|id| format!("{}://{}", scheme, id.server_name())));
2255
2256 let response = if let Some(server_url) = server_url {
2258 self.fetch_client_well_known_with_url(server_url).await
2260 } else {
2261 None
2262 };
2263
2264 if response.is_none() {
2266 warn!(
2269 "Fetching the well-known from the server name didn't work, using the homeserver url instead"
2270 );
2271 self.fetch_client_well_known_with_url(homeserver.to_string()).await
2272 } else {
2273 response
2274 }
2275 }
2276
2277 async fn fetch_client_well_known_with_url(
2278 &self,
2279 url: String,
2280 ) -> Option<discover_homeserver::Response> {
2281 let well_known = self
2282 .inner
2283 .http_client
2284 .send(
2285 discover_homeserver::Request::new(),
2286 Some(RequestConfig::short_retry()),
2287 url,
2288 None,
2289 (),
2290 Default::default(),
2291 )
2292 .await;
2293
2294 match well_known {
2295 Ok(well_known) => Some(well_known),
2296 Err(http_error) => {
2297 warn!("Failed to fetch client well-known: {http_error}");
2300 None
2301 }
2302 }
2303 }
2304
2305 async fn fetch_supported_versions(
2311 &self,
2312 failsafe: bool,
2313 ) -> HttpResult<SupportedVersionsResponse> {
2314 let server_versions = self.fetch_server_versions_inner(failsafe, None).await?;
2315 let supported_versions = SupportedVersionsResponse {
2316 versions: server_versions.versions,
2317 unstable_features: server_versions.unstable_features,
2318 };
2319
2320 Ok(supported_versions)
2321 }
2322
2323 pub async fn supported_versions(&self) -> HttpResult<SupportedVersions> {
2351 self.supported_versions_inner(false).await
2352 }
2353
2354 pub(crate) async fn supported_versions_inner(
2360 &self,
2361 failsafe: bool,
2362 ) -> HttpResult<SupportedVersions> {
2363 match self.supported_versions_cached_inner(failsafe).await {
2364 Ok(Some(value)) => {
2365 return Ok(value);
2366 }
2367 Ok(None) => {
2368 }
2370 Err(error) => {
2371 warn!("error when loading cached supported versions: {error}");
2372 }
2374 }
2375
2376 self.refresh_supported_versions_cache(failsafe).await
2377 }
2378
2379 async fn refresh_supported_versions_cache(
2385 &self,
2386 failsafe: bool,
2387 ) -> HttpResult<SupportedVersions> {
2388 let cached_supported_versions = &self.inner.caches.supported_versions;
2389
2390 let mut supported_versions_guard = match cached_supported_versions.refresh_lock.try_lock() {
2391 Ok(guard) => guard,
2392 Err(_) => {
2393 let guard = cached_supported_versions.refresh_lock.lock().await;
2395
2396 if let Err(error) = guard.as_ref() {
2397 return Err(HttpError::Cached(error.clone()));
2399 }
2400
2401 if let CachedValue::Cached(value) = cached_supported_versions.value()
2403 && !value.has_expired()
2404 {
2405 return Ok(value.into_data());
2406 }
2407
2408 guard
2410 }
2411 };
2412
2413 let response = match self.fetch_supported_versions(failsafe).await {
2414 Ok(response) => {
2415 *supported_versions_guard = Ok(());
2416 TtlValue::new(response)
2417 }
2418 Err(error) => {
2419 let error = Arc::new(error);
2420 *supported_versions_guard = Err(error.clone());
2421 return Err(HttpError::Cached(error));
2422 }
2423 };
2424
2425 let supported_versions = response.as_ref().map(|response| response.supported_versions());
2426
2427 if self.auth_ctx().has_valid_access_token() {
2429 if let Err(err) = self
2430 .state_store()
2431 .set_kv_data(
2432 StateStoreDataKey::SupportedVersions,
2433 StateStoreDataValue::SupportedVersions(response),
2434 )
2435 .await
2436 {
2437 warn!("error when caching supported versions: {err}");
2438 }
2439
2440 cached_supported_versions.set_value(supported_versions.clone());
2441 }
2442
2443 Ok(supported_versions.into_data())
2444 }
2445
2446 pub async fn supported_versions_cached(&self) -> Result<Option<SupportedVersions>, StoreError> {
2483 self.supported_versions_cached_inner(false).await
2484 }
2485
2486 async fn supported_versions_cached_inner(
2487 &self,
2488 failsafe: bool,
2489 ) -> Result<Option<SupportedVersions>, StoreError> {
2490 let supported_versions_cache = &self.inner.caches.supported_versions;
2491
2492 let value = if let CachedValue::Cached(cached) = supported_versions_cache.value() {
2493 cached
2494 } else if let Some(stored) = self
2495 .state_store()
2496 .get_kv_data(StateStoreDataKey::SupportedVersions)
2497 .await?
2498 .and_then(|value| value.into_supported_versions())
2499 {
2500 let stored = stored.map(|response| response.supported_versions());
2501
2502 supported_versions_cache.set_value(stored.clone());
2504
2505 stored
2506 } else {
2507 return Ok(None);
2508 };
2509
2510 if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2513 debug!("spawning task to refresh supported versions cache");
2514
2515 let client = self.clone();
2516 self.task_monitor().spawn_finite_task("refresh supported versions cache", async move {
2517 if let Err(error) = client.refresh_supported_versions_cache(failsafe).await {
2518 warn!("failed to refresh supported versions cache: {error}");
2519 }
2520 });
2521 }
2522
2523 Ok(Some(value.into_data()))
2524 }
2525
2526 pub async fn server_versions(&self) -> HttpResult<BTreeSet<MatrixVersion>> {
2545 Ok(self.supported_versions().await?.versions)
2546 }
2547
2548 pub async fn unstable_features(&self) -> HttpResult<BTreeSet<FeatureFlag>> {
2568 Ok(self.supported_versions().await?.features)
2569 }
2570
2571 pub async fn reset_supported_versions(&self) -> Result<()> {
2577 self.inner.caches.supported_versions.reset();
2579
2580 Ok(self.state_store().remove_kv_data(StateStoreDataKey::SupportedVersions).await?)
2582 }
2583
2584 async fn well_known_cached(
2589 &self,
2590 ) -> Result<CachedValue<Option<WellKnownResponse>>, StoreError> {
2591 let well_known_cache = &self.inner.caches.well_known;
2592
2593 let value = if let CachedValue::Cached(cached) = well_known_cache.value() {
2594 cached
2595 } else if let Some(stored) = self
2596 .state_store()
2597 .get_kv_data(StateStoreDataKey::WellKnown)
2598 .await?
2599 .and_then(|value| value.into_well_known())
2600 {
2601 well_known_cache.set_value(stored.clone());
2603
2604 stored
2605 } else {
2606 return Ok(CachedValue::NotSet);
2607 };
2608
2609 if value.has_expired() {
2611 debug!("spawning task to refresh well-known cache");
2612
2613 let client = self.clone();
2614 self.task_monitor().spawn_finite_task("refresh well-known cache", async move {
2615 client.refresh_well_known_cache().await;
2616 });
2617 }
2618
2619 Ok(CachedValue::Cached(value.into_data()))
2620 }
2621
2622 async fn refresh_well_known_cache(&self) -> Option<WellKnownResponse> {
2624 let well_known_cache = &self.inner.caches.well_known;
2625
2626 let _well_known_guard = match well_known_cache.refresh_lock.try_lock() {
2627 Ok(guard) => guard,
2628 Err(_) => {
2629 let guard = well_known_cache.refresh_lock.lock().await;
2631
2632 if let CachedValue::Cached(value) = well_known_cache.value()
2637 && !value.has_expired()
2638 {
2639 return value.into_data();
2640 }
2641
2642 guard
2644 }
2645 };
2646
2647 let well_known = TtlValue::new(self.fetch_client_well_known().await.map(Into::into));
2648
2649 if let Err(err) = self
2650 .state_store()
2651 .set_kv_data(
2652 StateStoreDataKey::WellKnown,
2653 StateStoreDataValue::WellKnown(well_known.clone()),
2654 )
2655 .await
2656 {
2657 warn!("error when caching well-known: {err}");
2658 }
2659
2660 well_known_cache.set_value(well_known.clone());
2661
2662 well_known.into_data()
2663 }
2664
2665 fn well_known_lookup_disabled(&self) -> bool {
2668 *self.inner.well_known_lookup_disabled.read().unwrap()
2669 }
2670
2671 pub fn disable_well_known_lookup(&self, disable: bool) {
2674 *self.inner.well_known_lookup_disabled.write().unwrap() = disable;
2675 }
2676
2677 async fn well_known(&self) -> Option<WellKnownResponse> {
2683 if self.well_known_lookup_disabled() {
2684 return None;
2685 }
2686
2687 match self.well_known_cached().await {
2688 Ok(CachedValue::Cached(value)) => {
2689 return value;
2690 }
2691 Ok(CachedValue::NotSet) => {
2692 }
2694 Err(error) => {
2695 warn!("error when loading cached well-known: {error}");
2696 }
2698 }
2699
2700 self.refresh_well_known_cache().await
2701 }
2702
2703 #[deprecated = "Use `Client::discover_rtc_transports` instead"]
2709 pub async fn rtc_foci(&self) -> HttpResult<Vec<RtcTransport>> {
2710 self.well_known_rtc_transports().await
2711 }
2712
2713 pub async fn well_known_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
2742 let well_known = self.well_known().await;
2743
2744 Ok(well_known.map(|well_known| well_known.rtc_foci).unwrap_or_default())
2745 }
2746
2747 async fn rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2754 match self.rtc_transports_cached() {
2755 CachedValue::Cached(value) => Ok(value),
2756 CachedValue::NotSet => self.refresh_rtc_transports_cache().await,
2758 }
2759 }
2760
2761 fn rtc_transports_cached(&self) -> CachedValue<Option<Vec<RtcTransport>>> {
2767 let cache = &self.inner.caches.rtc_transports;
2768
2769 let CachedValue::Cached(value) = cache.value() else {
2770 return CachedValue::NotSet;
2771 };
2772
2773 if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2776 debug!("spawning task to refresh RTC transports cache");
2777
2778 let client = self.clone();
2779 self.task_monitor().spawn_finite_task("refresh RTC transports cache", async move {
2780 if let Err(error) = client.refresh_rtc_transports_cache().await {
2781 warn!("failed to refresh RTC transports cache: {error}");
2782 }
2783 });
2784 }
2785
2786 CachedValue::Cached(value.into_data())
2787 }
2788
2789 async fn refresh_rtc_transports_cache(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2791 let cache = &self.inner.caches.rtc_transports;
2792
2793 let mut refresh_guard = match cache.refresh_lock.try_lock() {
2794 Ok(guard) => guard,
2795 Err(_) => {
2796 let guard = cache.refresh_lock.lock().await;
2798
2799 if let Err(error) = guard.as_ref() {
2800 return Err(HttpError::Cached(error.clone()));
2802 }
2803
2804 if let CachedValue::Cached(value) = cache.value()
2806 && !value.has_expired()
2807 {
2808 return Ok(value.into_data());
2809 }
2810
2811 guard
2813 }
2814 };
2815
2816 match self.fetch_rtc_transports().await {
2817 Ok(transports) => {
2818 *refresh_guard = Ok(());
2819 cache.set_value(TtlValue::new(Some(transports.clone())));
2820 Ok(Some(transports))
2821 }
2822 Err(error) if error.is_endpoint_not_implemented() => {
2823 debug!("homeserver does not implement the RTC transports endpoint");
2830 *refresh_guard = Ok(());
2831 cache.set_value(TtlValue::new(None));
2832 Ok(None)
2833 }
2834 Err(error) => {
2835 let error = Arc::new(error);
2836 *refresh_guard = Err(error.clone());
2837 Err(HttpError::Cached(error))
2838 }
2839 }
2840 }
2841
2842 pub async fn fetch_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
2845 let response = self
2846 .send(transports::v1::Request::new())
2847 .with_request_config(RequestConfig::short_retry())
2848 .await?;
2849 Ok(response.rtc_transports)
2850 }
2851
2852 pub fn reset_rtc_transports(&self) {
2857 self.inner.caches.rtc_transports.reset();
2858 }
2859
2860 pub async fn discover_rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2888 if let Some(transports) = self.rtc_transports().await? {
2889 return Ok(Some(transports));
2890 }
2891
2892 Ok(self.well_known().await.map(|well_known| well_known.rtc_foci))
2897 }
2898
2899 pub async fn tile_server(&self) -> Option<TileServerInfo> {
2907 self.well_known().await.and_then(|well_known| well_known.tile_server).map(Into::into)
2908 }
2909
2910 pub async fn reset_well_known(&self) -> Result<()> {
2915 self.inner.caches.well_known.reset();
2917
2918 Ok(self.state_store().remove_kv_data(StateStoreDataKey::WellKnown).await?)
2920 }
2921
2922 pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> HttpResult<bool> {
2937 Ok(self.unstable_features().await?.contains(&FeatureFlag::from("org.matrix.msc4028")))
2938 }
2939
2940 pub async fn devices(&self) -> HttpResult<get_devices::v3::Response> {
2962 let request = get_devices::v3::Request::new();
2963
2964 self.send(request).await
2965 }
2966
2967 pub async fn get_retention_configuration(
2974 &self,
2975 ) -> HttpResult<get_retention_configuration::unstable::Response> {
2976 self.send(get_retention_configuration::unstable::Request::default()).await
2977 }
2978
2979 pub async fn delete_devices(
3020 &self,
3021 devices: &[OwnedDeviceId],
3022 auth_data: Option<uiaa::AuthData>,
3023 ) -> HttpResult<delete_devices::v3::Response> {
3024 let mut request = delete_devices::v3::Request::new(devices.to_owned());
3025 request.auth = auth_data;
3026
3027 self.send(request).await
3028 }
3029
3030 pub async fn rename_device(
3040 &self,
3041 device_id: &DeviceId,
3042 display_name: &str,
3043 ) -> HttpResult<update_device::v3::Response> {
3044 let mut request = update_device::v3::Request::new(device_id.to_owned());
3045 request.display_name = Some(display_name.to_owned());
3046
3047 self.send(request).await
3048 }
3049
3050 pub async fn device_exists(&self, device_id: OwnedDeviceId) -> Result<bool> {
3059 let request = device::get_device::v3::Request::new(device_id);
3060 match self.send(request).await {
3061 Ok(_) => Ok(true),
3062 Err(err) => {
3063 if let Some(error) = err.as_client_api_error()
3064 && error.status_code == 404
3065 {
3066 Ok(false)
3067 } else {
3068 Err(err.into())
3069 }
3070 }
3071 }
3072 }
3073
3074 #[instrument(skip(self))]
3171 pub async fn sync_once(
3172 &self,
3173 sync_settings: crate::config::SyncSettings,
3174 ) -> Result<SyncResponse> {
3175 #[cfg(feature = "e2e-encryption")]
3182 if let Err(e) = self.send_outgoing_requests().await {
3183 error!(error = ?e, "Error while sending outgoing E2EE requests");
3184 }
3185
3186 let token = match sync_settings.token {
3187 SyncToken::Specific(token) => Some(token),
3188 SyncToken::NoToken => None,
3189 SyncToken::ReusePrevious => self.sync_token().await,
3190 };
3191
3192 let request = assign!(sync_events::v3::Request::new(), {
3193 filter: sync_settings.filter.map(|f| *f),
3194 since: token,
3195 full_state: sync_settings.full_state,
3196 set_presence: sync_settings.set_presence.unwrap_or_else(|| self.sync_presence()),
3197 timeout: sync_settings.timeout,
3198 use_state_after: true,
3199 });
3200 let mut request_config = self.request_config();
3201 if let Some(timeout) = sync_settings.timeout {
3202 let base_timeout = request_config.timeout.unwrap_or(Duration::from_secs(30));
3203 request_config.timeout = Some(base_timeout + timeout);
3204 }
3205
3206 let response = self.send(request).with_request_config(request_config).await?;
3207 let next_batch = response.next_batch.clone();
3208 let response = self.process_sync(response).await?;
3209
3210 #[cfg(feature = "e2e-encryption")]
3211 if let Err(e) = self.send_outgoing_requests().await {
3212 error!(error = ?e, "Error while sending outgoing E2EE requests");
3213 }
3214
3215 self.inner.sync_beat.notify(usize::MAX);
3216
3217 Ok(SyncResponse::new(next_batch, response))
3218 }
3219
3220 pub async fn sync(&self, sync_settings: crate::config::SyncSettings) -> Result<(), Error> {
3279 self.sync_with_callback(sync_settings, |_| async { LoopCtrl::Continue }).await
3280 }
3281
3282 #[instrument(skip_all)]
3339 pub async fn sync_with_callback<C>(
3340 &self,
3341 sync_settings: crate::config::SyncSettings,
3342 callback: impl Fn(SyncResponse) -> C,
3343 ) -> Result<(), Error>
3344 where
3345 C: Future<Output = LoopCtrl>,
3346 {
3347 self.sync_with_result_callback(sync_settings, |result| async {
3348 Ok(callback(result?).await)
3349 })
3350 .await
3351 }
3352
3353 #[instrument(skip(self, callback))]
3419 pub async fn sync_with_result_callback<C>(
3420 &self,
3421 sync_settings: crate::config::SyncSettings,
3422 callback: impl Fn(Result<SyncResponse, Error>) -> C,
3423 ) -> Result<(), Error>
3424 where
3425 C: Future<Output = Result<LoopCtrl, Error>>,
3426 {
3427 let mut sync_stream = Box::pin(self.sync_stream(sync_settings).await);
3428
3429 while let Some(result) = sync_stream.next().await {
3430 trace!("Running callback");
3431 if callback(result).await? == LoopCtrl::Break {
3432 trace!("Callback told us to stop");
3433 break;
3434 }
3435 trace!("Done running callback");
3436 }
3437
3438 Ok(())
3439 }
3440
3441 #[allow(unknown_lints, clippy::let_with_type_underscore)] #[instrument(skip(self))]
3484 pub async fn sync_stream(
3485 &self,
3486 mut sync_settings: crate::config::SyncSettings,
3487 ) -> impl Stream<Item = Result<SyncResponse>> + '_ {
3488 let mut is_first_sync = true;
3489 let mut timeout = None;
3490 let mut last_sync_time: Option<Instant> = None;
3491
3492 let parent_span = Span::current();
3493
3494 async_stream::stream!({
3495 loop {
3496 trace!("Syncing");
3497
3498 if sync_settings.ignore_timeout_on_first_sync {
3499 if is_first_sync {
3500 timeout = sync_settings.timeout.take();
3501 } else if sync_settings.timeout.is_none() && timeout.is_some() {
3502 sync_settings.timeout = timeout.take();
3503 }
3504
3505 is_first_sync = false;
3506 }
3507
3508 yield self
3509 .sync_loop_helper(&mut sync_settings)
3510 .instrument(parent_span.clone())
3511 .await;
3512
3513 Client::delay_sync(&mut last_sync_time).await
3514 }
3515 })
3516 }
3517
3518 pub(crate) async fn sync_token(&self) -> Option<String> {
3521 self.inner.base_client.sync_token().await
3522 }
3523
3524 pub async fn whoami(&self) -> HttpResult<whoami::v3::Response> {
3526 let request = whoami::v3::Request::new();
3527 self.send(request).await
3528 }
3529
3530 pub fn subscribe_to_session_changes(&self) -> broadcast::Receiver<SessionChange> {
3532 let broadcast = &self.auth_ctx().session_change_sender;
3533 broadcast.subscribe()
3534 }
3535
3536 pub fn set_session_callbacks(
3541 &self,
3542 reload_session_callback: Box<ReloadSessionCallback>,
3543 save_session_callback: Box<SaveSessionCallback>,
3544 ) -> Result<()> {
3545 self.inner
3546 .auth_ctx
3547 .reload_session_callback
3548 .set(reload_session_callback)
3549 .map_err(|_| Error::MultipleSessionCallbacks)?;
3550
3551 self.inner
3552 .auth_ctx
3553 .save_session_callback
3554 .set(save_session_callback)
3555 .map_err(|_| Error::MultipleSessionCallbacks)?;
3556
3557 Ok(())
3558 }
3559
3560 pub async fn notification_settings(&self) -> NotificationSettings {
3562 let ruleset = self.account().push_rules().await.unwrap_or_else(|_| Ruleset::new());
3563 NotificationSettings::new(self.clone(), ruleset)
3564 }
3565
3566 pub async fn notification_client(
3573 &self,
3574 cross_process_lock_config: CrossProcessLockConfig,
3575 ) -> Result<Client> {
3576 let client = Client {
3577 inner: ClientInner::new(
3578 self.inner.auth_ctx.clone(),
3579 self.server(),
3580 self.homeserver(),
3581 self.sliding_sync_version(),
3582 self.inner.sync_presence.clone(),
3583 self.inner.http_client.clone(),
3584 self.inner
3585 .base_client
3586 .clone_with_in_memory_state_store(cross_process_lock_config.clone(), false)
3587 .await?,
3588 self.inner.caches.supported_versions.value(),
3589 self.inner.caches.well_known.value(),
3590 self.inner.respect_login_well_known,
3591 self.well_known_lookup_disabled(),
3592 self.inner.event_cache.clone(),
3593 false,
3594 self.inner.send_queue_data.clone(),
3595 self.inner.latest_events.clone(),
3596 #[cfg(feature = "e2e-encryption")]
3597 self.inner.e2ee.encryption_settings,
3598 #[cfg(feature = "e2e-encryption")]
3599 self.inner.enable_share_history_on_invite,
3600 cross_process_lock_config,
3601 #[cfg(feature = "experimental-search")]
3602 self.inner.search_index.clone(),
3603 self.inner.thread_subscription_catchup.clone(),
3604 (*self.inner.media_fetcher.read().await).clone(),
3605 )
3606 .await,
3607 };
3608
3609 Ok(client)
3610 }
3611
3612 pub fn event_cache(&self) -> &EventCache {
3614 self.inner.event_cache.get().unwrap()
3616 }
3617
3618 pub async fn latest_events(&self) -> &LatestEvents {
3620 self.inner
3621 .latest_events
3622 .get_or_init(|| async {
3623 LatestEvents::new(
3624 WeakClient::from_client(self),
3625 self.event_cache().clone(),
3626 SendQueue::new(self.clone()),
3627 self.room_info_notable_update_receiver(),
3628 )
3629 })
3630 .await
3631 }
3632
3633 pub async fn await_room_remote_echo(&self, room_id: &RoomId) -> Room {
3639 loop {
3640 if let Some(room) = self.get_room(room_id) {
3641 if room.is_state_partially_or_fully_synced() {
3642 debug!("Found just created room!");
3643 return room;
3644 }
3645 debug!("Room wasn't partially synced, waiting for sync beat to try again");
3646 } else {
3647 debug!("Room wasn't found, waiting for sync beat to try again");
3648 }
3649 self.inner.sync_beat.listen().await;
3650 }
3651 }
3652
3653 pub async fn knock(
3656 &self,
3657 room_id_or_alias: OwnedRoomOrAliasId,
3658 reason: Option<String>,
3659 server_names: Vec<OwnedServerName>,
3660 ) -> Result<Room> {
3661 let request =
3662 assign!(knock_room::v3::Request::new(room_id_or_alias), { reason, via: server_names });
3663 let response = self.send(request).await?;
3664 let base_room = self.inner.base_client.room_knocked(&response.room_id).await?;
3665 Ok(Room::new(self.clone(), base_room))
3666 }
3667
3668 pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
3670 self.base_client().is_user_ignored(user_id).await
3671 }
3672
3673 pub async fn load_or_fetch_max_upload_size(&self) -> Result<UInt> {
3680 let max_upload_size_lock = self.inner.server_max_upload_size.lock().await;
3681 if let Some(data) = max_upload_size_lock.get() {
3682 return Ok(data.to_owned());
3683 }
3684
3685 let supported_versions = self.supported_versions().await?;
3687 let use_auth = authenticated_media::get_media_config::v1::Request::PATH_BUILDER
3688 .is_supported(&supported_versions);
3689
3690 let upload_size = if use_auth {
3691 self.send(authenticated_media::get_media_config::v1::Request::default())
3692 .await?
3693 .upload_size
3694 } else {
3695 #[allow(deprecated)]
3696 self.send(media::get_media_config::v3::Request::default()).await?.upload_size
3697 };
3698
3699 match max_upload_size_lock.set(upload_size) {
3700 Ok(_) => Ok(upload_size),
3701 Err(error) => {
3702 Err(Error::Media(MediaError::FetchMaxUploadSizeFailed(error.to_string())))
3703 }
3704 }
3705 }
3706
3707 #[cfg(feature = "e2e-encryption")]
3709 pub fn decryption_settings(&self) -> &DecryptionSettings {
3710 &self.base_client().decryption_settings
3711 }
3712
3713 #[cfg(feature = "experimental-search")]
3715 pub fn search_index(&self) -> &SearchIndex {
3716 &self.inner.search_index
3717 }
3718
3719 pub async fn enabled_thread_subscriptions(&self) -> Result<bool> {
3730 match self.base_client().threading_support {
3732 ThreadingSupport::Enabled { with_subscriptions: false }
3733 | ThreadingSupport::Disabled => return Ok(false),
3734 ThreadingSupport::Enabled { with_subscriptions: true } => {}
3735 }
3736
3737 let server_enabled = self
3739 .supported_versions()
3740 .await?
3741 .features
3742 .contains(&FeatureFlag::from("org.matrix.msc4306"));
3743
3744 Ok(server_enabled)
3745 }
3746
3747 pub async fn fetch_thread_subscriptions(
3753 &self,
3754 from: Option<String>,
3755 to: Option<String>,
3756 limit: Option<UInt>,
3757 ) -> Result<get_thread_subscriptions_changes::unstable::Response> {
3758 let request = assign!(get_thread_subscriptions_changes::unstable::Request::new(), {
3759 from,
3760 to,
3761 limit,
3762 });
3763 Ok(self.send(request).await?)
3764 }
3765
3766 pub(crate) fn thread_subscription_catchup(&self) -> &ThreadSubscriptionCatchup {
3767 self.inner.thread_subscription_catchup.get().unwrap()
3768 }
3769
3770 pub async fn pause(&self) -> Result<()> {
3787 info!("Client::pause — releasing database resources");
3788
3789 self.send_queue().set_enabled(false).await;
3791
3792 self.base_client().close_stores().await?;
3794
3795 info!("Client::pause — complete, all database connections released");
3796 Ok(())
3797 }
3798
3799 pub async fn resume(&self) -> Result<()> {
3807 info!("Client::resume — re-acquiring database resources");
3808
3809 self.base_client().reopen_stores().await?;
3811
3812 self.send_queue().set_enabled(true).await;
3814
3815 info!("Client::resume — complete");
3816 Ok(())
3817 }
3818
3819 #[doc(hidden)]
3825 pub async fn optimize_stores(&self) -> Result<()> {
3826 trace!("Optimizing state store...");
3827 self.state_store().optimize().await?;
3828
3829 trace!("Optimizing event cache store...");
3830 if let Some(clean_lock) = self.event_cache_store().lock().await?.as_clean() {
3831 clean_lock.optimize().await?;
3832 }
3833
3834 trace!("Optimizing media store...");
3835 self.media_store().lock().await?.optimize().await?;
3836
3837 Ok(())
3838 }
3839
3840 pub async fn get_store_sizes(&self) -> Result<StoreSizes> {
3842 #[cfg(feature = "e2e-encryption")]
3843 let crypto_store_size = if let Some(olm_machine) = self.olm_machine().await.as_ref()
3844 && let Ok(Some(store_size)) = olm_machine.store().get_size().await
3845 {
3846 Some(store_size)
3847 } else {
3848 None
3849 };
3850 #[cfg(not(feature = "e2e-encryption"))]
3851 let crypto_store_size = None;
3852
3853 let state_store_size = self.state_store().get_size().await.ok().flatten();
3854
3855 let event_cache_store_size = if let Some(clean_lock) =
3856 self.event_cache_store().lock().await?.as_clean()
3857 && let Ok(Some(store_size)) = clean_lock.get_size().await
3858 {
3859 Some(store_size)
3860 } else {
3861 None
3862 };
3863
3864 let media_store_size = self.media_store().lock().await?.get_size().await.ok().flatten();
3865
3866 Ok(StoreSizes {
3867 crypto_store: crypto_store_size,
3868 state_store: state_store_size,
3869 event_cache_store: event_cache_store_size,
3870 media_store: media_store_size,
3871 })
3872 }
3873
3874 pub fn task_monitor(&self) -> &TaskMonitor {
3877 &self.inner.task_monitor
3878 }
3879
3880 #[cfg(feature = "e2e-encryption")]
3883 pub fn subscribe_to_duplicate_key_upload_errors(
3884 &self,
3885 ) -> broadcast::Receiver<Option<DuplicateOneTimeKeyErrorMessage>> {
3886 self.inner.duplicate_key_upload_error_sender.subscribe()
3887 }
3888
3889 #[cfg(feature = "e2e-encryption")]
3894 pub async fn get_pending_key_bundle_details_for_room(
3895 &self,
3896 room_id: &RoomId,
3897 ) -> Result<Option<RoomPendingKeyBundleDetails>> {
3898 Ok(self.base_client().get_pending_key_bundle_details_for_room(room_id).await?)
3899 }
3900
3901 pub fn dm_room_definition(&self) -> &DmRoomDefinition {
3904 &self.inner.base_client.dm_room_definition
3905 }
3906
3907 pub async fn set_media_fetcher(&self, media_fetcher: Arc<dyn MediaFetcher>) {
3910 *self.inner.media_fetcher.write().await = media_fetcher;
3911 }
3912
3913 pub async fn get_media_fetcher(&self) -> Arc<dyn MediaFetcher> {
3916 self.inner.media_fetcher.read().await.clone()
3917 }
3918}
3919
3920#[derive(Debug, Clone)]
3923pub struct StoreSizes {
3924 pub crypto_store: Option<usize>,
3926 pub state_store: Option<usize>,
3928 pub event_cache_store: Option<usize>,
3930 pub media_store: Option<usize>,
3932}
3933
3934#[cfg(any(feature = "testing", test))]
3935impl Client {
3936 #[cfg(feature = "e2e-encryption")]
3938 pub async fn update_tracked_users_for_testing(
3939 &self,
3940 user_ids: impl IntoIterator<Item = &UserId>,
3941 ) {
3942 let olm = self.olm_machine().await;
3943 let olm = olm.as_ref().unwrap();
3944 olm.update_tracked_users(user_ids).await.unwrap();
3945 }
3946}
3947
3948#[derive(Clone, Debug)]
3951pub(crate) struct WeakClient {
3952 client: Weak<ClientInner>,
3953}
3954
3955impl WeakClient {
3956 pub(crate) fn from_inner(client: &Arc<ClientInner>) -> Self {
3958 Self { client: Arc::downgrade(client) }
3959 }
3960
3961 pub fn from_client(client: &Client) -> Self {
3963 Self::from_inner(&client.inner)
3964 }
3965
3966 pub fn get(&self) -> Option<Client> {
3968 self.client.upgrade().map(|inner| Client { inner })
3969 }
3970
3971 #[allow(dead_code)]
3974 pub fn strong_count(&self) -> usize {
3975 self.client.strong_count()
3976 }
3977}
3978
3979#[derive(Debug, Clone, Default)]
3981struct PreJoinRoomInfo {
3982 pub inviter: Option<RoomMember>,
3984}
3985
3986#[cfg(all(test, not(target_family = "wasm")))]
3988pub(crate) mod tests {
3989 use std::{sync::Arc, time::Duration};
3990
3991 use assert_matches::assert_matches;
3992 use assert_matches2::assert_let;
3993 use eyeball::SharedObservable;
3994 use futures_util::{FutureExt, StreamExt, pin_mut};
3995 use js_int::{UInt, uint};
3996 use matrix_sdk_base::{
3997 RoomState,
3998 store::{MemoryStore, StoreConfig},
3999 ttl::TtlValue,
4000 };
4001 use matrix_sdk_test::{
4002 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
4003 event_factory::EventFactory,
4004 };
4005 #[cfg(target_family = "wasm")]
4006 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
4007
4008 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
4009 use ruma::{
4010 RoomId, ServerName, UserId,
4011 api::{
4012 FeatureFlag, MatrixVersion,
4013 client::{room::create_room::v3::Request as CreateRoomRequest, rtc::RtcTransport},
4014 },
4015 assign,
4016 events::{
4017 ignored_user_list::IgnoredUserListEventContent,
4018 media_preview_config::{InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews},
4019 },
4020 owned_device_id, owned_room_id, owned_user_id,
4021 presence::PresenceState,
4022 room_alias_id, room_id, user_id,
4023 };
4024 use serde_json::json;
4025 use stream_assert::{assert_next_matches, assert_pending};
4026 use tokio::{
4027 spawn,
4028 time::{sleep, timeout},
4029 };
4030 use url::Url;
4031
4032 use super::Client;
4033 use crate::{
4034 Error, Result, TransmissionProgress,
4035 client::{WeakClient, caches::CachedValue, futures::SendMediaUploadRequest},
4036 config::{RequestConfig, SyncSettings},
4037 futures::SendRequest,
4038 media::MediaError,
4039 test_utils::{client::MockClientBuilder, mocks::MatrixMockServer},
4040 };
4041
4042 #[async_test]
4043 async fn test_sync_presence_is_shared_by_client_clones_and_notification_child() {
4044 let client = MockClientBuilder::new(None).build().await;
4045 let clone = client.clone();
4046 let notification_client =
4047 client.notification_client(CrossProcessLockConfig::SingleProcess).await.unwrap();
4048
4049 assert_eq!(client.sync_presence(), PresenceState::Online);
4050 assert_eq!(clone.sync_presence(), PresenceState::Online);
4051 assert_eq!(notification_client.sync_presence(), PresenceState::Online);
4052
4053 client
4054 .set_presence(PresenceState::Unavailable, None, false)
4055 .await
4056 .expect("presence should update");
4057
4058 assert_eq!(client.sync_presence(), PresenceState::Unavailable);
4059 assert_eq!(clone.sync_presence(), PresenceState::Unavailable);
4060 assert_eq!(notification_client.sync_presence(), PresenceState::Unavailable);
4061
4062 notification_client
4063 .set_presence(PresenceState::Offline, None, false)
4064 .await
4065 .expect("presence should update");
4066
4067 assert_eq!(client.sync_presence(), PresenceState::Offline);
4068 assert_eq!(clone.sync_presence(), PresenceState::Offline);
4069 assert_eq!(notification_client.sync_presence(), PresenceState::Offline);
4070 }
4071
4072 #[async_test]
4073 async fn test_sync_once_uses_client_sync_presence_unless_overridden() {
4074 let server = MatrixMockServer::new().await;
4075 let client = server.client_builder().build().await;
4076
4077 {
4078 let _sync_guard = server
4079 .mock_sync()
4080 .set_presence_missing()
4081 .ok(|_| {})
4082 .expect(1)
4083 .mount_as_scoped()
4084 .await;
4085
4086 client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4087 }
4088
4089 client
4090 .set_presence(PresenceState::Offline, None, false)
4091 .await
4092 .expect("presence should update");
4093
4094 {
4095 let _sync_guard = server
4096 .mock_sync()
4097 .set_presence("offline")
4098 .ok(|_| {})
4099 .expect(1)
4100 .mount_as_scoped()
4101 .await;
4102
4103 client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4104 }
4105
4106 {
4107 let _sync_guard = server
4108 .mock_sync()
4109 .set_presence("unavailable")
4110 .ok(|_| {})
4111 .expect(1)
4112 .mount_as_scoped()
4113 .await;
4114
4115 client
4116 .sync_once(SyncSettings::new().set_presence(PresenceState::Unavailable))
4117 .await
4118 .expect("sync should succeed");
4119 }
4120 }
4121
4122 #[async_test]
4123 async fn test_set_presence_sends_presence_status_update() {
4124 use wiremock::{
4125 Mock, ResponseTemplate,
4126 matchers::{body_partial_json, method, path_regex},
4127 };
4128
4129 let server = MatrixMockServer::new().await;
4130 let client = server.client_builder().build().await;
4131
4132 Mock::given(method("PUT"))
4133 .and(path_regex(r"^/_matrix/client/(r0|v3)/presence/.*/status$"))
4134 .and(body_partial_json(json!({
4135 "presence": "online",
4136 "status_msg": "Here"
4137 })))
4138 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
4139 .expect(1)
4140 .mount(server.server())
4141 .await;
4142
4143 client
4144 .set_presence(PresenceState::Online, Some("Here".to_owned()), true)
4145 .await
4146 .expect("presence update should succeed");
4147
4148 assert_eq!(client.sync_presence(), PresenceState::Online);
4149 }
4150
4151 #[async_test]
4152 async fn test_set_presence_requires_authentication() {
4153 let client = MockClientBuilder::new(None).unlogged().build().await;
4154
4155 assert_matches!(
4156 client.set_presence(PresenceState::Unavailable, None, true).await,
4157 Err(Error::AuthenticationRequired)
4158 );
4159 }
4160
4161 #[async_test]
4162 async fn test_set_presence_without_immediate_does_not_require_authentication() {
4163 let client = MockClientBuilder::new(None).unlogged().build().await;
4164
4165 client
4166 .set_presence(PresenceState::Offline, None, false)
4167 .await
4168 .expect("presence should update");
4169
4170 assert_eq!(client.sync_presence(), PresenceState::Offline);
4171 }
4172
4173 #[async_test]
4174 async fn test_account_data() {
4175 let server = MatrixMockServer::new().await;
4176 let client = server.client_builder().build().await;
4177
4178 let f = EventFactory::new();
4179 server
4180 .mock_sync()
4181 .ok_and_run(&client, |builder| {
4182 builder.add_global_account_data(
4183 f.ignored_user_list([owned_user_id!("@someone:example.org")]),
4184 );
4185 })
4186 .await;
4187
4188 let content = client
4189 .account()
4190 .account_data::<IgnoredUserListEventContent>()
4191 .await
4192 .unwrap()
4193 .unwrap()
4194 .deserialize()
4195 .unwrap();
4196
4197 assert_eq!(content.ignored_users.len(), 1);
4198 }
4199
4200 #[async_test]
4201 async fn test_successful_discovery() {
4202 let server = MatrixMockServer::new().await;
4204 let server_url = server.uri();
4205
4206 let homeserver = MatrixMockServer::new().await;
4208 let homeserver_url = homeserver.uri();
4209
4210 let domain = server_url.strip_prefix("http://").unwrap();
4212 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4213
4214 server
4216 .mock_well_known()
4217 .ok_with_homeserver_url(&homeserver_url)
4218 .mock_once()
4219 .named("well-known")
4220 .mount()
4221 .await;
4222
4223 homeserver.mock_versions().ok().mock_once().named("versions").mount().await;
4225
4226 let client = Client::builder()
4227 .insecure_server_name_no_tls(alice.server_name())
4228 .build()
4229 .await
4230 .unwrap();
4231
4232 assert_eq!(client.server().unwrap(), Url::parse(&server_url).unwrap());
4233 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4234 client.server_versions().await.unwrap();
4235 }
4236
4237 #[async_test]
4238 async fn test_homeserver_swap_resets_server_field() {
4239 let homeserver = MatrixMockServer::new().await;
4240 let homeserver_url = homeserver.uri();
4241
4242 let domain = homeserver_url.strip_prefix("http://").unwrap();
4243 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4244
4245 homeserver.mock_well_known().ok().mock_once().named("well-known").mount().await;
4246
4247 let client = Client::builder()
4248 .insecure_server_name_no_tls(alice.server_name())
4249 .build()
4250 .await
4251 .unwrap();
4252
4253 assert_eq!(client.server().unwrap(), Url::parse(&homeserver_url).unwrap());
4254 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4255
4256 let new_server = Url::parse("http://example.org").unwrap();
4257 client.set_homeserver(new_server.clone());
4260
4261 assert_eq!(client.homeserver(), new_server);
4263 assert!(client.server().is_none())
4266 }
4267
4268 #[async_test]
4269 async fn test_discovery_broken_server() {
4270 let server = MatrixMockServer::new().await;
4271 let server_url = server.uri();
4272 let domain = server_url.strip_prefix("http://").unwrap();
4273 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4274
4275 server.mock_well_known().error404().mock_once().named("well-known").mount().await;
4276
4277 assert!(
4278 Client::builder()
4279 .insecure_server_name_no_tls(alice.server_name())
4280 .build()
4281 .await
4282 .is_err(),
4283 "Creating a client from a user ID should fail when the .well-known request fails."
4284 );
4285 }
4286
4287 #[async_test]
4288 async fn test_room_creation() {
4289 let server = MatrixMockServer::new().await;
4290 let client = server.client_builder().build().await;
4291
4292 let f = EventFactory::new().sender(user_id!("@example:localhost"));
4293 server
4294 .mock_sync()
4295 .ok_and_run(&client, |builder| {
4296 builder.add_joined_room(
4297 JoinedRoomBuilder::default()
4298 .add_state_event(
4299 f.member(user_id!("@example:localhost")).display_name("example"),
4300 )
4301 .add_state_event(f.default_power_levels()),
4302 );
4303 })
4304 .await;
4305
4306 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
4307 assert_eq!(room.state(), RoomState::Joined);
4308 }
4309
4310 #[async_test]
4311 async fn test_retry_limit_http_requests() {
4312 let server = MatrixMockServer::new().await;
4313 let client = server
4314 .client_builder()
4315 .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(4)))
4316 .build()
4317 .await;
4318
4319 assert!(client.request_config().retry_limit.unwrap() == 4);
4320
4321 server.mock_who_am_i().error500().expect(4).mount().await;
4322
4323 client.whoami().await.unwrap_err();
4324 }
4325
4326 #[async_test]
4327 async fn test_retry_timeout_http_requests() {
4328 let retry_timeout = Duration::from_secs(5);
4330 let server = MatrixMockServer::new().await;
4331 let client = server
4332 .client_builder()
4333 .on_builder(|builder| {
4334 builder.request_config(RequestConfig::new().max_retry_time(retry_timeout))
4335 })
4336 .build()
4337 .await;
4338
4339 assert!(client.request_config().max_retry_time.unwrap() == retry_timeout);
4340
4341 server.mock_login().error500().expect(2..).mount().await;
4342
4343 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4344 }
4345
4346 #[async_test]
4347 async fn test_short_retry_initial_http_requests() {
4348 let server = MatrixMockServer::new().await;
4349 let client = server
4350 .client_builder()
4351 .on_builder(|builder| builder.request_config(RequestConfig::short_retry()))
4352 .build()
4353 .await;
4354
4355 server.mock_login().error500().expect(3..).mount().await;
4356
4357 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4358 }
4359
4360 #[async_test]
4361 async fn test_no_retry_http_requests() {
4362 let server = MatrixMockServer::new().await;
4363 let client = server.client_builder().build().await;
4364
4365 server.mock_devices().error500().mock_once().mount().await;
4366
4367 client.devices().await.unwrap_err();
4368 }
4369
4370 #[async_test]
4371 async fn test_set_homeserver() {
4372 let client = MockClientBuilder::new(None).build().await;
4373 assert_eq!(client.homeserver().as_ref(), "http://localhost/");
4374
4375 let homeserver = Url::parse("http://example.com/").unwrap();
4376 client.set_homeserver(homeserver.clone());
4377 assert_eq!(client.homeserver(), homeserver);
4378 }
4379
4380 #[async_test]
4381 async fn test_search_user_request() {
4382 let server = MatrixMockServer::new().await;
4383 let client = server.client_builder().build().await;
4384
4385 server.mock_user_directory().ok().mock_once().mount().await;
4386
4387 let response = client.search_users("test", 50).await.unwrap();
4388 assert_eq!(response.results.len(), 1);
4389 let result = &response.results[0];
4390 assert_eq!(result.user_id.to_string(), "@test:example.me");
4391 assert_eq!(result.display_name.clone().unwrap(), "Test");
4392 assert_eq!(result.avatar_url.clone().unwrap().to_string(), "mxc://example.me/someid");
4393 assert!(!response.limited);
4394 }
4395
4396 #[async_test]
4397 async fn test_request_unstable_features() {
4398 let server = MatrixMockServer::new().await;
4399 let client = server.client_builder().no_server_versions().build().await;
4400
4401 server
4402 .mock_versions()
4403 .with_feature("org.matrix.e2e_cross_signing", true)
4404 .ok()
4405 .mock_once()
4406 .mount()
4407 .await;
4408
4409 let unstable_features = client.unstable_features().await.unwrap();
4410 assert!(unstable_features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4411 assert!(!unstable_features.contains(&FeatureFlag::from("you.shall.pass")));
4412 }
4413
4414 #[async_test]
4415 async fn test_can_homeserver_push_encrypted_event_to_device() {
4416 let server = MatrixMockServer::new().await;
4417 let client = server.client_builder().no_server_versions().build().await;
4418
4419 server.mock_versions().with_push_encrypted_events().ok().mock_once().mount().await;
4420
4421 let msc4028_enabled = client.can_homeserver_push_encrypted_event_to_device().await.unwrap();
4422 assert!(msc4028_enabled);
4423 }
4424
4425 #[async_test]
4426 async fn test_recently_visited_rooms() {
4427 let client = MockClientBuilder::new(None).unlogged().build().await;
4429 assert_matches!(
4430 client.account().track_recently_visited_room(owned_room_id!("!alpha:localhost")).await,
4431 Err(Error::AuthenticationRequired)
4432 );
4433
4434 let client = MockClientBuilder::new(None).build().await;
4435 let account = client.account();
4436
4437 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 0);
4439
4440 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4442 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4443 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4444
4445 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4447 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4448
4449 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4451 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4452 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4453
4454 account.track_recently_visited_room(owned_room_id!("!beta:localhost")).await.unwrap();
4456 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4457 assert_eq!(
4458 account.get_recently_visited_rooms().await.unwrap(),
4459 [room_id!("!beta:localhost"), room_id!("!alpha:localhost")]
4460 );
4461
4462 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4464 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4465 assert_eq!(
4466 account.get_recently_visited_rooms().await.unwrap(),
4467 [room_id!("!alpha:localhost"), room_id!("!beta:localhost")]
4468 );
4469
4470 for n in 0..20 {
4472 account
4473 .track_recently_visited_room(RoomId::parse(format!("!{n}:localhost")).unwrap())
4474 .await
4475 .unwrap();
4476 }
4477
4478 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 20);
4479
4480 let rooms = account.get_recently_visited_rooms().await.unwrap();
4482 assert!(!rooms.contains(&owned_room_id!("!alpha:localhost")));
4483 assert!(!rooms.contains(&owned_room_id!("!beta:localhost")));
4484
4485 assert_eq!(rooms.first().unwrap(), room_id!("!19:localhost"));
4487 }
4488
4489 #[async_test]
4490 async fn test_client_no_cycle_with_event_cache() {
4491 let client = MockClientBuilder::new(None).build().await;
4492
4493 sleep(Duration::from_secs(1)).await;
4495
4496 let weak_client = WeakClient::from_client(&client);
4497 assert_eq!(weak_client.strong_count(), 1);
4498
4499 {
4500 let room_id = room_id!("!room:example.org");
4501
4502 let response = SyncResponseBuilder::default()
4504 .add_joined_room(JoinedRoomBuilder::new(room_id))
4505 .build_sync_response();
4506 client.inner.base_client.receive_sync_response(response).await.unwrap();
4507
4508 client.event_cache().subscribe().unwrap();
4509
4510 let (_room_event_cache, _drop_handles) =
4511 client.get_room(room_id).unwrap().event_cache().await.unwrap();
4512 }
4513
4514 drop(client);
4515
4516 sleep(Duration::from_secs(1)).await;
4518
4519 assert_eq!(weak_client.strong_count(), 0);
4521 let client = weak_client.get();
4522 assert!(
4523 client.is_none(),
4524 "too many strong references to the client: {}",
4525 Arc::strong_count(&client.unwrap().inner)
4526 );
4527 }
4528
4529 #[async_test]
4530 async fn test_supported_versions_caching() {
4531 let server = MatrixMockServer::new().await;
4532
4533 let versions_mock = server
4534 .mock_versions()
4535 .expect_default_access_token()
4536 .with_feature("org.matrix.e2e_cross_signing", true)
4537 .ok()
4538 .named("first versions mock")
4539 .expect(1)
4540 .mount_as_scoped()
4541 .await;
4542
4543 let memory_store = Arc::new(MemoryStore::new());
4544 let client = server
4545 .client_builder()
4546 .no_server_versions()
4547 .on_builder(|builder| {
4548 builder.store_config(
4549 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4550 .state_store(memory_store.clone()),
4551 )
4552 })
4553 .build()
4554 .await;
4555
4556 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4557
4558 assert_matches!(client.supported_versions_cached().await, Ok(Some(_)));
4560 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4562
4563 drop(client);
4564
4565 let client = server
4566 .client_builder()
4567 .no_server_versions()
4568 .on_builder(|builder| {
4569 builder.store_config(
4570 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4571 .state_store(memory_store.clone()),
4572 )
4573 })
4574 .build()
4575 .await;
4576
4577 assert!(
4579 client
4580 .unstable_features()
4581 .await
4582 .unwrap()
4583 .contains(&FeatureFlag::from("org.matrix.e2e_cross_signing"))
4584 );
4585
4586 let supported = client.supported_versions().await.unwrap();
4587 assert!(supported.versions.contains(&MatrixVersion::V1_0));
4588 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4589
4590 let supported = client.supported_versions().await.unwrap();
4592 assert!(supported.versions.contains(&MatrixVersion::V1_0));
4593 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4594
4595 drop(versions_mock);
4596
4597 client.reset_supported_versions().await.unwrap();
4599
4600 server.mock_versions().ok().expect(2).named("second versions mock").mount().await;
4601
4602 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4604 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4606 assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4607
4608 let supported_versions = client.supported_versions_cached().await.unwrap().unwrap();
4610 let mut ttl_value = TtlValue::new(supported_versions);
4611 ttl_value.expire();
4612 client.inner.caches.supported_versions.set_value(ttl_value);
4613
4614 client.supported_versions_cached().await.unwrap().unwrap();
4616
4617 sleep(Duration::from_secs(1)).await;
4619 assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4620 }
4621
4622 #[async_test]
4623 async fn test_well_known_caching() {
4624 let server = MatrixMockServer::new().await;
4625 let server_url = server.uri();
4626 let domain = server_url.strip_prefix("http://").unwrap();
4627 let server_name = <&ServerName>::try_from(domain).unwrap();
4628 let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4629
4630 let well_known_mock = server
4631 .mock_well_known()
4632 .ok()
4633 .named("well known mock")
4634 .expect(2) .mount_as_scoped()
4636 .await;
4637
4638 let memory_store = Arc::new(MemoryStore::new());
4639 let client = Client::builder()
4640 .insecure_server_name_no_tls(server_name)
4641 .store_config(
4642 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4643 .state_store(memory_store.clone()),
4644 )
4645 .build()
4646 .await
4647 .unwrap();
4648
4649 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4650
4651 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4653
4654 drop(client);
4655
4656 let client = server
4657 .client_builder()
4658 .no_server_versions()
4659 .on_builder(|builder| {
4660 builder.store_config(
4661 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4662 .state_store(memory_store.clone()),
4663 )
4664 })
4665 .build()
4666 .await;
4667
4668 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4670
4671 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4673
4674 drop(well_known_mock);
4675
4676 client.reset_well_known().await.unwrap();
4678
4679 server.mock_well_known().ok().named("second well known mock").expect(2).mount().await;
4680
4681 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4683 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4685
4686 let well_known = client.well_known().await;
4688 let mut ttl_value = TtlValue::new(well_known);
4689 ttl_value.expire();
4690 client.inner.caches.well_known.set_value(ttl_value);
4691
4692 client.well_known().await;
4694
4695 sleep(Duration::from_secs(5)).await;
4699 assert_matches!(client.inner.caches.well_known.value(), CachedValue::Cached(value) if !value.has_expired());
4700 }
4701
4702 #[async_test]
4703 async fn test_rtc_transports_caching() {
4704 use wiremock::{
4705 Mock, ResponseTemplate,
4706 matchers::{method, path_regex},
4707 };
4708
4709 let server = MatrixMockServer::new().await;
4710 let transports = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4711
4712 let transports_mock = Mock::given(method("GET"))
4713 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4714 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4715 "rtc_transports": [
4716 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4717 ]
4718 })))
4719 .named("first transports mock")
4720 .expect(1)
4721 .mount_as_scoped(server.server())
4722 .await;
4723
4724 let client = server.client_builder().build().await;
4725
4726 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4728 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4730 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4731
4732 drop(transports_mock);
4733
4734 client.reset_rtc_transports();
4736
4737 Mock::given(method("GET"))
4738 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4739 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4740 "rtc_transports": [
4741 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4742 ]
4743 })))
4744 .named("second transports mock")
4745 .expect(2)
4746 .mount(server.server())
4747 .await;
4748
4749 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4751 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4753
4754 let mut ttl_value = TtlValue::new(Some(transports.clone()));
4756 ttl_value.expire();
4757 client.inner.caches.rtc_transports.set_value(ttl_value);
4758
4759 client.rtc_transports().await.unwrap();
4761
4762 sleep(Duration::from_secs(1)).await;
4764 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4765 }
4766
4767 #[async_test]
4768 async fn test_rtc_transports_unsupported_caching() {
4769 use wiremock::{
4770 Mock, ResponseTemplate,
4771 matchers::{method, path_regex},
4772 };
4773
4774 let server = MatrixMockServer::new().await;
4775
4776 Mock::given(method("GET"))
4781 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4782 .respond_with(ResponseTemplate::new(404).set_body_json(json!({
4783 "errcode": "M_UNRECOGNIZED",
4784 "error": "Unrecognized request",
4785 })))
4786 .named("unrecognized transports mock")
4787 .expect(1)
4788 .mount(server.server())
4789 .await;
4790
4791 let client = server.client_builder().build().await;
4792
4793 assert_eq!(client.rtc_transports().await.unwrap(), None);
4796 assert_eq!(client.rtc_transports().await.unwrap(), None);
4798 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4799 }
4800
4801 async fn mock_rtc_transports_endpoint(
4805 server: &MatrixMockServer,
4806 supported: bool,
4807 ) -> wiremock::MockGuard {
4808 use wiremock::{
4809 Mock, ResponseTemplate,
4810 matchers::{method, path_regex},
4811 };
4812
4813 let response = if supported {
4814 ResponseTemplate::new(200).set_body_json(json!({
4815 "rtc_transports": [
4816 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4817 ]
4818 }))
4819 } else {
4820 ResponseTemplate::new(404).set_body_json(json!({
4821 "errcode": "M_UNRECOGNIZED",
4822 "error": "Unrecognized request",
4823 }))
4824 };
4825
4826 Mock::given(method("GET"))
4827 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4828 .respond_with(response)
4829 .named("transports mock")
4830 .expect(1)
4831 .mount_as_scoped(server.server())
4832 .await
4833 }
4834
4835 #[async_test]
4836 async fn test_discover_rtc_transports_prefers_the_endpoint() {
4837 let server = MatrixMockServer::new().await;
4838 let transports = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4839
4840 let _transports_mock = mock_rtc_transports_endpoint(&server, true).await;
4841
4842 let _well_known_mock = server
4845 .mock_well_known()
4846 .ok()
4847 .named("well-known mock")
4848 .expect(0)
4849 .mount_as_scoped()
4850 .await;
4851
4852 let client = server.client_builder().build().await;
4853
4854 assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(transports));
4855 }
4856
4857 #[async_test]
4858 async fn test_discover_rtc_transports_falls_back_to_well_known() {
4859 let server = MatrixMockServer::new().await;
4860 let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4862
4863 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4864
4865 let _well_known_mock = server
4866 .mock_well_known()
4867 .ok()
4868 .named("well-known mock")
4869 .expect(1)
4870 .mount_as_scoped()
4871 .await;
4872
4873 let client = server.client_builder().build().await;
4874
4875 assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(rtc_foci));
4878 }
4879
4880 async fn mock_well_known_never_called(server: &MatrixMockServer) -> wiremock::MockGuard {
4882 server.mock_well_known().ok().named("well-known mock").expect(0).mount_as_scoped().await
4883 }
4884
4885 #[async_test]
4886 async fn test_well_known_lookup_disabled() {
4887 let server = MatrixMockServer::new().await;
4888
4889 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4890
4891 let _well_known_mock = mock_well_known_never_called(&server).await;
4893
4894 let client = server
4896 .client_builder()
4897 .on_builder(|builder| builder.disable_well_known_lookup(true))
4898 .build()
4899 .await;
4900
4901 assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
4904 assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
4906 assert!(client.tile_server().await.is_none());
4907 assert!(client.fetch_client_well_known().await.is_none());
4908 }
4909
4910 #[async_test]
4911 async fn test_well_known_lookup_disabled_after_build() {
4912 let server = MatrixMockServer::new().await;
4913
4914 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4915
4916 let _well_known_mock = mock_well_known_never_called(&server).await;
4918
4919 let client = server.client_builder().build().await;
4921 client.disable_well_known_lookup(true);
4922
4923 assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
4926 assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
4928 assert!(client.tile_server().await.is_none());
4929 assert!(client.fetch_client_well_known().await.is_none());
4930 }
4931
4932 #[async_test]
4933 async fn test_missing_well_known_caching() {
4934 let server = MatrixMockServer::new().await;
4935 let rtc_foci: Vec<RtcTransport> = vec![];
4936
4937 let well_known_mock = server
4938 .mock_well_known()
4939 .error_unrecognized()
4940 .named("first well-known mock")
4941 .expect(1)
4942 .mount_as_scoped()
4943 .await;
4944
4945 let memory_store = Arc::new(MemoryStore::new());
4946 let client = server
4947 .client_builder()
4948 .on_builder(|builder| {
4949 builder.store_config(
4950 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4951 .state_store(memory_store.clone()),
4952 )
4953 })
4954 .build()
4955 .await;
4956
4957 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4958
4959 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4961
4962 drop(client);
4963
4964 let client = server
4965 .client_builder()
4966 .on_builder(|builder| {
4967 builder.store_config(
4968 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4969 .state_store(memory_store.clone()),
4970 )
4971 })
4972 .build()
4973 .await;
4974
4975 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4977
4978 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4980
4981 drop(well_known_mock);
4982
4983 client.reset_well_known().await.unwrap();
4985
4986 server
4987 .mock_well_known()
4988 .error_unrecognized()
4989 .expect(1)
4990 .named("second well-known mock")
4991 .mount()
4992 .await;
4993
4994 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4996 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4998 }
4999
5000 #[async_test]
5001 async fn test_no_network_doesnt_cause_infinite_retries() {
5002 let client = MockClientBuilder::new(None)
5004 .on_builder(|builder| builder.request_config(RequestConfig::new()))
5005 .build()
5006 .await;
5007
5008 client.whoami().await.unwrap_err();
5011 }
5012
5013 #[async_test]
5014 async fn test_await_room_remote_echo_returns_the_room_if_it_was_already_synced() {
5015 let server = MatrixMockServer::new().await;
5016 let client = server.client_builder().build().await;
5017
5018 let room_id = room_id!("!room:example.org");
5019
5020 server
5021 .mock_sync()
5022 .ok_and_run(&client, |builder| {
5023 builder.add_joined_room(JoinedRoomBuilder::new(room_id));
5024 })
5025 .await;
5026
5027 let room = client.await_room_remote_echo(room_id).now_or_never().unwrap();
5028 assert_eq!(room.room_id(), room_id);
5029 }
5030
5031 #[async_test]
5032 async fn test_await_room_remote_echo_returns_the_room_when_it_is_ready() {
5033 let server = MatrixMockServer::new().await;
5034 let client = server.client_builder().build().await;
5035
5036 let room_id = room_id!("!room:example.org");
5037
5038 let client = Arc::new(client);
5039
5040 spawn({
5043 let client = client.clone();
5044 async move {
5045 sleep(Duration::from_millis(100)).await;
5046
5047 server
5048 .mock_sync()
5049 .ok_and_run(&client, |builder| {
5050 builder.add_joined_room(JoinedRoomBuilder::new(room_id));
5051 })
5052 .await;
5053 }
5054 });
5055
5056 let room =
5057 timeout(Duration::from_secs(10), client.await_room_remote_echo(room_id)).await.unwrap();
5058 assert_eq!(room.room_id(), room_id);
5059 }
5060
5061 #[async_test]
5062 async fn test_await_room_remote_echo_will_timeout_if_no_room_is_found() {
5063 let client = MockClientBuilder::new(None).build().await;
5064
5065 let room_id = room_id!("!room:example.org");
5066 timeout(Duration::from_secs(1), client.await_room_remote_echo(room_id)).await.unwrap_err();
5069 }
5070
5071 #[async_test]
5072 async fn test_await_room_remote_echo_will_timeout_if_room_is_found_but_not_synced() {
5073 let server = MatrixMockServer::new().await;
5074 let client = server.client_builder().build().await;
5075
5076 server.mock_create_room().ok().mount().await;
5077
5078 let room = client
5080 .create_room(assign!(CreateRoomRequest::new(), {
5081 invite: vec![],
5082 is_direct: false,
5083 }))
5084 .await
5085 .unwrap();
5086
5087 timeout(Duration::from_secs(1), client.await_room_remote_echo(room.room_id()))
5089 .await
5090 .unwrap_err();
5091 }
5092
5093 #[async_test]
5094 async fn test_is_room_alias_available_if_alias_is_not_resolved() {
5095 let server = MatrixMockServer::new().await;
5096 let client = server.client_builder().build().await;
5097
5098 server.mock_room_directory_resolve_alias().not_found().expect(1).mount().await;
5099
5100 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5101 assert_matches!(ret, Ok(true));
5102 }
5103
5104 #[async_test]
5105 async fn test_is_room_alias_available_if_alias_is_resolved() {
5106 let server = MatrixMockServer::new().await;
5107 let client = server.client_builder().build().await;
5108
5109 server
5110 .mock_room_directory_resolve_alias()
5111 .ok("!some_room_id:matrix.org", Vec::new())
5112 .expect(1)
5113 .mount()
5114 .await;
5115
5116 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5117 assert_matches!(ret, Ok(false));
5118 }
5119
5120 #[async_test]
5121 async fn test_is_room_alias_available_if_error_found() {
5122 let server = MatrixMockServer::new().await;
5123 let client = server.client_builder().build().await;
5124
5125 server.mock_room_directory_resolve_alias().error500().expect(1).mount().await;
5126
5127 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5128 assert_matches!(ret, Err(_));
5129 }
5130
5131 #[async_test]
5132 async fn test_create_room_alias() {
5133 let server = MatrixMockServer::new().await;
5134 let client = server.client_builder().build().await;
5135
5136 server.mock_room_directory_create_room_alias().ok().expect(1).mount().await;
5137
5138 let ret = client
5139 .create_room_alias(
5140 room_alias_id!("#some_alias:matrix.org"),
5141 room_id!("!some_room:matrix.org"),
5142 )
5143 .await;
5144 assert_matches!(ret, Ok(()));
5145 }
5146
5147 #[async_test]
5148 async fn test_join_room_by_id_or_alias() {
5149 use wiremock::{
5150 Mock, ResponseTemplate,
5151 matchers::{method, path_regex},
5152 };
5153 let server = MatrixMockServer::new().await;
5154 let client = server.client_builder().build().await;
5155
5156 let target_room_id = room_id!("!some_id:matrix.org");
5157 let target_alias = room_alias_id!("#some_alias:matrix.org");
5158
5159 Mock::given(method("POST"))
5160 .and(path_regex("^/_matrix/client/v3/join/.*$"))
5161 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
5162 "room_id": target_room_id
5163 })))
5164 .mount(server.server())
5165 .await;
5166
5167 server
5168 .mock_room_directory_resolve_alias()
5169 .ok(target_room_id.as_str(), Vec::new())
5170 .mount()
5171 .await;
5172
5173 server.mock_room_join(target_room_id).ok().mount().await;
5174
5175 let ret = client.join_room_by_id_or_alias(target_alias.into(), &[]).await;
5176 assert!(ret.is_ok());
5177
5178 let ret = client.join_room_by_id_or_alias(target_room_id.into(), &[]).await;
5179 assert!(ret.is_ok());
5180 }
5181
5182 #[async_test]
5183 async fn test_room_preview_for_invited_room_hits_summary_endpoint() {
5184 let server = MatrixMockServer::new().await;
5185 let client = server.client_builder().build().await;
5186
5187 let room_id = room_id!("!a-room:matrix.org");
5188
5189 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5191
5192 let invited_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Invited);
5194
5195 let preview = client
5197 .get_room_preview(room_id.into(), Vec::new())
5198 .await
5199 .expect("Room preview should be retrieved");
5200
5201 assert_eq!(invited_room.room_id(), preview.room_id);
5202 }
5203
5204 #[async_test]
5205 async fn test_room_preview_for_left_room_hits_summary_endpoint() {
5206 let server = MatrixMockServer::new().await;
5207 let client = server.client_builder().build().await;
5208
5209 let room_id = room_id!("!a-room:matrix.org");
5210
5211 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5213
5214 let left_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Left);
5216
5217 let preview = client
5219 .get_room_preview(room_id.into(), Vec::new())
5220 .await
5221 .expect("Room preview should be retrieved");
5222
5223 assert_eq!(left_room.room_id(), preview.room_id);
5224 }
5225
5226 #[async_test]
5227 async fn test_room_preview_for_knocked_room_hits_summary_endpoint() {
5228 let server = MatrixMockServer::new().await;
5229 let client = server.client_builder().build().await;
5230
5231 let room_id = room_id!("!a-room:matrix.org");
5232
5233 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5235
5236 let knocked_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Knocked);
5238
5239 let preview = client
5241 .get_room_preview(room_id.into(), Vec::new())
5242 .await
5243 .expect("Room preview should be retrieved");
5244
5245 assert_eq!(knocked_room.room_id(), preview.room_id);
5246 }
5247
5248 #[async_test]
5249 async fn test_room_preview_for_joined_room_retrieves_local_room_info() {
5250 let server = MatrixMockServer::new().await;
5251 let client = server.client_builder().build().await;
5252
5253 let room_id = room_id!("!a-room:matrix.org");
5254
5255 server.mock_room_summary().ok(room_id).never().mount().await;
5257
5258 let joined_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Joined);
5260
5261 let preview = client
5263 .get_room_preview(room_id.into(), Vec::new())
5264 .await
5265 .expect("Room preview should be retrieved");
5266
5267 assert_eq!(joined_room.room_id(), preview.room_id);
5268 }
5269
5270 #[async_test]
5271 async fn test_media_preview_config() {
5272 let server = MatrixMockServer::new().await;
5273 let client = server.client_builder().build().await;
5274
5275 server
5276 .mock_sync()
5277 .ok_and_run(&client, |builder| {
5278 builder.add_custom_global_account_data(json!({
5279 "content": {
5280 "media_previews": "private",
5281 "invite_avatars": "off"
5282 },
5283 "type": "m.media_preview_config"
5284 }));
5285 })
5286 .await;
5287
5288 let (initial_value, stream) =
5289 client.account().observe_media_preview_config().await.unwrap();
5290
5291 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5292 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5293 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5294 pin_mut!(stream);
5295 assert_pending!(stream);
5296
5297 server
5298 .mock_sync()
5299 .ok_and_run(&client, |builder| {
5300 builder.add_custom_global_account_data(json!({
5301 "content": {
5302 "media_previews": "off",
5303 "invite_avatars": "on"
5304 },
5305 "type": "m.media_preview_config"
5306 }));
5307 })
5308 .await;
5309
5310 assert_next_matches!(
5311 stream,
5312 MediaPreviewConfigEventContent {
5313 media_previews: Some(MediaPreviews::Off),
5314 invite_avatars: Some(InviteAvatars::On),
5315 ..
5316 }
5317 );
5318 assert_pending!(stream);
5319 }
5320
5321 #[async_test]
5322 async fn test_unstable_media_preview_config() {
5323 let server = MatrixMockServer::new().await;
5324 let client = server.client_builder().build().await;
5325
5326 server
5327 .mock_sync()
5328 .ok_and_run(&client, |builder| {
5329 builder.add_custom_global_account_data(json!({
5330 "content": {
5331 "media_previews": "private",
5332 "invite_avatars": "off"
5333 },
5334 "type": "io.element.msc4278.media_preview_config"
5335 }));
5336 })
5337 .await;
5338
5339 let (initial_value, stream) =
5340 client.account().observe_media_preview_config().await.unwrap();
5341
5342 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5343 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5344 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5345 pin_mut!(stream);
5346 assert_pending!(stream);
5347
5348 server
5349 .mock_sync()
5350 .ok_and_run(&client, |builder| {
5351 builder.add_custom_global_account_data(json!({
5352 "content": {
5353 "media_previews": "off",
5354 "invite_avatars": "on"
5355 },
5356 "type": "io.element.msc4278.media_preview_config"
5357 }));
5358 })
5359 .await;
5360
5361 assert_next_matches!(
5362 stream,
5363 MediaPreviewConfigEventContent {
5364 media_previews: Some(MediaPreviews::Off),
5365 invite_avatars: Some(InviteAvatars::On),
5366 ..
5367 }
5368 );
5369 assert_pending!(stream);
5370 }
5371
5372 #[async_test]
5373 async fn test_media_preview_config_not_found() {
5374 let server = MatrixMockServer::new().await;
5375 let client = server.client_builder().build().await;
5376
5377 let (initial_value, _) = client.account().observe_media_preview_config().await.unwrap();
5378
5379 assert!(initial_value.is_none());
5380 }
5381
5382 #[async_test]
5383 async fn test_load_or_fetch_max_upload_size_with_auth_matrix_version() {
5384 let server = MatrixMockServer::new().await;
5387 let client = server.client_builder().build().await;
5388
5389 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5390
5391 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5392 client.load_or_fetch_max_upload_size().await.unwrap();
5393
5394 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5395 }
5396
5397 #[async_test]
5398 async fn test_load_or_fetch_max_upload_size_with_auth_stable_feature() {
5399 let server = MatrixMockServer::new().await;
5402 let client = server.client_builder().no_server_versions().build().await;
5403
5404 server
5405 .mock_versions()
5406 .with_versions(vec!["v1.7", "v1.8", "v1.9", "v1.10"])
5407 .with_feature("org.matrix.msc3916.stable", true)
5408 .ok()
5409 .named("versions")
5410 .expect(1)
5411 .mount()
5412 .await;
5413
5414 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5415
5416 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5417 client.load_or_fetch_max_upload_size().await.unwrap();
5418
5419 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5420 }
5421
5422 #[async_test]
5423 async fn test_load_or_fetch_max_upload_size_no_auth() {
5424 let server = MatrixMockServer::new().await;
5427 let client = server.client_builder().no_server_versions().build().await;
5428
5429 server
5430 .mock_versions()
5431 .with_versions(vec!["v1.1"])
5432 .ok()
5433 .named("versions")
5434 .expect(1)
5435 .mount()
5436 .await;
5437
5438 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5439
5440 server.mock_media_config().ok(uint!(2)).mock_once().mount().await;
5441 client.load_or_fetch_max_upload_size().await.unwrap();
5442
5443 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5444 }
5445
5446 #[async_test]
5447 async fn test_uploading_a_too_large_media_file() {
5448 let server = MatrixMockServer::new().await;
5449 let client = server.client_builder().build().await;
5450
5451 server.mock_authenticated_media_config().ok(uint!(1)).mock_once().mount().await;
5452 client.load_or_fetch_max_upload_size().await.unwrap();
5453 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(1));
5454
5455 let data = vec![1, 2];
5456 let upload_request =
5457 ruma::api::client::media::create_content::v3::Request::new(data.clone());
5458 let request = SendRequest {
5459 client: client.clone(),
5460 request: upload_request,
5461 config: None,
5462 send_progress: SharedObservable::new(TransmissionProgress::default()),
5463 };
5464 let media_request = SendMediaUploadRequest::new(request);
5465
5466 let error = media_request.await.err();
5467 assert_let!(Some(Error::Media(MediaError::MediaTooLargeToUpload { max, current })) = error);
5468 assert_eq!(max, uint!(1));
5469 assert_eq!(current, UInt::new_wrapping(data.len() as u64));
5470 }
5471
5472 #[async_test]
5473 async fn test_dont_ignore_timeout_on_first_sync() {
5474 let server = MatrixMockServer::new().await;
5475 let client = server.client_builder().build().await;
5476
5477 server
5478 .mock_sync()
5479 .timeout(Some(Duration::from_secs(30)))
5480 .ok(|_| {})
5481 .mock_once()
5482 .named("sync_with_timeout")
5483 .mount()
5484 .await;
5485
5486 let mut stream = Box::pin(client.sync_stream(SyncSettings::new()).await);
5488
5489 timeout(Duration::from_secs(1), async {
5490 stream.next().await.unwrap().unwrap();
5491 })
5492 .await
5493 .unwrap();
5494 }
5495
5496 #[async_test]
5497 async fn test_ignore_timeout_on_first_sync() {
5498 let server = MatrixMockServer::new().await;
5499 let client = server.client_builder().build().await;
5500
5501 server
5502 .mock_sync()
5503 .timeout(None)
5504 .ok(|_| {})
5505 .mock_once()
5506 .named("sync_no_timeout")
5507 .mount()
5508 .await;
5509 server
5510 .mock_sync()
5511 .timeout(Some(Duration::from_secs(30)))
5512 .ok(|_| {})
5513 .mock_once()
5514 .named("sync_with_timeout")
5515 .mount()
5516 .await;
5517
5518 let mut stream = Box::pin(
5520 client.sync_stream(SyncSettings::new().ignore_timeout_on_first_sync(true)).await,
5521 );
5522
5523 timeout(Duration::from_secs(1), async {
5524 stream.next().await.unwrap().unwrap();
5525 stream.next().await.unwrap().unwrap();
5526 })
5527 .await
5528 .unwrap();
5529 }
5530
5531 #[async_test]
5532 async fn test_get_dm_room_returns_the_room_we_have_with_this_user() {
5533 let server = MatrixMockServer::new().await;
5534 let client = server.client_builder().build().await;
5535 let user_id = user_id!("@invited:localhost");
5539
5540 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5542 let response = SyncResponseBuilder::default()
5543 .add_joined_room(JoinedRoomBuilder::default().add_state_event(f.member(user_id)))
5544 .add_global_account_data(
5545 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5546 )
5547 .build_sync_response();
5548 client.base_client().receive_sync_response(response).await.unwrap();
5549
5550 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5552 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5553 }
5554
5555 #[async_test]
5556 async fn test_get_dm_room_still_finds_room_where_participant_is_only_invited() {
5557 let server = MatrixMockServer::new().await;
5558 let client = server.client_builder().build().await;
5559 let user_id = user_id!("@invited:localhost");
5561
5562 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5564 let response = SyncResponseBuilder::default()
5565 .add_joined_room(
5566 JoinedRoomBuilder::default()
5567 .add_state_event(f.member(user_id).invited(user_id).display_name("example")),
5568 )
5569 .add_global_account_data(
5570 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5571 )
5572 .build_sync_response();
5573 client.base_client().receive_sync_response(response).await.unwrap();
5574
5575 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5577 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5578 }
5579
5580 #[async_test]
5581 async fn test_get_dm_room_still_finds_left_room() {
5582 let server = MatrixMockServer::new().await;
5586 let client = server.client_builder().build().await;
5587 let user_id = user_id!("@invited:localhost");
5591
5592 let f = EventFactory::new().sender(user_id);
5594 let response = SyncResponseBuilder::default()
5595 .add_joined_room(
5596 JoinedRoomBuilder::default().add_state_event(f.member(user_id).leave()),
5597 )
5598 .add_global_account_data(
5599 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5600 )
5601 .build_sync_response();
5602 client.base_client().receive_sync_response(response).await.unwrap();
5603
5604 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5606 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5607 }
5608
5609 #[async_test]
5610 async fn test_device_exists() {
5611 let server = MatrixMockServer::new().await;
5612 let client = server.client_builder().build().await;
5613
5614 server.mock_get_device().ok().expect(1).mount().await;
5615
5616 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(true));
5617 }
5618
5619 #[async_test]
5620 async fn test_device_exists_404() {
5621 let server = MatrixMockServer::new().await;
5622 let client = server.client_builder().build().await;
5623
5624 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(false));
5625 }
5626
5627 #[async_test]
5628 async fn test_device_exists_500() {
5629 let server = MatrixMockServer::new().await;
5630 let client = server.client_builder().build().await;
5631
5632 server.mock_get_device().error500().expect(1).mount().await;
5633
5634 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Err(_));
5635 }
5636
5637 #[async_test]
5638 async fn test_fetching_well_known_with_homeserver_url() {
5639 let server = MatrixMockServer::new().await;
5640 let client = server.client_builder().build().await;
5641 server.mock_well_known().ok().mount().await;
5642
5643 assert_matches!(client.fetch_client_well_known().await, Some(_));
5644 }
5645
5646 #[async_test]
5647 async fn test_fetching_well_known_with_server_name() {
5648 let server = MatrixMockServer::new().await;
5649 let server_name = ServerName::parse(server.server().address().to_string()).unwrap();
5650
5651 server.mock_well_known().ok().mount().await;
5652
5653 let client = MockClientBuilder::new(None)
5654 .on_builder(|builder| builder.insecure_server_name_no_tls(&server_name))
5655 .build()
5656 .await;
5657
5658 assert_matches!(client.fetch_client_well_known().await, Some(_));
5659 }
5660
5661 #[async_test]
5662 async fn test_fetching_well_known_with_domain_part_of_user_id() {
5663 let server = MatrixMockServer::new().await;
5664 server.mock_well_known().ok().mount().await;
5665
5666 let user_id =
5667 UserId::parse(format!("@user:{}", server.server().address())).expect("Invalid user id");
5668 let client = MockClientBuilder::new(None)
5669 .logged_in_with_token("A_TOKEN".to_owned(), user_id, owned_device_id!("ABCDEF"))
5670 .build()
5671 .await;
5672
5673 assert_matches!(client.fetch_client_well_known().await, Some(_));
5674 }
5675
5676 #[cfg(feature = "e2e-encryption")]
5677 #[async_test]
5678 async fn test_syncing_one_time_key_counts_updates() -> Result<()> {
5679 use wiremock::ResponseTemplate;
5680
5681 macro_rules! assert_key_count {
5682 ($client: ident, $count:literal) => {{
5683 let machine = $client.olm_machine().await;
5684 let uploaded_key_counts =
5685 machine.as_ref().unwrap().uploaded_key_count().await.unwrap();
5686 assert_eq!(uploaded_key_counts, $count)
5687 }};
5688 }
5689
5690 macro_rules! sync_with_key_count {
5691 ($client: ident, $server:ident, $count:literal) => {
5692 let count = Some($count);
5693 sync_with_key_count!($client, $server, count);
5694 };
5695 ($client: ident, $server:ident, $count:ident) => {{
5696 use rand::RngExt as _;
5697
5698 let next_batch: String = rand::rng()
5699 .sample_iter(&rand::distr::Alphanumeric)
5700 .take(16)
5701 .map(char::from)
5702 .collect();
5703
5704 let count: Option<u32> = $count;
5705
5706 let template = if let Some(count) = count {
5707 ResponseTemplate::new(200).set_body_json(json!({
5708 "next_batch": next_batch,
5709 "rooms": {"leave": {}, "join": {}, "invite": {}},
5710 "device_lists": {
5711 "changed": [],
5712 "left": [],
5713 },
5714 "device_one_time_keys_count": {
5715 "signed_curve25519": count
5716 },
5717 }))
5718 } else {
5719 ResponseTemplate::new(200).set_body_json(json!({
5720 "next_batch": next_batch,
5721 "rooms": {"leave": {}, "join": {}, "invite": {}},
5722 "device_lists": {
5723 "changed": [],
5724 "left": [],
5725 },
5726 "device_one_time_keys_count": {},
5727 }))
5728 };
5729
5730 let _sync_mock_guard = $server.mock_sync().respond_with(template).mount_as_scoped().await;
5731 $client.sync_once(Default::default()).await?;
5732 }}
5733 }
5734
5735 let server = MatrixMockServer::new().await;
5736 let client = server.client_builder().build().await;
5737
5738 server.mock_upload_keys().ok_with_signed_curve_key_count(50).mock_once().mount().await;
5739
5740 assert_key_count!(client, 0);
5742
5743 sync_with_key_count!(client, server, 50);
5745 assert_key_count!(client, 50);
5746
5747 sync_with_key_count!(client, server, 10);
5749 assert_key_count!(client, 10);
5750
5751 sync_with_key_count!(client, server, None);
5753 assert_key_count!(client, 0);
5754
5755 Ok(())
5756 }
5757
5758 #[async_test]
5759 async fn test_get_retention_configuration() {
5760 use wiremock::{
5761 Mock, ResponseTemplate,
5762 matchers::{method, path},
5763 };
5764
5765 let server = MatrixMockServer::new().await;
5766 let client = server.client_builder().build().await;
5767
5768 Mock::given(method("GET"))
5769 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5770 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
5771 "policies": {},
5772 "limits": {},
5773 })))
5774 .expect(1)
5775 .mount(server.server())
5776 .await;
5777
5778 let response = client.get_retention_configuration().await;
5779 assert!(response.is_ok());
5780 let response = response.unwrap();
5781 assert!(response.policies.is_empty());
5782 assert!(response.limits.max_lifetime.is_none());
5783 assert!(response.limits.min_lifetime.is_none());
5784 }
5785}