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 total_unread_notifications(&self) -> u64 {
1475 self.base_client()
1476 .rooms_filtered(RoomStateFilter::JOINED)
1477 .iter()
1478 .map(|room| room.num_unread_notifications().max(room.is_marked_unread().into()))
1479 .sum()
1480 }
1481
1482 pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1488 self.base_client().get_room(room_id).map(|room| Room::new(self.clone(), room))
1489 }
1490
1491 pub async fn get_room_preview(
1494 &self,
1495 room_or_alias_id: &RoomOrAliasId,
1496 via: Vec<OwnedServerName>,
1497 ) -> Result<RoomPreview> {
1498 let room_id = match <&RoomId>::try_from(room_or_alias_id) {
1499 Ok(room_id) => room_id.to_owned(),
1500 Err(alias) => self.resolve_room_alias(alias).await?.room_id,
1501 };
1502
1503 if let Some(room) = self.get_room(&room_id) {
1504 match room.state() {
1510 RoomState::Joined | RoomState::Banned => {
1511 return Ok(RoomPreview::from_known_room(&room).await);
1512 }
1513 RoomState::Left | RoomState::Invited | RoomState::Knocked => {}
1514 }
1515 }
1516
1517 RoomPreview::from_remote_room(self, room_id, room_or_alias_id, via).await
1518 }
1519
1520 pub async fn resolve_room_alias(
1527 &self,
1528 room_alias: &RoomAliasId,
1529 ) -> HttpResult<get_alias::v3::Response> {
1530 let request = get_alias::v3::Request::new(room_alias.to_owned());
1531 self.send(request).await
1532 }
1533
1534 pub async fn is_room_alias_available(&self, alias: &RoomAliasId) -> HttpResult<bool> {
1542 match self.resolve_room_alias(alias).await {
1543 Ok(_) => Ok(false),
1545 Err(error) => {
1546 match error.client_api_error_kind() {
1547 Some(ErrorKind::NotFound) => Ok(true),
1549 _ => Err(error),
1550 }
1551 }
1552 }
1553 }
1554
1555 pub async fn create_room_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> HttpResult<()> {
1557 let request = create_alias::v3::Request::new(alias.to_owned(), room_id.to_owned());
1558 self.send(request).await?;
1559 Ok(())
1560 }
1561
1562 pub async fn remove_room_alias(&self, alias: &RoomAliasId) -> HttpResult<()> {
1564 let request = delete_alias::v3::Request::new(alias.to_owned());
1565 self.send(request).await?;
1566 Ok(())
1567 }
1568
1569 pub(crate) fn maybe_update_login_well_known(&self, login_well_known: Option<&DiscoveryInfo>) {
1576 if self.inner.respect_login_well_known
1577 && let Some(well_known) = login_well_known
1578 && let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url)
1579 {
1580 self.set_homeserver(homeserver);
1581 }
1582 }
1583
1584 #[instrument(skip_all)]
1591 pub async fn restore_session(&self, session: impl Into<AuthSession>) -> Result<()> {
1592 self.restore_session_with(session, RoomLoadSettings::default()).await
1593 }
1594
1595 #[instrument(skip_all)]
1606 pub async fn restore_session_with(
1607 &self,
1608 session: impl Into<AuthSession>,
1609 room_load_settings: RoomLoadSettings,
1610 ) -> Result<()> {
1611 let session = session.into();
1612 match session {
1613 AuthSession::Matrix(session) => {
1614 Box::pin(self.matrix_auth().restore_session(session, room_load_settings)).await
1615 }
1616 AuthSession::OAuth(session) => {
1617 Box::pin(self.oauth().restore_session(*session, room_load_settings)).await
1618 }
1619 }
1620 }
1621
1622 pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1628 let Some(auth_api) = self.auth_api() else {
1629 return Err(RefreshTokenError::RefreshTokenRequired);
1630 };
1631
1632 match auth_api {
1633 AuthApi::Matrix(api) => {
1634 trace!("Token refresh: Using the homeserver.");
1635 Box::pin(api.refresh_access_token()).await?;
1636 }
1637 AuthApi::OAuth(api) => {
1638 trace!("Token refresh: Using OAuth 2.0.");
1639 Box::pin(api.refresh_access_token()).await?;
1640 }
1641 }
1642
1643 Ok(())
1644 }
1645
1646 pub async fn logout(&self) -> Result<(), Error> {
1653 let auth_api = self.auth_api().ok_or(Error::AuthenticationRequired)?;
1654 match auth_api {
1655 AuthApi::Matrix(matrix_auth) => {
1656 matrix_auth.logout().await?;
1657 Ok(())
1658 }
1659 AuthApi::OAuth(oauth) => Ok(oauth.logout().await?),
1660 }
1661 }
1662
1663 #[instrument(skip(self, definition))]
1709 pub async fn get_or_upload_filter(
1710 &self,
1711 filter_name: &str,
1712 definition: FilterDefinition,
1713 ) -> Result<String> {
1714 if let Some(filter) = self.inner.base_client.get_filter(filter_name).await? {
1715 debug!("Found filter locally");
1716 Ok(filter)
1717 } else {
1718 debug!("Didn't find filter locally");
1719 let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?;
1720 let request = FilterUploadRequest::new(user_id.to_owned(), definition);
1721 let response = self.send(request).await?;
1722
1723 self.inner.base_client.receive_filter_upload(filter_name, &response).await?;
1724
1725 Ok(response.filter_id)
1726 }
1727 }
1728
1729 async fn prepare_join_room_by_id(&self, room_id: &RoomId) -> Option<PreJoinRoomInfo> {
1731 let room = self.get_room(room_id)?;
1732
1733 let inviter = match room.invite_details().await {
1734 Ok(details) => details.inviter,
1735 Err(Error::WrongRoomState(_)) => None,
1736 Err(e) => {
1737 warn!("Error fetching invite details for room: {e:?}");
1738 None
1739 }
1740 };
1741
1742 Some(PreJoinRoomInfo { inviter })
1743 }
1744
1745 async fn finish_join_room(
1758 &self,
1759 room_id: &RoomId,
1760 pre_join_room_info: Option<PreJoinRoomInfo>,
1761 ) -> Result<Room> {
1762 info!(?room_id, ?pre_join_room_info, "Completing room join");
1763 let mark_as_dm = if let Some(room) = self.get_room(room_id) {
1764 room.state() == RoomState::Invited
1765 && room.is_direct().await.unwrap_or_else(|e| {
1766 warn!(%room_id, "is_direct() failed: {e}");
1767 false
1768 })
1769 } else {
1770 false
1771 };
1772
1773 let base_room = self
1774 .base_client()
1775 .room_joined(
1776 room_id,
1777 pre_join_room_info
1778 .as_ref()
1779 .and_then(|info| info.inviter.as_ref())
1780 .map(|i| i.user_id().to_owned()),
1781 )
1782 .await?;
1783 let room = Room::new(self.clone(), base_room);
1784
1785 if mark_as_dm {
1786 room.set_is_direct(true).await?;
1787 }
1788
1789 #[cfg(feature = "e2e-encryption")]
1796 if self.inner.enable_share_history_on_invite
1797 && let Some(inviter) =
1798 pre_join_room_info.as_ref().and_then(|info| info.inviter.as_ref())
1799 {
1800 crate::room::shared_room_history::maybe_accept_key_bundle(&room, inviter.user_id())
1801 .await?;
1802 }
1803
1804 #[cfg(not(feature = "e2e-encryption"))]
1806 let _ = pre_join_room_info.map(|i| i.inviter);
1807
1808 Ok(room)
1809 }
1810
1811 #[instrument(skip(self))]
1819 pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result<Room> {
1820 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1823
1824 let request = join_room_by_id::v3::Request::new(room_id.to_owned());
1825 let response = self.send(request).await?;
1826 self.finish_join_room(&response.room_id, pre_join_info).await
1827 }
1828
1829 #[instrument(skip(self))]
1840 pub async fn join_room_by_id_or_alias(
1841 &self,
1842 alias: &RoomOrAliasId,
1843 server_names: &[OwnedServerName],
1844 ) -> Result<Room> {
1845 let room_id = match <&RoomId>::try_from(alias) {
1846 Ok(room_id) => room_id,
1847 Err(room_alias) => &self.resolve_room_alias(room_alias).await?.room_id,
1848 };
1849 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1850 let request = assign!(join_room_by_id_or_alias::v3::Request::new(alias.to_owned()), {
1851 via: server_names.to_owned(),
1852 });
1853 let response = self.send(request).await?;
1854 self.finish_join_room(&response.room_id, pre_join_info).await
1855 }
1856
1857 #[cfg_attr(not(target_family = "wasm"), deny(clippy::future_not_send))]
1886 pub async fn public_rooms(
1887 &self,
1888 limit: Option<u32>,
1889 since: Option<&str>,
1890 server: Option<&ServerName>,
1891 ) -> HttpResult<get_public_rooms::v3::Response> {
1892 let limit = limit.map(UInt::from);
1893
1894 let request = assign!(get_public_rooms::v3::Request::new(), {
1895 limit,
1896 since: since.map(ToOwned::to_owned),
1897 server: server.map(ToOwned::to_owned),
1898 });
1899 self.send(request).await
1900 }
1901
1902 pub async fn create_room(&self, request: create_room::v3::Request) -> Result<Room> {
1932 let invite = request.invite.clone();
1933 let is_direct_room = request.is_direct;
1934 let response = self.send(request).await?;
1935
1936 let base_room = self.base_client().get_or_create_room(&response.room_id, RoomState::Joined);
1937
1938 let joined_room = Room::new(self.clone(), base_room);
1939
1940 if is_direct_room
1941 && !invite.is_empty()
1942 && let Err(error) =
1943 self.account().mark_as_dm(joined_room.room_id(), invite.as_slice()).await
1944 {
1945 error!("Failed to mark room as DM: {error}");
1947 }
1948
1949 Ok(joined_room)
1950 }
1951
1952 pub async fn create_dm(&self, user_id: &UserId) -> Result<Room> {
1965 #[cfg(feature = "e2e-encryption")]
1966 let initial_state = vec![
1967 InitialStateEvent::with_empty_state_key(
1968 RoomEncryptionEventContent::with_recommended_defaults(),
1969 )
1970 .to_raw_any(),
1971 ];
1972
1973 #[cfg(not(feature = "e2e-encryption"))]
1974 let initial_state = vec![];
1975
1976 let request = assign!(create_room::v3::Request::new(), {
1977 invite: vec![user_id.to_owned()],
1978 is_direct: true,
1979 preset: Some(create_room::v3::RoomPreset::TrustedPrivateChat),
1980 initial_state,
1981 });
1982
1983 self.create_room(request).await
1984 }
1985
1986 pub fn get_dm_room(&self, user_id: &UserId) -> Option<Room> {
1988 self.get_dm_rooms(user_id).next()
1989 }
1990
1991 pub fn get_dm_rooms(&self, user_id: &UserId) -> impl Iterator<Item = Room> {
1993 let rooms = self.joined_rooms();
1994
1995 let dm_definition = &self.base_client().dm_room_definition;
1996
1997 let rooms = rooms.into_iter().filter(move |r| {
1999 let targets = r.direct_targets();
2000 let targets_match =
2001 targets.len() == 1 && targets.contains(<&DirectUserIdentifier>::from(user_id));
2002 match dm_definition {
2003 DmRoomDefinition::MatrixSpec => targets_match,
2004 DmRoomDefinition::TwoMembers => {
2005 let service_members_count =
2006 r.service_members().map(|s| s.len()).unwrap_or_default() as u64;
2007 let active_non_service_members =
2008 r.active_members_count().saturating_sub(service_members_count);
2009 targets_match && active_non_service_members <= 2
2010 }
2011 }
2012 });
2013
2014 trace!(?user_id, ?rooms, "Found DM rooms with user");
2015 rooms
2016 }
2017
2018 pub async fn public_rooms_filtered(
2050 &self,
2051 request: get_public_rooms_filtered::v3::Request,
2052 ) -> HttpResult<get_public_rooms_filtered::v3::Response> {
2053 self.send(request).await
2054 }
2055
2056 pub fn send<Request>(&self, request: Request) -> SendRequest<Request>
2095 where
2096 Request: OutgoingRequest + Clone + Debug,
2097 Request::Authentication: SupportedAuthScheme,
2098 Request::PathBuilder: SupportedPathBuilder,
2099 for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2100 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2101 {
2102 SendRequest {
2103 client: self.clone(),
2104 request,
2105 config: None,
2106 send_progress: Default::default(),
2107 }
2108 }
2109
2110 pub(crate) async fn send_inner<Request>(
2111 &self,
2112 request: Request,
2113 config: Option<RequestConfig>,
2114 send_progress: SharedObservable<TransmissionProgress>,
2115 ) -> HttpResult<Request::IncomingResponse>
2116 where
2117 Request: OutgoingRequest + Debug,
2118 Request::Authentication: SupportedAuthScheme,
2119 Request::PathBuilder: SupportedPathBuilder,
2120 for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2121 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2122 {
2123 let homeserver = self.homeserver().to_string();
2124 let access_token = self.access_token();
2125 let skip_auth = config.map(|c| c.skip_auth).unwrap_or(self.request_config().skip_auth);
2126
2127 let path_builder_input =
2128 Request::PathBuilder::get_path_builder_input(self, skip_auth).await?;
2129
2130 let result = self
2131 .inner
2132 .http_client
2133 .send(
2134 request,
2135 config,
2136 homeserver,
2137 access_token.as_deref(),
2138 path_builder_input,
2139 send_progress,
2140 )
2141 .await;
2142
2143 if let Err(Some(ErrorKind::UnknownToken { .. })) =
2144 result.as_ref().map_err(HttpError::client_api_error_kind)
2145 && let Some(access_token) = &access_token
2146 {
2147 self.auth_ctx().set_access_token_expired(access_token);
2149 }
2150
2151 result
2152 }
2153
2154 fn broadcast_unknown_token(&self, unknown_token_data: &UnknownTokenErrorData) {
2155 _ = self
2156 .inner
2157 .auth_ctx
2158 .session_change_sender
2159 .send(SessionChange::UnknownToken(unknown_token_data.clone()));
2160 }
2161
2162 pub async fn fetch_server_versions(
2164 &self,
2165 request_config: Option<RequestConfig>,
2166 ) -> HttpResult<get_supported_versions::Response> {
2167 self.fetch_server_versions_inner(false, request_config).await
2170 }
2171
2172 pub(crate) async fn fetch_server_versions_inner(
2178 &self,
2179 failsafe: bool,
2180 request_config: Option<RequestConfig>,
2181 ) -> HttpResult<get_supported_versions::Response> {
2182 if !failsafe {
2183 return self
2185 .send(get_supported_versions::Request::new())
2186 .with_request_config(request_config)
2187 .await;
2188 }
2189
2190 let homeserver = self.homeserver().to_string();
2191
2192 if !request_config.as_ref().is_some_and(|config| config.skip_auth && !config.force_auth)
2194 && self.auth_ctx().has_valid_access_token()
2195 && let Some(access_token) = self.access_token()
2196 {
2197 let result = self
2198 .inner
2199 .http_client
2200 .send(
2201 get_supported_versions::Request::new(),
2202 request_config,
2203 homeserver.clone(),
2204 Some(&access_token),
2205 (),
2206 Default::default(),
2207 )
2208 .await;
2209
2210 if let Err(Some(ErrorKind::UnknownToken { .. })) =
2211 result.as_ref().map_err(HttpError::client_api_error_kind)
2212 {
2213 self.auth_ctx().set_access_token_expired(&access_token);
2216 } else {
2217 return result;
2219 }
2220 }
2221
2222 self.inner
2224 .http_client
2225 .send(
2226 get_supported_versions::Request::new(),
2227 request_config,
2228 homeserver.clone(),
2229 None,
2230 (),
2231 Default::default(),
2232 )
2233 .await
2234 }
2235
2236 pub async fn fetch_client_well_known(&self) -> Option<discover_homeserver::Response> {
2249 if self.well_known_lookup_disabled() {
2250 return None;
2251 }
2252
2253 let homeserver = self.homeserver();
2254 let scheme = homeserver.scheme();
2255
2256 let server_url = self
2260 .server()
2261 .map(|server| server.to_string())
2262 .or_else(|| self.user_id().map(|id| format!("{}://{}", scheme, id.server_name())));
2266
2267 let response = if let Some(server_url) = server_url {
2269 self.fetch_client_well_known_with_url(server_url).await
2271 } else {
2272 None
2273 };
2274
2275 if response.is_none() {
2277 warn!(
2280 "Fetching the well-known from the server name didn't work, using the homeserver url instead"
2281 );
2282 self.fetch_client_well_known_with_url(homeserver.to_string()).await
2283 } else {
2284 response
2285 }
2286 }
2287
2288 async fn fetch_client_well_known_with_url(
2289 &self,
2290 url: String,
2291 ) -> Option<discover_homeserver::Response> {
2292 let well_known = self
2293 .inner
2294 .http_client
2295 .send(
2296 discover_homeserver::Request::new(),
2297 Some(RequestConfig::short_retry()),
2298 url,
2299 None,
2300 (),
2301 Default::default(),
2302 )
2303 .await;
2304
2305 match well_known {
2306 Ok(well_known) => Some(well_known),
2307 Err(http_error) => {
2308 warn!("Failed to fetch client well-known: {http_error}");
2311 None
2312 }
2313 }
2314 }
2315
2316 async fn fetch_supported_versions(
2322 &self,
2323 failsafe: bool,
2324 ) -> HttpResult<SupportedVersionsResponse> {
2325 let server_versions = self.fetch_server_versions_inner(failsafe, None).await?;
2326 let supported_versions = SupportedVersionsResponse {
2327 versions: server_versions.versions,
2328 unstable_features: server_versions.unstable_features,
2329 };
2330
2331 Ok(supported_versions)
2332 }
2333
2334 pub async fn supported_versions(&self) -> HttpResult<SupportedVersions> {
2362 self.supported_versions_inner(false).await
2363 }
2364
2365 pub(crate) async fn supported_versions_inner(
2371 &self,
2372 failsafe: bool,
2373 ) -> HttpResult<SupportedVersions> {
2374 match self.supported_versions_cached_inner(failsafe).await {
2375 Ok(Some(value)) => {
2376 return Ok(value);
2377 }
2378 Ok(None) => {
2379 }
2381 Err(error) => {
2382 warn!("error when loading cached supported versions: {error}");
2383 }
2385 }
2386
2387 self.refresh_supported_versions_cache(failsafe).await
2388 }
2389
2390 async fn refresh_supported_versions_cache(
2396 &self,
2397 failsafe: bool,
2398 ) -> HttpResult<SupportedVersions> {
2399 let cached_supported_versions = &self.inner.caches.supported_versions;
2400
2401 let mut supported_versions_guard = match cached_supported_versions.refresh_lock.try_lock() {
2402 Ok(guard) => guard,
2403 Err(_) => {
2404 let guard = cached_supported_versions.refresh_lock.lock().await;
2406
2407 if let Err(error) = guard.as_ref() {
2408 return Err(HttpError::Cached(error.clone()));
2410 }
2411
2412 if let CachedValue::Cached(value) = cached_supported_versions.value()
2414 && !value.has_expired()
2415 {
2416 return Ok(value.into_data());
2417 }
2418
2419 guard
2421 }
2422 };
2423
2424 let response = match self.fetch_supported_versions(failsafe).await {
2425 Ok(response) => {
2426 *supported_versions_guard = Ok(());
2427 TtlValue::new(response)
2428 }
2429 Err(error) => {
2430 let error = Arc::new(error);
2431 *supported_versions_guard = Err(error.clone());
2432 return Err(HttpError::Cached(error));
2433 }
2434 };
2435
2436 let supported_versions = response.as_ref().map(|response| response.supported_versions());
2437
2438 if self.auth_ctx().has_valid_access_token() {
2440 if let Err(err) = self
2441 .state_store()
2442 .set_kv_data(
2443 StateStoreDataKey::SupportedVersions,
2444 StateStoreDataValue::SupportedVersions(response),
2445 )
2446 .await
2447 {
2448 warn!("error when caching supported versions: {err}");
2449 }
2450
2451 cached_supported_versions.set_value(supported_versions.clone());
2452 }
2453
2454 Ok(supported_versions.into_data())
2455 }
2456
2457 pub async fn supported_versions_cached(&self) -> Result<Option<SupportedVersions>, StoreError> {
2494 self.supported_versions_cached_inner(false).await
2495 }
2496
2497 async fn supported_versions_cached_inner(
2498 &self,
2499 failsafe: bool,
2500 ) -> Result<Option<SupportedVersions>, StoreError> {
2501 let supported_versions_cache = &self.inner.caches.supported_versions;
2502
2503 let value = if let CachedValue::Cached(cached) = supported_versions_cache.value() {
2504 cached
2505 } else if let Some(stored) = self
2506 .state_store()
2507 .get_kv_data(StateStoreDataKey::SupportedVersions)
2508 .await?
2509 .and_then(|value| value.into_supported_versions())
2510 {
2511 let stored = stored.map(|response| response.supported_versions());
2512
2513 supported_versions_cache.set_value(stored.clone());
2515
2516 stored
2517 } else {
2518 return Ok(None);
2519 };
2520
2521 if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2524 debug!("spawning task to refresh supported versions cache");
2525
2526 let client = self.clone();
2527 self.task_monitor().spawn_finite_task("refresh supported versions cache", async move {
2528 if let Err(error) = client.refresh_supported_versions_cache(failsafe).await {
2529 warn!("failed to refresh supported versions cache: {error}");
2530 }
2531 });
2532 }
2533
2534 Ok(Some(value.into_data()))
2535 }
2536
2537 pub async fn server_versions(&self) -> HttpResult<BTreeSet<MatrixVersion>> {
2556 Ok(self.supported_versions().await?.versions)
2557 }
2558
2559 pub async fn unstable_features(&self) -> HttpResult<BTreeSet<FeatureFlag>> {
2579 Ok(self.supported_versions().await?.features)
2580 }
2581
2582 pub async fn reset_supported_versions(&self) -> Result<()> {
2588 self.inner.caches.supported_versions.reset();
2590
2591 Ok(self.state_store().remove_kv_data(StateStoreDataKey::SupportedVersions).await?)
2593 }
2594
2595 async fn well_known_cached(
2600 &self,
2601 ) -> Result<CachedValue<Option<WellKnownResponse>>, StoreError> {
2602 let well_known_cache = &self.inner.caches.well_known;
2603
2604 let value = if let CachedValue::Cached(cached) = well_known_cache.value() {
2605 cached
2606 } else if let Some(stored) = self
2607 .state_store()
2608 .get_kv_data(StateStoreDataKey::WellKnown)
2609 .await?
2610 .and_then(|value| value.into_well_known())
2611 {
2612 well_known_cache.set_value(stored.clone());
2614
2615 stored
2616 } else {
2617 return Ok(CachedValue::NotSet);
2618 };
2619
2620 if value.has_expired() {
2622 debug!("spawning task to refresh well-known cache");
2623
2624 let client = self.clone();
2625 self.task_monitor().spawn_finite_task("refresh well-known cache", async move {
2626 client.refresh_well_known_cache().await;
2627 });
2628 }
2629
2630 Ok(CachedValue::Cached(value.into_data()))
2631 }
2632
2633 async fn refresh_well_known_cache(&self) -> Option<WellKnownResponse> {
2635 let well_known_cache = &self.inner.caches.well_known;
2636
2637 let _well_known_guard = match well_known_cache.refresh_lock.try_lock() {
2638 Ok(guard) => guard,
2639 Err(_) => {
2640 let guard = well_known_cache.refresh_lock.lock().await;
2642
2643 if let CachedValue::Cached(value) = well_known_cache.value()
2648 && !value.has_expired()
2649 {
2650 return value.into_data();
2651 }
2652
2653 guard
2655 }
2656 };
2657
2658 let well_known = TtlValue::new(self.fetch_client_well_known().await.map(Into::into));
2659
2660 if let Err(err) = self
2661 .state_store()
2662 .set_kv_data(
2663 StateStoreDataKey::WellKnown,
2664 StateStoreDataValue::WellKnown(well_known.clone()),
2665 )
2666 .await
2667 {
2668 warn!("error when caching well-known: {err}");
2669 }
2670
2671 well_known_cache.set_value(well_known.clone());
2672
2673 well_known.into_data()
2674 }
2675
2676 fn well_known_lookup_disabled(&self) -> bool {
2679 *self.inner.well_known_lookup_disabled.read().unwrap()
2680 }
2681
2682 pub fn disable_well_known_lookup(&self, disable: bool) {
2685 *self.inner.well_known_lookup_disabled.write().unwrap() = disable;
2686 }
2687
2688 async fn well_known(&self) -> Option<WellKnownResponse> {
2694 if self.well_known_lookup_disabled() {
2695 return None;
2696 }
2697
2698 match self.well_known_cached().await {
2699 Ok(CachedValue::Cached(value)) => {
2700 return value;
2701 }
2702 Ok(CachedValue::NotSet) => {
2703 }
2705 Err(error) => {
2706 warn!("error when loading cached well-known: {error}");
2707 }
2709 }
2710
2711 self.refresh_well_known_cache().await
2712 }
2713
2714 #[deprecated = "Use `Client::discover_rtc_transports` instead"]
2720 pub async fn rtc_foci(&self) -> HttpResult<Vec<RtcTransport>> {
2721 self.well_known_rtc_transports().await
2722 }
2723
2724 pub async fn well_known_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
2753 let well_known = self.well_known().await;
2754
2755 Ok(well_known.map(|well_known| well_known.rtc_foci).unwrap_or_default())
2756 }
2757
2758 async fn rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2765 match self.rtc_transports_cached() {
2766 CachedValue::Cached(value) => Ok(value),
2767 CachedValue::NotSet => self.refresh_rtc_transports_cache().await,
2769 }
2770 }
2771
2772 fn rtc_transports_cached(&self) -> CachedValue<Option<Vec<RtcTransport>>> {
2778 let cache = &self.inner.caches.rtc_transports;
2779
2780 let CachedValue::Cached(value) = cache.value() else {
2781 return CachedValue::NotSet;
2782 };
2783
2784 if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2787 debug!("spawning task to refresh RTC transports cache");
2788
2789 let client = self.clone();
2790 self.task_monitor().spawn_finite_task("refresh RTC transports cache", async move {
2791 if let Err(error) = client.refresh_rtc_transports_cache().await {
2792 warn!("failed to refresh RTC transports cache: {error}");
2793 }
2794 });
2795 }
2796
2797 CachedValue::Cached(value.into_data())
2798 }
2799
2800 async fn refresh_rtc_transports_cache(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2802 let cache = &self.inner.caches.rtc_transports;
2803
2804 let mut refresh_guard = match cache.refresh_lock.try_lock() {
2805 Ok(guard) => guard,
2806 Err(_) => {
2807 let guard = cache.refresh_lock.lock().await;
2809
2810 if let Err(error) = guard.as_ref() {
2811 return Err(HttpError::Cached(error.clone()));
2813 }
2814
2815 if let CachedValue::Cached(value) = cache.value()
2817 && !value.has_expired()
2818 {
2819 return Ok(value.into_data());
2820 }
2821
2822 guard
2824 }
2825 };
2826
2827 match self.fetch_rtc_transports().await {
2828 Ok(transports) => {
2829 *refresh_guard = Ok(());
2830 cache.set_value(TtlValue::new(Some(transports.clone())));
2831 Ok(Some(transports))
2832 }
2833 Err(error) if error.is_endpoint_not_implemented() => {
2834 debug!("homeserver does not implement the RTC transports endpoint");
2841 *refresh_guard = Ok(());
2842 cache.set_value(TtlValue::new(None));
2843 Ok(None)
2844 }
2845 Err(error) => {
2846 let error = Arc::new(error);
2847 *refresh_guard = Err(error.clone());
2848 Err(HttpError::Cached(error))
2849 }
2850 }
2851 }
2852
2853 pub async fn fetch_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
2856 let response = self
2857 .send(transports::v1::Request::new())
2858 .with_request_config(RequestConfig::short_retry())
2859 .await?;
2860 Ok(response.rtc_transports)
2861 }
2862
2863 pub fn reset_rtc_transports(&self) {
2868 self.inner.caches.rtc_transports.reset();
2869 }
2870
2871 pub async fn discover_rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2899 if let Some(transports) = self.rtc_transports().await? {
2900 return Ok(Some(transports));
2901 }
2902
2903 Ok(self.well_known().await.map(|well_known| well_known.rtc_foci))
2908 }
2909
2910 pub async fn tile_server(&self) -> Option<TileServerInfo> {
2918 self.well_known().await.and_then(|well_known| well_known.tile_server).map(Into::into)
2919 }
2920
2921 pub async fn reset_well_known(&self) -> Result<()> {
2926 self.inner.caches.well_known.reset();
2928
2929 Ok(self.state_store().remove_kv_data(StateStoreDataKey::WellKnown).await?)
2931 }
2932
2933 pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> HttpResult<bool> {
2948 Ok(self.unstable_features().await?.contains(&FeatureFlag::from("org.matrix.msc4028")))
2949 }
2950
2951 pub async fn devices(&self) -> HttpResult<get_devices::v3::Response> {
2973 let request = get_devices::v3::Request::new();
2974
2975 self.send(request).await
2976 }
2977
2978 pub async fn get_retention_configuration(
2985 &self,
2986 ) -> HttpResult<get_retention_configuration::unstable::Response> {
2987 self.send(get_retention_configuration::unstable::Request::default()).await
2988 }
2989
2990 pub async fn delete_devices(
3031 &self,
3032 devices: &[OwnedDeviceId],
3033 auth_data: Option<uiaa::AuthData>,
3034 ) -> HttpResult<delete_devices::v3::Response> {
3035 let mut request = delete_devices::v3::Request::new(devices.to_owned());
3036 request.auth = auth_data;
3037
3038 self.send(request).await
3039 }
3040
3041 pub async fn rename_device(
3051 &self,
3052 device_id: &DeviceId,
3053 display_name: &str,
3054 ) -> HttpResult<update_device::v3::Response> {
3055 let mut request = update_device::v3::Request::new(device_id.to_owned());
3056 request.display_name = Some(display_name.to_owned());
3057
3058 self.send(request).await
3059 }
3060
3061 pub async fn device_exists(&self, device_id: OwnedDeviceId) -> Result<bool> {
3070 let request = device::get_device::v3::Request::new(device_id);
3071 match self.send(request).await {
3072 Ok(_) => Ok(true),
3073 Err(err) => {
3074 if let Some(error) = err.as_client_api_error()
3075 && error.status_code == 404
3076 {
3077 Ok(false)
3078 } else {
3079 Err(err.into())
3080 }
3081 }
3082 }
3083 }
3084
3085 #[instrument(skip(self))]
3182 pub async fn sync_once(
3183 &self,
3184 sync_settings: crate::config::SyncSettings,
3185 ) -> Result<SyncResponse> {
3186 #[cfg(feature = "e2e-encryption")]
3193 if let Err(e) = self.send_outgoing_requests().await {
3194 error!(error = ?e, "Error while sending outgoing E2EE requests");
3195 }
3196
3197 let token = match sync_settings.token {
3198 SyncToken::Specific(token) => Some(token),
3199 SyncToken::NoToken => None,
3200 SyncToken::ReusePrevious => self.sync_token().await,
3201 };
3202
3203 let request = assign!(sync_events::v3::Request::new(), {
3204 filter: sync_settings.filter.map(|f| *f),
3205 since: token,
3206 full_state: sync_settings.full_state,
3207 set_presence: sync_settings.set_presence.unwrap_or_else(|| self.sync_presence()),
3208 timeout: sync_settings.timeout,
3209 use_state_after: true,
3210 });
3211 let mut request_config = self.request_config();
3212 if let Some(timeout) = sync_settings.timeout {
3213 let base_timeout = request_config.timeout.unwrap_or(Duration::from_secs(30));
3214 request_config.timeout = Some(base_timeout + timeout);
3215 }
3216
3217 let response = self.send(request).with_request_config(request_config).await?;
3218 let next_batch = response.next_batch.clone();
3219 let response = self.process_sync(response).await?;
3220
3221 #[cfg(feature = "e2e-encryption")]
3222 if let Err(e) = self.send_outgoing_requests().await {
3223 error!(error = ?e, "Error while sending outgoing E2EE requests");
3224 }
3225
3226 self.inner.sync_beat.notify(usize::MAX);
3227
3228 Ok(SyncResponse::new(next_batch, response))
3229 }
3230
3231 pub async fn sync(&self, sync_settings: crate::config::SyncSettings) -> Result<(), Error> {
3290 self.sync_with_callback(sync_settings, |_| async { LoopCtrl::Continue }).await
3291 }
3292
3293 #[instrument(skip_all)]
3350 pub async fn sync_with_callback<C>(
3351 &self,
3352 sync_settings: crate::config::SyncSettings,
3353 callback: impl Fn(SyncResponse) -> C,
3354 ) -> Result<(), Error>
3355 where
3356 C: Future<Output = LoopCtrl>,
3357 {
3358 self.sync_with_result_callback(sync_settings, |result| async {
3359 Ok(callback(result?).await)
3360 })
3361 .await
3362 }
3363
3364 #[instrument(skip(self, callback))]
3430 pub async fn sync_with_result_callback<C>(
3431 &self,
3432 sync_settings: crate::config::SyncSettings,
3433 callback: impl Fn(Result<SyncResponse, Error>) -> C,
3434 ) -> Result<(), Error>
3435 where
3436 C: Future<Output = Result<LoopCtrl, Error>>,
3437 {
3438 let mut sync_stream = Box::pin(self.sync_stream(sync_settings).await);
3439
3440 while let Some(result) = sync_stream.next().await {
3441 trace!("Running callback");
3442 if callback(result).await? == LoopCtrl::Break {
3443 trace!("Callback told us to stop");
3444 break;
3445 }
3446 trace!("Done running callback");
3447 }
3448
3449 Ok(())
3450 }
3451
3452 #[allow(unknown_lints, clippy::let_with_type_underscore)] #[instrument(skip(self))]
3495 pub async fn sync_stream(
3496 &self,
3497 mut sync_settings: crate::config::SyncSettings,
3498 ) -> impl Stream<Item = Result<SyncResponse>> + '_ {
3499 let mut is_first_sync = true;
3500 let mut timeout = None;
3501 let mut last_sync_time: Option<Instant> = None;
3502
3503 let parent_span = Span::current();
3504
3505 async_stream::stream!({
3506 loop {
3507 trace!("Syncing");
3508
3509 if sync_settings.ignore_timeout_on_first_sync {
3510 if is_first_sync {
3511 timeout = sync_settings.timeout.take();
3512 } else if sync_settings.timeout.is_none() && timeout.is_some() {
3513 sync_settings.timeout = timeout.take();
3514 }
3515
3516 is_first_sync = false;
3517 }
3518
3519 yield self
3520 .sync_loop_helper(&mut sync_settings)
3521 .instrument(parent_span.clone())
3522 .await;
3523
3524 Client::delay_sync(&mut last_sync_time).await
3525 }
3526 })
3527 }
3528
3529 pub(crate) async fn sync_token(&self) -> Option<String> {
3532 self.inner.base_client.sync_token().await
3533 }
3534
3535 pub async fn whoami(&self) -> HttpResult<whoami::v3::Response> {
3537 let request = whoami::v3::Request::new();
3538 self.send(request).await
3539 }
3540
3541 pub fn subscribe_to_session_changes(&self) -> broadcast::Receiver<SessionChange> {
3543 let broadcast = &self.auth_ctx().session_change_sender;
3544 broadcast.subscribe()
3545 }
3546
3547 pub fn set_session_callbacks(
3552 &self,
3553 reload_session_callback: Box<ReloadSessionCallback>,
3554 save_session_callback: Box<SaveSessionCallback>,
3555 ) -> Result<()> {
3556 self.inner
3557 .auth_ctx
3558 .reload_session_callback
3559 .set(reload_session_callback)
3560 .map_err(|_| Error::MultipleSessionCallbacks)?;
3561
3562 self.inner
3563 .auth_ctx
3564 .save_session_callback
3565 .set(save_session_callback)
3566 .map_err(|_| Error::MultipleSessionCallbacks)?;
3567
3568 Ok(())
3569 }
3570
3571 pub async fn notification_settings(&self) -> NotificationSettings {
3573 let ruleset = self.account().push_rules().await.unwrap_or_else(|_| Ruleset::new());
3574 NotificationSettings::new(self.clone(), ruleset)
3575 }
3576
3577 pub async fn notification_client(
3584 &self,
3585 cross_process_lock_config: CrossProcessLockConfig,
3586 ) -> Result<Client> {
3587 let client = Client {
3588 inner: ClientInner::new(
3589 self.inner.auth_ctx.clone(),
3590 self.server(),
3591 self.homeserver(),
3592 self.sliding_sync_version(),
3593 self.inner.sync_presence.clone(),
3594 self.inner.http_client.clone(),
3595 self.inner
3596 .base_client
3597 .clone_with_in_memory_state_store(cross_process_lock_config.clone(), false)
3598 .await?,
3599 self.inner.caches.supported_versions.value(),
3600 self.inner.caches.well_known.value(),
3601 self.inner.respect_login_well_known,
3602 self.well_known_lookup_disabled(),
3603 self.inner.event_cache.clone(),
3604 false,
3605 self.inner.send_queue_data.clone(),
3606 self.inner.latest_events.clone(),
3607 #[cfg(feature = "e2e-encryption")]
3608 self.inner.e2ee.encryption_settings,
3609 #[cfg(feature = "e2e-encryption")]
3610 self.inner.enable_share_history_on_invite,
3611 cross_process_lock_config,
3612 #[cfg(feature = "experimental-search")]
3613 self.inner.search_index.clone(),
3614 self.inner.thread_subscription_catchup.clone(),
3615 (*self.inner.media_fetcher.read().await).clone(),
3616 )
3617 .await,
3618 };
3619
3620 Ok(client)
3621 }
3622
3623 pub fn event_cache(&self) -> &EventCache {
3625 self.inner.event_cache.get().unwrap()
3627 }
3628
3629 pub async fn latest_events(&self) -> &LatestEvents {
3631 self.inner
3632 .latest_events
3633 .get_or_init(|| async {
3634 LatestEvents::new(
3635 WeakClient::from_client(self),
3636 self.event_cache().clone(),
3637 SendQueue::new(self.clone()),
3638 self.room_info_notable_update_receiver(),
3639 )
3640 })
3641 .await
3642 }
3643
3644 pub async fn await_room_remote_echo(&self, room_id: &RoomId) -> Room {
3650 loop {
3651 if let Some(room) = self.get_room(room_id) {
3652 if room.is_state_partially_or_fully_synced() {
3653 debug!("Found just created room!");
3654 return room;
3655 }
3656 debug!("Room wasn't partially synced, waiting for sync beat to try again");
3657 } else {
3658 debug!("Room wasn't found, waiting for sync beat to try again");
3659 }
3660 self.inner.sync_beat.listen().await;
3661 }
3662 }
3663
3664 pub async fn knock(
3667 &self,
3668 room_id_or_alias: OwnedRoomOrAliasId,
3669 reason: Option<String>,
3670 server_names: Vec<OwnedServerName>,
3671 ) -> Result<Room> {
3672 let request =
3673 assign!(knock_room::v3::Request::new(room_id_or_alias), { reason, via: server_names });
3674 let response = self.send(request).await?;
3675 let base_room = self.inner.base_client.room_knocked(&response.room_id).await?;
3676 Ok(Room::new(self.clone(), base_room))
3677 }
3678
3679 pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
3681 self.base_client().is_user_ignored(user_id).await
3682 }
3683
3684 pub async fn load_or_fetch_max_upload_size(&self) -> Result<UInt> {
3691 let max_upload_size_lock = self.inner.server_max_upload_size.lock().await;
3692 if let Some(data) = max_upload_size_lock.get() {
3693 return Ok(data.to_owned());
3694 }
3695
3696 let supported_versions = self.supported_versions().await?;
3698 let use_auth = authenticated_media::get_media_config::v1::Request::PATH_BUILDER
3699 .is_supported(&supported_versions);
3700
3701 let upload_size = if use_auth {
3702 self.send(authenticated_media::get_media_config::v1::Request::default())
3703 .await?
3704 .upload_size
3705 } else {
3706 #[allow(deprecated)]
3707 self.send(media::get_media_config::v3::Request::default()).await?.upload_size
3708 };
3709
3710 match max_upload_size_lock.set(upload_size) {
3711 Ok(_) => Ok(upload_size),
3712 Err(error) => {
3713 Err(Error::Media(MediaError::FetchMaxUploadSizeFailed(error.to_string())))
3714 }
3715 }
3716 }
3717
3718 #[cfg(feature = "e2e-encryption")]
3720 pub fn decryption_settings(&self) -> &DecryptionSettings {
3721 &self.base_client().decryption_settings
3722 }
3723
3724 #[cfg(feature = "experimental-search")]
3726 pub fn search_index(&self) -> &SearchIndex {
3727 &self.inner.search_index
3728 }
3729
3730 pub async fn enabled_thread_subscriptions(&self) -> Result<bool> {
3741 match self.base_client().threading_support {
3743 ThreadingSupport::Enabled { with_subscriptions: false }
3744 | ThreadingSupport::Disabled => return Ok(false),
3745 ThreadingSupport::Enabled { with_subscriptions: true } => {}
3746 }
3747
3748 let server_enabled = self
3750 .supported_versions()
3751 .await?
3752 .features
3753 .contains(&FeatureFlag::from("org.matrix.msc4306"));
3754
3755 Ok(server_enabled)
3756 }
3757
3758 pub async fn is_global_profile_sync_enabled(&self) -> Result<bool> {
3763 if matches!(self.sliding_sync_version(), SlidingSyncVersion::None) {
3764 return Ok(false);
3765 }
3766
3767 Ok(self
3768 .supported_versions()
3769 .await?
3770 .features
3771 .contains(&FeatureFlag::from("org.matrix.msc4262")))
3772 }
3773
3774 pub async fn fetch_thread_subscriptions(
3780 &self,
3781 from: Option<String>,
3782 to: Option<String>,
3783 limit: Option<UInt>,
3784 ) -> Result<get_thread_subscriptions_changes::unstable::Response> {
3785 let request = assign!(get_thread_subscriptions_changes::unstable::Request::new(), {
3786 from,
3787 to,
3788 limit,
3789 });
3790 Ok(self.send(request).await?)
3791 }
3792
3793 pub(crate) fn thread_subscription_catchup(&self) -> &ThreadSubscriptionCatchup {
3794 self.inner.thread_subscription_catchup.get().unwrap()
3795 }
3796
3797 pub async fn pause(&self) -> Result<()> {
3814 info!("Client::pause — releasing database resources");
3815
3816 self.send_queue().set_enabled(false).await;
3818
3819 self.base_client().close_stores().await?;
3821
3822 info!("Client::pause — complete, all database connections released");
3823 Ok(())
3824 }
3825
3826 pub async fn resume(&self) -> Result<()> {
3834 info!("Client::resume — re-acquiring database resources");
3835
3836 self.base_client().reopen_stores().await?;
3838
3839 self.send_queue().set_enabled(true).await;
3841
3842 info!("Client::resume — complete");
3843 Ok(())
3844 }
3845
3846 #[doc(hidden)]
3852 pub async fn optimize_stores(&self) -> Result<()> {
3853 trace!("Optimizing state store...");
3854 self.state_store().optimize().await?;
3855
3856 trace!("Optimizing event cache store...");
3857 if let Some(clean_lock) = self.event_cache_store().lock().await?.as_clean() {
3858 clean_lock.optimize().await?;
3859 }
3860
3861 trace!("Optimizing media store...");
3862 self.media_store().lock().await?.optimize().await?;
3863
3864 Ok(())
3865 }
3866
3867 pub async fn get_store_sizes(&self) -> Result<StoreSizes> {
3869 #[cfg(feature = "e2e-encryption")]
3870 let crypto_store_size = if let Some(olm_machine) = self.olm_machine().await.as_ref()
3871 && let Ok(Some(store_size)) = olm_machine.store().get_size().await
3872 {
3873 Some(store_size)
3874 } else {
3875 None
3876 };
3877 #[cfg(not(feature = "e2e-encryption"))]
3878 let crypto_store_size = None;
3879
3880 let state_store_size = self.state_store().get_size().await.ok().flatten();
3881
3882 let event_cache_store_size = if let Some(clean_lock) =
3883 self.event_cache_store().lock().await?.as_clean()
3884 && let Ok(Some(store_size)) = clean_lock.get_size().await
3885 {
3886 Some(store_size)
3887 } else {
3888 None
3889 };
3890
3891 let media_store_size = self.media_store().lock().await?.get_size().await.ok().flatten();
3892
3893 Ok(StoreSizes {
3894 crypto_store: crypto_store_size,
3895 state_store: state_store_size,
3896 event_cache_store: event_cache_store_size,
3897 media_store: media_store_size,
3898 })
3899 }
3900
3901 pub fn task_monitor(&self) -> &TaskMonitor {
3904 &self.inner.task_monitor
3905 }
3906
3907 #[cfg(feature = "e2e-encryption")]
3910 pub fn subscribe_to_duplicate_key_upload_errors(
3911 &self,
3912 ) -> broadcast::Receiver<Option<DuplicateOneTimeKeyErrorMessage>> {
3913 self.inner.duplicate_key_upload_error_sender.subscribe()
3914 }
3915
3916 #[cfg(feature = "e2e-encryption")]
3921 pub async fn get_pending_key_bundle_details_for_room(
3922 &self,
3923 room_id: &RoomId,
3924 ) -> Result<Option<RoomPendingKeyBundleDetails>> {
3925 Ok(self.base_client().get_pending_key_bundle_details_for_room(room_id).await?)
3926 }
3927
3928 pub fn dm_room_definition(&self) -> &DmRoomDefinition {
3931 &self.inner.base_client.dm_room_definition
3932 }
3933
3934 pub async fn set_media_fetcher(&self, media_fetcher: Arc<dyn MediaFetcher>) {
3937 *self.inner.media_fetcher.write().await = media_fetcher;
3938 }
3939
3940 pub async fn get_media_fetcher(&self) -> Arc<dyn MediaFetcher> {
3943 self.inner.media_fetcher.read().await.clone()
3944 }
3945}
3946
3947#[derive(Debug, Clone)]
3950pub struct StoreSizes {
3951 pub crypto_store: Option<usize>,
3953 pub state_store: Option<usize>,
3955 pub event_cache_store: Option<usize>,
3957 pub media_store: Option<usize>,
3959}
3960
3961#[cfg(any(feature = "testing", test))]
3962impl Client {
3963 #[cfg(feature = "e2e-encryption")]
3965 pub async fn update_tracked_users_for_testing(
3966 &self,
3967 user_ids: impl IntoIterator<Item = &UserId>,
3968 ) {
3969 let olm = self.olm_machine().await;
3970 let olm = olm.as_ref().unwrap();
3971 olm.update_tracked_users(user_ids).await.unwrap();
3972 }
3973}
3974
3975#[derive(Clone, Debug)]
3978pub(crate) struct WeakClient {
3979 client: Weak<ClientInner>,
3980}
3981
3982impl WeakClient {
3983 pub(crate) fn from_inner(client: &Arc<ClientInner>) -> Self {
3985 Self { client: Arc::downgrade(client) }
3986 }
3987
3988 pub fn from_client(client: &Client) -> Self {
3990 Self::from_inner(&client.inner)
3991 }
3992
3993 pub fn get(&self) -> Option<Client> {
3995 self.client.upgrade().map(|inner| Client { inner })
3996 }
3997
3998 #[allow(dead_code)]
4001 pub fn strong_count(&self) -> usize {
4002 self.client.strong_count()
4003 }
4004}
4005
4006#[derive(Debug, Clone, Default)]
4008struct PreJoinRoomInfo {
4009 pub inviter: Option<RoomMember>,
4011}
4012
4013#[cfg(all(test, not(target_family = "wasm")))]
4015pub(crate) mod tests {
4016 use std::{sync::Arc, time::Duration};
4017
4018 use assert_matches::assert_matches;
4019 use assert_matches2::assert_let;
4020 use eyeball::SharedObservable;
4021 use futures_util::{FutureExt, StreamExt, pin_mut};
4022 use js_int::{UInt, uint};
4023 use matrix_sdk_base::{
4024 RoomState,
4025 store::{MemoryStore, StoreConfig},
4026 ttl::TtlValue,
4027 };
4028 use matrix_sdk_test::{
4029 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
4030 event_factory::EventFactory,
4031 };
4032 #[cfg(target_family = "wasm")]
4033 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
4034
4035 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
4036 use ruma::{
4037 RoomId, ServerName, UserId,
4038 api::{
4039 FeatureFlag, MatrixVersion,
4040 client::{room::create_room::v3::Request as CreateRoomRequest, rtc::RtcTransport},
4041 },
4042 assign,
4043 events::{
4044 ignored_user_list::IgnoredUserListEventContent,
4045 media_preview_config::{InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews},
4046 },
4047 owned_device_id, owned_room_id, owned_user_id,
4048 presence::PresenceState,
4049 room_alias_id, room_id, user_id,
4050 };
4051 use serde_json::json;
4052 use stream_assert::{assert_next_matches, assert_pending};
4053 use tokio::{
4054 spawn,
4055 time::{sleep, timeout},
4056 };
4057 use url::Url;
4058
4059 use super::Client;
4060 use crate::{
4061 Error, Result, TransmissionProgress,
4062 client::{WeakClient, caches::CachedValue, futures::SendMediaUploadRequest},
4063 config::{RequestConfig, SyncSettings},
4064 futures::SendRequest,
4065 media::MediaError,
4066 test_utils::{client::MockClientBuilder, mocks::MatrixMockServer},
4067 };
4068
4069 #[async_test]
4070 async fn test_sync_presence_is_shared_by_client_clones_and_notification_child() {
4071 let client = MockClientBuilder::new(None).build().await;
4072 let clone = client.clone();
4073 let notification_client =
4074 client.notification_client(CrossProcessLockConfig::SingleProcess).await.unwrap();
4075
4076 assert_eq!(client.sync_presence(), PresenceState::Online);
4077 assert_eq!(clone.sync_presence(), PresenceState::Online);
4078 assert_eq!(notification_client.sync_presence(), PresenceState::Online);
4079
4080 client
4081 .set_presence(PresenceState::Unavailable, None, false)
4082 .await
4083 .expect("presence should update");
4084
4085 assert_eq!(client.sync_presence(), PresenceState::Unavailable);
4086 assert_eq!(clone.sync_presence(), PresenceState::Unavailable);
4087 assert_eq!(notification_client.sync_presence(), PresenceState::Unavailable);
4088
4089 notification_client
4090 .set_presence(PresenceState::Offline, None, false)
4091 .await
4092 .expect("presence should update");
4093
4094 assert_eq!(client.sync_presence(), PresenceState::Offline);
4095 assert_eq!(clone.sync_presence(), PresenceState::Offline);
4096 assert_eq!(notification_client.sync_presence(), PresenceState::Offline);
4097 }
4098
4099 #[async_test]
4100 async fn test_sync_once_uses_client_sync_presence_unless_overridden() {
4101 let server = MatrixMockServer::new().await;
4102 let client = server.client_builder().build().await;
4103
4104 {
4105 let _sync_guard = server
4106 .mock_sync()
4107 .set_presence_missing()
4108 .ok(|_| {})
4109 .expect(1)
4110 .mount_as_scoped()
4111 .await;
4112
4113 client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4114 }
4115
4116 client
4117 .set_presence(PresenceState::Offline, None, false)
4118 .await
4119 .expect("presence should update");
4120
4121 {
4122 let _sync_guard = server
4123 .mock_sync()
4124 .set_presence("offline")
4125 .ok(|_| {})
4126 .expect(1)
4127 .mount_as_scoped()
4128 .await;
4129
4130 client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4131 }
4132
4133 {
4134 let _sync_guard = server
4135 .mock_sync()
4136 .set_presence("unavailable")
4137 .ok(|_| {})
4138 .expect(1)
4139 .mount_as_scoped()
4140 .await;
4141
4142 client
4143 .sync_once(SyncSettings::new().set_presence(PresenceState::Unavailable))
4144 .await
4145 .expect("sync should succeed");
4146 }
4147 }
4148
4149 #[async_test]
4150 async fn test_set_presence_sends_presence_status_update() {
4151 use wiremock::{
4152 Mock, ResponseTemplate,
4153 matchers::{body_partial_json, method, path_regex},
4154 };
4155
4156 let server = MatrixMockServer::new().await;
4157 let client = server.client_builder().build().await;
4158
4159 Mock::given(method("PUT"))
4160 .and(path_regex(r"^/_matrix/client/(r0|v3)/presence/.*/status$"))
4161 .and(body_partial_json(json!({
4162 "presence": "online",
4163 "status_msg": "Here"
4164 })))
4165 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
4166 .expect(1)
4167 .mount(server.server())
4168 .await;
4169
4170 client
4171 .set_presence(PresenceState::Online, Some("Here".to_owned()), true)
4172 .await
4173 .expect("presence update should succeed");
4174
4175 assert_eq!(client.sync_presence(), PresenceState::Online);
4176 }
4177
4178 #[async_test]
4179 async fn test_set_presence_requires_authentication() {
4180 let client = MockClientBuilder::new(None).unlogged().build().await;
4181
4182 assert_matches!(
4183 client.set_presence(PresenceState::Unavailable, None, true).await,
4184 Err(Error::AuthenticationRequired)
4185 );
4186 }
4187
4188 #[async_test]
4189 async fn test_set_presence_without_immediate_does_not_require_authentication() {
4190 let client = MockClientBuilder::new(None).unlogged().build().await;
4191
4192 client
4193 .set_presence(PresenceState::Offline, None, false)
4194 .await
4195 .expect("presence should update");
4196
4197 assert_eq!(client.sync_presence(), PresenceState::Offline);
4198 }
4199
4200 #[async_test]
4201 async fn test_account_data() {
4202 let server = MatrixMockServer::new().await;
4203 let client = server.client_builder().build().await;
4204
4205 let f = EventFactory::new();
4206 server
4207 .mock_sync()
4208 .ok_and_run(&client, |builder| {
4209 builder.add_global_account_data(
4210 f.ignored_user_list([owned_user_id!("@someone:example.org")]),
4211 );
4212 })
4213 .await;
4214
4215 let content = client
4216 .account()
4217 .account_data::<IgnoredUserListEventContent>()
4218 .await
4219 .unwrap()
4220 .unwrap()
4221 .deserialize()
4222 .unwrap();
4223
4224 assert_eq!(content.ignored_users.len(), 1);
4225 }
4226
4227 #[async_test]
4228 async fn test_successful_discovery() {
4229 let server = MatrixMockServer::new().await;
4231 let server_url = server.uri();
4232
4233 let homeserver = MatrixMockServer::new().await;
4235 let homeserver_url = homeserver.uri();
4236
4237 let domain = server_url.strip_prefix("http://").unwrap();
4239 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4240
4241 server
4243 .mock_well_known()
4244 .ok_with_homeserver_url(&homeserver_url)
4245 .mock_once()
4246 .named("well-known")
4247 .mount()
4248 .await;
4249
4250 homeserver.mock_versions().ok().mock_once().named("versions").mount().await;
4252
4253 let client = Client::builder()
4254 .insecure_server_name_no_tls(alice.server_name())
4255 .build()
4256 .await
4257 .unwrap();
4258
4259 assert_eq!(client.server().unwrap(), Url::parse(&server_url).unwrap());
4260 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4261 client.server_versions().await.unwrap();
4262 }
4263
4264 #[async_test]
4265 async fn test_homeserver_swap_resets_server_field() {
4266 let homeserver = MatrixMockServer::new().await;
4267 let homeserver_url = homeserver.uri();
4268
4269 let domain = homeserver_url.strip_prefix("http://").unwrap();
4270 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4271
4272 homeserver.mock_well_known().ok().mock_once().named("well-known").mount().await;
4273
4274 let client = Client::builder()
4275 .insecure_server_name_no_tls(alice.server_name())
4276 .build()
4277 .await
4278 .unwrap();
4279
4280 assert_eq!(client.server().unwrap(), Url::parse(&homeserver_url).unwrap());
4281 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4282
4283 let new_server = Url::parse("http://example.org").unwrap();
4284 client.set_homeserver(new_server.clone());
4287
4288 assert_eq!(client.homeserver(), new_server);
4290 assert!(client.server().is_none())
4293 }
4294
4295 #[async_test]
4296 async fn test_discovery_broken_server() {
4297 let server = MatrixMockServer::new().await;
4298 let server_url = server.uri();
4299 let domain = server_url.strip_prefix("http://").unwrap();
4300 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4301
4302 server.mock_well_known().error404().mock_once().named("well-known").mount().await;
4303
4304 assert!(
4305 Client::builder()
4306 .insecure_server_name_no_tls(alice.server_name())
4307 .build()
4308 .await
4309 .is_err(),
4310 "Creating a client from a user ID should fail when the .well-known request fails."
4311 );
4312 }
4313
4314 #[async_test]
4315 async fn test_room_creation() {
4316 let server = MatrixMockServer::new().await;
4317 let client = server.client_builder().build().await;
4318
4319 let f = EventFactory::new().sender(user_id!("@example:localhost"));
4320 server
4321 .mock_sync()
4322 .ok_and_run(&client, |builder| {
4323 builder.add_joined_room(
4324 JoinedRoomBuilder::default()
4325 .add_state_event(
4326 f.member(user_id!("@example:localhost")).display_name("example"),
4327 )
4328 .add_state_event(f.default_power_levels()),
4329 );
4330 })
4331 .await;
4332
4333 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
4334 assert_eq!(room.state(), RoomState::Joined);
4335 }
4336
4337 #[async_test]
4338 async fn test_retry_limit_http_requests() {
4339 let server = MatrixMockServer::new().await;
4340 let client = server
4341 .client_builder()
4342 .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(4)))
4343 .build()
4344 .await;
4345
4346 assert!(client.request_config().retry_limit.unwrap() == 4);
4347
4348 server.mock_who_am_i().error500().expect(4).mount().await;
4349
4350 client.whoami().await.unwrap_err();
4351 }
4352
4353 #[async_test]
4354 async fn test_retry_timeout_http_requests() {
4355 let retry_timeout = Duration::from_secs(5);
4357 let server = MatrixMockServer::new().await;
4358 let client = server
4359 .client_builder()
4360 .on_builder(|builder| {
4361 builder.request_config(RequestConfig::new().max_retry_time(retry_timeout))
4362 })
4363 .build()
4364 .await;
4365
4366 assert!(client.request_config().max_retry_time.unwrap() == retry_timeout);
4367
4368 server.mock_login().error500().expect(2..).mount().await;
4369
4370 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4371 }
4372
4373 #[async_test]
4374 async fn test_short_retry_initial_http_requests() {
4375 let server = MatrixMockServer::new().await;
4376 let client = server
4377 .client_builder()
4378 .on_builder(|builder| builder.request_config(RequestConfig::short_retry()))
4379 .build()
4380 .await;
4381
4382 server.mock_login().error500().expect(3..).mount().await;
4383
4384 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4385 }
4386
4387 #[async_test]
4388 async fn test_no_retry_http_requests() {
4389 let server = MatrixMockServer::new().await;
4390 let client = server.client_builder().build().await;
4391
4392 server.mock_devices().error500().mock_once().mount().await;
4393
4394 client.devices().await.unwrap_err();
4395 }
4396
4397 #[async_test]
4398 async fn test_set_homeserver() {
4399 let client = MockClientBuilder::new(None).build().await;
4400 assert_eq!(client.homeserver().as_ref(), "http://localhost/");
4401
4402 let homeserver = Url::parse("http://example.com/").unwrap();
4403 client.set_homeserver(homeserver.clone());
4404 assert_eq!(client.homeserver(), homeserver);
4405 }
4406
4407 #[async_test]
4408 async fn test_search_user_request() {
4409 let server = MatrixMockServer::new().await;
4410 let client = server.client_builder().build().await;
4411
4412 server.mock_user_directory().ok().mock_once().mount().await;
4413
4414 let response = client.search_users("test", 50).await.unwrap();
4415 assert_eq!(response.results.len(), 1);
4416 let result = &response.results[0];
4417 assert_eq!(result.user_id.to_string(), "@test:example.me");
4418 assert_eq!(result.display_name.clone().unwrap(), "Test");
4419 assert_eq!(result.avatar_url.clone().unwrap().to_string(), "mxc://example.me/someid");
4420 assert!(!response.limited);
4421 }
4422
4423 #[async_test]
4424 async fn test_request_unstable_features() {
4425 let server = MatrixMockServer::new().await;
4426 let client = server.client_builder().no_server_versions().build().await;
4427
4428 server
4429 .mock_versions()
4430 .with_feature("org.matrix.e2e_cross_signing", true)
4431 .ok()
4432 .mock_once()
4433 .mount()
4434 .await;
4435
4436 let unstable_features = client.unstable_features().await.unwrap();
4437 assert!(unstable_features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4438 assert!(!unstable_features.contains(&FeatureFlag::from("you.shall.pass")));
4439 }
4440
4441 #[async_test]
4442 async fn test_can_homeserver_push_encrypted_event_to_device() {
4443 let server = MatrixMockServer::new().await;
4444 let client = server.client_builder().no_server_versions().build().await;
4445
4446 server.mock_versions().with_push_encrypted_events().ok().mock_once().mount().await;
4447
4448 let msc4028_enabled = client.can_homeserver_push_encrypted_event_to_device().await.unwrap();
4449 assert!(msc4028_enabled);
4450 }
4451
4452 #[async_test]
4453 async fn test_recently_visited_rooms() {
4454 let client = MockClientBuilder::new(None).unlogged().build().await;
4456 assert_matches!(
4457 client.account().track_recently_visited_room(owned_room_id!("!alpha:localhost")).await,
4458 Err(Error::AuthenticationRequired)
4459 );
4460
4461 let client = MockClientBuilder::new(None).build().await;
4462 let account = client.account();
4463
4464 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 0);
4466
4467 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4469 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4470 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4471
4472 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4474 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4475
4476 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4478 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4479 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4480
4481 account.track_recently_visited_room(owned_room_id!("!beta:localhost")).await.unwrap();
4483 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4484 assert_eq!(
4485 account.get_recently_visited_rooms().await.unwrap(),
4486 [room_id!("!beta:localhost"), room_id!("!alpha:localhost")]
4487 );
4488
4489 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4491 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4492 assert_eq!(
4493 account.get_recently_visited_rooms().await.unwrap(),
4494 [room_id!("!alpha:localhost"), room_id!("!beta:localhost")]
4495 );
4496
4497 for n in 0..20 {
4499 account
4500 .track_recently_visited_room(RoomId::parse(format!("!{n}:localhost")).unwrap())
4501 .await
4502 .unwrap();
4503 }
4504
4505 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 20);
4506
4507 let rooms = account.get_recently_visited_rooms().await.unwrap();
4509 assert!(!rooms.contains(&owned_room_id!("!alpha:localhost")));
4510 assert!(!rooms.contains(&owned_room_id!("!beta:localhost")));
4511
4512 assert_eq!(rooms.first().unwrap(), "!19:localhost");
4514 }
4515
4516 #[async_test]
4517 async fn test_client_no_cycle_with_event_cache() {
4518 let client = MockClientBuilder::new(None).build().await;
4519
4520 sleep(Duration::from_secs(1)).await;
4522
4523 let weak_client = WeakClient::from_client(&client);
4524 assert_eq!(weak_client.strong_count(), 1);
4525
4526 {
4527 let room_id = room_id!("!room:example.org");
4528
4529 let response = SyncResponseBuilder::default()
4531 .add_joined_room(JoinedRoomBuilder::new(room_id))
4532 .build_sync_response();
4533 client.inner.base_client.receive_sync_response(response).await.unwrap();
4534
4535 client.event_cache().subscribe().unwrap();
4536
4537 let (_room_event_cache, _drop_handles) =
4538 client.get_room(room_id).unwrap().event_cache().await.unwrap();
4539 }
4540
4541 drop(client);
4542
4543 sleep(Duration::from_secs(1)).await;
4545
4546 assert_eq!(weak_client.strong_count(), 0);
4548 let client = weak_client.get();
4549 assert!(
4550 client.is_none(),
4551 "too many strong references to the client: {}",
4552 Arc::strong_count(&client.unwrap().inner)
4553 );
4554 }
4555
4556 #[async_test]
4557 async fn test_supported_versions_caching() {
4558 let server = MatrixMockServer::new().await;
4559
4560 let versions_mock = server
4561 .mock_versions()
4562 .expect_default_access_token()
4563 .with_feature("org.matrix.e2e_cross_signing", true)
4564 .ok()
4565 .named("first versions mock")
4566 .expect(1)
4567 .mount_as_scoped()
4568 .await;
4569
4570 let memory_store = Arc::new(MemoryStore::new());
4571 let client = server
4572 .client_builder()
4573 .no_server_versions()
4574 .on_builder(|builder| {
4575 builder.store_config(
4576 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4577 .state_store(memory_store.clone()),
4578 )
4579 })
4580 .build()
4581 .await;
4582
4583 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4584
4585 assert_matches!(client.supported_versions_cached().await, Ok(Some(_)));
4587 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4589
4590 drop(client);
4591
4592 let client = server
4593 .client_builder()
4594 .no_server_versions()
4595 .on_builder(|builder| {
4596 builder.store_config(
4597 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4598 .state_store(memory_store.clone()),
4599 )
4600 })
4601 .build()
4602 .await;
4603
4604 assert!(
4606 client
4607 .unstable_features()
4608 .await
4609 .unwrap()
4610 .contains(&FeatureFlag::from("org.matrix.e2e_cross_signing"))
4611 );
4612
4613 let supported = client.supported_versions().await.unwrap();
4614 assert!(supported.versions.contains(&MatrixVersion::V1_0));
4615 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4616
4617 let supported = client.supported_versions().await.unwrap();
4619 assert!(supported.versions.contains(&MatrixVersion::V1_0));
4620 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4621
4622 drop(versions_mock);
4623
4624 client.reset_supported_versions().await.unwrap();
4626
4627 server.mock_versions().ok().expect(2).named("second versions mock").mount().await;
4628
4629 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4631 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4633 assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4634
4635 let supported_versions = client.supported_versions_cached().await.unwrap().unwrap();
4637 let mut ttl_value = TtlValue::new(supported_versions);
4638 ttl_value.expire();
4639 client.inner.caches.supported_versions.set_value(ttl_value);
4640
4641 client.supported_versions_cached().await.unwrap().unwrap();
4643
4644 sleep(Duration::from_secs(1)).await;
4646 assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4647 }
4648
4649 #[async_test]
4650 async fn test_well_known_caching() {
4651 let server = MatrixMockServer::new().await;
4652 let server_url = server.uri();
4653 let domain = server_url.strip_prefix("http://").unwrap();
4654 let server_name = <&ServerName>::try_from(domain).unwrap();
4655 let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4656
4657 let well_known_mock = server
4658 .mock_well_known()
4659 .ok()
4660 .named("well known mock")
4661 .expect(2) .mount_as_scoped()
4663 .await;
4664
4665 let memory_store = Arc::new(MemoryStore::new());
4666 let client = Client::builder()
4667 .insecure_server_name_no_tls(server_name)
4668 .store_config(
4669 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4670 .state_store(memory_store.clone()),
4671 )
4672 .build()
4673 .await
4674 .unwrap();
4675
4676 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4677
4678 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4680
4681 drop(client);
4682
4683 let client = server
4684 .client_builder()
4685 .no_server_versions()
4686 .on_builder(|builder| {
4687 builder.store_config(
4688 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4689 .state_store(memory_store.clone()),
4690 )
4691 })
4692 .build()
4693 .await;
4694
4695 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4697
4698 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4700
4701 drop(well_known_mock);
4702
4703 client.reset_well_known().await.unwrap();
4705
4706 server.mock_well_known().ok().named("second well known mock").expect(2).mount().await;
4707
4708 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4710 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4712
4713 let well_known = client.well_known().await;
4715 let mut ttl_value = TtlValue::new(well_known);
4716 ttl_value.expire();
4717 client.inner.caches.well_known.set_value(ttl_value);
4718
4719 client.well_known().await;
4721
4722 sleep(Duration::from_secs(5)).await;
4726 assert_matches!(client.inner.caches.well_known.value(), CachedValue::Cached(value) if !value.has_expired());
4727 }
4728
4729 #[async_test]
4730 async fn test_rtc_transports_caching() {
4731 use wiremock::{
4732 Mock, ResponseTemplate,
4733 matchers::{method, path_regex},
4734 };
4735
4736 let server = MatrixMockServer::new().await;
4737 let transports = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4738
4739 let transports_mock = Mock::given(method("GET"))
4740 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4741 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4742 "rtc_transports": [
4743 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4744 ]
4745 })))
4746 .named("first transports mock")
4747 .expect(1)
4748 .mount_as_scoped(server.server())
4749 .await;
4750
4751 let client = server.client_builder().build().await;
4752
4753 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4755 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4757 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4758
4759 drop(transports_mock);
4760
4761 client.reset_rtc_transports();
4763
4764 Mock::given(method("GET"))
4765 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4766 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4767 "rtc_transports": [
4768 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4769 ]
4770 })))
4771 .named("second transports mock")
4772 .expect(2)
4773 .mount(server.server())
4774 .await;
4775
4776 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4778 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4780
4781 let mut ttl_value = TtlValue::new(Some(transports.clone()));
4783 ttl_value.expire();
4784 client.inner.caches.rtc_transports.set_value(ttl_value);
4785
4786 client.rtc_transports().await.unwrap();
4788
4789 sleep(Duration::from_secs(1)).await;
4791 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4792 }
4793
4794 #[async_test]
4795 async fn test_rtc_transports_unsupported_caching() {
4796 use wiremock::{
4797 Mock, ResponseTemplate,
4798 matchers::{method, path_regex},
4799 };
4800
4801 let server = MatrixMockServer::new().await;
4802
4803 Mock::given(method("GET"))
4808 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4809 .respond_with(ResponseTemplate::new(404).set_body_json(json!({
4810 "errcode": "M_UNRECOGNIZED",
4811 "error": "Unrecognized request",
4812 })))
4813 .named("unrecognized transports mock")
4814 .expect(1)
4815 .mount(server.server())
4816 .await;
4817
4818 let client = server.client_builder().build().await;
4819
4820 assert_eq!(client.rtc_transports().await.unwrap(), None);
4823 assert_eq!(client.rtc_transports().await.unwrap(), None);
4825 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4826 }
4827
4828 async fn mock_rtc_transports_endpoint(
4832 server: &MatrixMockServer,
4833 supported: bool,
4834 ) -> wiremock::MockGuard {
4835 use wiremock::{
4836 Mock, ResponseTemplate,
4837 matchers::{method, path_regex},
4838 };
4839
4840 let response = if supported {
4841 ResponseTemplate::new(200).set_body_json(json!({
4842 "rtc_transports": [
4843 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4844 ]
4845 }))
4846 } else {
4847 ResponseTemplate::new(404).set_body_json(json!({
4848 "errcode": "M_UNRECOGNIZED",
4849 "error": "Unrecognized request",
4850 }))
4851 };
4852
4853 Mock::given(method("GET"))
4854 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4855 .respond_with(response)
4856 .named("transports mock")
4857 .expect(1)
4858 .mount_as_scoped(server.server())
4859 .await
4860 }
4861
4862 #[async_test]
4863 async fn test_discover_rtc_transports_prefers_the_endpoint() {
4864 let server = MatrixMockServer::new().await;
4865 let transports = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4866
4867 let _transports_mock = mock_rtc_transports_endpoint(&server, true).await;
4868
4869 let _well_known_mock = server
4872 .mock_well_known()
4873 .ok()
4874 .named("well-known mock")
4875 .expect(0)
4876 .mount_as_scoped()
4877 .await;
4878
4879 let client = server.client_builder().build().await;
4880
4881 assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(transports));
4882 }
4883
4884 #[async_test]
4885 async fn test_discover_rtc_transports_falls_back_to_well_known() {
4886 let server = MatrixMockServer::new().await;
4887 let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4889
4890 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4891
4892 let _well_known_mock = server
4893 .mock_well_known()
4894 .ok()
4895 .named("well-known mock")
4896 .expect(1)
4897 .mount_as_scoped()
4898 .await;
4899
4900 let client = server.client_builder().build().await;
4901
4902 assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(rtc_foci));
4905 }
4906
4907 async fn mock_well_known_never_called(server: &MatrixMockServer) -> wiremock::MockGuard {
4909 server.mock_well_known().ok().named("well-known mock").expect(0).mount_as_scoped().await
4910 }
4911
4912 #[async_test]
4913 async fn test_well_known_lookup_disabled() {
4914 let server = MatrixMockServer::new().await;
4915
4916 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4917
4918 let _well_known_mock = mock_well_known_never_called(&server).await;
4920
4921 let client = server
4923 .client_builder()
4924 .on_builder(|builder| builder.disable_well_known_lookup(true))
4925 .build()
4926 .await;
4927
4928 assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
4931 assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
4933 assert!(client.tile_server().await.is_none());
4934 assert!(client.fetch_client_well_known().await.is_none());
4935 }
4936
4937 #[async_test]
4938 async fn test_well_known_lookup_disabled_after_build() {
4939 let server = MatrixMockServer::new().await;
4940
4941 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4942
4943 let _well_known_mock = mock_well_known_never_called(&server).await;
4945
4946 let client = server.client_builder().build().await;
4948 client.disable_well_known_lookup(true);
4949
4950 assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
4953 assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
4955 assert!(client.tile_server().await.is_none());
4956 assert!(client.fetch_client_well_known().await.is_none());
4957 }
4958
4959 #[async_test]
4960 async fn test_missing_well_known_caching() {
4961 let server = MatrixMockServer::new().await;
4962 let rtc_foci: Vec<RtcTransport> = vec![];
4963
4964 let well_known_mock = server
4965 .mock_well_known()
4966 .error_unrecognized()
4967 .named("first well-known mock")
4968 .expect(1)
4969 .mount_as_scoped()
4970 .await;
4971
4972 let memory_store = Arc::new(MemoryStore::new());
4973 let client = server
4974 .client_builder()
4975 .on_builder(|builder| {
4976 builder.store_config(
4977 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4978 .state_store(memory_store.clone()),
4979 )
4980 })
4981 .build()
4982 .await;
4983
4984 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4985
4986 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4988
4989 drop(client);
4990
4991 let client = server
4992 .client_builder()
4993 .on_builder(|builder| {
4994 builder.store_config(
4995 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4996 .state_store(memory_store.clone()),
4997 )
4998 })
4999 .build()
5000 .await;
5001
5002 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5004
5005 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5007
5008 drop(well_known_mock);
5009
5010 client.reset_well_known().await.unwrap();
5012
5013 server
5014 .mock_well_known()
5015 .error_unrecognized()
5016 .expect(1)
5017 .named("second well-known mock")
5018 .mount()
5019 .await;
5020
5021 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5023 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5025 }
5026
5027 #[async_test]
5028 async fn test_no_network_doesnt_cause_infinite_retries() {
5029 let client = MockClientBuilder::new(None)
5031 .on_builder(|builder| builder.request_config(RequestConfig::new()))
5032 .build()
5033 .await;
5034
5035 client.whoami().await.unwrap_err();
5038 }
5039
5040 #[async_test]
5041 async fn test_await_room_remote_echo_returns_the_room_if_it_was_already_synced() {
5042 let server = MatrixMockServer::new().await;
5043 let client = server.client_builder().build().await;
5044
5045 let room_id = room_id!("!room:example.org");
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 let room = client.await_room_remote_echo(room_id).now_or_never().unwrap();
5055 assert_eq!(room.room_id(), room_id);
5056 }
5057
5058 #[async_test]
5059 async fn test_await_room_remote_echo_returns_the_room_when_it_is_ready() {
5060 let server = MatrixMockServer::new().await;
5061 let client = server.client_builder().build().await;
5062
5063 let room_id = room_id!("!room:example.org");
5064
5065 let client = Arc::new(client);
5066
5067 spawn({
5070 let client = client.clone();
5071 async move {
5072 sleep(Duration::from_millis(100)).await;
5073
5074 server
5075 .mock_sync()
5076 .ok_and_run(&client, |builder| {
5077 builder.add_joined_room(JoinedRoomBuilder::new(room_id));
5078 })
5079 .await;
5080 }
5081 });
5082
5083 let room =
5084 timeout(Duration::from_secs(10), client.await_room_remote_echo(room_id)).await.unwrap();
5085 assert_eq!(room.room_id(), room_id);
5086 }
5087
5088 #[async_test]
5089 async fn test_await_room_remote_echo_will_timeout_if_no_room_is_found() {
5090 let client = MockClientBuilder::new(None).build().await;
5091
5092 let room_id = room_id!("!room:example.org");
5093 timeout(Duration::from_secs(1), client.await_room_remote_echo(room_id)).await.unwrap_err();
5096 }
5097
5098 #[async_test]
5099 async fn test_await_room_remote_echo_will_timeout_if_room_is_found_but_not_synced() {
5100 let server = MatrixMockServer::new().await;
5101 let client = server.client_builder().build().await;
5102
5103 server.mock_create_room().ok().mount().await;
5104
5105 let room = client
5107 .create_room(assign!(CreateRoomRequest::new(), {
5108 invite: vec![],
5109 is_direct: false,
5110 }))
5111 .await
5112 .unwrap();
5113
5114 timeout(Duration::from_secs(1), client.await_room_remote_echo(room.room_id()))
5116 .await
5117 .unwrap_err();
5118 }
5119
5120 #[async_test]
5121 async fn test_is_room_alias_available_if_alias_is_not_resolved() {
5122 let server = MatrixMockServer::new().await;
5123 let client = server.client_builder().build().await;
5124
5125 server.mock_room_directory_resolve_alias().not_found().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, Ok(true));
5129 }
5130
5131 #[async_test]
5132 async fn test_is_room_alias_available_if_alias_is_resolved() {
5133 let server = MatrixMockServer::new().await;
5134 let client = server.client_builder().build().await;
5135
5136 server
5137 .mock_room_directory_resolve_alias()
5138 .ok("!some_room_id:matrix.org", Vec::new())
5139 .expect(1)
5140 .mount()
5141 .await;
5142
5143 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5144 assert_matches!(ret, Ok(false));
5145 }
5146
5147 #[async_test]
5148 async fn test_is_room_alias_available_if_error_found() {
5149 let server = MatrixMockServer::new().await;
5150 let client = server.client_builder().build().await;
5151
5152 server.mock_room_directory_resolve_alias().error500().expect(1).mount().await;
5153
5154 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5155 assert_matches!(ret, Err(_));
5156 }
5157
5158 #[async_test]
5159 async fn test_create_room_alias() {
5160 let server = MatrixMockServer::new().await;
5161 let client = server.client_builder().build().await;
5162
5163 server.mock_room_directory_create_room_alias().ok().expect(1).mount().await;
5164
5165 let ret = client
5166 .create_room_alias(
5167 room_alias_id!("#some_alias:matrix.org"),
5168 room_id!("!some_room:matrix.org"),
5169 )
5170 .await;
5171 assert_matches!(ret, Ok(()));
5172 }
5173
5174 #[async_test]
5175 async fn test_join_room_by_id_or_alias() {
5176 use wiremock::{
5177 Mock, ResponseTemplate,
5178 matchers::{method, path_regex},
5179 };
5180 let server = MatrixMockServer::new().await;
5181 let client = server.client_builder().build().await;
5182
5183 let target_room_id = room_id!("!some_id:matrix.org");
5184 let target_alias = room_alias_id!("#some_alias:matrix.org");
5185
5186 Mock::given(method("POST"))
5187 .and(path_regex("^/_matrix/client/v3/join/.*$"))
5188 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
5189 "room_id": target_room_id
5190 })))
5191 .mount(server.server())
5192 .await;
5193
5194 server
5195 .mock_room_directory_resolve_alias()
5196 .ok(target_room_id.as_str(), Vec::new())
5197 .mount()
5198 .await;
5199
5200 server.mock_room_join(target_room_id).ok().mount().await;
5201
5202 let ret = client.join_room_by_id_or_alias(target_alias.into(), &[]).await;
5203 assert!(ret.is_ok());
5204
5205 let ret = client.join_room_by_id_or_alias(target_room_id.into(), &[]).await;
5206 assert!(ret.is_ok());
5207 }
5208
5209 #[async_test]
5210 async fn test_room_preview_for_invited_room_hits_summary_endpoint() {
5211 let server = MatrixMockServer::new().await;
5212 let client = server.client_builder().build().await;
5213
5214 let room_id = room_id!("!a-room:matrix.org");
5215
5216 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5218
5219 let invited_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Invited);
5221
5222 let preview = client
5224 .get_room_preview(room_id.into(), Vec::new())
5225 .await
5226 .expect("Room preview should be retrieved");
5227
5228 assert_eq!(invited_room.room_id(), preview.room_id);
5229 }
5230
5231 #[async_test]
5232 async fn test_room_preview_for_left_room_hits_summary_endpoint() {
5233 let server = MatrixMockServer::new().await;
5234 let client = server.client_builder().build().await;
5235
5236 let room_id = room_id!("!a-room:matrix.org");
5237
5238 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5240
5241 let left_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Left);
5243
5244 let preview = client
5246 .get_room_preview(room_id.into(), Vec::new())
5247 .await
5248 .expect("Room preview should be retrieved");
5249
5250 assert_eq!(left_room.room_id(), preview.room_id);
5251 }
5252
5253 #[async_test]
5254 async fn test_room_preview_for_knocked_room_hits_summary_endpoint() {
5255 let server = MatrixMockServer::new().await;
5256 let client = server.client_builder().build().await;
5257
5258 let room_id = room_id!("!a-room:matrix.org");
5259
5260 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5262
5263 let knocked_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Knocked);
5265
5266 let preview = client
5268 .get_room_preview(room_id.into(), Vec::new())
5269 .await
5270 .expect("Room preview should be retrieved");
5271
5272 assert_eq!(knocked_room.room_id(), preview.room_id);
5273 }
5274
5275 #[async_test]
5276 async fn test_room_preview_for_joined_room_retrieves_local_room_info() {
5277 let server = MatrixMockServer::new().await;
5278 let client = server.client_builder().build().await;
5279
5280 let room_id = room_id!("!a-room:matrix.org");
5281
5282 server.mock_room_summary().ok(room_id).never().mount().await;
5284
5285 let joined_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Joined);
5287
5288 let preview = client
5290 .get_room_preview(room_id.into(), Vec::new())
5291 .await
5292 .expect("Room preview should be retrieved");
5293
5294 assert_eq!(joined_room.room_id(), preview.room_id);
5295 }
5296
5297 #[async_test]
5298 async fn test_media_preview_config() {
5299 let server = MatrixMockServer::new().await;
5300 let client = server.client_builder().build().await;
5301
5302 server
5303 .mock_sync()
5304 .ok_and_run(&client, |builder| {
5305 builder.add_custom_global_account_data(json!({
5306 "content": {
5307 "media_previews": "private",
5308 "invite_avatars": "off"
5309 },
5310 "type": "m.media_preview_config"
5311 }));
5312 })
5313 .await;
5314
5315 let (initial_value, stream) =
5316 client.account().observe_media_preview_config().await.unwrap();
5317
5318 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5319 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5320 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5321 pin_mut!(stream);
5322 assert_pending!(stream);
5323
5324 server
5325 .mock_sync()
5326 .ok_and_run(&client, |builder| {
5327 builder.add_custom_global_account_data(json!({
5328 "content": {
5329 "media_previews": "off",
5330 "invite_avatars": "on"
5331 },
5332 "type": "m.media_preview_config"
5333 }));
5334 })
5335 .await;
5336
5337 assert_next_matches!(
5338 stream,
5339 MediaPreviewConfigEventContent {
5340 media_previews: Some(MediaPreviews::Off),
5341 invite_avatars: Some(InviteAvatars::On),
5342 ..
5343 }
5344 );
5345 assert_pending!(stream);
5346 }
5347
5348 #[async_test]
5349 async fn test_unstable_media_preview_config() {
5350 let server = MatrixMockServer::new().await;
5351 let client = server.client_builder().build().await;
5352
5353 server
5354 .mock_sync()
5355 .ok_and_run(&client, |builder| {
5356 builder.add_custom_global_account_data(json!({
5357 "content": {
5358 "media_previews": "private",
5359 "invite_avatars": "off"
5360 },
5361 "type": "io.element.msc4278.media_preview_config"
5362 }));
5363 })
5364 .await;
5365
5366 let (initial_value, stream) =
5367 client.account().observe_media_preview_config().await.unwrap();
5368
5369 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5370 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5371 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5372 pin_mut!(stream);
5373 assert_pending!(stream);
5374
5375 server
5376 .mock_sync()
5377 .ok_and_run(&client, |builder| {
5378 builder.add_custom_global_account_data(json!({
5379 "content": {
5380 "media_previews": "off",
5381 "invite_avatars": "on"
5382 },
5383 "type": "io.element.msc4278.media_preview_config"
5384 }));
5385 })
5386 .await;
5387
5388 assert_next_matches!(
5389 stream,
5390 MediaPreviewConfigEventContent {
5391 media_previews: Some(MediaPreviews::Off),
5392 invite_avatars: Some(InviteAvatars::On),
5393 ..
5394 }
5395 );
5396 assert_pending!(stream);
5397 }
5398
5399 #[async_test]
5400 async fn test_media_preview_config_not_found() {
5401 let server = MatrixMockServer::new().await;
5402 let client = server.client_builder().build().await;
5403
5404 let (initial_value, _) = client.account().observe_media_preview_config().await.unwrap();
5405
5406 assert!(initial_value.is_none());
5407 }
5408
5409 #[async_test]
5410 async fn test_load_or_fetch_max_upload_size_with_auth_matrix_version() {
5411 let server = MatrixMockServer::new().await;
5414 let client = server.client_builder().build().await;
5415
5416 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5417
5418 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5419 client.load_or_fetch_max_upload_size().await.unwrap();
5420
5421 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5422 }
5423
5424 #[async_test]
5425 async fn test_load_or_fetch_max_upload_size_with_auth_stable_feature() {
5426 let server = MatrixMockServer::new().await;
5429 let client = server.client_builder().no_server_versions().build().await;
5430
5431 server
5432 .mock_versions()
5433 .with_versions(vec!["v1.7", "v1.8", "v1.9", "v1.10"])
5434 .with_feature("org.matrix.msc3916.stable", true)
5435 .ok()
5436 .named("versions")
5437 .expect(1)
5438 .mount()
5439 .await;
5440
5441 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5442
5443 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5444 client.load_or_fetch_max_upload_size().await.unwrap();
5445
5446 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5447 }
5448
5449 #[async_test]
5450 async fn test_load_or_fetch_max_upload_size_no_auth() {
5451 let server = MatrixMockServer::new().await;
5454 let client = server.client_builder().no_server_versions().build().await;
5455
5456 server
5457 .mock_versions()
5458 .with_versions(vec!["v1.1"])
5459 .ok()
5460 .named("versions")
5461 .expect(1)
5462 .mount()
5463 .await;
5464
5465 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5466
5467 server.mock_media_config().ok(uint!(2)).mock_once().mount().await;
5468 client.load_or_fetch_max_upload_size().await.unwrap();
5469
5470 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5471 }
5472
5473 #[async_test]
5474 async fn test_uploading_a_too_large_media_file() {
5475 let server = MatrixMockServer::new().await;
5476 let client = server.client_builder().build().await;
5477
5478 server.mock_authenticated_media_config().ok(uint!(1)).mock_once().mount().await;
5479 client.load_or_fetch_max_upload_size().await.unwrap();
5480 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(1));
5481
5482 let data = vec![1, 2];
5483 let upload_request =
5484 ruma::api::client::media::create_content::v3::Request::new(data.clone());
5485 let request = SendRequest {
5486 client: client.clone(),
5487 request: upload_request,
5488 config: None,
5489 send_progress: SharedObservable::new(TransmissionProgress::default()),
5490 };
5491 let media_request = SendMediaUploadRequest::new(request);
5492
5493 let error = media_request.await.err();
5494 assert_let!(Some(Error::Media(MediaError::MediaTooLargeToUpload { max, current })) = error);
5495 assert_eq!(max, uint!(1));
5496 assert_eq!(current, UInt::new_wrapping(data.len() as u64));
5497 }
5498
5499 #[async_test]
5500 async fn test_dont_ignore_timeout_on_first_sync() {
5501 let server = MatrixMockServer::new().await;
5502 let client = server.client_builder().build().await;
5503
5504 server
5505 .mock_sync()
5506 .timeout(Some(Duration::from_secs(30)))
5507 .ok(|_| {})
5508 .mock_once()
5509 .named("sync_with_timeout")
5510 .mount()
5511 .await;
5512
5513 let mut stream = Box::pin(client.sync_stream(SyncSettings::new()).await);
5515
5516 timeout(Duration::from_secs(1), async {
5517 stream.next().await.unwrap().unwrap();
5518 })
5519 .await
5520 .unwrap();
5521 }
5522
5523 #[async_test]
5524 async fn test_ignore_timeout_on_first_sync() {
5525 let server = MatrixMockServer::new().await;
5526 let client = server.client_builder().build().await;
5527
5528 server
5529 .mock_sync()
5530 .timeout(None)
5531 .ok(|_| {})
5532 .mock_once()
5533 .named("sync_no_timeout")
5534 .mount()
5535 .await;
5536 server
5537 .mock_sync()
5538 .timeout(Some(Duration::from_secs(30)))
5539 .ok(|_| {})
5540 .mock_once()
5541 .named("sync_with_timeout")
5542 .mount()
5543 .await;
5544
5545 let mut stream = Box::pin(
5547 client.sync_stream(SyncSettings::new().ignore_timeout_on_first_sync(true)).await,
5548 );
5549
5550 timeout(Duration::from_secs(1), async {
5551 stream.next().await.unwrap().unwrap();
5552 stream.next().await.unwrap().unwrap();
5553 })
5554 .await
5555 .unwrap();
5556 }
5557
5558 #[async_test]
5559 async fn test_get_dm_room_returns_the_room_we_have_with_this_user() {
5560 let server = MatrixMockServer::new().await;
5561 let client = server.client_builder().build().await;
5562 let user_id = user_id!("@invited:localhost");
5566
5567 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5569 let response = SyncResponseBuilder::default()
5570 .add_joined_room(JoinedRoomBuilder::default().add_state_event(f.member(user_id)))
5571 .add_global_account_data(
5572 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5573 )
5574 .build_sync_response();
5575 client.base_client().receive_sync_response(response).await.unwrap();
5576
5577 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5579 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5580 }
5581
5582 #[async_test]
5583 async fn test_get_dm_room_still_finds_room_where_participant_is_only_invited() {
5584 let server = MatrixMockServer::new().await;
5585 let client = server.client_builder().build().await;
5586 let user_id = user_id!("@invited:localhost");
5588
5589 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5591 let response = SyncResponseBuilder::default()
5592 .add_joined_room(
5593 JoinedRoomBuilder::default()
5594 .add_state_event(f.member(user_id).invited(user_id).display_name("example")),
5595 )
5596 .add_global_account_data(
5597 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5598 )
5599 .build_sync_response();
5600 client.base_client().receive_sync_response(response).await.unwrap();
5601
5602 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5604 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5605 }
5606
5607 #[async_test]
5608 async fn test_get_dm_room_still_finds_left_room() {
5609 let server = MatrixMockServer::new().await;
5613 let client = server.client_builder().build().await;
5614 let user_id = user_id!("@invited:localhost");
5618
5619 let f = EventFactory::new().sender(user_id);
5621 let response = SyncResponseBuilder::default()
5622 .add_joined_room(
5623 JoinedRoomBuilder::default().add_state_event(f.member(user_id).leave()),
5624 )
5625 .add_global_account_data(
5626 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5627 )
5628 .build_sync_response();
5629 client.base_client().receive_sync_response(response).await.unwrap();
5630
5631 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5633 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5634 }
5635
5636 #[async_test]
5637 async fn test_device_exists() {
5638 let server = MatrixMockServer::new().await;
5639 let client = server.client_builder().build().await;
5640
5641 server.mock_get_device().ok().expect(1).mount().await;
5642
5643 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(true));
5644 }
5645
5646 #[async_test]
5647 async fn test_device_exists_404() {
5648 let server = MatrixMockServer::new().await;
5649 let client = server.client_builder().build().await;
5650
5651 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(false));
5652 }
5653
5654 #[async_test]
5655 async fn test_device_exists_500() {
5656 let server = MatrixMockServer::new().await;
5657 let client = server.client_builder().build().await;
5658
5659 server.mock_get_device().error500().expect(1).mount().await;
5660
5661 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Err(_));
5662 }
5663
5664 #[async_test]
5665 async fn test_fetching_well_known_with_homeserver_url() {
5666 let server = MatrixMockServer::new().await;
5667 let client = server.client_builder().build().await;
5668 server.mock_well_known().ok().mount().await;
5669
5670 assert_matches!(client.fetch_client_well_known().await, Some(_));
5671 }
5672
5673 #[async_test]
5674 async fn test_fetching_well_known_with_server_name() {
5675 let server = MatrixMockServer::new().await;
5676 let server_name = ServerName::parse(server.server().address().to_string()).unwrap();
5677
5678 server.mock_well_known().ok().mount().await;
5679
5680 let client = MockClientBuilder::new(None)
5681 .on_builder(|builder| builder.insecure_server_name_no_tls(&server_name))
5682 .build()
5683 .await;
5684
5685 assert_matches!(client.fetch_client_well_known().await, Some(_));
5686 }
5687
5688 #[async_test]
5689 async fn test_fetching_well_known_with_domain_part_of_user_id() {
5690 let server = MatrixMockServer::new().await;
5691 server.mock_well_known().ok().mount().await;
5692
5693 let user_id =
5694 UserId::parse(format!("@user:{}", server.server().address())).expect("Invalid user id");
5695 let client = MockClientBuilder::new(None)
5696 .logged_in_with_token("A_TOKEN".to_owned(), user_id, owned_device_id!("ABCDEF"))
5697 .build()
5698 .await;
5699
5700 assert_matches!(client.fetch_client_well_known().await, Some(_));
5701 }
5702
5703 #[cfg(feature = "e2e-encryption")]
5704 #[async_test]
5705 async fn test_syncing_one_time_key_counts_updates() -> Result<()> {
5706 use wiremock::ResponseTemplate;
5707
5708 macro_rules! assert_key_count {
5709 ($client: ident, $count:literal) => {{
5710 let machine = $client.olm_machine().await;
5711 let uploaded_key_counts =
5712 machine.as_ref().unwrap().uploaded_key_count().await.unwrap();
5713 assert_eq!(uploaded_key_counts, $count)
5714 }};
5715 }
5716
5717 macro_rules! sync_with_key_count {
5718 ($client: ident, $server:ident, $count:literal) => {
5719 let count = Some($count);
5720 sync_with_key_count!($client, $server, count);
5721 };
5722 ($client: ident, $server:ident, $count:ident) => {{
5723 use rand::RngExt as _;
5724
5725 let next_batch: String = rand::rng()
5726 .sample_iter(&rand::distr::Alphanumeric)
5727 .take(16)
5728 .map(char::from)
5729 .collect();
5730
5731 let count: Option<u32> = $count;
5732
5733 let template = if let Some(count) = count {
5734 ResponseTemplate::new(200).set_body_json(json!({
5735 "next_batch": next_batch,
5736 "rooms": {"leave": {}, "join": {}, "invite": {}},
5737 "device_lists": {
5738 "changed": [],
5739 "left": [],
5740 },
5741 "device_one_time_keys_count": {
5742 "signed_curve25519": count
5743 },
5744 }))
5745 } else {
5746 ResponseTemplate::new(200).set_body_json(json!({
5747 "next_batch": next_batch,
5748 "rooms": {"leave": {}, "join": {}, "invite": {}},
5749 "device_lists": {
5750 "changed": [],
5751 "left": [],
5752 },
5753 "device_one_time_keys_count": {},
5754 }))
5755 };
5756
5757 let _sync_mock_guard = $server.mock_sync().respond_with(template).mount_as_scoped().await;
5758 $client.sync_once(Default::default()).await?;
5759 }}
5760 }
5761
5762 let server = MatrixMockServer::new().await;
5763 let client = server.client_builder().build().await;
5764
5765 server.mock_upload_keys().ok_with_signed_curve_key_count(50).mock_once().mount().await;
5766
5767 assert_key_count!(client, 0);
5769
5770 sync_with_key_count!(client, server, 50);
5772 assert_key_count!(client, 50);
5773
5774 sync_with_key_count!(client, server, 10);
5776 assert_key_count!(client, 10);
5777
5778 sync_with_key_count!(client, server, None);
5780 assert_key_count!(client, 0);
5781
5782 Ok(())
5783 }
5784
5785 #[async_test]
5786 async fn test_get_retention_configuration() {
5787 use wiremock::{
5788 Mock, ResponseTemplate,
5789 matchers::{method, path},
5790 };
5791
5792 let server = MatrixMockServer::new().await;
5793 let client = server.client_builder().build().await;
5794
5795 Mock::given(method("GET"))
5796 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5797 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
5798 "policies": {},
5799 "limits": {},
5800 })))
5801 .expect(1)
5802 .mount(server.server())
5803 .await;
5804
5805 let response = client.get_retention_configuration().await;
5806 assert!(response.is_ok());
5807 let response = response.unwrap();
5808 assert!(response.policies.is_empty());
5809 assert!(response.limits.max_lifetime.is_none());
5810 assert!(response.limits.min_lifetime.is_none());
5811 }
5812}