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 room::create_room,
65 rtc::{RtcTransport, transports},
66 session::login::v3::DiscoveryInfo,
67 sync::sync_events,
68 threads::get_thread_subscriptions_changes,
69 uiaa,
70 user_directory::search_users,
71 },
72 error::{ErrorKind, FromHttpResponseError, UnknownTokenErrorData},
73 path_builder::PathBuilder,
74 },
75 assign,
76 events::{beacon_info::OriginalSyncBeaconInfoEvent, direct::DirectUserIdentifier},
77 presence::PresenceState,
78 push::Ruleset,
79 time::Instant,
80};
81use serde::de::DeserializeOwned;
82use tokio::sync::{Mutex, OnceCell, RwLock, RwLockReadGuard, broadcast};
83use tracing::{Instrument, Span, debug, error, info, instrument, trace, warn};
84use url::Url;
85
86use self::{
87 caches::{Cache, CachedValue, ClientCaches},
88 futures::SendRequest,
89};
90use crate::{
91 Account, AuthApi, AuthSession, Error, HttpError, Media, Pusher, RefreshTokenError, Result,
92 Room, SessionTokens, TransmissionProgress,
93 authentication::{
94 AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback, matrix::MatrixAuth,
95 oauth::OAuth,
96 },
97 client::{
98 homeserver_capabilities::HomeserverCapabilities,
99 thread_subscriptions::ThreadSubscriptionCatchup,
100 },
101 config::{RequestConfig, SyncToken},
102 deduplicating_handler::DeduplicatingHandler,
103 error::HttpResult,
104 event_cache::EventCache,
105 event_handler::{
106 EventHandler, EventHandlerContext, EventHandlerDropGuard, EventHandlerHandle,
107 EventHandlerStore, ObservableEventHandler, SyncEvent,
108 },
109 http_client::{HttpClient, SupportedAuthScheme, SupportedPathBuilder},
110 latest_events::LatestEvents,
111 live_locations_observer::BeaconInfoUpdate,
112 media::{MediaError, MediaFetcher},
113 notification_settings::NotificationSettings,
114 room::RoomMember,
115 room_preview::RoomPreview,
116 send_queue::{SendQueue, SendQueueData},
117 sliding_sync::Version as SlidingSyncVersion,
118 sync::{RoomUpdate, SyncResponse},
119};
120#[cfg(feature = "e2e-encryption")]
121use crate::{
122 cross_process_lock::CrossProcessLock,
123 encryption::{
124 DuplicateOneTimeKeyErrorMessage, Encryption, EncryptionData, EncryptionSettings,
125 VerificationState,
126 },
127};
128
129mod builder;
130pub(crate) mod caches;
131pub(crate) mod futures;
132pub(crate) mod homeserver_capabilities;
133pub(crate) mod thread_subscriptions;
134
135pub use self::builder::{ClientBuildError, ClientBuilder, sanitize_server_name};
136#[cfg(feature = "experimental-search")]
137use crate::search_index::SearchIndex;
138
139#[cfg(not(target_family = "wasm"))]
140type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()> + Send>>;
141#[cfg(target_family = "wasm")]
142type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()>>>;
143
144#[cfg(not(target_family = "wasm"))]
145type NotificationHandlerFn =
146 Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut + Send + Sync>;
147#[cfg(target_family = "wasm")]
148type NotificationHandlerFn = Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut>;
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum LoopCtrl {
158 Continue,
160 Break,
162}
163
164#[derive(Debug, Clone, PartialEq)]
166pub enum SessionChange {
167 UnknownToken(UnknownTokenErrorData),
169 TokensRefreshed,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
175#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
176pub struct ServerVendorInfo {
177 pub server_name: String,
179 pub version: String,
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Hash)]
186#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
187pub struct TileServerInfo {
188 pub map_style_url: String,
192}
193
194impl From<discover_homeserver::TileServerInfo> for TileServerInfo {
195 fn from(value: discover_homeserver::TileServerInfo) -> Self {
196 Self { map_style_url: value.map_style_url }
197 }
198}
199
200#[derive(Clone)]
204pub struct Client {
205 pub(crate) inner: Arc<ClientInner>,
206}
207
208#[derive(Default)]
209pub(crate) struct ClientLocks {
210 pub(crate) mark_as_dm_lock: Mutex<()>,
214
215 #[cfg(feature = "e2e-encryption")]
221 pub(crate) open_secret_store_lock: Mutex<()>,
222
223 #[cfg(feature = "e2e-encryption")]
230 pub(crate) store_secret_lock: Mutex<()>,
231
232 #[cfg(feature = "e2e-encryption")]
234 pub(crate) backup_modify_lock: Mutex<()>,
235
236 #[cfg(feature = "e2e-encryption")]
239 pub(crate) backup_upload_lock: Mutex<()>,
240
241 #[cfg(feature = "e2e-encryption")]
244 pub(crate) group_session_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
245
246 #[cfg(feature = "e2e-encryption")]
248 pub(crate) key_claim_lock: Mutex<()>,
249
250 pub(crate) members_request_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
253
254 pub(crate) encryption_state_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
257
258 pub(crate) read_receipt_deduplicated_handler: DeduplicatingHandler<(String, OwnedEventId)>,
261
262 #[cfg(feature = "e2e-encryption")]
263 pub(crate) cross_process_crypto_store_lock: OnceCell<CrossProcessLock<LockableCryptoStore>>,
264
265 #[cfg(feature = "e2e-encryption")]
284 pub(crate) crypto_store_generation: Arc<Mutex<Option<u64>>>,
285}
286
287pub(crate) struct ClientInner {
288 pub(crate) auth_ctx: Arc<AuthCtx>,
290
291 server: StdRwLock<Option<Url>>,
303
304 homeserver: StdRwLock<Url>,
308
309 sliding_sync_version: StdRwLock<SlidingSyncVersion>,
311
312 sync_presence: Arc<StdRwLock<PresenceState>>,
317
318 pub(crate) http_client: HttpClient,
320
321 pub(super) base_client: BaseClient,
323
324 pub(crate) caches: ClientCaches,
326
327 pub(crate) locks: ClientLocks,
331
332 cross_process_lock_config: CrossProcessLockConfig,
341
342 pub(crate) typing_notice_times: StdRwLock<BTreeMap<OwnedRoomId, Instant>>,
345
346 pub(crate) event_handlers: EventHandlerStore,
348
349 notification_handlers: RwLock<Vec<NotificationHandlerFn>>,
351
352 pub(crate) room_update_channels: StdMutex<BTreeMap<OwnedRoomId, broadcast::Sender<RoomUpdate>>>,
354
355 pub(crate) room_updates_sender: broadcast::Sender<RoomUpdates>,
358
359 respect_login_well_known: bool,
362
363 well_known_lookup_disabled: StdRwLock<bool>,
367
368 pub(crate) sync_beat: event_listener::Event,
374
375 pub(crate) event_cache: OnceCell<EventCache>,
379
380 #[cfg(feature = "e2e-encryption")]
382 pub(crate) e2ee: EncryptionData,
383
384 #[cfg(feature = "e2e-encryption")]
386 pub(crate) verification_state: SharedObservable<VerificationState>,
387
388 #[cfg(feature = "e2e-encryption")]
393 pub(crate) enable_share_history_on_invite: bool,
394
395 pub(crate) send_queue_data: Arc<SendQueueData>,
399
400 pub(crate) server_max_upload_size: Mutex<OnceCell<UInt>>,
403
404 latest_events: OnceCell<LatestEvents>,
408
409 thread_subscription_catchup: OnceCell<Arc<ThreadSubscriptionCatchup>>,
412
413 #[cfg(feature = "experimental-search")]
414 search_index: SearchIndex,
416
417 pub(crate) task_monitor: TaskMonitor,
419
420 #[cfg(feature = "e2e-encryption")]
423 pub(crate) duplicate_key_upload_error_sender:
424 broadcast::Sender<Option<DuplicateOneTimeKeyErrorMessage>>,
425
426 pub(crate) media_fetcher: RwLock<Arc<dyn MediaFetcher>>,
427
428 #[cfg(feature = "unstable-msc4426")]
437 pub(crate) automatic_call_status:
438 StdMutex<Option<crate::automatic_call_status::AutomaticCallStatus>>,
439}
440
441impl ClientInner {
442 #[allow(clippy::too_many_arguments)]
448 async fn new(
449 auth_ctx: Arc<AuthCtx>,
450 server: Option<Url>,
451 homeserver: Url,
452 sliding_sync_version: SlidingSyncVersion,
453 sync_presence: Arc<StdRwLock<PresenceState>>,
454 http_client: HttpClient,
455 base_client: BaseClient,
456 supported_versions: CachedValue<TtlValue<SupportedVersions>>,
457 well_known: CachedValue<TtlValue<Option<WellKnownResponse>>>,
458 respect_login_well_known: bool,
459 well_known_lookup_disabled: bool,
460 event_cache: OnceCell<EventCache>,
461 send_queue: Arc<SendQueueData>,
462 latest_events: OnceCell<LatestEvents>,
463 #[cfg(feature = "e2e-encryption")] encryption_settings: EncryptionSettings,
464 #[cfg(feature = "e2e-encryption")] enable_share_history_on_invite: bool,
465 cross_process_lock_config: CrossProcessLockConfig,
466 #[cfg(feature = "experimental-search")] search_index_handler: SearchIndex,
467 thread_subscription_catchup: OnceCell<Arc<ThreadSubscriptionCatchup>>,
468 media_fetcher: Arc<dyn MediaFetcher>,
469 ) -> Arc<Self> {
470 let caches = ClientCaches {
471 supported_versions: Cache::with_value(supported_versions),
472 well_known: Cache::with_value(well_known),
473 server_metadata: Cache::new(),
474 homeserver_capabilities: Cache::new(),
475 rtc_transports: Cache::new(),
476 };
477
478 let client = Self {
479 server: StdRwLock::new(server),
480 homeserver: StdRwLock::new(homeserver),
481 auth_ctx,
482 sliding_sync_version: StdRwLock::new(sliding_sync_version),
483 sync_presence,
484 http_client,
485 base_client,
486 caches,
487 locks: Default::default(),
488 cross_process_lock_config,
489 typing_notice_times: Default::default(),
490 event_handlers: Default::default(),
491 notification_handlers: Default::default(),
492 room_update_channels: Default::default(),
493 room_updates_sender: broadcast::Sender::new(32),
496 respect_login_well_known,
497 well_known_lookup_disabled: StdRwLock::new(well_known_lookup_disabled),
498 sync_beat: event_listener::Event::new(),
499 event_cache,
500 send_queue_data: send_queue,
501 latest_events,
502 #[cfg(feature = "e2e-encryption")]
503 e2ee: EncryptionData::new(encryption_settings),
504 #[cfg(feature = "e2e-encryption")]
505 verification_state: SharedObservable::new(VerificationState::Unknown),
506 #[cfg(feature = "e2e-encryption")]
507 enable_share_history_on_invite,
508 server_max_upload_size: Mutex::new(OnceCell::new()),
509 #[cfg(feature = "experimental-search")]
510 search_index: search_index_handler,
511 thread_subscription_catchup,
512 task_monitor: TaskMonitor::new(),
513 #[cfg(feature = "e2e-encryption")]
514 duplicate_key_upload_error_sender: broadcast::channel(1).0,
515 media_fetcher: RwLock::new(media_fetcher),
516 #[cfg(feature = "unstable-msc4426")]
517 automatic_call_status: StdMutex::new(None),
518 };
519
520 #[allow(clippy::let_and_return)]
521 let client = Arc::new(client);
522
523 #[cfg(feature = "e2e-encryption")]
524 client.e2ee.initialize_tasks(&client);
525
526 let init_event_cache = client.event_cache.get_or_init(|| async {
527 EventCache::new(&client, client.base_client.event_cache_store().clone())
528 });
529
530 let init_thread_subscription_catchup = client
531 .thread_subscription_catchup
532 .get_or_init(|| ThreadSubscriptionCatchup::new(Client { inner: client.clone() }));
533
534 let _ = join!(init_event_cache, init_thread_subscription_catchup);
535
536 client
537 }
538}
539
540#[cfg(not(tarpaulin_include))]
541impl Debug for Client {
542 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
543 write!(fmt, "Client")
544 }
545}
546
547impl Client {
548 pub async fn new(homeserver_url: Url) -> Result<Self, ClientBuildError> {
554 Self::builder().homeserver_url(homeserver_url).build().await
555 }
556
557 pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<Vec<String>> {
560 self.inner.base_client.subscribe_to_ignore_user_list_changes()
561 }
562
563 pub fn builder() -> ClientBuilder {
565 ClientBuilder::new()
566 }
567
568 pub(crate) fn base_client(&self) -> &BaseClient {
569 &self.inner.base_client
570 }
571
572 pub fn http_client(&self) -> &reqwest::Client {
574 &self.inner.http_client.inner
575 }
576
577 pub(crate) fn locks(&self) -> &ClientLocks {
578 &self.inner.locks
579 }
580
581 pub(crate) fn auth_ctx(&self) -> &AuthCtx {
582 &self.inner.auth_ctx
583 }
584
585 pub fn cross_process_lock_config(&self) -> &CrossProcessLockConfig {
593 &self.inner.cross_process_lock_config
594 }
595
596 fn set_homeserver(&self, homeserver_url: Url) {
604 let mut homeserver = self.inner.homeserver.write().unwrap();
605 let mut server = self.inner.server.write().unwrap();
606
607 *homeserver = homeserver_url;
608 *server = None;
609 }
610
611 #[cfg(feature = "e2e-encryption")]
613 pub(crate) async fn switch_homeserver_and_re_resolve_well_known(
614 &self,
615 homeserver_url: Url,
616 ) -> Result<()> {
617 self.set_homeserver(homeserver_url);
618 self.reset_well_known().await?;
619 if let Some(well_known) = self.well_known().await {
620 self.set_homeserver(Url::parse(&well_known.homeserver.base_url)?);
621 }
622 Ok(())
623 }
624
625 pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
628 HomeserverCapabilities::new(self.clone())
629 }
630
631 #[cfg(feature = "federation-api")]
653 pub async fn server_vendor_info(
654 &self,
655 request_config: Option<RequestConfig>,
656 ) -> HttpResult<ServerVendorInfo> {
657 use ruma::api::federation::discovery::get_server_version;
658
659 let res = self
660 .send_inner(get_server_version::v1::Request::new(), request_config, Default::default())
661 .await?;
662
663 let server = res.server.unwrap_or_default();
665 let server_name_str = server.name.unwrap_or_else(|| "unknown".to_owned());
666 let version = server.version.unwrap_or_else(|| "unknown".to_owned());
667
668 Ok(ServerVendorInfo { server_name: server_name_str, version })
669 }
670
671 pub fn request_config(&self) -> RequestConfig {
681 self.inner.http_client.request_config
682 }
683
684 pub fn is_active(&self) -> bool {
694 self.inner.base_client.is_active()
695 }
696
697 pub fn server(&self) -> Option<Url> {
701 self.inner.server.read().unwrap().clone()
702 }
703
704 pub fn homeserver(&self) -> Url {
706 self.inner.homeserver.read().unwrap().clone()
707 }
708
709 pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
711 self.inner.sliding_sync_version.read().unwrap().clone()
712 }
713
714 pub fn set_sliding_sync_version(&self, version: SlidingSyncVersion) {
716 let mut lock = self.inner.sliding_sync_version.write().unwrap();
717 *lock = version;
718 }
719
720 pub(crate) fn sync_presence(&self) -> PresenceState {
722 self.inner.sync_presence.read().unwrap().clone()
723 }
724
725 pub fn session_meta(&self) -> Option<&SessionMeta> {
731 self.base_client().session_meta()
732 }
733
734 pub fn room_info_notable_update_receiver(&self) -> broadcast::Receiver<RoomInfoNotableUpdate> {
737 self.base_client().room_info_notable_update_receiver()
738 }
739
740 pub fn subscribe_to_global_profile_updates(
746 &self,
747 ) -> broadcast::Receiver<BTreeSet<ruma::OwnedUserId>> {
748 self.base_client().subscribe_to_global_profile_updates()
749 }
750
751 pub fn subscribe_to_own_profile(
762 &self,
763 ) -> Result<impl Stream<Item = ruma::profile::UserProfile> + use<>> {
764 let own_user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
765 let mut updates = self.subscribe_to_global_profile_updates();
766 let client = self.clone();
767
768 Ok(async_stream::stream! {
769 match client.state_store().get_global_profile(&own_user_id).await {
771 Ok(profile) => yield profile.unwrap_or_default(),
772 Err(error) => error!(?error, "Failed to load the stored global profile"),
773 }
774
775 while let Ok(updated_user_ids) = updates.recv().await {
776 if !updated_user_ids.contains(&own_user_id) {
777 continue;
778 }
779
780 match client.state_store().get_global_profile(&own_user_id).await {
781 Ok(profile) => yield profile.unwrap_or_default(),
782 Err(error) => error!(?error, "Failed to load the updated global profile"),
783 }
784 }
785 })
786 }
787
788 pub async fn search_users(
798 &self,
799 search_term: &str,
800 limit: u64,
801 ) -> HttpResult<search_users::v3::Response> {
802 let mut request = search_users::v3::Request::new(search_term.to_owned());
803
804 if let Some(limit) = UInt::new(limit) {
805 request.limit = limit;
806 }
807
808 self.send(request).await
809 }
810
811 pub fn user_id(&self) -> Option<&UserId> {
813 self.session_meta().map(|s| s.user_id.as_ref())
814 }
815
816 pub fn device_id(&self) -> Option<&DeviceId> {
818 self.session_meta().map(|s| s.device_id.as_ref())
819 }
820
821 pub fn access_token(&self) -> Option<String> {
825 self.auth_ctx().access_token()
826 }
827
828 pub async fn set_presence(
836 &self,
837 presence: PresenceState,
838 status_msg: Option<String>,
839 immediate: bool,
840 ) -> Result<()> {
841 *self.inner.sync_presence.write().unwrap() = presence.clone();
842
843 if !immediate {
844 return Ok(());
845 }
846
847 let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
848 let mut request = set_presence_status::v3::Request::new(user_id, presence);
849 request.status_msg = status_msg;
850
851 self.send(request).await?;
852
853 Ok(())
854 }
855
856 pub fn session_tokens(&self) -> Option<SessionTokens> {
864 self.auth_ctx().session_tokens()
865 }
866
867 pub fn auth_api(&self) -> Option<AuthApi> {
871 match self.auth_ctx().auth_data.get()? {
872 AuthData::Matrix => Some(AuthApi::Matrix(self.matrix_auth())),
873 AuthData::OAuth(_) => Some(AuthApi::OAuth(self.oauth())),
874 }
875 }
876
877 pub fn session(&self) -> Option<AuthSession> {
884 match self.auth_api()? {
885 AuthApi::Matrix(api) => api.session().map(Into::into),
886 AuthApi::OAuth(api) => api.full_session().map(Into::into),
887 }
888 }
889
890 pub fn state_store(&self) -> &DynStateStore {
892 self.base_client().state_store()
893 }
894
895 pub fn event_cache_store(&self) -> &EventCacheStoreLock {
897 self.base_client().event_cache_store()
898 }
899
900 pub fn media_store(&self) -> &MediaStoreLock {
902 self.base_client().media_store()
903 }
904
905 pub fn matrix_auth(&self) -> MatrixAuth {
907 MatrixAuth::new(self.clone())
908 }
909
910 pub fn account(&self) -> Account {
912 Account::new(self.clone())
913 }
914
915 #[cfg(feature = "e2e-encryption")]
917 pub fn encryption(&self) -> Encryption {
918 Encryption::new(self.clone())
919 }
920
921 pub fn media(&self) -> Media {
923 Media::new(self.clone())
924 }
925
926 pub fn pusher(&self) -> Pusher {
928 Pusher::new(self.clone())
929 }
930
931 pub fn oauth(&self) -> OAuth {
933 OAuth::new(self.clone())
934 }
935
936 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
1068 where
1069 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
1070 H: EventHandler<Ev, Ctx>,
1071 {
1072 self.add_event_handler_impl(handler, None)
1073 }
1074
1075 pub fn add_room_event_handler<Ev, Ctx, H>(
1086 &self,
1087 room_id: &RoomId,
1088 handler: H,
1089 ) -> EventHandlerHandle
1090 where
1091 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
1092 H: EventHandler<Ev, Ctx>,
1093 {
1094 self.add_event_handler_impl(handler, Some(room_id.to_owned()))
1095 }
1096
1097 pub fn observe_events<Ev, Ctx>(&self) -> ObservableEventHandler<(Ev, Ctx)>
1172 where
1173 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1174 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1175 {
1176 self.observe_room_events_impl(None)
1177 }
1178
1179 pub fn observe_room_events<Ev, Ctx>(
1189 &self,
1190 room_id: &RoomId,
1191 ) -> ObservableEventHandler<(Ev, Ctx)>
1192 where
1193 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1194 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1195 {
1196 self.observe_room_events_impl(Some(room_id.to_owned()))
1197 }
1198
1199 fn observe_room_events_impl<Ev, Ctx>(
1202 &self,
1203 room_id: Option<OwnedRoomId>,
1204 ) -> ObservableEventHandler<(Ev, Ctx)>
1205 where
1206 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1207 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1208 {
1209 let shared_observable = SharedObservable::new(None);
1212
1213 ObservableEventHandler::new(
1214 shared_observable.clone(),
1215 self.event_handler_drop_guard(self.add_event_handler_impl(
1216 move |event: Ev, context: Ctx| {
1217 shared_observable.set(Some((event, context)));
1218
1219 ready(())
1220 },
1221 room_id,
1222 )),
1223 )
1224 }
1225
1226 pub fn observe_own_beacon_info_updates(
1232 &self,
1233 ) -> Result<impl Stream<Item = BeaconInfoUpdate> + use<>> {
1234 let observer = self.observe_events::<OriginalSyncBeaconInfoEvent, Room>();
1235 let mut stream = observer.subscribe();
1236 let own_user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
1237 Ok(async_stream::stream! {
1238 let _observer = observer;
1239
1240 while let Some((event, room)) = stream.next().await {
1241 if event.state_key != own_user_id {
1242 continue;
1243 }
1244 yield BeaconInfoUpdate {
1245 room_id: room.room_id().to_owned(),
1246 event_id: event.event_id,
1247 content: event.content,
1248 };
1249 }
1250 })
1251 }
1252
1253 pub fn remove_event_handler(&self, handle: EventHandlerHandle) {
1311 self.inner.event_handlers.remove(handle);
1312 }
1313
1314 pub fn event_handler_drop_guard(&self, handle: EventHandlerHandle) -> EventHandlerDropGuard {
1319 EventHandlerDropGuard::new(handle, self.clone())
1320 }
1321
1322 pub fn add_event_handler_context<T>(&self, ctx: T)
1363 where
1364 T: Clone + Send + Sync + 'static,
1365 {
1366 self.inner.event_handlers.add_context(ctx);
1367 }
1368
1369 pub async fn register_notification_handler<H, Fut>(&self, handler: H) -> &Self
1375 where
1376 H: Fn(Notification, Room, Client) -> Fut + SendOutsideWasm + SyncOutsideWasm + 'static,
1377 Fut: Future<Output = ()> + SendOutsideWasm + 'static,
1378 {
1379 self.inner.notification_handlers.write().await.push(Box::new(
1380 move |notification, room, client| Box::pin((handler)(notification, room, client)),
1381 ));
1382
1383 self
1384 }
1385
1386 pub fn subscribe_to_room_updates(&self, room_id: &RoomId) -> broadcast::Receiver<RoomUpdate> {
1391 match self.inner.room_update_channels.lock().unwrap().entry(room_id.to_owned()) {
1392 btree_map::Entry::Vacant(entry) => {
1393 let (tx, rx) = broadcast::channel(8);
1394 entry.insert(tx);
1395 rx
1396 }
1397 btree_map::Entry::Occupied(entry) => entry.get().subscribe(),
1398 }
1399 }
1400
1401 pub fn subscribe_to_all_room_updates(&self) -> broadcast::Receiver<RoomUpdates> {
1404 self.inner.room_updates_sender.subscribe()
1405 }
1406
1407 pub(crate) async fn notification_handlers(
1408 &self,
1409 ) -> RwLockReadGuard<'_, Vec<NotificationHandlerFn>> {
1410 self.inner.notification_handlers.read().await
1411 }
1412
1413 pub fn rooms(&self) -> Vec<Room> {
1417 self.base_client().rooms().into_iter().map(|room| Room::new(self.clone(), room)).collect()
1418 }
1419
1420 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
1422 self.base_client()
1423 .rooms_filtered(filter)
1424 .into_iter()
1425 .map(|room| Room::new(self.clone(), room))
1426 .collect()
1427 }
1428
1429 pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + '_) {
1431 let (rooms, stream) = self.base_client().rooms_stream();
1432
1433 let map_room = |room| Room::new(self.clone(), room);
1434
1435 (
1436 rooms.into_iter().map(map_room).collect(),
1437 stream.map(move |diffs| diffs.into_iter().map(|diff| diff.map(map_room)).collect()),
1438 )
1439 }
1440
1441 pub fn joined_rooms(&self) -> Vec<Room> {
1443 self.rooms_filtered(RoomStateFilter::JOINED)
1444 }
1445
1446 pub fn invited_rooms(&self) -> Vec<Room> {
1448 self.rooms_filtered(RoomStateFilter::INVITED)
1449 }
1450
1451 pub fn left_rooms(&self) -> Vec<Room> {
1453 self.rooms_filtered(RoomStateFilter::LEFT)
1454 }
1455
1456 pub fn joined_space_rooms(&self) -> Vec<Room> {
1458 self.base_client()
1459 .rooms_filtered(RoomStateFilter::JOINED)
1460 .into_iter()
1461 .flat_map(|room| room.is_space().then_some(Room::new(self.clone(), room)))
1462 .collect()
1463 }
1464
1465 pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1471 self.base_client().get_room(room_id).map(|room| Room::new(self.clone(), room))
1472 }
1473
1474 pub async fn get_room_preview(
1477 &self,
1478 room_or_alias_id: &RoomOrAliasId,
1479 via: Vec<OwnedServerName>,
1480 ) -> Result<RoomPreview> {
1481 let room_id = match <&RoomId>::try_from(room_or_alias_id) {
1482 Ok(room_id) => room_id.to_owned(),
1483 Err(alias) => self.resolve_room_alias(alias).await?.room_id,
1484 };
1485
1486 if let Some(room) = self.get_room(&room_id) {
1487 match room.state() {
1493 RoomState::Joined | RoomState::Banned => {
1494 return Ok(RoomPreview::from_known_room(&room).await);
1495 }
1496 RoomState::Left | RoomState::Invited | RoomState::Knocked => {}
1497 }
1498 }
1499
1500 RoomPreview::from_remote_room(self, room_id, room_or_alias_id, via).await
1501 }
1502
1503 pub async fn resolve_room_alias(
1510 &self,
1511 room_alias: &RoomAliasId,
1512 ) -> HttpResult<get_alias::v3::Response> {
1513 let request = get_alias::v3::Request::new(room_alias.to_owned());
1514 self.send(request).await
1515 }
1516
1517 pub async fn is_room_alias_available(&self, alias: &RoomAliasId) -> HttpResult<bool> {
1525 match self.resolve_room_alias(alias).await {
1526 Ok(_) => Ok(false),
1528 Err(error) => {
1529 match error.client_api_error_kind() {
1530 Some(ErrorKind::NotFound) => Ok(true),
1532 _ => Err(error),
1533 }
1534 }
1535 }
1536 }
1537
1538 pub async fn create_room_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> HttpResult<()> {
1540 let request = create_alias::v3::Request::new(alias.to_owned(), room_id.to_owned());
1541 self.send(request).await?;
1542 Ok(())
1543 }
1544
1545 pub async fn remove_room_alias(&self, alias: &RoomAliasId) -> HttpResult<()> {
1547 let request = delete_alias::v3::Request::new(alias.to_owned());
1548 self.send(request).await?;
1549 Ok(())
1550 }
1551
1552 pub(crate) fn maybe_update_login_well_known(&self, login_well_known: Option<&DiscoveryInfo>) {
1559 if self.inner.respect_login_well_known
1560 && let Some(well_known) = login_well_known
1561 && let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url)
1562 {
1563 self.set_homeserver(homeserver);
1564 }
1565 }
1566
1567 #[instrument(skip_all)]
1574 pub async fn restore_session(&self, session: impl Into<AuthSession>) -> Result<()> {
1575 self.restore_session_with(session, RoomLoadSettings::default()).await
1576 }
1577
1578 #[instrument(skip_all)]
1589 pub async fn restore_session_with(
1590 &self,
1591 session: impl Into<AuthSession>,
1592 room_load_settings: RoomLoadSettings,
1593 ) -> Result<()> {
1594 let session = session.into();
1595 match session {
1596 AuthSession::Matrix(session) => {
1597 Box::pin(self.matrix_auth().restore_session(session, room_load_settings)).await
1598 }
1599 AuthSession::OAuth(session) => {
1600 Box::pin(self.oauth().restore_session(*session, room_load_settings)).await
1601 }
1602 }
1603 }
1604
1605 pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1611 let Some(auth_api) = self.auth_api() else {
1612 return Err(RefreshTokenError::RefreshTokenRequired);
1613 };
1614
1615 match auth_api {
1616 AuthApi::Matrix(api) => {
1617 trace!("Token refresh: Using the homeserver.");
1618 Box::pin(api.refresh_access_token()).await?;
1619 }
1620 AuthApi::OAuth(api) => {
1621 trace!("Token refresh: Using OAuth 2.0.");
1622 Box::pin(api.refresh_access_token()).await?;
1623 }
1624 }
1625
1626 Ok(())
1627 }
1628
1629 pub async fn logout(&self) -> Result<(), Error> {
1636 let auth_api = self.auth_api().ok_or(Error::AuthenticationRequired)?;
1637 match auth_api {
1638 AuthApi::Matrix(matrix_auth) => {
1639 matrix_auth.logout().await?;
1640 Ok(())
1641 }
1642 AuthApi::OAuth(oauth) => Ok(oauth.logout().await?),
1643 }
1644 }
1645
1646 #[instrument(skip(self, definition))]
1692 pub async fn get_or_upload_filter(
1693 &self,
1694 filter_name: &str,
1695 definition: FilterDefinition,
1696 ) -> Result<String> {
1697 if let Some(filter) = self.inner.base_client.get_filter(filter_name).await? {
1698 debug!("Found filter locally");
1699 Ok(filter)
1700 } else {
1701 debug!("Didn't find filter locally");
1702 let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?;
1703 let request = FilterUploadRequest::new(user_id.to_owned(), definition);
1704 let response = self.send(request).await?;
1705
1706 self.inner.base_client.receive_filter_upload(filter_name, &response).await?;
1707
1708 Ok(response.filter_id)
1709 }
1710 }
1711
1712 async fn prepare_join_room_by_id(&self, room_id: &RoomId) -> Option<PreJoinRoomInfo> {
1714 let room = self.get_room(room_id)?;
1715
1716 let inviter = match room.invite_details().await {
1717 Ok(details) => details.inviter,
1718 Err(Error::WrongRoomState(_)) => None,
1719 Err(e) => {
1720 warn!("Error fetching invite details for room: {e:?}");
1721 None
1722 }
1723 };
1724
1725 Some(PreJoinRoomInfo { inviter })
1726 }
1727
1728 async fn finish_join_room(
1741 &self,
1742 room_id: &RoomId,
1743 pre_join_room_info: Option<PreJoinRoomInfo>,
1744 ) -> Result<Room> {
1745 info!(?room_id, ?pre_join_room_info, "Completing room join");
1746 let mark_as_dm = if let Some(room) = self.get_room(room_id) {
1747 room.state() == RoomState::Invited
1748 && room.is_direct().await.unwrap_or_else(|e| {
1749 warn!(%room_id, "is_direct() failed: {e}");
1750 false
1751 })
1752 } else {
1753 false
1754 };
1755
1756 let base_room = self
1757 .base_client()
1758 .room_joined(
1759 room_id,
1760 pre_join_room_info
1761 .as_ref()
1762 .and_then(|info| info.inviter.as_ref())
1763 .map(|i| i.user_id().to_owned()),
1764 )
1765 .await?;
1766 let room = Room::new(self.clone(), base_room);
1767
1768 if mark_as_dm {
1769 room.set_is_direct(true).await?;
1770 }
1771
1772 #[cfg(feature = "e2e-encryption")]
1779 if self.inner.enable_share_history_on_invite
1780 && let Some(inviter) =
1781 pre_join_room_info.as_ref().and_then(|info| info.inviter.as_ref())
1782 {
1783 crate::room::shared_room_history::maybe_accept_key_bundle(&room, inviter.user_id())
1784 .await?;
1785 }
1786
1787 #[cfg(not(feature = "e2e-encryption"))]
1789 let _ = pre_join_room_info.map(|i| i.inviter);
1790
1791 Ok(room)
1792 }
1793
1794 #[instrument(skip(self))]
1802 pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result<Room> {
1803 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1806
1807 let request = join_room_by_id::v3::Request::new(room_id.to_owned());
1808 let response = self.send(request).await?;
1809 self.finish_join_room(&response.room_id, pre_join_info).await
1810 }
1811
1812 #[instrument(skip(self))]
1823 pub async fn join_room_by_id_or_alias(
1824 &self,
1825 alias: &RoomOrAliasId,
1826 server_names: &[OwnedServerName],
1827 ) -> Result<Room> {
1828 let room_id = match <&RoomId>::try_from(alias) {
1829 Ok(room_id) => room_id,
1830 Err(room_alias) => &self.resolve_room_alias(room_alias).await?.room_id,
1831 };
1832 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1833 let request = assign!(join_room_by_id_or_alias::v3::Request::new(alias.to_owned()), {
1834 via: server_names.to_owned(),
1835 });
1836 let response = self.send(request).await?;
1837 self.finish_join_room(&response.room_id, pre_join_info).await
1838 }
1839
1840 #[cfg_attr(not(target_family = "wasm"), deny(clippy::future_not_send))]
1869 pub async fn public_rooms(
1870 &self,
1871 limit: Option<u32>,
1872 since: Option<&str>,
1873 server: Option<&ServerName>,
1874 ) -> HttpResult<get_public_rooms::v3::Response> {
1875 let limit = limit.map(UInt::from);
1876
1877 let request = assign!(get_public_rooms::v3::Request::new(), {
1878 limit,
1879 since: since.map(ToOwned::to_owned),
1880 server: server.map(ToOwned::to_owned),
1881 });
1882 self.send(request).await
1883 }
1884
1885 pub async fn create_room(&self, request: create_room::v3::Request) -> Result<Room> {
1915 let invite = request.invite.clone();
1916 let is_direct_room = request.is_direct;
1917 let response = self.send(request).await?;
1918
1919 let base_room = self.base_client().get_or_create_room(&response.room_id, RoomState::Joined);
1920
1921 let joined_room = Room::new(self.clone(), base_room);
1922
1923 if is_direct_room
1924 && !invite.is_empty()
1925 && let Err(error) =
1926 self.account().mark_as_dm(joined_room.room_id(), invite.as_slice()).await
1927 {
1928 error!("Failed to mark room as DM: {error}");
1930 }
1931
1932 Ok(joined_room)
1933 }
1934
1935 pub async fn create_dm(&self, user_id: &UserId) -> Result<Room> {
1948 #[cfg(feature = "e2e-encryption")]
1949 let initial_state = vec![
1950 InitialStateEvent::with_empty_state_key(
1951 RoomEncryptionEventContent::with_recommended_defaults(),
1952 )
1953 .to_raw_any(),
1954 ];
1955
1956 #[cfg(not(feature = "e2e-encryption"))]
1957 let initial_state = vec![];
1958
1959 let request = assign!(create_room::v3::Request::new(), {
1960 invite: vec![user_id.to_owned()],
1961 is_direct: true,
1962 preset: Some(create_room::v3::RoomPreset::TrustedPrivateChat),
1963 initial_state,
1964 });
1965
1966 self.create_room(request).await
1967 }
1968
1969 pub fn get_dm_room(&self, user_id: &UserId) -> Option<Room> {
1971 self.get_dm_rooms(user_id).next()
1972 }
1973
1974 pub fn get_dm_rooms(&self, user_id: &UserId) -> impl Iterator<Item = Room> {
1976 let rooms = self.joined_rooms();
1977
1978 let dm_definition = &self.base_client().dm_room_definition;
1979
1980 let rooms = rooms.into_iter().filter(move |r| {
1982 let targets = r.direct_targets();
1983 let targets_match =
1984 targets.len() == 1 && targets.contains(<&DirectUserIdentifier>::from(user_id));
1985 match dm_definition {
1986 DmRoomDefinition::MatrixSpec => targets_match,
1987 DmRoomDefinition::TwoMembers => {
1988 let service_members_count =
1989 r.service_members().map(|s| s.len()).unwrap_or_default() as u64;
1990 let active_non_service_members =
1991 r.active_members_count().saturating_sub(service_members_count);
1992 targets_match && active_non_service_members <= 2
1993 }
1994 }
1995 });
1996
1997 trace!(?user_id, ?rooms, "Found DM rooms with user");
1998 rooms
1999 }
2000
2001 pub async fn public_rooms_filtered(
2033 &self,
2034 request: get_public_rooms_filtered::v3::Request,
2035 ) -> HttpResult<get_public_rooms_filtered::v3::Response> {
2036 self.send(request).await
2037 }
2038
2039 pub fn send<Request>(&self, request: Request) -> SendRequest<Request>
2078 where
2079 Request: OutgoingRequest + Clone + Debug,
2080 Request::Authentication: SupportedAuthScheme,
2081 Request::PathBuilder: SupportedPathBuilder,
2082 for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2083 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2084 {
2085 SendRequest {
2086 client: self.clone(),
2087 request,
2088 config: None,
2089 send_progress: Default::default(),
2090 }
2091 }
2092
2093 pub(crate) async fn send_inner<Request>(
2094 &self,
2095 request: Request,
2096 config: Option<RequestConfig>,
2097 send_progress: SharedObservable<TransmissionProgress>,
2098 ) -> HttpResult<Request::IncomingResponse>
2099 where
2100 Request: OutgoingRequest + Debug,
2101 Request::Authentication: SupportedAuthScheme,
2102 Request::PathBuilder: SupportedPathBuilder,
2103 for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2104 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2105 {
2106 let homeserver = self.homeserver().to_string();
2107 let access_token = self.access_token();
2108 let skip_auth = config.map(|c| c.skip_auth).unwrap_or(self.request_config().skip_auth);
2109
2110 let path_builder_input =
2111 Request::PathBuilder::get_path_builder_input(self, skip_auth).await?;
2112
2113 let result = self
2114 .inner
2115 .http_client
2116 .send(
2117 request,
2118 config,
2119 homeserver,
2120 access_token.as_deref(),
2121 path_builder_input,
2122 send_progress,
2123 )
2124 .await;
2125
2126 if let Err(Some(ErrorKind::UnknownToken { .. })) =
2127 result.as_ref().map_err(HttpError::client_api_error_kind)
2128 && let Some(access_token) = &access_token
2129 {
2130 self.auth_ctx().set_access_token_expired(access_token);
2132 }
2133
2134 result
2135 }
2136
2137 fn broadcast_unknown_token(&self, unknown_token_data: &UnknownTokenErrorData) {
2138 _ = self
2139 .inner
2140 .auth_ctx
2141 .session_change_sender
2142 .send(SessionChange::UnknownToken(unknown_token_data.clone()));
2143 }
2144
2145 pub async fn fetch_server_versions(
2147 &self,
2148 request_config: Option<RequestConfig>,
2149 ) -> HttpResult<get_supported_versions::Response> {
2150 self.fetch_server_versions_inner(false, request_config).await
2153 }
2154
2155 pub(crate) async fn fetch_server_versions_inner(
2161 &self,
2162 failsafe: bool,
2163 request_config: Option<RequestConfig>,
2164 ) -> HttpResult<get_supported_versions::Response> {
2165 if !failsafe {
2166 return self
2168 .send(get_supported_versions::Request::new())
2169 .with_request_config(request_config)
2170 .await;
2171 }
2172
2173 let homeserver = self.homeserver().to_string();
2174
2175 if !request_config.as_ref().is_some_and(|config| config.skip_auth && !config.force_auth)
2177 && self.auth_ctx().has_valid_access_token()
2178 && let Some(access_token) = self.access_token()
2179 {
2180 let result = self
2181 .inner
2182 .http_client
2183 .send(
2184 get_supported_versions::Request::new(),
2185 request_config,
2186 homeserver.clone(),
2187 Some(&access_token),
2188 (),
2189 Default::default(),
2190 )
2191 .await;
2192
2193 if let Err(Some(ErrorKind::UnknownToken { .. })) =
2194 result.as_ref().map_err(HttpError::client_api_error_kind)
2195 {
2196 self.auth_ctx().set_access_token_expired(&access_token);
2199 } else {
2200 return result;
2202 }
2203 }
2204
2205 self.inner
2207 .http_client
2208 .send(
2209 get_supported_versions::Request::new(),
2210 request_config,
2211 homeserver.clone(),
2212 None,
2213 (),
2214 Default::default(),
2215 )
2216 .await
2217 }
2218
2219 pub async fn fetch_client_well_known(&self) -> Option<discover_homeserver::Response> {
2232 if self.well_known_lookup_disabled() {
2233 return None;
2234 }
2235
2236 let homeserver = self.homeserver();
2237 let scheme = homeserver.scheme();
2238
2239 let server_url = self
2243 .server()
2244 .map(|server| server.to_string())
2245 .or_else(|| self.user_id().map(|id| format!("{}://{}", scheme, id.server_name())));
2249
2250 let response = if let Some(server_url) = server_url {
2252 self.fetch_client_well_known_with_url(server_url).await
2254 } else {
2255 None
2256 };
2257
2258 if response.is_none() {
2260 warn!(
2263 "Fetching the well-known from the server name didn't work, using the homeserver url instead"
2264 );
2265 self.fetch_client_well_known_with_url(homeserver.to_string()).await
2266 } else {
2267 response
2268 }
2269 }
2270
2271 async fn fetch_client_well_known_with_url(
2272 &self,
2273 url: String,
2274 ) -> Option<discover_homeserver::Response> {
2275 let well_known = self
2276 .inner
2277 .http_client
2278 .send(
2279 discover_homeserver::Request::new(),
2280 Some(RequestConfig::short_retry()),
2281 url,
2282 None,
2283 (),
2284 Default::default(),
2285 )
2286 .await;
2287
2288 match well_known {
2289 Ok(well_known) => Some(well_known),
2290 Err(http_error) => {
2291 warn!("Failed to fetch client well-known: {http_error}");
2294 None
2295 }
2296 }
2297 }
2298
2299 async fn fetch_supported_versions(
2305 &self,
2306 failsafe: bool,
2307 ) -> HttpResult<SupportedVersionsResponse> {
2308 let server_versions = self.fetch_server_versions_inner(failsafe, None).await?;
2309 let supported_versions = SupportedVersionsResponse {
2310 versions: server_versions.versions,
2311 unstable_features: server_versions.unstable_features,
2312 };
2313
2314 Ok(supported_versions)
2315 }
2316
2317 pub async fn supported_versions(&self) -> HttpResult<SupportedVersions> {
2345 self.supported_versions_inner(false).await
2346 }
2347
2348 pub(crate) async fn supported_versions_inner(
2354 &self,
2355 failsafe: bool,
2356 ) -> HttpResult<SupportedVersions> {
2357 match self.supported_versions_cached_inner(failsafe).await {
2358 Ok(Some(value)) => {
2359 return Ok(value);
2360 }
2361 Ok(None) => {
2362 }
2364 Err(error) => {
2365 warn!("error when loading cached supported versions: {error}");
2366 }
2368 }
2369
2370 self.refresh_supported_versions_cache(failsafe).await
2371 }
2372
2373 async fn refresh_supported_versions_cache(
2379 &self,
2380 failsafe: bool,
2381 ) -> HttpResult<SupportedVersions> {
2382 let cached_supported_versions = &self.inner.caches.supported_versions;
2383
2384 let mut supported_versions_guard = match cached_supported_versions.refresh_lock.try_lock() {
2385 Ok(guard) => guard,
2386 Err(_) => {
2387 let guard = cached_supported_versions.refresh_lock.lock().await;
2389
2390 if let Err(error) = guard.as_ref() {
2391 return Err(HttpError::Cached(error.clone()));
2393 }
2394
2395 if let CachedValue::Cached(value) = cached_supported_versions.value()
2397 && !value.has_expired()
2398 {
2399 return Ok(value.into_data());
2400 }
2401
2402 guard
2404 }
2405 };
2406
2407 let response = match self.fetch_supported_versions(failsafe).await {
2408 Ok(response) => {
2409 *supported_versions_guard = Ok(());
2410 TtlValue::new(response)
2411 }
2412 Err(error) => {
2413 let error = Arc::new(error);
2414 *supported_versions_guard = Err(error.clone());
2415 return Err(HttpError::Cached(error));
2416 }
2417 };
2418
2419 let supported_versions = response.as_ref().map(|response| response.supported_versions());
2420
2421 if self.auth_ctx().has_valid_access_token() {
2423 if let Err(err) = self
2424 .state_store()
2425 .set_kv_data(
2426 StateStoreDataKey::SupportedVersions,
2427 StateStoreDataValue::SupportedVersions(response),
2428 )
2429 .await
2430 {
2431 warn!("error when caching supported versions: {err}");
2432 }
2433
2434 cached_supported_versions.set_value(supported_versions.clone());
2435 }
2436
2437 Ok(supported_versions.into_data())
2438 }
2439
2440 pub async fn supported_versions_cached(&self) -> Result<Option<SupportedVersions>, StoreError> {
2477 self.supported_versions_cached_inner(false).await
2478 }
2479
2480 async fn supported_versions_cached_inner(
2481 &self,
2482 failsafe: bool,
2483 ) -> Result<Option<SupportedVersions>, StoreError> {
2484 let supported_versions_cache = &self.inner.caches.supported_versions;
2485
2486 let value = if let CachedValue::Cached(cached) = supported_versions_cache.value() {
2487 cached
2488 } else if let Some(stored) = self
2489 .state_store()
2490 .get_kv_data(StateStoreDataKey::SupportedVersions)
2491 .await?
2492 .and_then(|value| value.into_supported_versions())
2493 {
2494 let stored = stored.map(|response| response.supported_versions());
2495
2496 supported_versions_cache.set_value(stored.clone());
2498
2499 stored
2500 } else {
2501 return Ok(None);
2502 };
2503
2504 if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2507 debug!("spawning task to refresh supported versions cache");
2508
2509 let client = self.clone();
2510 self.task_monitor().spawn_finite_task("refresh supported versions cache", async move {
2511 if let Err(error) = client.refresh_supported_versions_cache(failsafe).await {
2512 warn!("failed to refresh supported versions cache: {error}");
2513 }
2514 });
2515 }
2516
2517 Ok(Some(value.into_data()))
2518 }
2519
2520 pub async fn server_versions(&self) -> HttpResult<BTreeSet<MatrixVersion>> {
2539 Ok(self.supported_versions().await?.versions)
2540 }
2541
2542 pub async fn unstable_features(&self) -> HttpResult<BTreeSet<FeatureFlag>> {
2562 Ok(self.supported_versions().await?.features)
2563 }
2564
2565 pub async fn reset_supported_versions(&self) -> Result<()> {
2571 self.inner.caches.supported_versions.reset();
2573
2574 Ok(self.state_store().remove_kv_data(StateStoreDataKey::SupportedVersions).await?)
2576 }
2577
2578 async fn well_known_cached(
2583 &self,
2584 ) -> Result<CachedValue<Option<WellKnownResponse>>, StoreError> {
2585 let well_known_cache = &self.inner.caches.well_known;
2586
2587 let value = if let CachedValue::Cached(cached) = well_known_cache.value() {
2588 cached
2589 } else if let Some(stored) = self
2590 .state_store()
2591 .get_kv_data(StateStoreDataKey::WellKnown)
2592 .await?
2593 .and_then(|value| value.into_well_known())
2594 {
2595 well_known_cache.set_value(stored.clone());
2597
2598 stored
2599 } else {
2600 return Ok(CachedValue::NotSet);
2601 };
2602
2603 if value.has_expired() {
2605 debug!("spawning task to refresh well-known cache");
2606
2607 let client = self.clone();
2608 self.task_monitor().spawn_finite_task("refresh well-known cache", async move {
2609 client.refresh_well_known_cache().await;
2610 });
2611 }
2612
2613 Ok(CachedValue::Cached(value.into_data()))
2614 }
2615
2616 async fn refresh_well_known_cache(&self) -> Option<WellKnownResponse> {
2618 let well_known_cache = &self.inner.caches.well_known;
2619
2620 let _well_known_guard = match well_known_cache.refresh_lock.try_lock() {
2621 Ok(guard) => guard,
2622 Err(_) => {
2623 let guard = well_known_cache.refresh_lock.lock().await;
2625
2626 if let CachedValue::Cached(value) = well_known_cache.value()
2631 && !value.has_expired()
2632 {
2633 return value.into_data();
2634 }
2635
2636 guard
2638 }
2639 };
2640
2641 let well_known = TtlValue::new(self.fetch_client_well_known().await.map(Into::into));
2642
2643 if let Err(err) = self
2644 .state_store()
2645 .set_kv_data(
2646 StateStoreDataKey::WellKnown,
2647 StateStoreDataValue::WellKnown(well_known.clone()),
2648 )
2649 .await
2650 {
2651 warn!("error when caching well-known: {err}");
2652 }
2653
2654 well_known_cache.set_value(well_known.clone());
2655
2656 well_known.into_data()
2657 }
2658
2659 fn well_known_lookup_disabled(&self) -> bool {
2662 *self.inner.well_known_lookup_disabled.read().unwrap()
2663 }
2664
2665 pub fn disable_well_known_lookup(&self, disable: bool) {
2668 *self.inner.well_known_lookup_disabled.write().unwrap() = disable;
2669 }
2670
2671 async fn well_known(&self) -> Option<WellKnownResponse> {
2677 if self.well_known_lookup_disabled() {
2678 return None;
2679 }
2680
2681 match self.well_known_cached().await {
2682 Ok(CachedValue::Cached(value)) => {
2683 return value;
2684 }
2685 Ok(CachedValue::NotSet) => {
2686 }
2688 Err(error) => {
2689 warn!("error when loading cached well-known: {error}");
2690 }
2692 }
2693
2694 self.refresh_well_known_cache().await
2695 }
2696
2697 #[deprecated = "Use `Client::discover_rtc_transports` instead"]
2703 pub async fn rtc_foci(&self) -> HttpResult<Vec<RtcTransport>> {
2704 self.well_known_rtc_transports().await
2705 }
2706
2707 pub async fn well_known_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
2736 let well_known = self.well_known().await;
2737
2738 Ok(well_known.map(|well_known| well_known.rtc_foci).unwrap_or_default())
2739 }
2740
2741 async fn rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2748 match self.rtc_transports_cached() {
2749 CachedValue::Cached(value) => Ok(value),
2750 CachedValue::NotSet => self.refresh_rtc_transports_cache().await,
2752 }
2753 }
2754
2755 fn rtc_transports_cached(&self) -> CachedValue<Option<Vec<RtcTransport>>> {
2761 let cache = &self.inner.caches.rtc_transports;
2762
2763 let CachedValue::Cached(value) = cache.value() else {
2764 return CachedValue::NotSet;
2765 };
2766
2767 if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2770 debug!("spawning task to refresh RTC transports cache");
2771
2772 let client = self.clone();
2773 self.task_monitor().spawn_finite_task("refresh RTC transports cache", async move {
2774 if let Err(error) = client.refresh_rtc_transports_cache().await {
2775 warn!("failed to refresh RTC transports cache: {error}");
2776 }
2777 });
2778 }
2779
2780 CachedValue::Cached(value.into_data())
2781 }
2782
2783 async fn refresh_rtc_transports_cache(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2785 let cache = &self.inner.caches.rtc_transports;
2786
2787 let mut refresh_guard = match cache.refresh_lock.try_lock() {
2788 Ok(guard) => guard,
2789 Err(_) => {
2790 let guard = cache.refresh_lock.lock().await;
2792
2793 if let Err(error) = guard.as_ref() {
2794 return Err(HttpError::Cached(error.clone()));
2796 }
2797
2798 if let CachedValue::Cached(value) = cache.value()
2800 && !value.has_expired()
2801 {
2802 return Ok(value.into_data());
2803 }
2804
2805 guard
2807 }
2808 };
2809
2810 match self.fetch_rtc_transports().await {
2811 Ok(transports) => {
2812 *refresh_guard = Ok(());
2813 cache.set_value(TtlValue::new(Some(transports.clone())));
2814 Ok(Some(transports))
2815 }
2816 Err(error) if error.is_endpoint_not_implemented() => {
2817 debug!("homeserver does not implement the RTC transports endpoint");
2824 *refresh_guard = Ok(());
2825 cache.set_value(TtlValue::new(None));
2826 Ok(None)
2827 }
2828 Err(error) => {
2829 let error = Arc::new(error);
2830 *refresh_guard = Err(error.clone());
2831 Err(HttpError::Cached(error))
2832 }
2833 }
2834 }
2835
2836 pub async fn fetch_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
2839 let response = self
2840 .send(transports::v1::Request::new())
2841 .with_request_config(RequestConfig::short_retry())
2842 .await?;
2843 Ok(response.rtc_transports)
2844 }
2845
2846 pub fn reset_rtc_transports(&self) {
2851 self.inner.caches.rtc_transports.reset();
2852 }
2853
2854 pub async fn discover_rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2882 if let Some(transports) = self.rtc_transports().await? {
2883 return Ok(Some(transports));
2884 }
2885
2886 Ok(self.well_known().await.map(|well_known| well_known.rtc_foci))
2891 }
2892
2893 pub async fn tile_server(&self) -> Option<TileServerInfo> {
2901 self.well_known().await.and_then(|well_known| well_known.tile_server).map(Into::into)
2902 }
2903
2904 pub async fn reset_well_known(&self) -> Result<()> {
2909 self.inner.caches.well_known.reset();
2911
2912 Ok(self.state_store().remove_kv_data(StateStoreDataKey::WellKnown).await?)
2914 }
2915
2916 pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> HttpResult<bool> {
2931 Ok(self.unstable_features().await?.contains(&FeatureFlag::from("org.matrix.msc4028")))
2932 }
2933
2934 pub async fn devices(&self) -> HttpResult<get_devices::v3::Response> {
2956 let request = get_devices::v3::Request::new();
2957
2958 self.send(request).await
2959 }
2960
2961 pub async fn delete_devices(
3002 &self,
3003 devices: &[OwnedDeviceId],
3004 auth_data: Option<uiaa::AuthData>,
3005 ) -> HttpResult<delete_devices::v3::Response> {
3006 let mut request = delete_devices::v3::Request::new(devices.to_owned());
3007 request.auth = auth_data;
3008
3009 self.send(request).await
3010 }
3011
3012 pub async fn rename_device(
3022 &self,
3023 device_id: &DeviceId,
3024 display_name: &str,
3025 ) -> HttpResult<update_device::v3::Response> {
3026 let mut request = update_device::v3::Request::new(device_id.to_owned());
3027 request.display_name = Some(display_name.to_owned());
3028
3029 self.send(request).await
3030 }
3031
3032 pub async fn device_exists(&self, device_id: OwnedDeviceId) -> Result<bool> {
3041 let request = device::get_device::v3::Request::new(device_id);
3042 match self.send(request).await {
3043 Ok(_) => Ok(true),
3044 Err(err) => {
3045 if let Some(error) = err.as_client_api_error()
3046 && error.status_code == 404
3047 {
3048 Ok(false)
3049 } else {
3050 Err(err.into())
3051 }
3052 }
3053 }
3054 }
3055
3056 #[instrument(skip(self))]
3153 pub async fn sync_once(
3154 &self,
3155 sync_settings: crate::config::SyncSettings,
3156 ) -> Result<SyncResponse> {
3157 #[cfg(feature = "e2e-encryption")]
3164 if let Err(e) = self.send_outgoing_requests().await {
3165 error!(error = ?e, "Error while sending outgoing E2EE requests");
3166 }
3167
3168 let token = match sync_settings.token {
3169 SyncToken::Specific(token) => Some(token),
3170 SyncToken::NoToken => None,
3171 SyncToken::ReusePrevious => self.sync_token().await,
3172 };
3173
3174 let request = assign!(sync_events::v3::Request::new(), {
3175 filter: sync_settings.filter.map(|f| *f),
3176 since: token,
3177 full_state: sync_settings.full_state,
3178 set_presence: sync_settings.set_presence.unwrap_or_else(|| self.sync_presence()),
3179 timeout: sync_settings.timeout,
3180 use_state_after: true,
3181 });
3182 let mut request_config = self.request_config();
3183 if let Some(timeout) = sync_settings.timeout {
3184 let base_timeout = request_config.timeout.unwrap_or(Duration::from_secs(30));
3185 request_config.timeout = Some(base_timeout + timeout);
3186 }
3187
3188 let response = self.send(request).with_request_config(request_config).await?;
3189 let next_batch = response.next_batch.clone();
3190 let response = self.process_sync(response).await?;
3191
3192 #[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 self.inner.sync_beat.notify(usize::MAX);
3198
3199 Ok(SyncResponse::new(next_batch, response))
3200 }
3201
3202 pub async fn sync(&self, sync_settings: crate::config::SyncSettings) -> Result<(), Error> {
3261 self.sync_with_callback(sync_settings, |_| async { LoopCtrl::Continue }).await
3262 }
3263
3264 #[instrument(skip_all)]
3321 pub async fn sync_with_callback<C>(
3322 &self,
3323 sync_settings: crate::config::SyncSettings,
3324 callback: impl Fn(SyncResponse) -> C,
3325 ) -> Result<(), Error>
3326 where
3327 C: Future<Output = LoopCtrl>,
3328 {
3329 self.sync_with_result_callback(sync_settings, |result| async {
3330 Ok(callback(result?).await)
3331 })
3332 .await
3333 }
3334
3335 #[instrument(skip(self, callback))]
3401 pub async fn sync_with_result_callback<C>(
3402 &self,
3403 sync_settings: crate::config::SyncSettings,
3404 callback: impl Fn(Result<SyncResponse, Error>) -> C,
3405 ) -> Result<(), Error>
3406 where
3407 C: Future<Output = Result<LoopCtrl, Error>>,
3408 {
3409 let mut sync_stream = Box::pin(self.sync_stream(sync_settings).await);
3410
3411 while let Some(result) = sync_stream.next().await {
3412 trace!("Running callback");
3413 if callback(result).await? == LoopCtrl::Break {
3414 trace!("Callback told us to stop");
3415 break;
3416 }
3417 trace!("Done running callback");
3418 }
3419
3420 Ok(())
3421 }
3422
3423 #[allow(unknown_lints, clippy::let_with_type_underscore)] #[instrument(skip(self))]
3466 pub async fn sync_stream(
3467 &self,
3468 mut sync_settings: crate::config::SyncSettings,
3469 ) -> impl Stream<Item = Result<SyncResponse>> + '_ {
3470 let mut is_first_sync = true;
3471 let mut timeout = None;
3472 let mut last_sync_time: Option<Instant> = None;
3473
3474 let parent_span = Span::current();
3475
3476 async_stream::stream!({
3477 loop {
3478 trace!("Syncing");
3479
3480 if sync_settings.ignore_timeout_on_first_sync {
3481 if is_first_sync {
3482 timeout = sync_settings.timeout.take();
3483 } else if sync_settings.timeout.is_none() && timeout.is_some() {
3484 sync_settings.timeout = timeout.take();
3485 }
3486
3487 is_first_sync = false;
3488 }
3489
3490 yield self
3491 .sync_loop_helper(&mut sync_settings)
3492 .instrument(parent_span.clone())
3493 .await;
3494
3495 Client::delay_sync(&mut last_sync_time).await
3496 }
3497 })
3498 }
3499
3500 pub(crate) async fn sync_token(&self) -> Option<String> {
3503 self.inner.base_client.sync_token().await
3504 }
3505
3506 pub async fn whoami(&self) -> HttpResult<whoami::v3::Response> {
3508 let request = whoami::v3::Request::new();
3509 self.send(request).await
3510 }
3511
3512 pub fn subscribe_to_session_changes(&self) -> broadcast::Receiver<SessionChange> {
3514 let broadcast = &self.auth_ctx().session_change_sender;
3515 broadcast.subscribe()
3516 }
3517
3518 pub fn set_session_callbacks(
3523 &self,
3524 reload_session_callback: Box<ReloadSessionCallback>,
3525 save_session_callback: Box<SaveSessionCallback>,
3526 ) -> Result<()> {
3527 self.inner
3528 .auth_ctx
3529 .reload_session_callback
3530 .set(reload_session_callback)
3531 .map_err(|_| Error::MultipleSessionCallbacks)?;
3532
3533 self.inner
3534 .auth_ctx
3535 .save_session_callback
3536 .set(save_session_callback)
3537 .map_err(|_| Error::MultipleSessionCallbacks)?;
3538
3539 Ok(())
3540 }
3541
3542 pub async fn notification_settings(&self) -> NotificationSettings {
3544 let ruleset = self.account().push_rules().await.unwrap_or_else(|_| Ruleset::new());
3545 NotificationSettings::new(self.clone(), ruleset)
3546 }
3547
3548 pub async fn notification_client(
3555 &self,
3556 cross_process_lock_config: CrossProcessLockConfig,
3557 ) -> Result<Client> {
3558 let client = Client {
3559 inner: ClientInner::new(
3560 self.inner.auth_ctx.clone(),
3561 self.server(),
3562 self.homeserver(),
3563 self.sliding_sync_version(),
3564 self.inner.sync_presence.clone(),
3565 self.inner.http_client.clone(),
3566 self.inner
3567 .base_client
3568 .clone_with_in_memory_state_store(cross_process_lock_config.clone(), false)
3569 .await?,
3570 self.inner.caches.supported_versions.value(),
3571 self.inner.caches.well_known.value(),
3572 self.inner.respect_login_well_known,
3573 self.well_known_lookup_disabled(),
3574 self.inner.event_cache.clone(),
3575 self.inner.send_queue_data.clone(),
3576 self.inner.latest_events.clone(),
3577 #[cfg(feature = "e2e-encryption")]
3578 self.inner.e2ee.encryption_settings,
3579 #[cfg(feature = "e2e-encryption")]
3580 self.inner.enable_share_history_on_invite,
3581 cross_process_lock_config,
3582 #[cfg(feature = "experimental-search")]
3583 self.inner.search_index.clone(),
3584 self.inner.thread_subscription_catchup.clone(),
3585 (*self.inner.media_fetcher.read().await).clone(),
3586 )
3587 .await,
3588 };
3589
3590 Ok(client)
3591 }
3592
3593 pub fn event_cache(&self) -> &EventCache {
3595 self.inner.event_cache.get().unwrap()
3597 }
3598
3599 pub async fn latest_events(&self) -> &LatestEvents {
3601 self.inner
3602 .latest_events
3603 .get_or_init(|| async {
3604 LatestEvents::new(
3605 WeakClient::from_client(self),
3606 self.event_cache().clone(),
3607 SendQueue::new(self.clone()),
3608 self.room_info_notable_update_receiver(),
3609 )
3610 })
3611 .await
3612 }
3613
3614 pub async fn await_room_remote_echo(&self, room_id: &RoomId) -> Room {
3620 loop {
3621 if let Some(room) = self.get_room(room_id) {
3622 if room.is_state_partially_or_fully_synced() {
3623 debug!("Found just created room!");
3624 return room;
3625 }
3626 debug!("Room wasn't partially synced, waiting for sync beat to try again");
3627 } else {
3628 debug!("Room wasn't found, waiting for sync beat to try again");
3629 }
3630 self.inner.sync_beat.listen().await;
3631 }
3632 }
3633
3634 pub async fn knock(
3637 &self,
3638 room_id_or_alias: OwnedRoomOrAliasId,
3639 reason: Option<String>,
3640 server_names: Vec<OwnedServerName>,
3641 ) -> Result<Room> {
3642 let request =
3643 assign!(knock_room::v3::Request::new(room_id_or_alias), { reason, via: server_names });
3644 let response = self.send(request).await?;
3645 let base_room = self.inner.base_client.room_knocked(&response.room_id).await?;
3646 Ok(Room::new(self.clone(), base_room))
3647 }
3648
3649 pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
3651 self.base_client().is_user_ignored(user_id).await
3652 }
3653
3654 pub async fn load_or_fetch_max_upload_size(&self) -> Result<UInt> {
3661 let max_upload_size_lock = self.inner.server_max_upload_size.lock().await;
3662 if let Some(data) = max_upload_size_lock.get() {
3663 return Ok(data.to_owned());
3664 }
3665
3666 let supported_versions = self.supported_versions().await?;
3668 let use_auth = authenticated_media::get_media_config::v1::Request::PATH_BUILDER
3669 .is_supported(&supported_versions);
3670
3671 let upload_size = if use_auth {
3672 self.send(authenticated_media::get_media_config::v1::Request::default())
3673 .await?
3674 .upload_size
3675 } else {
3676 #[allow(deprecated)]
3677 self.send(media::get_media_config::v3::Request::default()).await?.upload_size
3678 };
3679
3680 match max_upload_size_lock.set(upload_size) {
3681 Ok(_) => Ok(upload_size),
3682 Err(error) => {
3683 Err(Error::Media(MediaError::FetchMaxUploadSizeFailed(error.to_string())))
3684 }
3685 }
3686 }
3687
3688 #[cfg(feature = "e2e-encryption")]
3690 pub fn decryption_settings(&self) -> &DecryptionSettings {
3691 &self.base_client().decryption_settings
3692 }
3693
3694 #[cfg(feature = "experimental-search")]
3696 pub fn search_index(&self) -> &SearchIndex {
3697 &self.inner.search_index
3698 }
3699
3700 pub async fn enabled_thread_subscriptions(&self) -> Result<bool> {
3711 match self.base_client().threading_support {
3713 ThreadingSupport::Enabled { with_subscriptions: false }
3714 | ThreadingSupport::Disabled => return Ok(false),
3715 ThreadingSupport::Enabled { with_subscriptions: true } => {}
3716 }
3717
3718 let server_enabled = self
3720 .supported_versions()
3721 .await?
3722 .features
3723 .contains(&FeatureFlag::from("org.matrix.msc4306"));
3724
3725 Ok(server_enabled)
3726 }
3727
3728 pub async fn fetch_thread_subscriptions(
3734 &self,
3735 from: Option<String>,
3736 to: Option<String>,
3737 limit: Option<UInt>,
3738 ) -> Result<get_thread_subscriptions_changes::unstable::Response> {
3739 let request = assign!(get_thread_subscriptions_changes::unstable::Request::new(), {
3740 from,
3741 to,
3742 limit,
3743 });
3744 Ok(self.send(request).await?)
3745 }
3746
3747 pub(crate) fn thread_subscription_catchup(&self) -> &ThreadSubscriptionCatchup {
3748 self.inner.thread_subscription_catchup.get().unwrap()
3749 }
3750
3751 pub async fn pause(&self) -> Result<()> {
3768 info!("Client::pause — releasing database resources");
3769
3770 self.send_queue().set_enabled(false).await;
3772
3773 self.base_client().close_stores().await?;
3775
3776 info!("Client::pause — complete, all database connections released");
3777 Ok(())
3778 }
3779
3780 pub async fn resume(&self) -> Result<()> {
3788 info!("Client::resume — re-acquiring database resources");
3789
3790 self.base_client().reopen_stores().await?;
3792
3793 self.send_queue().set_enabled(true).await;
3795
3796 info!("Client::resume — complete");
3797 Ok(())
3798 }
3799
3800 #[doc(hidden)]
3806 pub async fn optimize_stores(&self) -> Result<()> {
3807 trace!("Optimizing state store...");
3808 self.state_store().optimize().await?;
3809
3810 trace!("Optimizing event cache store...");
3811 if let Some(clean_lock) = self.event_cache_store().lock().await?.as_clean() {
3812 clean_lock.optimize().await?;
3813 }
3814
3815 trace!("Optimizing media store...");
3816 self.media_store().lock().await?.optimize().await?;
3817
3818 Ok(())
3819 }
3820
3821 pub async fn get_store_sizes(&self) -> Result<StoreSizes> {
3823 #[cfg(feature = "e2e-encryption")]
3824 let crypto_store_size = if let Some(olm_machine) = self.olm_machine().await.as_ref()
3825 && let Ok(Some(store_size)) = olm_machine.store().get_size().await
3826 {
3827 Some(store_size)
3828 } else {
3829 None
3830 };
3831 #[cfg(not(feature = "e2e-encryption"))]
3832 let crypto_store_size = None;
3833
3834 let state_store_size = self.state_store().get_size().await.ok().flatten();
3835
3836 let event_cache_store_size = if let Some(clean_lock) =
3837 self.event_cache_store().lock().await?.as_clean()
3838 && let Ok(Some(store_size)) = clean_lock.get_size().await
3839 {
3840 Some(store_size)
3841 } else {
3842 None
3843 };
3844
3845 let media_store_size = self.media_store().lock().await?.get_size().await.ok().flatten();
3846
3847 Ok(StoreSizes {
3848 crypto_store: crypto_store_size,
3849 state_store: state_store_size,
3850 event_cache_store: event_cache_store_size,
3851 media_store: media_store_size,
3852 })
3853 }
3854
3855 pub fn task_monitor(&self) -> &TaskMonitor {
3858 &self.inner.task_monitor
3859 }
3860
3861 #[cfg(feature = "e2e-encryption")]
3864 pub fn subscribe_to_duplicate_key_upload_errors(
3865 &self,
3866 ) -> broadcast::Receiver<Option<DuplicateOneTimeKeyErrorMessage>> {
3867 self.inner.duplicate_key_upload_error_sender.subscribe()
3868 }
3869
3870 #[cfg(feature = "e2e-encryption")]
3875 pub async fn get_pending_key_bundle_details_for_room(
3876 &self,
3877 room_id: &RoomId,
3878 ) -> Result<Option<RoomPendingKeyBundleDetails>> {
3879 Ok(self.base_client().get_pending_key_bundle_details_for_room(room_id).await?)
3880 }
3881
3882 pub fn dm_room_definition(&self) -> &DmRoomDefinition {
3885 &self.inner.base_client.dm_room_definition
3886 }
3887
3888 pub async fn set_media_fetcher(&self, media_fetcher: Arc<dyn MediaFetcher>) {
3891 *self.inner.media_fetcher.write().await = media_fetcher;
3892 }
3893
3894 pub async fn get_media_fetcher(&self) -> Arc<dyn MediaFetcher> {
3897 self.inner.media_fetcher.read().await.clone()
3898 }
3899}
3900
3901#[derive(Debug, Clone)]
3904pub struct StoreSizes {
3905 pub crypto_store: Option<usize>,
3907 pub state_store: Option<usize>,
3909 pub event_cache_store: Option<usize>,
3911 pub media_store: Option<usize>,
3913}
3914
3915#[cfg(any(feature = "testing", test))]
3916impl Client {
3917 #[cfg(feature = "e2e-encryption")]
3919 pub async fn update_tracked_users_for_testing(
3920 &self,
3921 user_ids: impl IntoIterator<Item = &UserId>,
3922 ) {
3923 let olm = self.olm_machine().await;
3924 let olm = olm.as_ref().unwrap();
3925 olm.update_tracked_users(user_ids).await.unwrap();
3926 }
3927}
3928
3929#[derive(Clone, Debug)]
3932pub(crate) struct WeakClient {
3933 client: Weak<ClientInner>,
3934}
3935
3936impl WeakClient {
3937 pub(crate) fn from_inner(client: &Arc<ClientInner>) -> Self {
3939 Self { client: Arc::downgrade(client) }
3940 }
3941
3942 pub fn from_client(client: &Client) -> Self {
3944 Self::from_inner(&client.inner)
3945 }
3946
3947 pub fn get(&self) -> Option<Client> {
3949 self.client.upgrade().map(|inner| Client { inner })
3950 }
3951
3952 #[allow(dead_code)]
3955 pub fn strong_count(&self) -> usize {
3956 self.client.strong_count()
3957 }
3958}
3959
3960#[derive(Debug, Clone, Default)]
3962struct PreJoinRoomInfo {
3963 pub inviter: Option<RoomMember>,
3965}
3966
3967#[cfg(all(test, not(target_family = "wasm")))]
3969pub(crate) mod tests {
3970 use std::{sync::Arc, time::Duration};
3971
3972 use assert_matches::assert_matches;
3973 use assert_matches2::assert_let;
3974 use eyeball::SharedObservable;
3975 use futures_util::{FutureExt, StreamExt, pin_mut};
3976 use js_int::{UInt, uint};
3977 use matrix_sdk_base::{
3978 RoomState,
3979 store::{MemoryStore, StoreConfig},
3980 ttl::TtlValue,
3981 };
3982 use matrix_sdk_test::{
3983 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
3984 event_factory::EventFactory,
3985 };
3986 #[cfg(target_family = "wasm")]
3987 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
3988
3989 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
3990 use ruma::{
3991 RoomId, ServerName, UserId,
3992 api::{
3993 FeatureFlag, MatrixVersion,
3994 client::{room::create_room::v3::Request as CreateRoomRequest, rtc::RtcTransport},
3995 },
3996 assign,
3997 events::{
3998 ignored_user_list::IgnoredUserListEventContent,
3999 media_preview_config::{InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews},
4000 },
4001 owned_device_id, owned_room_id, owned_user_id,
4002 presence::PresenceState,
4003 room_alias_id, room_id, user_id,
4004 };
4005 use serde_json::json;
4006 use stream_assert::{assert_next_matches, assert_pending};
4007 use tokio::{
4008 spawn,
4009 time::{sleep, timeout},
4010 };
4011 use url::Url;
4012
4013 use super::Client;
4014 use crate::{
4015 Error, Result, TransmissionProgress,
4016 client::{WeakClient, caches::CachedValue, futures::SendMediaUploadRequest},
4017 config::{RequestConfig, SyncSettings},
4018 futures::SendRequest,
4019 media::MediaError,
4020 test_utils::{client::MockClientBuilder, mocks::MatrixMockServer},
4021 };
4022
4023 #[async_test]
4024 async fn test_sync_presence_is_shared_by_client_clones_and_notification_child() {
4025 let client = MockClientBuilder::new(None).build().await;
4026 let clone = client.clone();
4027 let notification_client =
4028 client.notification_client(CrossProcessLockConfig::SingleProcess).await.unwrap();
4029
4030 assert_eq!(client.sync_presence(), PresenceState::Online);
4031 assert_eq!(clone.sync_presence(), PresenceState::Online);
4032 assert_eq!(notification_client.sync_presence(), PresenceState::Online);
4033
4034 client
4035 .set_presence(PresenceState::Unavailable, None, false)
4036 .await
4037 .expect("presence should update");
4038
4039 assert_eq!(client.sync_presence(), PresenceState::Unavailable);
4040 assert_eq!(clone.sync_presence(), PresenceState::Unavailable);
4041 assert_eq!(notification_client.sync_presence(), PresenceState::Unavailable);
4042
4043 notification_client
4044 .set_presence(PresenceState::Offline, None, false)
4045 .await
4046 .expect("presence should update");
4047
4048 assert_eq!(client.sync_presence(), PresenceState::Offline);
4049 assert_eq!(clone.sync_presence(), PresenceState::Offline);
4050 assert_eq!(notification_client.sync_presence(), PresenceState::Offline);
4051 }
4052
4053 #[async_test]
4054 async fn test_sync_once_uses_client_sync_presence_unless_overridden() {
4055 let server = MatrixMockServer::new().await;
4056 let client = server.client_builder().build().await;
4057
4058 {
4059 let _sync_guard = server
4060 .mock_sync()
4061 .set_presence_missing()
4062 .ok(|_| {})
4063 .expect(1)
4064 .mount_as_scoped()
4065 .await;
4066
4067 client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4068 }
4069
4070 client
4071 .set_presence(PresenceState::Offline, None, false)
4072 .await
4073 .expect("presence should update");
4074
4075 {
4076 let _sync_guard = server
4077 .mock_sync()
4078 .set_presence("offline")
4079 .ok(|_| {})
4080 .expect(1)
4081 .mount_as_scoped()
4082 .await;
4083
4084 client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4085 }
4086
4087 {
4088 let _sync_guard = server
4089 .mock_sync()
4090 .set_presence("unavailable")
4091 .ok(|_| {})
4092 .expect(1)
4093 .mount_as_scoped()
4094 .await;
4095
4096 client
4097 .sync_once(SyncSettings::new().set_presence(PresenceState::Unavailable))
4098 .await
4099 .expect("sync should succeed");
4100 }
4101 }
4102
4103 #[async_test]
4104 async fn test_set_presence_sends_presence_status_update() {
4105 use wiremock::{
4106 Mock, ResponseTemplate,
4107 matchers::{body_partial_json, method, path_regex},
4108 };
4109
4110 let server = MatrixMockServer::new().await;
4111 let client = server.client_builder().build().await;
4112
4113 Mock::given(method("PUT"))
4114 .and(path_regex(r"^/_matrix/client/(r0|v3)/presence/.*/status$"))
4115 .and(body_partial_json(json!({
4116 "presence": "online",
4117 "status_msg": "Here"
4118 })))
4119 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
4120 .expect(1)
4121 .mount(server.server())
4122 .await;
4123
4124 client
4125 .set_presence(PresenceState::Online, Some("Here".to_owned()), true)
4126 .await
4127 .expect("presence update should succeed");
4128
4129 assert_eq!(client.sync_presence(), PresenceState::Online);
4130 }
4131
4132 #[async_test]
4133 async fn test_set_presence_requires_authentication() {
4134 let client = MockClientBuilder::new(None).unlogged().build().await;
4135
4136 assert_matches!(
4137 client.set_presence(PresenceState::Unavailable, None, true).await,
4138 Err(Error::AuthenticationRequired)
4139 );
4140 }
4141
4142 #[async_test]
4143 async fn test_set_presence_without_immediate_does_not_require_authentication() {
4144 let client = MockClientBuilder::new(None).unlogged().build().await;
4145
4146 client
4147 .set_presence(PresenceState::Offline, None, false)
4148 .await
4149 .expect("presence should update");
4150
4151 assert_eq!(client.sync_presence(), PresenceState::Offline);
4152 }
4153
4154 #[async_test]
4155 async fn test_account_data() {
4156 let server = MatrixMockServer::new().await;
4157 let client = server.client_builder().build().await;
4158
4159 let f = EventFactory::new();
4160 server
4161 .mock_sync()
4162 .ok_and_run(&client, |builder| {
4163 builder.add_global_account_data(
4164 f.ignored_user_list([owned_user_id!("@someone:example.org")]),
4165 );
4166 })
4167 .await;
4168
4169 let content = client
4170 .account()
4171 .account_data::<IgnoredUserListEventContent>()
4172 .await
4173 .unwrap()
4174 .unwrap()
4175 .deserialize()
4176 .unwrap();
4177
4178 assert_eq!(content.ignored_users.len(), 1);
4179 }
4180
4181 #[async_test]
4182 async fn test_successful_discovery() {
4183 let server = MatrixMockServer::new().await;
4185 let server_url = server.uri();
4186
4187 let homeserver = MatrixMockServer::new().await;
4189 let homeserver_url = homeserver.uri();
4190
4191 let domain = server_url.strip_prefix("http://").unwrap();
4193 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4194
4195 server
4197 .mock_well_known()
4198 .ok_with_homeserver_url(&homeserver_url)
4199 .mock_once()
4200 .named("well-known")
4201 .mount()
4202 .await;
4203
4204 homeserver.mock_versions().ok().mock_once().named("versions").mount().await;
4206
4207 let client = Client::builder()
4208 .insecure_server_name_no_tls(alice.server_name())
4209 .build()
4210 .await
4211 .unwrap();
4212
4213 assert_eq!(client.server().unwrap(), Url::parse(&server_url).unwrap());
4214 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4215 client.server_versions().await.unwrap();
4216 }
4217
4218 #[async_test]
4219 async fn test_homeserver_swap_resets_server_field() {
4220 let homeserver = MatrixMockServer::new().await;
4221 let homeserver_url = homeserver.uri();
4222
4223 let domain = homeserver_url.strip_prefix("http://").unwrap();
4224 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4225
4226 homeserver.mock_well_known().ok().mock_once().named("well-known").mount().await;
4227
4228 let client = Client::builder()
4229 .insecure_server_name_no_tls(alice.server_name())
4230 .build()
4231 .await
4232 .unwrap();
4233
4234 assert_eq!(client.server().unwrap(), Url::parse(&homeserver_url).unwrap());
4235 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4236
4237 let new_server = Url::parse("http://example.org").unwrap();
4238 client.set_homeserver(new_server.clone());
4241
4242 assert_eq!(client.homeserver(), new_server);
4244 assert!(client.server().is_none())
4247 }
4248
4249 #[async_test]
4250 async fn test_discovery_broken_server() {
4251 let server = MatrixMockServer::new().await;
4252 let server_url = server.uri();
4253 let domain = server_url.strip_prefix("http://").unwrap();
4254 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4255
4256 server.mock_well_known().error404().mock_once().named("well-known").mount().await;
4257
4258 assert!(
4259 Client::builder()
4260 .insecure_server_name_no_tls(alice.server_name())
4261 .build()
4262 .await
4263 .is_err(),
4264 "Creating a client from a user ID should fail when the .well-known request fails."
4265 );
4266 }
4267
4268 #[async_test]
4269 async fn test_room_creation() {
4270 let server = MatrixMockServer::new().await;
4271 let client = server.client_builder().build().await;
4272
4273 let f = EventFactory::new().sender(user_id!("@example:localhost"));
4274 server
4275 .mock_sync()
4276 .ok_and_run(&client, |builder| {
4277 builder.add_joined_room(
4278 JoinedRoomBuilder::default()
4279 .add_state_event(
4280 f.member(user_id!("@example:localhost")).display_name("example"),
4281 )
4282 .add_state_event(f.default_power_levels()),
4283 );
4284 })
4285 .await;
4286
4287 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
4288 assert_eq!(room.state(), RoomState::Joined);
4289 }
4290
4291 #[async_test]
4292 async fn test_retry_limit_http_requests() {
4293 let server = MatrixMockServer::new().await;
4294 let client = server
4295 .client_builder()
4296 .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(4)))
4297 .build()
4298 .await;
4299
4300 assert!(client.request_config().retry_limit.unwrap() == 4);
4301
4302 server.mock_who_am_i().error500().expect(4).mount().await;
4303
4304 client.whoami().await.unwrap_err();
4305 }
4306
4307 #[async_test]
4308 async fn test_retry_timeout_http_requests() {
4309 let retry_timeout = Duration::from_secs(5);
4311 let server = MatrixMockServer::new().await;
4312 let client = server
4313 .client_builder()
4314 .on_builder(|builder| {
4315 builder.request_config(RequestConfig::new().max_retry_time(retry_timeout))
4316 })
4317 .build()
4318 .await;
4319
4320 assert!(client.request_config().max_retry_time.unwrap() == retry_timeout);
4321
4322 server.mock_login().error500().expect(2..).mount().await;
4323
4324 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4325 }
4326
4327 #[async_test]
4328 async fn test_short_retry_initial_http_requests() {
4329 let server = MatrixMockServer::new().await;
4330 let client = server
4331 .client_builder()
4332 .on_builder(|builder| builder.request_config(RequestConfig::short_retry()))
4333 .build()
4334 .await;
4335
4336 server.mock_login().error500().expect(3..).mount().await;
4337
4338 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4339 }
4340
4341 #[async_test]
4342 async fn test_no_retry_http_requests() {
4343 let server = MatrixMockServer::new().await;
4344 let client = server.client_builder().build().await;
4345
4346 server.mock_devices().error500().mock_once().mount().await;
4347
4348 client.devices().await.unwrap_err();
4349 }
4350
4351 #[async_test]
4352 async fn test_set_homeserver() {
4353 let client = MockClientBuilder::new(None).build().await;
4354 assert_eq!(client.homeserver().as_ref(), "http://localhost/");
4355
4356 let homeserver = Url::parse("http://example.com/").unwrap();
4357 client.set_homeserver(homeserver.clone());
4358 assert_eq!(client.homeserver(), homeserver);
4359 }
4360
4361 #[async_test]
4362 async fn test_search_user_request() {
4363 let server = MatrixMockServer::new().await;
4364 let client = server.client_builder().build().await;
4365
4366 server.mock_user_directory().ok().mock_once().mount().await;
4367
4368 let response = client.search_users("test", 50).await.unwrap();
4369 assert_eq!(response.results.len(), 1);
4370 let result = &response.results[0];
4371 assert_eq!(result.user_id.to_string(), "@test:example.me");
4372 assert_eq!(result.display_name.clone().unwrap(), "Test");
4373 assert_eq!(result.avatar_url.clone().unwrap().to_string(), "mxc://example.me/someid");
4374 assert!(!response.limited);
4375 }
4376
4377 #[async_test]
4378 async fn test_request_unstable_features() {
4379 let server = MatrixMockServer::new().await;
4380 let client = server.client_builder().no_server_versions().build().await;
4381
4382 server
4383 .mock_versions()
4384 .with_feature("org.matrix.e2e_cross_signing", true)
4385 .ok()
4386 .mock_once()
4387 .mount()
4388 .await;
4389
4390 let unstable_features = client.unstable_features().await.unwrap();
4391 assert!(unstable_features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4392 assert!(!unstable_features.contains(&FeatureFlag::from("you.shall.pass")));
4393 }
4394
4395 #[async_test]
4396 async fn test_can_homeserver_push_encrypted_event_to_device() {
4397 let server = MatrixMockServer::new().await;
4398 let client = server.client_builder().no_server_versions().build().await;
4399
4400 server.mock_versions().with_push_encrypted_events().ok().mock_once().mount().await;
4401
4402 let msc4028_enabled = client.can_homeserver_push_encrypted_event_to_device().await.unwrap();
4403 assert!(msc4028_enabled);
4404 }
4405
4406 #[async_test]
4407 async fn test_recently_visited_rooms() {
4408 let client = MockClientBuilder::new(None).unlogged().build().await;
4410 assert_matches!(
4411 client.account().track_recently_visited_room(owned_room_id!("!alpha:localhost")).await,
4412 Err(Error::AuthenticationRequired)
4413 );
4414
4415 let client = MockClientBuilder::new(None).build().await;
4416 let account = client.account();
4417
4418 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 0);
4420
4421 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4423 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4424 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4425
4426 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4428 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4429
4430 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4432 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4433 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4434
4435 account.track_recently_visited_room(owned_room_id!("!beta:localhost")).await.unwrap();
4437 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4438 assert_eq!(
4439 account.get_recently_visited_rooms().await.unwrap(),
4440 [room_id!("!beta:localhost"), room_id!("!alpha:localhost")]
4441 );
4442
4443 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4445 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4446 assert_eq!(
4447 account.get_recently_visited_rooms().await.unwrap(),
4448 [room_id!("!alpha:localhost"), room_id!("!beta:localhost")]
4449 );
4450
4451 for n in 0..20 {
4453 account
4454 .track_recently_visited_room(RoomId::parse(format!("!{n}:localhost")).unwrap())
4455 .await
4456 .unwrap();
4457 }
4458
4459 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 20);
4460
4461 let rooms = account.get_recently_visited_rooms().await.unwrap();
4463 assert!(!rooms.contains(&owned_room_id!("!alpha:localhost")));
4464 assert!(!rooms.contains(&owned_room_id!("!beta:localhost")));
4465
4466 assert_eq!(rooms.first().unwrap(), room_id!("!19:localhost"));
4468 }
4469
4470 #[async_test]
4471 async fn test_client_no_cycle_with_event_cache() {
4472 let client = MockClientBuilder::new(None).build().await;
4473
4474 sleep(Duration::from_secs(1)).await;
4476
4477 let weak_client = WeakClient::from_client(&client);
4478 assert_eq!(weak_client.strong_count(), 1);
4479
4480 {
4481 let room_id = room_id!("!room:example.org");
4482
4483 let response = SyncResponseBuilder::default()
4485 .add_joined_room(JoinedRoomBuilder::new(room_id))
4486 .build_sync_response();
4487 client.inner.base_client.receive_sync_response(response).await.unwrap();
4488
4489 client.event_cache().subscribe().unwrap();
4490
4491 let (_room_event_cache, _drop_handles) =
4492 client.get_room(room_id).unwrap().event_cache().await.unwrap();
4493 }
4494
4495 drop(client);
4496
4497 sleep(Duration::from_secs(1)).await;
4499
4500 assert_eq!(weak_client.strong_count(), 0);
4502 let client = weak_client.get();
4503 assert!(
4504 client.is_none(),
4505 "too many strong references to the client: {}",
4506 Arc::strong_count(&client.unwrap().inner)
4507 );
4508 }
4509
4510 #[async_test]
4511 async fn test_supported_versions_caching() {
4512 let server = MatrixMockServer::new().await;
4513
4514 let versions_mock = server
4515 .mock_versions()
4516 .expect_default_access_token()
4517 .with_feature("org.matrix.e2e_cross_signing", true)
4518 .ok()
4519 .named("first versions mock")
4520 .expect(1)
4521 .mount_as_scoped()
4522 .await;
4523
4524 let memory_store = Arc::new(MemoryStore::new());
4525 let client = server
4526 .client_builder()
4527 .no_server_versions()
4528 .on_builder(|builder| {
4529 builder.store_config(
4530 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4531 .state_store(memory_store.clone()),
4532 )
4533 })
4534 .build()
4535 .await;
4536
4537 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4538
4539 assert_matches!(client.supported_versions_cached().await, Ok(Some(_)));
4541 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4543
4544 drop(client);
4545
4546 let client = server
4547 .client_builder()
4548 .no_server_versions()
4549 .on_builder(|builder| {
4550 builder.store_config(
4551 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4552 .state_store(memory_store.clone()),
4553 )
4554 })
4555 .build()
4556 .await;
4557
4558 assert!(
4560 client
4561 .unstable_features()
4562 .await
4563 .unwrap()
4564 .contains(&FeatureFlag::from("org.matrix.e2e_cross_signing"))
4565 );
4566
4567 let supported = client.supported_versions().await.unwrap();
4568 assert!(supported.versions.contains(&MatrixVersion::V1_0));
4569 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4570
4571 let supported = client.supported_versions().await.unwrap();
4573 assert!(supported.versions.contains(&MatrixVersion::V1_0));
4574 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4575
4576 drop(versions_mock);
4577
4578 client.reset_supported_versions().await.unwrap();
4580
4581 server.mock_versions().ok().expect(2).named("second versions mock").mount().await;
4582
4583 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4585 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4587 assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4588
4589 let supported_versions = client.supported_versions_cached().await.unwrap().unwrap();
4591 let mut ttl_value = TtlValue::new(supported_versions);
4592 ttl_value.expire();
4593 client.inner.caches.supported_versions.set_value(ttl_value);
4594
4595 client.supported_versions_cached().await.unwrap().unwrap();
4597
4598 sleep(Duration::from_secs(1)).await;
4600 assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4601 }
4602
4603 #[async_test]
4604 async fn test_well_known_caching() {
4605 let server = MatrixMockServer::new().await;
4606 let server_url = server.uri();
4607 let domain = server_url.strip_prefix("http://").unwrap();
4608 let server_name = <&ServerName>::try_from(domain).unwrap();
4609 let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4610
4611 let well_known_mock = server
4612 .mock_well_known()
4613 .ok()
4614 .named("well known mock")
4615 .expect(2) .mount_as_scoped()
4617 .await;
4618
4619 let memory_store = Arc::new(MemoryStore::new());
4620 let client = Client::builder()
4621 .insecure_server_name_no_tls(server_name)
4622 .store_config(
4623 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4624 .state_store(memory_store.clone()),
4625 )
4626 .build()
4627 .await
4628 .unwrap();
4629
4630 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4631
4632 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4634
4635 drop(client);
4636
4637 let client = server
4638 .client_builder()
4639 .no_server_versions()
4640 .on_builder(|builder| {
4641 builder.store_config(
4642 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4643 .state_store(memory_store.clone()),
4644 )
4645 })
4646 .build()
4647 .await;
4648
4649 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4651
4652 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4654
4655 drop(well_known_mock);
4656
4657 client.reset_well_known().await.unwrap();
4659
4660 server.mock_well_known().ok().named("second well known mock").expect(2).mount().await;
4661
4662 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4664 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4666
4667 let well_known = client.well_known().await;
4669 let mut ttl_value = TtlValue::new(well_known);
4670 ttl_value.expire();
4671 client.inner.caches.well_known.set_value(ttl_value);
4672
4673 client.well_known().await;
4675
4676 sleep(Duration::from_secs(5)).await;
4680 assert_matches!(client.inner.caches.well_known.value(), CachedValue::Cached(value) if !value.has_expired());
4681 }
4682
4683 #[async_test]
4684 async fn test_rtc_transports_caching() {
4685 use wiremock::{
4686 Mock, ResponseTemplate,
4687 matchers::{method, path_regex},
4688 };
4689
4690 let server = MatrixMockServer::new().await;
4691 let transports = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4692
4693 let transports_mock = Mock::given(method("GET"))
4694 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4695 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4696 "rtc_transports": [
4697 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4698 ]
4699 })))
4700 .named("first transports mock")
4701 .expect(1)
4702 .mount_as_scoped(server.server())
4703 .await;
4704
4705 let client = server.client_builder().build().await;
4706
4707 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4709 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4711 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4712
4713 drop(transports_mock);
4714
4715 client.reset_rtc_transports();
4717
4718 Mock::given(method("GET"))
4719 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4720 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4721 "rtc_transports": [
4722 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4723 ]
4724 })))
4725 .named("second transports mock")
4726 .expect(2)
4727 .mount(server.server())
4728 .await;
4729
4730 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4732 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4734
4735 let mut ttl_value = TtlValue::new(Some(transports.clone()));
4737 ttl_value.expire();
4738 client.inner.caches.rtc_transports.set_value(ttl_value);
4739
4740 client.rtc_transports().await.unwrap();
4742
4743 sleep(Duration::from_secs(1)).await;
4745 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4746 }
4747
4748 #[async_test]
4749 async fn test_rtc_transports_unsupported_caching() {
4750 use wiremock::{
4751 Mock, ResponseTemplate,
4752 matchers::{method, path_regex},
4753 };
4754
4755 let server = MatrixMockServer::new().await;
4756
4757 Mock::given(method("GET"))
4762 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4763 .respond_with(ResponseTemplate::new(404).set_body_json(json!({
4764 "errcode": "M_UNRECOGNIZED",
4765 "error": "Unrecognized request",
4766 })))
4767 .named("unrecognized transports mock")
4768 .expect(1)
4769 .mount(server.server())
4770 .await;
4771
4772 let client = server.client_builder().build().await;
4773
4774 assert_eq!(client.rtc_transports().await.unwrap(), None);
4777 assert_eq!(client.rtc_transports().await.unwrap(), None);
4779 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4780 }
4781
4782 async fn mock_rtc_transports_endpoint(
4786 server: &MatrixMockServer,
4787 supported: bool,
4788 ) -> wiremock::MockGuard {
4789 use wiremock::{
4790 Mock, ResponseTemplate,
4791 matchers::{method, path_regex},
4792 };
4793
4794 let response = if supported {
4795 ResponseTemplate::new(200).set_body_json(json!({
4796 "rtc_transports": [
4797 { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4798 ]
4799 }))
4800 } else {
4801 ResponseTemplate::new(404).set_body_json(json!({
4802 "errcode": "M_UNRECOGNIZED",
4803 "error": "Unrecognized request",
4804 }))
4805 };
4806
4807 Mock::given(method("GET"))
4808 .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4809 .respond_with(response)
4810 .named("transports mock")
4811 .expect(1)
4812 .mount_as_scoped(server.server())
4813 .await
4814 }
4815
4816 #[async_test]
4817 async fn test_discover_rtc_transports_prefers_the_endpoint() {
4818 let server = MatrixMockServer::new().await;
4819 let transports = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4820
4821 let _transports_mock = mock_rtc_transports_endpoint(&server, true).await;
4822
4823 let _well_known_mock = server
4826 .mock_well_known()
4827 .ok()
4828 .named("well-known mock")
4829 .expect(0)
4830 .mount_as_scoped()
4831 .await;
4832
4833 let client = server.client_builder().build().await;
4834
4835 assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(transports));
4836 }
4837
4838 #[async_test]
4839 async fn test_discover_rtc_transports_falls_back_to_well_known() {
4840 let server = MatrixMockServer::new().await;
4841 let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4843
4844 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4845
4846 let _well_known_mock = server
4847 .mock_well_known()
4848 .ok()
4849 .named("well-known mock")
4850 .expect(1)
4851 .mount_as_scoped()
4852 .await;
4853
4854 let client = server.client_builder().build().await;
4855
4856 assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(rtc_foci));
4859 }
4860
4861 async fn mock_well_known_never_called(server: &MatrixMockServer) -> wiremock::MockGuard {
4863 server.mock_well_known().ok().named("well-known mock").expect(0).mount_as_scoped().await
4864 }
4865
4866 #[async_test]
4867 async fn test_well_known_lookup_disabled() {
4868 let server = MatrixMockServer::new().await;
4869
4870 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4871
4872 let _well_known_mock = mock_well_known_never_called(&server).await;
4874
4875 let client = server
4877 .client_builder()
4878 .on_builder(|builder| builder.disable_well_known_lookup(true))
4879 .build()
4880 .await;
4881
4882 assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
4885 assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
4887 assert!(client.tile_server().await.is_none());
4888 assert!(client.fetch_client_well_known().await.is_none());
4889 }
4890
4891 #[async_test]
4892 async fn test_well_known_lookup_disabled_after_build() {
4893 let server = MatrixMockServer::new().await;
4894
4895 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4896
4897 let _well_known_mock = mock_well_known_never_called(&server).await;
4899
4900 let client = server.client_builder().build().await;
4902 client.disable_well_known_lookup(true);
4903
4904 assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
4907 assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
4909 assert!(client.tile_server().await.is_none());
4910 assert!(client.fetch_client_well_known().await.is_none());
4911 }
4912
4913 #[async_test]
4914 async fn test_missing_well_known_caching() {
4915 let server = MatrixMockServer::new().await;
4916 let rtc_foci: Vec<RtcTransport> = vec![];
4917
4918 let well_known_mock = server
4919 .mock_well_known()
4920 .error_unrecognized()
4921 .named("first well-known mock")
4922 .expect(1)
4923 .mount_as_scoped()
4924 .await;
4925
4926 let memory_store = Arc::new(MemoryStore::new());
4927 let client = server
4928 .client_builder()
4929 .on_builder(|builder| {
4930 builder.store_config(
4931 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4932 .state_store(memory_store.clone()),
4933 )
4934 })
4935 .build()
4936 .await;
4937
4938 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4939
4940 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4942
4943 drop(client);
4944
4945 let client = server
4946 .client_builder()
4947 .on_builder(|builder| {
4948 builder.store_config(
4949 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4950 .state_store(memory_store.clone()),
4951 )
4952 })
4953 .build()
4954 .await;
4955
4956 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4958
4959 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4961
4962 drop(well_known_mock);
4963
4964 client.reset_well_known().await.unwrap();
4966
4967 server
4968 .mock_well_known()
4969 .error_unrecognized()
4970 .expect(1)
4971 .named("second well-known mock")
4972 .mount()
4973 .await;
4974
4975 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4977 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4979 }
4980
4981 #[async_test]
4982 async fn test_no_network_doesnt_cause_infinite_retries() {
4983 let client = MockClientBuilder::new(None)
4985 .on_builder(|builder| builder.request_config(RequestConfig::new()))
4986 .build()
4987 .await;
4988
4989 client.whoami().await.unwrap_err();
4992 }
4993
4994 #[async_test]
4995 async fn test_await_room_remote_echo_returns_the_room_if_it_was_already_synced() {
4996 let server = MatrixMockServer::new().await;
4997 let client = server.client_builder().build().await;
4998
4999 let room_id = room_id!("!room:example.org");
5000
5001 server
5002 .mock_sync()
5003 .ok_and_run(&client, |builder| {
5004 builder.add_joined_room(JoinedRoomBuilder::new(room_id));
5005 })
5006 .await;
5007
5008 let room = client.await_room_remote_echo(room_id).now_or_never().unwrap();
5009 assert_eq!(room.room_id(), room_id);
5010 }
5011
5012 #[async_test]
5013 async fn test_await_room_remote_echo_returns_the_room_when_it_is_ready() {
5014 let server = MatrixMockServer::new().await;
5015 let client = server.client_builder().build().await;
5016
5017 let room_id = room_id!("!room:example.org");
5018
5019 let client = Arc::new(client);
5020
5021 spawn({
5024 let client = client.clone();
5025 async move {
5026 sleep(Duration::from_millis(100)).await;
5027
5028 server
5029 .mock_sync()
5030 .ok_and_run(&client, |builder| {
5031 builder.add_joined_room(JoinedRoomBuilder::new(room_id));
5032 })
5033 .await;
5034 }
5035 });
5036
5037 let room =
5038 timeout(Duration::from_secs(10), client.await_room_remote_echo(room_id)).await.unwrap();
5039 assert_eq!(room.room_id(), room_id);
5040 }
5041
5042 #[async_test]
5043 async fn test_await_room_remote_echo_will_timeout_if_no_room_is_found() {
5044 let client = MockClientBuilder::new(None).build().await;
5045
5046 let room_id = room_id!("!room:example.org");
5047 timeout(Duration::from_secs(1), client.await_room_remote_echo(room_id)).await.unwrap_err();
5050 }
5051
5052 #[async_test]
5053 async fn test_await_room_remote_echo_will_timeout_if_room_is_found_but_not_synced() {
5054 let server = MatrixMockServer::new().await;
5055 let client = server.client_builder().build().await;
5056
5057 server.mock_create_room().ok().mount().await;
5058
5059 let room = client
5061 .create_room(assign!(CreateRoomRequest::new(), {
5062 invite: vec![],
5063 is_direct: false,
5064 }))
5065 .await
5066 .unwrap();
5067
5068 timeout(Duration::from_secs(1), client.await_room_remote_echo(room.room_id()))
5070 .await
5071 .unwrap_err();
5072 }
5073
5074 #[async_test]
5075 async fn test_is_room_alias_available_if_alias_is_not_resolved() {
5076 let server = MatrixMockServer::new().await;
5077 let client = server.client_builder().build().await;
5078
5079 server.mock_room_directory_resolve_alias().not_found().expect(1).mount().await;
5080
5081 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5082 assert_matches!(ret, Ok(true));
5083 }
5084
5085 #[async_test]
5086 async fn test_is_room_alias_available_if_alias_is_resolved() {
5087 let server = MatrixMockServer::new().await;
5088 let client = server.client_builder().build().await;
5089
5090 server
5091 .mock_room_directory_resolve_alias()
5092 .ok("!some_room_id:matrix.org", Vec::new())
5093 .expect(1)
5094 .mount()
5095 .await;
5096
5097 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5098 assert_matches!(ret, Ok(false));
5099 }
5100
5101 #[async_test]
5102 async fn test_is_room_alias_available_if_error_found() {
5103 let server = MatrixMockServer::new().await;
5104 let client = server.client_builder().build().await;
5105
5106 server.mock_room_directory_resolve_alias().error500().expect(1).mount().await;
5107
5108 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5109 assert_matches!(ret, Err(_));
5110 }
5111
5112 #[async_test]
5113 async fn test_create_room_alias() {
5114 let server = MatrixMockServer::new().await;
5115 let client = server.client_builder().build().await;
5116
5117 server.mock_room_directory_create_room_alias().ok().expect(1).mount().await;
5118
5119 let ret = client
5120 .create_room_alias(
5121 room_alias_id!("#some_alias:matrix.org"),
5122 room_id!("!some_room:matrix.org"),
5123 )
5124 .await;
5125 assert_matches!(ret, Ok(()));
5126 }
5127
5128 #[async_test]
5129 async fn test_join_room_by_id_or_alias() {
5130 use wiremock::{
5131 Mock, ResponseTemplate,
5132 matchers::{method, path_regex},
5133 };
5134 let server = MatrixMockServer::new().await;
5135 let client = server.client_builder().build().await;
5136
5137 let target_room_id = room_id!("!some_id:matrix.org");
5138 let target_alias = room_alias_id!("#some_alias:matrix.org");
5139
5140 Mock::given(method("POST"))
5141 .and(path_regex("^/_matrix/client/v3/join/.*$"))
5142 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
5143 "room_id": target_room_id
5144 })))
5145 .mount(server.server())
5146 .await;
5147
5148 server
5149 .mock_room_directory_resolve_alias()
5150 .ok(target_room_id.as_str(), Vec::new())
5151 .mount()
5152 .await;
5153
5154 server.mock_room_join(target_room_id).ok().mount().await;
5155
5156 let ret = client.join_room_by_id_or_alias(target_alias.into(), &[]).await;
5157 assert!(ret.is_ok());
5158
5159 let ret = client.join_room_by_id_or_alias(target_room_id.into(), &[]).await;
5160 assert!(ret.is_ok());
5161 }
5162
5163 #[async_test]
5164 async fn test_room_preview_for_invited_room_hits_summary_endpoint() {
5165 let server = MatrixMockServer::new().await;
5166 let client = server.client_builder().build().await;
5167
5168 let room_id = room_id!("!a-room:matrix.org");
5169
5170 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5172
5173 let invited_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Invited);
5175
5176 let preview = client
5178 .get_room_preview(room_id.into(), Vec::new())
5179 .await
5180 .expect("Room preview should be retrieved");
5181
5182 assert_eq!(invited_room.room_id(), preview.room_id);
5183 }
5184
5185 #[async_test]
5186 async fn test_room_preview_for_left_room_hits_summary_endpoint() {
5187 let server = MatrixMockServer::new().await;
5188 let client = server.client_builder().build().await;
5189
5190 let room_id = room_id!("!a-room:matrix.org");
5191
5192 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5194
5195 let left_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Left);
5197
5198 let preview = client
5200 .get_room_preview(room_id.into(), Vec::new())
5201 .await
5202 .expect("Room preview should be retrieved");
5203
5204 assert_eq!(left_room.room_id(), preview.room_id);
5205 }
5206
5207 #[async_test]
5208 async fn test_room_preview_for_knocked_room_hits_summary_endpoint() {
5209 let server = MatrixMockServer::new().await;
5210 let client = server.client_builder().build().await;
5211
5212 let room_id = room_id!("!a-room:matrix.org");
5213
5214 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5216
5217 let knocked_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Knocked);
5219
5220 let preview = client
5222 .get_room_preview(room_id.into(), Vec::new())
5223 .await
5224 .expect("Room preview should be retrieved");
5225
5226 assert_eq!(knocked_room.room_id(), preview.room_id);
5227 }
5228
5229 #[async_test]
5230 async fn test_room_preview_for_joined_room_retrieves_local_room_info() {
5231 let server = MatrixMockServer::new().await;
5232 let client = server.client_builder().build().await;
5233
5234 let room_id = room_id!("!a-room:matrix.org");
5235
5236 server.mock_room_summary().ok(room_id).never().mount().await;
5238
5239 let joined_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Joined);
5241
5242 let preview = client
5244 .get_room_preview(room_id.into(), Vec::new())
5245 .await
5246 .expect("Room preview should be retrieved");
5247
5248 assert_eq!(joined_room.room_id(), preview.room_id);
5249 }
5250
5251 #[async_test]
5252 async fn test_media_preview_config() {
5253 let server = MatrixMockServer::new().await;
5254 let client = server.client_builder().build().await;
5255
5256 server
5257 .mock_sync()
5258 .ok_and_run(&client, |builder| {
5259 builder.add_custom_global_account_data(json!({
5260 "content": {
5261 "media_previews": "private",
5262 "invite_avatars": "off"
5263 },
5264 "type": "m.media_preview_config"
5265 }));
5266 })
5267 .await;
5268
5269 let (initial_value, stream) =
5270 client.account().observe_media_preview_config().await.unwrap();
5271
5272 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5273 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5274 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5275 pin_mut!(stream);
5276 assert_pending!(stream);
5277
5278 server
5279 .mock_sync()
5280 .ok_and_run(&client, |builder| {
5281 builder.add_custom_global_account_data(json!({
5282 "content": {
5283 "media_previews": "off",
5284 "invite_avatars": "on"
5285 },
5286 "type": "m.media_preview_config"
5287 }));
5288 })
5289 .await;
5290
5291 assert_next_matches!(
5292 stream,
5293 MediaPreviewConfigEventContent {
5294 media_previews: Some(MediaPreviews::Off),
5295 invite_avatars: Some(InviteAvatars::On),
5296 ..
5297 }
5298 );
5299 assert_pending!(stream);
5300 }
5301
5302 #[async_test]
5303 async fn test_unstable_media_preview_config() {
5304 let server = MatrixMockServer::new().await;
5305 let client = server.client_builder().build().await;
5306
5307 server
5308 .mock_sync()
5309 .ok_and_run(&client, |builder| {
5310 builder.add_custom_global_account_data(json!({
5311 "content": {
5312 "media_previews": "private",
5313 "invite_avatars": "off"
5314 },
5315 "type": "io.element.msc4278.media_preview_config"
5316 }));
5317 })
5318 .await;
5319
5320 let (initial_value, stream) =
5321 client.account().observe_media_preview_config().await.unwrap();
5322
5323 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5324 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5325 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5326 pin_mut!(stream);
5327 assert_pending!(stream);
5328
5329 server
5330 .mock_sync()
5331 .ok_and_run(&client, |builder| {
5332 builder.add_custom_global_account_data(json!({
5333 "content": {
5334 "media_previews": "off",
5335 "invite_avatars": "on"
5336 },
5337 "type": "io.element.msc4278.media_preview_config"
5338 }));
5339 })
5340 .await;
5341
5342 assert_next_matches!(
5343 stream,
5344 MediaPreviewConfigEventContent {
5345 media_previews: Some(MediaPreviews::Off),
5346 invite_avatars: Some(InviteAvatars::On),
5347 ..
5348 }
5349 );
5350 assert_pending!(stream);
5351 }
5352
5353 #[async_test]
5354 async fn test_media_preview_config_not_found() {
5355 let server = MatrixMockServer::new().await;
5356 let client = server.client_builder().build().await;
5357
5358 let (initial_value, _) = client.account().observe_media_preview_config().await.unwrap();
5359
5360 assert!(initial_value.is_none());
5361 }
5362
5363 #[async_test]
5364 async fn test_load_or_fetch_max_upload_size_with_auth_matrix_version() {
5365 let server = MatrixMockServer::new().await;
5368 let client = server.client_builder().build().await;
5369
5370 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5371
5372 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5373 client.load_or_fetch_max_upload_size().await.unwrap();
5374
5375 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5376 }
5377
5378 #[async_test]
5379 async fn test_load_or_fetch_max_upload_size_with_auth_stable_feature() {
5380 let server = MatrixMockServer::new().await;
5383 let client = server.client_builder().no_server_versions().build().await;
5384
5385 server
5386 .mock_versions()
5387 .with_versions(vec!["v1.7", "v1.8", "v1.9", "v1.10"])
5388 .with_feature("org.matrix.msc3916.stable", true)
5389 .ok()
5390 .named("versions")
5391 .expect(1)
5392 .mount()
5393 .await;
5394
5395 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5396
5397 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5398 client.load_or_fetch_max_upload_size().await.unwrap();
5399
5400 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5401 }
5402
5403 #[async_test]
5404 async fn test_load_or_fetch_max_upload_size_no_auth() {
5405 let server = MatrixMockServer::new().await;
5408 let client = server.client_builder().no_server_versions().build().await;
5409
5410 server
5411 .mock_versions()
5412 .with_versions(vec!["v1.1"])
5413 .ok()
5414 .named("versions")
5415 .expect(1)
5416 .mount()
5417 .await;
5418
5419 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5420
5421 server.mock_media_config().ok(uint!(2)).mock_once().mount().await;
5422 client.load_or_fetch_max_upload_size().await.unwrap();
5423
5424 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5425 }
5426
5427 #[async_test]
5428 async fn test_uploading_a_too_large_media_file() {
5429 let server = MatrixMockServer::new().await;
5430 let client = server.client_builder().build().await;
5431
5432 server.mock_authenticated_media_config().ok(uint!(1)).mock_once().mount().await;
5433 client.load_or_fetch_max_upload_size().await.unwrap();
5434 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(1));
5435
5436 let data = vec![1, 2];
5437 let upload_request =
5438 ruma::api::client::media::create_content::v3::Request::new(data.clone());
5439 let request = SendRequest {
5440 client: client.clone(),
5441 request: upload_request,
5442 config: None,
5443 send_progress: SharedObservable::new(TransmissionProgress::default()),
5444 };
5445 let media_request = SendMediaUploadRequest::new(request);
5446
5447 let error = media_request.await.err();
5448 assert_let!(Some(Error::Media(MediaError::MediaTooLargeToUpload { max, current })) = error);
5449 assert_eq!(max, uint!(1));
5450 assert_eq!(current, UInt::new_wrapping(data.len() as u64));
5451 }
5452
5453 #[async_test]
5454 async fn test_dont_ignore_timeout_on_first_sync() {
5455 let server = MatrixMockServer::new().await;
5456 let client = server.client_builder().build().await;
5457
5458 server
5459 .mock_sync()
5460 .timeout(Some(Duration::from_secs(30)))
5461 .ok(|_| {})
5462 .mock_once()
5463 .named("sync_with_timeout")
5464 .mount()
5465 .await;
5466
5467 let mut stream = Box::pin(client.sync_stream(SyncSettings::new()).await);
5469
5470 timeout(Duration::from_secs(1), async {
5471 stream.next().await.unwrap().unwrap();
5472 })
5473 .await
5474 .unwrap();
5475 }
5476
5477 #[async_test]
5478 async fn test_ignore_timeout_on_first_sync() {
5479 let server = MatrixMockServer::new().await;
5480 let client = server.client_builder().build().await;
5481
5482 server
5483 .mock_sync()
5484 .timeout(None)
5485 .ok(|_| {})
5486 .mock_once()
5487 .named("sync_no_timeout")
5488 .mount()
5489 .await;
5490 server
5491 .mock_sync()
5492 .timeout(Some(Duration::from_secs(30)))
5493 .ok(|_| {})
5494 .mock_once()
5495 .named("sync_with_timeout")
5496 .mount()
5497 .await;
5498
5499 let mut stream = Box::pin(
5501 client.sync_stream(SyncSettings::new().ignore_timeout_on_first_sync(true)).await,
5502 );
5503
5504 timeout(Duration::from_secs(1), async {
5505 stream.next().await.unwrap().unwrap();
5506 stream.next().await.unwrap().unwrap();
5507 })
5508 .await
5509 .unwrap();
5510 }
5511
5512 #[async_test]
5513 async fn test_get_dm_room_returns_the_room_we_have_with_this_user() {
5514 let server = MatrixMockServer::new().await;
5515 let client = server.client_builder().build().await;
5516 let user_id = user_id!("@invited:localhost");
5520
5521 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5523 let response = SyncResponseBuilder::default()
5524 .add_joined_room(JoinedRoomBuilder::default().add_state_event(f.member(user_id)))
5525 .add_global_account_data(
5526 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5527 )
5528 .build_sync_response();
5529 client.base_client().receive_sync_response(response).await.unwrap();
5530
5531 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5533 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5534 }
5535
5536 #[async_test]
5537 async fn test_get_dm_room_still_finds_room_where_participant_is_only_invited() {
5538 let server = MatrixMockServer::new().await;
5539 let client = server.client_builder().build().await;
5540 let user_id = user_id!("@invited:localhost");
5542
5543 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5545 let response = SyncResponseBuilder::default()
5546 .add_joined_room(
5547 JoinedRoomBuilder::default()
5548 .add_state_event(f.member(user_id).invited(user_id).display_name("example")),
5549 )
5550 .add_global_account_data(
5551 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5552 )
5553 .build_sync_response();
5554 client.base_client().receive_sync_response(response).await.unwrap();
5555
5556 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5558 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5559 }
5560
5561 #[async_test]
5562 async fn test_get_dm_room_still_finds_left_room() {
5563 let server = MatrixMockServer::new().await;
5567 let client = server.client_builder().build().await;
5568 let user_id = user_id!("@invited:localhost");
5572
5573 let f = EventFactory::new().sender(user_id);
5575 let response = SyncResponseBuilder::default()
5576 .add_joined_room(
5577 JoinedRoomBuilder::default().add_state_event(f.member(user_id).leave()),
5578 )
5579 .add_global_account_data(
5580 f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5581 )
5582 .build_sync_response();
5583 client.base_client().receive_sync_response(response).await.unwrap();
5584
5585 let found_room = client.get_dm_room(user_id).expect("DM not found!");
5587 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5588 }
5589
5590 #[async_test]
5591 async fn test_device_exists() {
5592 let server = MatrixMockServer::new().await;
5593 let client = server.client_builder().build().await;
5594
5595 server.mock_get_device().ok().expect(1).mount().await;
5596
5597 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(true));
5598 }
5599
5600 #[async_test]
5601 async fn test_device_exists_404() {
5602 let server = MatrixMockServer::new().await;
5603 let client = server.client_builder().build().await;
5604
5605 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(false));
5606 }
5607
5608 #[async_test]
5609 async fn test_device_exists_500() {
5610 let server = MatrixMockServer::new().await;
5611 let client = server.client_builder().build().await;
5612
5613 server.mock_get_device().error500().expect(1).mount().await;
5614
5615 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Err(_));
5616 }
5617
5618 #[async_test]
5619 async fn test_fetching_well_known_with_homeserver_url() {
5620 let server = MatrixMockServer::new().await;
5621 let client = server.client_builder().build().await;
5622 server.mock_well_known().ok().mount().await;
5623
5624 assert_matches!(client.fetch_client_well_known().await, Some(_));
5625 }
5626
5627 #[async_test]
5628 async fn test_fetching_well_known_with_server_name() {
5629 let server = MatrixMockServer::new().await;
5630 let server_name = ServerName::parse(server.server().address().to_string()).unwrap();
5631
5632 server.mock_well_known().ok().mount().await;
5633
5634 let client = MockClientBuilder::new(None)
5635 .on_builder(|builder| builder.insecure_server_name_no_tls(&server_name))
5636 .build()
5637 .await;
5638
5639 assert_matches!(client.fetch_client_well_known().await, Some(_));
5640 }
5641
5642 #[async_test]
5643 async fn test_fetching_well_known_with_domain_part_of_user_id() {
5644 let server = MatrixMockServer::new().await;
5645 server.mock_well_known().ok().mount().await;
5646
5647 let user_id =
5648 UserId::parse(format!("@user:{}", server.server().address())).expect("Invalid user id");
5649 let client = MockClientBuilder::new(None)
5650 .logged_in_with_token("A_TOKEN".to_owned(), user_id, owned_device_id!("ABCDEF"))
5651 .build()
5652 .await;
5653
5654 assert_matches!(client.fetch_client_well_known().await, Some(_));
5655 }
5656
5657 #[cfg(feature = "e2e-encryption")]
5658 #[async_test]
5659 async fn test_syncing_one_time_key_counts_updates() -> Result<()> {
5660 use wiremock::ResponseTemplate;
5661
5662 macro_rules! assert_key_count {
5663 ($client: ident, $count:literal) => {{
5664 let machine = $client.olm_machine().await;
5665 let uploaded_key_counts =
5666 machine.as_ref().unwrap().uploaded_key_count().await.unwrap();
5667 assert_eq!(uploaded_key_counts, $count)
5668 }};
5669 }
5670
5671 macro_rules! sync_with_key_count {
5672 ($client: ident, $server:ident, $count:literal) => {
5673 let count = Some($count);
5674 sync_with_key_count!($client, $server, count);
5675 };
5676 ($client: ident, $server:ident, $count:ident) => {{
5677 use rand::RngExt as _;
5678
5679 let next_batch: String = rand::rng()
5680 .sample_iter(&rand::distr::Alphanumeric)
5681 .take(16)
5682 .map(char::from)
5683 .collect();
5684
5685 let count: Option<u32> = $count;
5686
5687 let template = if let Some(count) = count {
5688 ResponseTemplate::new(200).set_body_json(json!({
5689 "next_batch": next_batch,
5690 "rooms": {"leave": {}, "join": {}, "invite": {}},
5691 "device_lists": {
5692 "changed": [],
5693 "left": [],
5694 },
5695 "device_one_time_keys_count": {
5696 "signed_curve25519": count
5697 },
5698 }))
5699 } else {
5700 ResponseTemplate::new(200).set_body_json(json!({
5701 "next_batch": next_batch,
5702 "rooms": {"leave": {}, "join": {}, "invite": {}},
5703 "device_lists": {
5704 "changed": [],
5705 "left": [],
5706 },
5707 "device_one_time_keys_count": {},
5708 }))
5709 };
5710
5711 let _sync_mock_guard = $server.mock_sync().respond_with(template).mount_as_scoped().await;
5712 $client.sync_once(Default::default()).await?;
5713 }}
5714 }
5715
5716 let server = MatrixMockServer::new().await;
5717 let client = server.client_builder().build().await;
5718
5719 server.mock_upload_keys().ok_with_signed_curve_key_count(50).mock_once().mount().await;
5720
5721 assert_key_count!(client, 0);
5723
5724 sync_with_key_count!(client, server, 50);
5726 assert_key_count!(client, 50);
5727
5728 sync_with_key_count!(client, server, 10);
5730 assert_key_count!(client, 10);
5731
5732 sync_with_key_count!(client, server, None);
5734 assert_key_count!(client, 0);
5735
5736 Ok(())
5737 }
5738}