1#[cfg(feature = "unstable-msc4536")]
19use std::sync::OnceLock as StdOnceLock;
20use std::{
21 collections::{BTreeMap, BTreeSet, btree_map},
22 fmt::{self, Debug},
23 future::{Future, ready},
24 pin::Pin,
25 sync::{Arc, Mutex as StdMutex, RwLock as StdRwLock, Weak},
26 time::Duration,
27};
28
29use async_stream::stream;
30use eyeball::{SharedObservable, Subscriber};
31use eyeball_im::{Vector, VectorDiff};
32use futures_core::Stream;
33use futures_util::{StreamExt, future::OptionFuture, join};
34#[cfg(feature = "e2e-encryption")]
35use matrix_sdk_base::crypto::{
36 DecryptionSettings, store::LockableCryptoStore, store::types::RoomPendingKeyBundleDetails,
37 vodozemac::olm,
38};
39use matrix_sdk_base::{
40 BaseClient, DmRoomDefinition, RoomInfoNotableUpdate, RoomState, RoomStateFilter,
41 SendOutsideWasm, SessionMeta, StateStoreDataKey, StateStoreDataValue, StoreError,
42 SyncOutsideWasm, ThreadingSupport,
43 deserialized_responses::EncryptionInfo,
44 event_cache::store::EventCacheStoreLock,
45 media::store::MediaStoreLock,
46 store::{DynStateStore, RoomLoadSettings, SupportedVersionsResponse, WellKnownResponse},
47 sync::{Notification, RoomUpdates},
48 task_monitor::TaskMonitor,
49};
50use matrix_sdk_common::{cross_process_lock::CrossProcessLockConfig, sleep::sleep, ttl::TtlValue};
51#[cfg(feature = "e2e-encryption")]
52use ruma::events::{InitialStateEvent, room::encryption::RoomEncryptionEventContent};
53use ruma::{
54 DeviceId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
55 OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, ServerName, UInt, UserId,
56 api::{
57 FeatureFlag, MatrixVersion, Metadata, OutgoingRequest, SupportedVersions,
58 client::{
59 account::whoami,
60 alias::{create_alias, delete_alias, get_alias},
61 authenticated_media,
62 delayed_events::update_delayed_event,
63 device::{self, delete_devices, get_devices, update_device},
64 directory::{get_public_rooms, get_public_rooms_filtered},
65 discovery::{discover_homeserver, get_supported_versions},
66 filter::{FilterDefinition, create_filter::v3::Request as FilterUploadRequest},
67 knock::knock_room,
68 media,
69 membership::{join_room_by_id, join_room_by_id_or_alias},
70 presence::set_presence as set_presence_status,
71 retention::get_retention_configuration,
72 room::create_room,
73 rtc::{RtcTransport, transports},
74 session::login::v3::DiscoveryInfo,
75 sync::sync_events,
76 threads::get_thread_subscriptions_changes,
77 uiaa,
78 user_directory::search_users,
79 },
80 error::{ErrorKind, FromHttpResponseError, UnknownTokenErrorData},
81 path_builder::PathBuilder,
82 },
83 assign,
84 events::{
85 AnyToDeviceEvent, ToDeviceEventType, beacon_info::OriginalSyncBeaconInfoEvent,
86 direct::DirectUserIdentifier,
87 },
88 presence::PresenceState,
89 push::Ruleset,
90 serde::Raw,
91 time::Instant,
92};
93use serde::de::DeserializeOwned;
94use tokio::sync::{Mutex, OnceCell, RwLock, RwLockReadGuard, broadcast, mpsc::unbounded_channel};
95use tracing::{Instrument, Span, debug, error, info, instrument, trace, warn};
96use url::Url;
97
98use self::{
99 caches::{Cache, CachedValue, ClientCaches},
100 futures::SendRequest,
101};
102#[cfg(feature = "unstable-msc4536")]
103use crate::client::global_profiles::GlobalProfileFetcher;
104use crate::{
105 Account, AuthApi, AuthSession, Error, HttpError, Media, Pusher, RefreshTokenError, Result,
106 Room, SessionTokens, TransmissionProgress,
107 authentication::{
108 AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback, matrix::MatrixAuth,
109 oauth::OAuth,
110 },
111 client::{
112 homeserver_capabilities::HomeserverCapabilities,
113 thread_subscriptions::ThreadSubscriptionCatchup,
114 },
115 config::{RequestConfig, SyncToken},
116 deduplicating_handler::DeduplicatingHandler,
117 error::HttpResult,
118 event_cache::EventCache,
119 event_handler::{
120 EventHandler, EventHandlerContext, EventHandlerDropGuard, EventHandlerHandle,
121 EventHandlerStore, ObservableEventHandler, SyncEvent,
122 },
123 http_client::{HttpClient, SupportedAuthScheme, SupportedPathBuilder},
124 latest_events::LatestEvents,
125 live_locations_observer::BeaconInfoUpdate,
126 media::{MediaError, MediaFetcher},
127 notification_settings::NotificationSettings,
128 room::RoomMember,
129 room_preview::RoomPreview,
130 send_queue::{SendQueue, SendQueueData},
131 sliding_sync::Version as SlidingSyncVersion,
132 sync::{RoomUpdate, SyncResponse},
133};
134#[cfg(feature = "e2e-encryption")]
135use crate::{
136 cross_process_lock::CrossProcessLock,
137 encryption::{
138 DuplicateOneTimeKeyErrorMessage, Encryption, EncryptionData, EncryptionSettings,
139 VerificationState,
140 },
141};
142
143mod builder;
144pub(crate) mod caches;
145pub(crate) mod futures;
146#[cfg(feature = "unstable-msc4536")]
147pub(crate) mod global_profiles;
148pub(crate) mod homeserver_capabilities;
149pub(crate) mod thread_subscriptions;
150
151pub use self::builder::{ClientBuildError, ClientBuilder, sanitize_server_name};
152#[cfg(feature = "experimental-search")]
153use crate::search_index::SearchIndex;
154
155#[cfg(not(target_family = "wasm"))]
156type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()> + Send>>;
157#[cfg(target_family = "wasm")]
158type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()>>>;
159
160#[cfg(not(target_family = "wasm"))]
161type NotificationHandlerFn =
162 Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut + Send + Sync>;
163#[cfg(target_family = "wasm")]
164type NotificationHandlerFn = Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut>;
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub enum LoopCtrl {
174 Continue,
176 Break,
178}
179
180#[derive(Debug, Clone, PartialEq)]
182pub enum SessionChange {
183 UnknownToken(UnknownTokenErrorData),
185 TokensRefreshed,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
192pub struct ServerVendorInfo {
193 pub server_name: String,
195 pub version: String,
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, Hash)]
202#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
203pub struct TileServerInfo {
204 pub map_style_url: String,
208}
209
210impl From<discover_homeserver::TileServerInfo> for TileServerInfo {
211 fn from(value: discover_homeserver::TileServerInfo) -> Self {
212 Self { map_style_url: value.map_style_url }
213 }
214}
215
216#[derive(Clone)]
220pub struct Client {
221 pub(crate) inner: Arc<ClientInner>,
222}
223
224#[derive(Default)]
225pub(crate) struct ClientLocks {
226 pub(crate) mark_as_dm_lock: Mutex<()>,
230
231 #[cfg(feature = "e2e-encryption")]
237 pub(crate) open_secret_store_lock: Mutex<()>,
238
239 #[cfg(feature = "e2e-encryption")]
246 pub(crate) store_secret_lock: Mutex<()>,
247
248 #[cfg(feature = "e2e-encryption")]
250 pub(crate) backup_modify_lock: Mutex<()>,
251
252 #[cfg(feature = "e2e-encryption")]
255 pub(crate) backup_upload_lock: Mutex<()>,
256
257 #[cfg(feature = "e2e-encryption")]
260 pub(crate) group_session_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
261
262 #[cfg(feature = "e2e-encryption")]
264 pub(crate) key_claim_lock: Mutex<()>,
265
266 pub(crate) members_request_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
269
270 pub(crate) encryption_state_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
273
274 pub(crate) read_receipt_deduplicated_handler: DeduplicatingHandler<(String, OwnedEventId)>,
277
278 #[cfg(feature = "e2e-encryption")]
279 pub(crate) cross_process_crypto_store_lock: OnceCell<CrossProcessLock<LockableCryptoStore>>,
280
281 #[cfg(feature = "e2e-encryption")]
299 pub(crate) crypto_store_generation: Arc<Mutex<Option<u64>>>,
300}
301
302pub(crate) struct ClientInner {
303 pub(crate) auth_ctx: Arc<AuthCtx>,
305
306 server: StdRwLock<Option<Url>>,
318
319 homeserver: StdRwLock<Url>,
323
324 sliding_sync_version: StdRwLock<SlidingSyncVersion>,
326
327 sync_presence: Arc<StdRwLock<PresenceState>>,
332
333 pub(crate) http_client: HttpClient,
335
336 pub(super) base_client: BaseClient,
338
339 pub(crate) caches: ClientCaches,
341
342 pub(crate) locks: ClientLocks,
346
347 cross_process_lock_config: CrossProcessLockConfig,
356
357 pub(crate) typing_notice_times: StdRwLock<BTreeMap<OwnedRoomId, Instant>>,
360
361 pub(crate) event_handlers: EventHandlerStore,
363
364 notification_handlers: RwLock<Vec<NotificationHandlerFn>>,
366
367 pub(crate) room_update_channels: StdMutex<BTreeMap<OwnedRoomId, broadcast::Sender<RoomUpdate>>>,
369
370 pub(crate) room_updates_sender: broadcast::Sender<RoomUpdates>,
373
374 respect_login_well_known: bool,
377
378 well_known_lookup_disabled: StdRwLock<bool>,
382
383 pub(crate) sync_beat: event_listener::Event,
389
390 pub(crate) event_cache: OnceCell<EventCache>,
392
393 #[cfg(feature = "e2e-encryption")]
395 pub(crate) e2ee: EncryptionData,
396
397 #[cfg(feature = "e2e-encryption")]
399 pub(crate) verification_state: SharedObservable<VerificationState>,
400
401 #[cfg(feature = "e2e-encryption")]
406 pub(crate) enable_share_history_on_invite: bool,
407
408 pub(crate) send_queue_data: Arc<SendQueueData>,
412
413 pub(crate) server_max_upload_size: Mutex<OnceCell<UInt>>,
416
417 latest_events: OnceCell<LatestEvents>,
421
422 thread_subscription_catchup: OnceCell<Arc<ThreadSubscriptionCatchup>>,
425
426 #[cfg(feature = "unstable-msc4536")]
429 global_profile_fetcher: StdOnceLock<GlobalProfileFetcher>,
430
431 #[cfg(feature = "experimental-search")]
432 search_index: SearchIndex,
434
435 pub(crate) task_monitor: TaskMonitor,
437
438 #[cfg(feature = "e2e-encryption")]
441 pub(crate) duplicate_key_upload_error_sender:
442 broadcast::Sender<Option<DuplicateOneTimeKeyErrorMessage>>,
443
444 pub(crate) media_fetcher: RwLock<Arc<dyn MediaFetcher>>,
445
446 #[cfg(feature = "unstable-msc4426")]
454 pub(crate) automatic_call_status:
455 StdMutex<Option<crate::automatic_call_status::AutomaticCallStatus>>,
456}
457
458impl ClientInner {
459 #[allow(clippy::too_many_arguments)]
465 async fn new(
466 auth_ctx: Arc<AuthCtx>,
467 server: Option<Url>,
468 homeserver: Url,
469 sliding_sync_version: SlidingSyncVersion,
470 sync_presence: Arc<StdRwLock<PresenceState>>,
471 http_client: HttpClient,
472 base_client: BaseClient,
473 supported_versions: CachedValue<TtlValue<SupportedVersions>>,
474 well_known: CachedValue<TtlValue<Option<WellKnownResponse>>>,
475 respect_login_well_known: bool,
476 well_known_lookup_disabled: bool,
477 enable_automatic_back_pagination: bool,
478 enable_event_cache: bool,
479 send_queue: Arc<SendQueueData>,
480 #[cfg(feature = "e2e-encryption")] encryption_settings: EncryptionSettings,
481 #[cfg(feature = "e2e-encryption")] enable_share_history_on_invite: bool,
482 cross_process_lock_config: CrossProcessLockConfig,
483 #[cfg(feature = "experimental-search")] search_index_handler: SearchIndex,
484 thread_subscription_catchup: OnceCell<Arc<ThreadSubscriptionCatchup>>,
485 media_fetcher: Arc<dyn MediaFetcher>,
486 ) -> Arc<Self> {
487 let caches = ClientCaches {
488 supported_versions: Cache::with_value(supported_versions),
489 well_known: Cache::with_value(well_known),
490 server_metadata: Cache::new(),
491 homeserver_capabilities: Cache::new(),
492 rtc_transports: Cache::new(),
493 };
494
495 let client = Self {
496 server: StdRwLock::new(server),
497 homeserver: StdRwLock::new(homeserver),
498 auth_ctx,
499 sliding_sync_version: StdRwLock::new(sliding_sync_version),
500 sync_presence,
501 http_client,
502 base_client,
503 caches,
504 locks: Default::default(),
505 cross_process_lock_config,
506 typing_notice_times: Default::default(),
507 event_handlers: Default::default(),
508 notification_handlers: Default::default(),
509 room_update_channels: Default::default(),
510 room_updates_sender: broadcast::Sender::new(32),
513 respect_login_well_known,
514 well_known_lookup_disabled: StdRwLock::new(well_known_lookup_disabled),
515 sync_beat: event_listener::Event::new(),
516 event_cache: OnceCell::new(),
517 send_queue_data: send_queue,
518 latest_events: OnceCell::new(),
519 #[cfg(feature = "e2e-encryption")]
520 e2ee: EncryptionData::new(encryption_settings),
521 #[cfg(feature = "e2e-encryption")]
522 verification_state: SharedObservable::new(VerificationState::Unknown),
523 #[cfg(feature = "e2e-encryption")]
524 enable_share_history_on_invite,
525 server_max_upload_size: Mutex::new(OnceCell::new()),
526 #[cfg(feature = "experimental-search")]
527 search_index: search_index_handler,
528 thread_subscription_catchup,
529 #[cfg(feature = "unstable-msc4536")]
530 global_profile_fetcher: StdOnceLock::new(),
531 task_monitor: TaskMonitor::new(),
532 #[cfg(feature = "e2e-encryption")]
533 duplicate_key_upload_error_sender: broadcast::channel(1).0,
534 media_fetcher: RwLock::new(media_fetcher),
535 #[cfg(feature = "unstable-msc4426")]
536 automatic_call_status: StdMutex::new(None),
537 };
538
539 #[allow(clippy::let_and_return)]
540 let client = Arc::new(client);
541
542 #[cfg(feature = "e2e-encryption")]
543 client.e2ee.initialize_tasks(&client);
544
545 let init_event_cache = OptionFuture::from(enable_event_cache.then(|| {
546 client.event_cache.get_or_init(|| async {
547 EventCache::new(
548 &client,
549 client.base_client.event_cache_store().clone(),
550 enable_automatic_back_pagination,
551 )
552 })
553 }));
554
555 let init_thread_subscription_catchup = client
556 .thread_subscription_catchup
557 .get_or_init(|| ThreadSubscriptionCatchup::new(Client { inner: client.clone() }));
558
559 let _ = join!(init_event_cache, init_thread_subscription_catchup);
560
561 client
562 }
563}
564
565#[cfg(not(tarpaulin_include))]
566impl Debug for Client {
567 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
568 write!(fmt, "Client")
569 }
570}
571
572impl Client {
573 pub async fn new(homeserver_url: Url) -> Result<Self, ClientBuildError> {
579 Self::builder().homeserver_url(homeserver_url).build().await
580 }
581
582 #[cfg_attr(
599 feature = "e2e-encryption",
600 doc = " - `custom_account` - A custom
601 [`matrix_sdk_base::crypto::vodozemac::olm::Account`] to be used for the
602 identity and one-time keys of this [`BaseClient`]. If no account is
603 provided, a new default one or one from the store will be used. If an
604 account is provided and one already exists in the store for this
605 [`UserId`]/[`DeviceId`] combination, an error will be raised. This is
606 useful if one wishes to create identity keys before knowing the
607 user/device IDs, e.g., to use the identity key as the device ID."
608 )]
609 pub(crate) async fn activate(
616 &self,
617 session_meta: SessionMeta,
618 room_load_settings: RoomLoadSettings,
619 #[cfg(feature = "e2e-encryption")] custom_account: Option<olm::Account>,
620 ) -> Result<()> {
621 self.inner
623 .base_client
624 .activate(
625 session_meta,
626 room_load_settings,
627 #[cfg(feature = "e2e-encryption")]
628 custom_account,
629 )
630 .await?;
631
632 #[cfg(feature = "e2e-encryption")]
634 if let Some(event_cache) = self.event_cache() {
635 event_cache.initialise_redecryptor()?;
636 }
637
638 Ok(())
639 }
640
641 pub fn downgrade(&self) -> WeakClient {
646 WeakClient::from_inner(&self.inner)
647 }
648
649 pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<Vec<OwnedUserId>> {
652 self.inner.base_client.subscribe_to_ignore_user_list_changes()
653 }
654
655 pub fn builder() -> ClientBuilder {
657 ClientBuilder::new()
658 }
659
660 pub(crate) fn base_client(&self) -> &BaseClient {
661 &self.inner.base_client
662 }
663
664 pub fn http_client(&self) -> &reqwest::Client {
666 &self.inner.http_client.inner
667 }
668
669 pub(crate) fn locks(&self) -> &ClientLocks {
670 &self.inner.locks
671 }
672
673 pub(crate) fn auth_ctx(&self) -> &AuthCtx {
674 &self.inner.auth_ctx
675 }
676
677 pub fn cross_process_lock_config(&self) -> &CrossProcessLockConfig {
684 &self.inner.cross_process_lock_config
685 }
686
687 fn set_homeserver(&self, homeserver_url: Url) {
695 let mut homeserver = self.inner.homeserver.write().unwrap();
696 let mut server = self.inner.server.write().unwrap();
697
698 *homeserver = homeserver_url;
699 *server = None;
700 }
701
702 #[cfg(feature = "e2e-encryption")]
704 pub(crate) async fn switch_homeserver_and_re_resolve_well_known(
705 &self,
706 homeserver_url: Url,
707 ) -> Result<()> {
708 self.set_homeserver(homeserver_url);
709 self.reset_well_known().await?;
710 if let Some(well_known) = self.well_known().await {
711 self.set_homeserver(Url::parse(&well_known.homeserver.base_url)?);
712 }
713 Ok(())
714 }
715
716 pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
719 HomeserverCapabilities::new(self.clone())
720 }
721
722 #[cfg(feature = "federation-api")]
744 pub async fn server_vendor_info(
745 &self,
746 request_config: Option<RequestConfig>,
747 ) -> HttpResult<ServerVendorInfo> {
748 use ruma::api::federation::discovery::get_server_version;
749
750 let res = self
751 .send_inner(get_server_version::v1::Request::new(), request_config, Default::default())
752 .await?;
753
754 let server = res.server.unwrap_or_default();
756 let server_name_str = server.name.unwrap_or_else(|| "unknown".to_owned());
757 let version = server.version.unwrap_or_else(|| "unknown".to_owned());
758
759 Ok(ServerVendorInfo { server_name: server_name_str, version })
760 }
761
762 pub fn request_config(&self) -> RequestConfig {
772 self.inner.http_client.request_config
773 }
774
775 pub fn is_active(&self) -> bool {
785 self.inner.base_client.is_active()
786 }
787
788 pub fn server(&self) -> Option<Url> {
792 self.inner.server.read().unwrap().clone()
793 }
794
795 pub fn homeserver(&self) -> Url {
797 self.inner.homeserver.read().unwrap().clone()
798 }
799
800 pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
802 self.inner.sliding_sync_version.read().unwrap().clone()
803 }
804
805 pub fn set_sliding_sync_version(&self, version: SlidingSyncVersion) {
807 let mut lock = self.inner.sliding_sync_version.write().unwrap();
808 *lock = version;
809 }
810
811 pub(crate) fn sync_presence(&self) -> PresenceState {
813 self.inner.sync_presence.read().unwrap().clone()
814 }
815
816 pub fn session_meta(&self) -> Option<&SessionMeta> {
822 self.base_client().session_meta()
823 }
824
825 pub fn room_info_notable_update_receiver(&self) -> broadcast::Receiver<RoomInfoNotableUpdate> {
828 self.base_client().room_info_notable_update_receiver()
829 }
830
831 pub fn subscribe_to_global_profile_updates(
837 &self,
838 ) -> broadcast::Receiver<BTreeSet<OwnedUserId>> {
839 self.base_client().subscribe_to_global_profile_updates()
840 }
841
842 pub fn subscribe_to_own_profile(
853 &self,
854 ) -> Result<impl Stream<Item = ruma::profile::UserProfile> + use<>> {
855 let own_user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
856 let mut updates = self.subscribe_to_global_profile_updates();
857 let client = self.clone();
858
859 Ok(async_stream::stream! {
860 match client.state_store().get_global_profile(&own_user_id).await {
862 Ok(profile) => yield profile.unwrap_or_default(),
863 Err(error) => error!(?error, "Failed to load the stored global profile"),
864 }
865
866 while let Ok(updated_user_ids) = updates.recv().await {
867 if !updated_user_ids.contains(&own_user_id) {
868 continue;
869 }
870
871 match client.state_store().get_global_profile(&own_user_id).await {
872 Ok(profile) => yield profile.unwrap_or_default(),
873 Err(error) => error!(?error, "Failed to load the updated global profile"),
874 }
875 }
876 })
877 }
878
879 pub async fn search_users(
889 &self,
890 search_term: &str,
891 limit: u64,
892 ) -> HttpResult<search_users::v3::Response> {
893 let mut request = search_users::v3::Request::new(search_term.to_owned());
894
895 if let Some(limit) = UInt::new(limit) {
896 request.limit = limit;
897 }
898
899 self.send(request).await
900 }
901
902 pub fn user_id(&self) -> Option<&UserId> {
904 self.session_meta().map(|s| s.user_id.as_ref())
905 }
906
907 pub fn device_id(&self) -> Option<&DeviceId> {
909 self.session_meta().map(|s| s.device_id.as_ref())
910 }
911
912 pub fn access_token(&self) -> Option<String> {
916 self.auth_ctx().access_token()
917 }
918
919 pub async fn set_presence(
927 &self,
928 presence: PresenceState,
929 status_msg: Option<String>,
930 immediate: bool,
931 ) -> Result<()> {
932 *self.inner.sync_presence.write().unwrap() = presence.clone();
933
934 if !immediate {
935 return Ok(());
936 }
937
938 let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
939 let mut request = set_presence_status::v3::Request::new(user_id, presence);
940 request.status_msg = status_msg;
941
942 self.send(request).await?;
943
944 Ok(())
945 }
946
947 pub fn session_tokens(&self) -> Option<SessionTokens> {
955 self.auth_ctx().session_tokens()
956 }
957
958 pub fn auth_api(&self) -> Option<AuthApi> {
962 match self.auth_ctx().auth_data.get()? {
963 AuthData::Matrix => Some(AuthApi::Matrix(self.matrix_auth())),
964 AuthData::OAuth(_) => Some(AuthApi::OAuth(self.oauth())),
965 }
966 }
967
968 pub fn session(&self) -> Option<AuthSession> {
975 match self.auth_api()? {
976 AuthApi::Matrix(api) => api.session().map(Into::into),
977 AuthApi::OAuth(api) => api.full_session().map(Into::into),
978 }
979 }
980
981 pub fn state_store(&self) -> &DynStateStore {
983 self.base_client().state_store()
984 }
985
986 pub fn event_cache_store(&self) -> &EventCacheStoreLock {
988 self.base_client().event_cache_store()
989 }
990
991 pub fn media_store(&self) -> &MediaStoreLock {
993 self.base_client().media_store()
994 }
995
996 pub fn matrix_auth(&self) -> MatrixAuth {
998 MatrixAuth::new(self.clone())
999 }
1000
1001 pub fn account(&self) -> Account {
1003 Account::new(self.clone())
1004 }
1005
1006 #[cfg(feature = "e2e-encryption")]
1008 pub fn encryption(&self) -> Encryption {
1009 Encryption::new(self.clone())
1010 }
1011
1012 pub fn media(&self) -> Media {
1014 Media::new(self.clone())
1015 }
1016
1017 pub fn pusher(&self) -> Pusher {
1019 Pusher::new(self.clone())
1020 }
1021
1022 pub fn oauth(&self) -> OAuth {
1024 OAuth::new(self.clone())
1025 }
1026
1027 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
1158 where
1159 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
1160 H: EventHandler<Ev, Ctx>,
1161 {
1162 self.add_event_handler_impl(handler, None)
1163 }
1164
1165 pub fn add_room_event_handler<Ev, Ctx, H>(
1176 &self,
1177 room_id: &RoomId,
1178 handler: H,
1179 ) -> EventHandlerHandle
1180 where
1181 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
1182 H: EventHandler<Ev, Ctx>,
1183 {
1184 self.add_event_handler_impl(handler, Some(room_id.to_owned()))
1185 }
1186
1187 pub fn observe_events<Ev, Ctx>(&self) -> ObservableEventHandler<(Ev, Ctx)>
1261 where
1262 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1263 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1264 {
1265 self.observe_room_events_impl(None)
1266 }
1267
1268 pub fn observe_room_events<Ev, Ctx>(
1278 &self,
1279 room_id: &RoomId,
1280 ) -> ObservableEventHandler<(Ev, Ctx)>
1281 where
1282 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1283 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1284 {
1285 self.observe_room_events_impl(Some(room_id.to_owned()))
1286 }
1287
1288 fn observe_room_events_impl<Ev, Ctx>(
1291 &self,
1292 room_id: Option<OwnedRoomId>,
1293 ) -> ObservableEventHandler<(Ev, Ctx)>
1294 where
1295 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1296 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1297 {
1298 let shared_observable = SharedObservable::new(None);
1301
1302 ObservableEventHandler::new(
1303 shared_observable.clone(),
1304 self.event_handler_drop_guard(self.add_event_handler_impl(
1305 move |event: Ev, context: Ctx| {
1306 shared_observable.set(Some((event, context)));
1307
1308 ready(())
1309 },
1310 room_id,
1311 )),
1312 )
1313 }
1314
1315 pub fn observe_own_beacon_info_updates(
1321 &self,
1322 ) -> Result<impl Stream<Item = BeaconInfoUpdate> + use<>> {
1323 let observer = self.observe_events::<OriginalSyncBeaconInfoEvent, Room>();
1324 let mut stream = observer.subscribe();
1325 let own_user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
1326 Ok(async_stream::stream! {
1327 let _observer = observer;
1328
1329 while let Some((event, room)) = stream.next().await {
1330 if event.state_key != own_user_id {
1331 continue;
1332 }
1333 yield BeaconInfoUpdate {
1334 room_id: room.room_id().to_owned(),
1335 event_id: event.event_id,
1336 content: event.content,
1337 };
1338 }
1339 })
1340 }
1341
1342 pub fn remove_event_handler(&self, handle: EventHandlerHandle) {
1400 self.inner.event_handlers.remove(handle);
1401 }
1402
1403 pub fn event_handler_drop_guard(&self, handle: EventHandlerHandle) -> EventHandlerDropGuard {
1408 EventHandlerDropGuard::new(handle, self.clone())
1409 }
1410
1411 pub fn add_event_handler_context<T>(&self, ctx: T)
1452 where
1453 T: Clone + Send + Sync + 'static,
1454 {
1455 self.inner.event_handlers.add_context(ctx);
1456 }
1457
1458 pub async fn register_notification_handler<H, Fut>(&self, handler: H) -> &Self
1464 where
1465 H: Fn(Notification, Room, Client) -> Fut + SendOutsideWasm + SyncOutsideWasm + 'static,
1466 Fut: Future<Output = ()> + SendOutsideWasm + 'static,
1467 {
1468 self.inner.notification_handlers.write().await.push(Box::new(
1469 move |notification, room, client| Box::pin((handler)(notification, room, client)),
1470 ));
1471
1472 self
1473 }
1474
1475 pub fn subscribe_to_room_updates(&self, room_id: &RoomId) -> broadcast::Receiver<RoomUpdate> {
1480 match self.inner.room_update_channels.lock().unwrap().entry(room_id.to_owned()) {
1481 btree_map::Entry::Vacant(entry) => {
1482 let (tx, rx) = broadcast::channel(8);
1483 entry.insert(tx);
1484 rx
1485 }
1486 btree_map::Entry::Occupied(entry) => entry.get().subscribe(),
1487 }
1488 }
1489
1490 pub fn subscribe_to_all_room_updates(&self) -> broadcast::Receiver<RoomUpdates> {
1493 self.inner.room_updates_sender.subscribe()
1494 }
1495
1496 pub fn subscribe_to_custom_to_device_messages(
1534 &self,
1535 event_types: Vec<ToDeviceEventType>,
1536 ) -> impl Stream<Item = ToDeviceMessage> + use<> {
1537 let (sender, mut receiver) = unbounded_channel();
1538
1539 let handle = self.add_event_handler(
1543 move |raw: Raw<AnyToDeviceEvent>, encryption_info: Option<EncryptionInfo>| {
1544 let Ok(Some(event_type)) = raw.get_field::<ToDeviceEventType>("type") else {
1545 trace!("Ignoring a to-device message without a type");
1546 return ready(());
1547 };
1548
1549 if is_internal_to_device_type(&event_type) {
1550 trace!(%event_type, "Not forwarding an internal to-device message");
1551 } else if event_types.is_empty() || event_types.contains(&event_type) {
1552 let _ = sender.send(ToDeviceMessage { raw, encryption_info });
1555 }
1556
1557 ready(())
1558 },
1559 );
1560
1561 let drop_guard = self.event_handler_drop_guard(handle);
1562
1563 stream! {
1564 let _drop_guard = drop_guard;
1566
1567 while let Some(message) = receiver.recv().await {
1568 yield message;
1569 }
1570 }
1571 }
1572
1573 pub(crate) async fn notification_handlers(
1574 &self,
1575 ) -> RwLockReadGuard<'_, Vec<NotificationHandlerFn>> {
1576 self.inner.notification_handlers.read().await
1577 }
1578
1579 pub fn rooms(&self) -> Vec<Room> {
1583 self.base_client().rooms().into_iter().map(|room| Room::new(self.clone(), room)).collect()
1584 }
1585
1586 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
1588 self.base_client()
1589 .rooms_filtered(filter)
1590 .into_iter()
1591 .map(|room| Room::new(self.clone(), room))
1592 .collect()
1593 }
1594
1595 pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + '_) {
1597 let (rooms, stream) = self.base_client().rooms_stream();
1598
1599 let map_room = |room| Room::new(self.clone(), room);
1600
1601 (
1602 rooms.into_iter().map(map_room).collect(),
1603 stream.map(move |diffs| diffs.into_iter().map(|diff| diff.map(map_room)).collect()),
1604 )
1605 }
1606
1607 pub fn joined_rooms(&self) -> Vec<Room> {
1609 self.rooms_filtered(RoomStateFilter::JOINED)
1610 }
1611
1612 pub fn invited_rooms(&self) -> Vec<Room> {
1614 self.rooms_filtered(RoomStateFilter::INVITED)
1615 }
1616
1617 pub fn left_rooms(&self) -> Vec<Room> {
1619 self.rooms_filtered(RoomStateFilter::LEFT)
1620 }
1621
1622 pub fn joined_space_rooms(&self) -> Vec<Room> {
1624 self.base_client()
1625 .rooms_filtered(RoomStateFilter::JOINED)
1626 .into_iter()
1627 .flat_map(|room| room.is_space().then_some(Room::new(self.clone(), room)))
1628 .collect()
1629 }
1630
1631 pub fn total_unread_notifications(&self) -> u64 {
1638 self.base_client()
1639 .rooms_filtered(RoomStateFilter::JOINED)
1640 .iter()
1641 .map(|room| (room, room.num_unread_notifications().max(room.is_marked_unread().into())))
1642 .filter(|(_, count)| *count > 0)
1644 .filter(|(room, _)| !room.is_space())
1645 .filter(|(room, _)| {
1646 !room
1647 .successor_room()
1648 .and_then(|successor| self.base_client().get_room(&successor.room_id))
1649 .is_some_and(|successor| {
1650 matches!(
1651 successor.state(),
1652 RoomState::Joined | RoomState::Left | RoomState::Banned
1653 )
1654 })
1655 })
1656 .map(|(_, count)| count)
1657 .sum()
1658 }
1659
1660 pub fn observe_total_unread_notifications(
1665 &self,
1666 min_interval: Duration,
1667 ) -> impl Stream<Item = u64> + use<> {
1668 let mut update_receiver = self.room_info_notable_update_receiver();
1669 let this = self.clone();
1670
1671 stream! {
1672 let mut current = this.total_unread_notifications();
1674
1675 yield current;
1677
1678 loop {
1682 while update_receiver.try_recv().is_ok() {}
1684
1685 sleep(min_interval).await;
1687
1688 if update_receiver.recv().await.is_err() {
1692 break;
1693 }
1694
1695 let next = this.total_unread_notifications();
1697
1698 if next != current {
1699 current = next;
1700
1701 yield current;
1703 }
1704 }
1705 }
1706 }
1707 pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1713 self.base_client().get_room(room_id).map(|room| Room::new(self.clone(), room))
1714 }
1715
1716 pub async fn get_room_preview(
1719 &self,
1720 room_or_alias_id: &RoomOrAliasId,
1721 via: Vec<OwnedServerName>,
1722 ) -> Result<RoomPreview> {
1723 let room_id = match <&RoomId>::try_from(room_or_alias_id) {
1724 Ok(room_id) => room_id.to_owned(),
1725 Err(alias) => self.resolve_room_alias(alias).await?.room_id,
1726 };
1727
1728 if let Some(room) = self.get_room(&room_id) {
1729 match room.state() {
1736 RoomState::Joined | RoomState::Banned => {
1737 return Ok(RoomPreview::from_known_room(&room).await);
1738 }
1739 RoomState::Left | RoomState::Invited | RoomState::Knocked => {}
1740 }
1741 }
1742
1743 RoomPreview::from_remote_room(self, room_id, room_or_alias_id, via).await
1744 }
1745
1746 pub async fn resolve_room_alias(
1753 &self,
1754 room_alias: &RoomAliasId,
1755 ) -> HttpResult<get_alias::v3::Response> {
1756 let request = get_alias::v3::Request::new(room_alias.to_owned());
1757 self.send(request).await
1758 }
1759
1760 pub async fn is_room_alias_available(&self, alias: &RoomAliasId) -> HttpResult<bool> {
1769 match self.resolve_room_alias(alias).await {
1770 Ok(_) => Ok(false),
1772 Err(error) => {
1773 match error.client_api_error_kind() {
1774 Some(ErrorKind::NotFound) => Ok(true),
1776 _ => Err(error),
1777 }
1778 }
1779 }
1780 }
1781
1782 pub async fn create_room_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> HttpResult<()> {
1784 let request = create_alias::v3::Request::new(alias.to_owned(), room_id.to_owned());
1785 self.send(request).await?;
1786 Ok(())
1787 }
1788
1789 pub async fn remove_room_alias(&self, alias: &RoomAliasId) -> HttpResult<()> {
1791 let request = delete_alias::v3::Request::new(alias.to_owned());
1792 self.send(request).await?;
1793 Ok(())
1794 }
1795
1796 pub(crate) fn maybe_update_login_well_known(&self, login_well_known: Option<&DiscoveryInfo>) {
1803 if self.inner.respect_login_well_known
1804 && let Some(well_known) = login_well_known
1805 && let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url)
1806 {
1807 self.set_homeserver(homeserver);
1808 }
1809 }
1810
1811 #[instrument(skip_all)]
1818 pub async fn restore_session(&self, session: impl Into<AuthSession>) -> Result<()> {
1819 self.restore_session_with(session, RoomLoadSettings::default()).await
1820 }
1821
1822 #[instrument(skip_all)]
1852 pub async fn restore_session_with(
1853 &self,
1854 session: impl Into<AuthSession>,
1855 room_load_settings: RoomLoadSettings,
1856 ) -> Result<()> {
1857 let session = session.into();
1858 match session {
1859 AuthSession::Matrix(session) => {
1860 Box::pin(self.matrix_auth().restore_session(session, room_load_settings)).await
1861 }
1862 AuthSession::OAuth(session) => {
1863 Box::pin(self.oauth().restore_session(*session, room_load_settings)).await
1864 }
1865 }
1866 }
1867
1868 pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1874 let Some(auth_api) = self.auth_api() else {
1875 return Err(RefreshTokenError::RefreshTokenRequired);
1876 };
1877
1878 match auth_api {
1879 AuthApi::Matrix(api) => {
1880 trace!("Token refresh: Using the homeserver.");
1881 Box::pin(api.refresh_access_token()).await?;
1882 }
1883 AuthApi::OAuth(api) => {
1884 trace!("Token refresh: Using OAuth 2.0.");
1885 Box::pin(api.refresh_access_token()).await?;
1886 }
1887 }
1888
1889 Ok(())
1890 }
1891
1892 pub async fn logout(&self) -> Result<(), Error> {
1899 let auth_api = self.auth_api().ok_or(Error::AuthenticationRequired)?;
1900 match auth_api {
1901 AuthApi::Matrix(matrix_auth) => {
1902 matrix_auth.logout().await?;
1903 Ok(())
1904 }
1905 AuthApi::OAuth(oauth) => Ok(oauth.logout().await?),
1906 }
1907 }
1908
1909 #[instrument(skip(self, definition))]
1955 pub async fn get_or_upload_filter(
1956 &self,
1957 filter_name: &str,
1958 definition: FilterDefinition,
1959 ) -> Result<String> {
1960 if let Some(filter) = self.inner.base_client.get_filter(filter_name).await? {
1961 debug!("Found filter locally");
1962 Ok(filter)
1963 } else {
1964 debug!("Didn't find filter locally");
1965 let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?;
1966 let request = FilterUploadRequest::new(user_id.to_owned(), definition);
1967 let response = self.send(request).await?;
1968
1969 self.inner.base_client.receive_filter_upload(filter_name, &response).await?;
1970
1971 Ok(response.filter_id)
1972 }
1973 }
1974
1975 async fn prepare_join_room_by_id(&self, room_id: &RoomId) -> Option<PreJoinRoomInfo> {
1977 let room = self.get_room(room_id)?;
1978
1979 let inviter = match room.invite_details().await {
1980 Ok(details) => details.inviter,
1981 Err(Error::WrongRoomState(_)) => None,
1982 Err(e) => {
1983 warn!("Error fetching invite details for room: {e:?}");
1984 None
1985 }
1986 };
1987
1988 Some(PreJoinRoomInfo { inviter })
1989 }
1990
1991 async fn finish_join_room(
2004 &self,
2005 room_id: &RoomId,
2006 pre_join_room_info: Option<PreJoinRoomInfo>,
2007 ) -> Result<Room> {
2008 info!(?room_id, ?pre_join_room_info, "Completing room join");
2009 let mark_as_dm = if let Some(room) = self.get_room(room_id) {
2010 room.state() == RoomState::Invited
2011 && room.is_direct().await.unwrap_or_else(|e| {
2012 warn!(%room_id, "is_direct() failed: {e}");
2013 false
2014 })
2015 } else {
2016 false
2017 };
2018
2019 let base_room = self
2020 .base_client()
2021 .room_joined(
2022 room_id,
2023 pre_join_room_info
2024 .as_ref()
2025 .and_then(|info| info.inviter.as_ref())
2026 .map(|i| i.user_id().to_owned()),
2027 )
2028 .await?;
2029 let room = Room::new(self.clone(), base_room);
2030
2031 if mark_as_dm {
2032 room.set_is_direct(true).await?;
2033 }
2034
2035 #[cfg(feature = "e2e-encryption")]
2042 if self.inner.enable_share_history_on_invite
2043 && let Some(inviter) =
2044 pre_join_room_info.as_ref().and_then(|info| info.inviter.as_ref())
2045 {
2046 crate::room::shared_room_history::maybe_accept_key_bundle(&room, inviter.user_id())
2047 .await?;
2048 }
2049
2050 #[cfg(not(feature = "e2e-encryption"))]
2052 let _ = pre_join_room_info.map(|i| i.inviter);
2053
2054 Ok(room)
2055 }
2056
2057 #[instrument(skip(self))]
2065 pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result<Room> {
2066 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
2070
2071 let request = join_room_by_id::v3::Request::new(room_id.to_owned());
2072 let response = self.send(request).await?;
2073 self.finish_join_room(&response.room_id, pre_join_info).await
2074 }
2075
2076 #[instrument(skip(self))]
2087 pub async fn join_room_by_id_or_alias(
2088 &self,
2089 alias: &RoomOrAliasId,
2090 server_names: &[OwnedServerName],
2091 ) -> Result<Room> {
2092 let room_id = match <&RoomId>::try_from(alias) {
2093 Ok(room_id) => room_id,
2094 Err(room_alias) => &self.resolve_room_alias(room_alias).await?.room_id,
2095 };
2096 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
2097 let request = assign!(join_room_by_id_or_alias::v3::Request::new(alias.to_owned()), {
2098 via: server_names.to_owned(),
2099 });
2100 let response = self.send(request).await?;
2101 self.finish_join_room(&response.room_id, pre_join_info).await
2102 }
2103
2104 pub async fn public_rooms(
2134 &self,
2135 limit: Option<u32>,
2136 since: Option<&str>,
2137 server: Option<&ServerName>,
2138 ) -> HttpResult<get_public_rooms::v3::Response> {
2139 let limit = limit.map(UInt::from);
2140
2141 let request = assign!(get_public_rooms::v3::Request::new(), {
2142 limit,
2143 since: since.map(ToOwned::to_owned),
2144 server: server.map(ToOwned::to_owned),
2145 });
2146 self.send(request).await
2147 }
2148
2149 pub async fn create_room(&self, request: create_room::v3::Request) -> Result<Room> {
2179 let invite = request.invite.clone();
2180 let is_direct_room = request.is_direct;
2181 let response = self.send(request).await?;
2182
2183 let base_room = self.base_client().get_or_create_room(&response.room_id, RoomState::Joined);
2184
2185 let joined_room = Room::new(self.clone(), base_room);
2186
2187 if is_direct_room
2188 && !invite.is_empty()
2189 && let Err(error) =
2190 self.account().mark_as_dm(joined_room.room_id(), invite.as_slice()).await
2191 {
2192 error!("Failed to mark room as DM: {error}");
2194 }
2195
2196 Ok(joined_room)
2197 }
2198
2199 pub async fn create_dm(&self, user_id: &UserId) -> Result<Room> {
2212 #[cfg(feature = "e2e-encryption")]
2213 let initial_state = vec![
2214 InitialStateEvent::with_empty_state_key(
2215 RoomEncryptionEventContent::with_recommended_defaults(),
2216 )
2217 .to_raw_any(),
2218 ];
2219
2220 #[cfg(not(feature = "e2e-encryption"))]
2221 let initial_state = vec![];
2222
2223 let request = assign!(create_room::v3::Request::new(), {
2224 invite: vec![user_id.to_owned()],
2225 is_direct: true,
2226 preset: Some(create_room::v3::RoomPreset::TrustedPrivateChat),
2227 initial_state,
2228 });
2229
2230 self.create_room(request).await
2231 }
2232
2233 pub fn get_dm_room(&self, user_id: &UserId) -> Option<Room> {
2235 self.get_dm_rooms(user_id).next()
2236 }
2237
2238 pub fn get_dm_rooms(&self, user_id: &UserId) -> impl Iterator<Item = Room> {
2240 let rooms = self.joined_rooms();
2241
2242 let dm_definition = &self.base_client().dm_room_definition;
2243
2244 let rooms = rooms.into_iter().filter(move |r| {
2246 let targets = r.direct_targets();
2247 let targets_match =
2248 targets.len() == 1 && targets.contains(<&DirectUserIdentifier>::from(user_id));
2249 match dm_definition {
2250 DmRoomDefinition::MatrixSpec => targets_match,
2251 DmRoomDefinition::TwoMembers => {
2252 let service_members_count =
2253 r.service_members().map(|s| s.len()).unwrap_or_default() as u64;
2254 let active_non_service_members =
2255 r.active_members_count().saturating_sub(service_members_count);
2256 targets_match && active_non_service_members <= 2
2257 }
2258 }
2259 });
2260
2261 trace!(?user_id, ?rooms, "Found DM rooms with user");
2262 rooms
2263 }
2264
2265 pub async fn public_rooms_filtered(
2297 &self,
2298 request: get_public_rooms_filtered::v3::Request,
2299 ) -> HttpResult<get_public_rooms_filtered::v3::Response> {
2300 self.send(request).await
2301 }
2302
2303 pub fn send<Request>(&self, request: Request) -> SendRequest<Request>
2340 where
2341 Request: OutgoingRequest + Clone + Debug,
2342 Request::Authentication: SupportedAuthScheme,
2343 Request::PathBuilder: SupportedPathBuilder,
2344 for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2345 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2346 {
2347 SendRequest {
2348 client: self.clone(),
2349 request,
2350 config: None,
2351 send_progress: Default::default(),
2352 }
2353 }
2354
2355 pub(crate) async fn send_inner<Request>(
2356 &self,
2357 request: Request,
2358 config: Option<RequestConfig>,
2359 send_progress: SharedObservable<TransmissionProgress>,
2360 ) -> HttpResult<Request::IncomingResponse>
2361 where
2362 Request: OutgoingRequest + Debug,
2363 Request::Authentication: SupportedAuthScheme,
2364 Request::PathBuilder: SupportedPathBuilder,
2365 for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2366 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2367 {
2368 let homeserver = self.homeserver().to_string();
2369 let access_token = self.access_token();
2370 let skip_auth = config.map(|c| c.skip_auth).unwrap_or(self.request_config().skip_auth);
2371
2372 let path_builder_input =
2373 Request::PathBuilder::get_path_builder_input(self, skip_auth).await?;
2374
2375 let result = self
2376 .inner
2377 .http_client
2378 .send(
2379 request,
2380 config,
2381 homeserver,
2382 access_token.as_deref(),
2383 path_builder_input,
2384 send_progress,
2385 )
2386 .await;
2387
2388 if let Err(Some(ErrorKind::UnknownToken { .. })) =
2389 result.as_ref().map_err(HttpError::client_api_error_kind)
2390 && let Some(access_token) = &access_token
2391 {
2392 self.auth_ctx().set_access_token_expired(access_token);
2394 }
2395
2396 result
2397 }
2398
2399 fn broadcast_unknown_token(&self, unknown_token_data: &UnknownTokenErrorData) {
2400 _ = self
2401 .inner
2402 .auth_ctx
2403 .session_change_sender
2404 .send(SessionChange::UnknownToken(unknown_token_data.clone()));
2405 }
2406
2407 pub async fn fetch_server_versions(
2409 &self,
2410 request_config: Option<RequestConfig>,
2411 ) -> HttpResult<get_supported_versions::Response> {
2412 self.fetch_server_versions_inner(false, request_config).await
2415 }
2416
2417 pub(crate) async fn fetch_server_versions_inner(
2423 &self,
2424 failsafe: bool,
2425 request_config: Option<RequestConfig>,
2426 ) -> HttpResult<get_supported_versions::Response> {
2427 if !failsafe {
2428 return self
2430 .send(get_supported_versions::Request::new())
2431 .with_request_config(request_config)
2432 .await;
2433 }
2434
2435 let homeserver = self.homeserver().to_string();
2436
2437 if !request_config.as_ref().is_some_and(|config| config.skip_auth && !config.force_auth)
2439 && self.auth_ctx().has_valid_access_token()
2440 && let Some(access_token) = self.access_token()
2441 {
2442 let result = self
2443 .inner
2444 .http_client
2445 .send(
2446 get_supported_versions::Request::new(),
2447 request_config,
2448 homeserver.clone(),
2449 Some(&access_token),
2450 (),
2451 Default::default(),
2452 )
2453 .await;
2454
2455 if let Err(Some(ErrorKind::UnknownToken { .. })) =
2456 result.as_ref().map_err(HttpError::client_api_error_kind)
2457 {
2458 self.auth_ctx().set_access_token_expired(&access_token);
2461 } else {
2462 return result;
2465 }
2466 }
2467
2468 self.inner
2470 .http_client
2471 .send(
2472 get_supported_versions::Request::new(),
2473 request_config,
2474 homeserver.clone(),
2475 None,
2476 (),
2477 Default::default(),
2478 )
2479 .await
2480 }
2481
2482 pub async fn fetch_client_well_known(&self) -> Option<discover_homeserver::Response> {
2495 if self.well_known_lookup_disabled() {
2496 return None;
2497 }
2498
2499 let homeserver = self.homeserver();
2500 let scheme = homeserver.scheme();
2501
2502 let server_url = self
2507 .server()
2508 .map(|server| server.to_string())
2509 .or_else(|| self.user_id().map(|id| format!("{}://{}", scheme, id.server_name())));
2514
2515 let response = if let Some(server_url) = server_url {
2517 self.fetch_client_well_known_with_url(server_url).await
2519 } else {
2520 None
2521 };
2522
2523 if response.is_none() {
2526 warn!(
2530 "Fetching the well-known from the server name didn't work, using the homeserver url instead"
2531 );
2532 self.fetch_client_well_known_with_url(homeserver.to_string()).await
2533 } else {
2534 response
2535 }
2536 }
2537
2538 async fn fetch_client_well_known_with_url(
2539 &self,
2540 url: String,
2541 ) -> Option<discover_homeserver::Response> {
2542 let well_known = self
2543 .inner
2544 .http_client
2545 .send(
2546 discover_homeserver::Request::new(),
2547 Some(RequestConfig::short_retry()),
2548 url,
2549 None,
2550 (),
2551 Default::default(),
2552 )
2553 .await;
2554
2555 match well_known {
2556 Ok(well_known) => Some(well_known),
2557 Err(http_error) => {
2558 warn!("Failed to fetch client well-known: {http_error}");
2561 None
2562 }
2563 }
2564 }
2565
2566 async fn fetch_supported_versions(
2572 &self,
2573 failsafe: bool,
2574 ) -> HttpResult<SupportedVersionsResponse> {
2575 let server_versions = self.fetch_server_versions_inner(failsafe, None).await?;
2576 let supported_versions = SupportedVersionsResponse {
2577 versions: server_versions.versions,
2578 unstable_features: server_versions.unstable_features,
2579 };
2580
2581 Ok(supported_versions)
2582 }
2583
2584 pub async fn supported_versions(&self) -> HttpResult<SupportedVersions> {
2612 self.supported_versions_inner(false).await
2613 }
2614
2615 pub(crate) async fn supported_versions_inner(
2621 &self,
2622 failsafe: bool,
2623 ) -> HttpResult<SupportedVersions> {
2624 match self.supported_versions_cached_inner(failsafe).await {
2625 Ok(Some(value)) => {
2626 return Ok(value);
2627 }
2628 Ok(None) => {
2629 }
2631 Err(error) => {
2632 warn!("error when loading cached supported versions: {error}");
2633 }
2635 }
2636
2637 self.refresh_supported_versions_cache(failsafe).await
2638 }
2639
2640 async fn refresh_supported_versions_cache(
2646 &self,
2647 failsafe: bool,
2648 ) -> HttpResult<SupportedVersions> {
2649 let cached_supported_versions = &self.inner.caches.supported_versions;
2650
2651 let mut supported_versions_guard = match cached_supported_versions.refresh_lock.try_lock() {
2652 Ok(guard) => guard,
2653 Err(_) => {
2654 let guard = cached_supported_versions.refresh_lock.lock().await;
2657
2658 if let Err(error) = guard.as_ref() {
2659 return Err(HttpError::Cached(error.clone()));
2661 }
2662
2663 if let CachedValue::Cached(value) = cached_supported_versions.value()
2665 && !value.has_expired()
2666 {
2667 return Ok(value.into_data());
2668 }
2669
2670 guard
2673 }
2674 };
2675
2676 let response = match self.fetch_supported_versions(failsafe).await {
2677 Ok(response) => {
2678 *supported_versions_guard = Ok(());
2679 TtlValue::new(response)
2680 }
2681 Err(error) => {
2682 let error = Arc::new(error);
2683 *supported_versions_guard = Err(error.clone());
2684 return Err(HttpError::Cached(error));
2685 }
2686 };
2687
2688 let supported_versions = response.as_ref().map(|response| response.supported_versions());
2689
2690 if self.auth_ctx().has_valid_access_token() {
2692 if let Err(err) = self
2693 .state_store()
2694 .set_kv_data(
2695 StateStoreDataKey::SupportedVersions,
2696 StateStoreDataValue::SupportedVersions(response),
2697 )
2698 .await
2699 {
2700 warn!("error when caching supported versions: {err}");
2701 }
2702
2703 cached_supported_versions.set_value(supported_versions.clone());
2704 }
2705
2706 Ok(supported_versions.into_data())
2707 }
2708
2709 pub async fn supported_versions_cached(&self) -> Result<Option<SupportedVersions>, StoreError> {
2746 self.supported_versions_cached_inner(false).await
2747 }
2748
2749 async fn supported_versions_cached_inner(
2750 &self,
2751 failsafe: bool,
2752 ) -> Result<Option<SupportedVersions>, StoreError> {
2753 let supported_versions_cache = &self.inner.caches.supported_versions;
2754
2755 let value = if let CachedValue::Cached(cached) = supported_versions_cache.value() {
2756 cached
2757 } else if let Some(stored) = self
2758 .state_store()
2759 .get_kv_data(StateStoreDataKey::SupportedVersions)
2760 .await?
2761 .and_then(|value| value.into_supported_versions())
2762 {
2763 let stored = stored.map(|response| response.supported_versions());
2764
2765 supported_versions_cache.set_value(stored.clone());
2767
2768 stored
2769 } else {
2770 return Ok(None);
2771 };
2772
2773 if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2776 debug!("spawning task to refresh supported versions cache");
2777
2778 let client = self.clone();
2779 self.task_monitor().spawn_finite_task("refresh supported versions cache", async move {
2780 if let Err(error) = client.refresh_supported_versions_cache(failsafe).await {
2781 warn!("failed to refresh supported versions cache: {error}");
2782 }
2783 });
2784 }
2785
2786 Ok(Some(value.into_data()))
2787 }
2788
2789 pub async fn server_versions(&self) -> HttpResult<BTreeSet<MatrixVersion>> {
2808 Ok(self.supported_versions().await?.versions)
2809 }
2810
2811 pub async fn unstable_features(&self) -> HttpResult<BTreeSet<FeatureFlag>> {
2831 Ok(self.supported_versions().await?.features)
2832 }
2833
2834 pub async fn reset_supported_versions(&self) -> Result<()> {
2840 self.inner.caches.supported_versions.reset();
2842
2843 Ok(self.state_store().remove_kv_data(StateStoreDataKey::SupportedVersions).await?)
2845 }
2846
2847 async fn well_known_cached(
2852 &self,
2853 ) -> Result<CachedValue<Option<WellKnownResponse>>, StoreError> {
2854 let well_known_cache = &self.inner.caches.well_known;
2855
2856 let value = if let CachedValue::Cached(cached) = well_known_cache.value() {
2857 cached
2858 } else if let Some(stored) = self
2859 .state_store()
2860 .get_kv_data(StateStoreDataKey::WellKnown)
2861 .await?
2862 .and_then(|value| value.into_well_known())
2863 {
2864 well_known_cache.set_value(stored.clone());
2866
2867 stored
2868 } else {
2869 return Ok(CachedValue::NotSet);
2870 };
2871
2872 if value.has_expired() {
2874 debug!("spawning task to refresh well-known cache");
2875
2876 let client = self.clone();
2877 self.task_monitor().spawn_finite_task("refresh well-known cache", async move {
2878 client.refresh_well_known_cache().await;
2879 });
2880 }
2881
2882 Ok(CachedValue::Cached(value.into_data()))
2883 }
2884
2885 async fn refresh_well_known_cache(&self) -> Option<WellKnownResponse> {
2887 let well_known_cache = &self.inner.caches.well_known;
2888
2889 let _well_known_guard = match well_known_cache.refresh_lock.try_lock() {
2890 Ok(guard) => guard,
2891 Err(_) => {
2892 let guard = well_known_cache.refresh_lock.lock().await;
2895
2896 if let CachedValue::Cached(value) = well_known_cache.value()
2901 && !value.has_expired()
2902 {
2903 return value.into_data();
2904 }
2905
2906 guard
2909 }
2910 };
2911
2912 let well_known = TtlValue::new(self.fetch_client_well_known().await.map(Into::into));
2913
2914 if let Err(err) = self
2915 .state_store()
2916 .set_kv_data(
2917 StateStoreDataKey::WellKnown,
2918 StateStoreDataValue::WellKnown(well_known.clone()),
2919 )
2920 .await
2921 {
2922 warn!("error when caching well-known: {err}");
2923 }
2924
2925 well_known_cache.set_value(well_known.clone());
2926
2927 well_known.into_data()
2928 }
2929
2930 fn well_known_lookup_disabled(&self) -> bool {
2933 *self.inner.well_known_lookup_disabled.read().unwrap()
2934 }
2935
2936 pub fn disable_well_known_lookup(&self, disable: bool) {
2939 *self.inner.well_known_lookup_disabled.write().unwrap() = disable;
2940 }
2941
2942 async fn well_known(&self) -> Option<WellKnownResponse> {
2948 if self.well_known_lookup_disabled() {
2949 return None;
2950 }
2951
2952 match self.well_known_cached().await {
2953 Ok(CachedValue::Cached(value)) => {
2954 return value;
2955 }
2956 Ok(CachedValue::NotSet) => {
2957 }
2959 Err(error) => {
2960 warn!("error when loading cached well-known: {error}");
2961 }
2963 }
2964
2965 self.refresh_well_known_cache().await
2966 }
2967
2968 #[deprecated = "Use `Client::discover_rtc_transports` instead"]
2974 pub async fn rtc_foci(&self) -> HttpResult<Vec<RtcTransport>> {
2975 self.well_known_rtc_transports().await
2976 }
2977
2978 pub async fn well_known_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
3011 let well_known = self.well_known().await;
3012
3013 Ok(well_known.map(|well_known| well_known.rtc_foci).unwrap_or_default())
3014 }
3015
3016 async fn rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
3023 match self.rtc_transports_cached() {
3024 CachedValue::Cached(value) => Ok(value),
3025 CachedValue::NotSet => self.refresh_rtc_transports_cache().await,
3027 }
3028 }
3029
3030 fn rtc_transports_cached(&self) -> CachedValue<Option<Vec<RtcTransport>>> {
3036 let cache = &self.inner.caches.rtc_transports;
3037
3038 let CachedValue::Cached(value) = cache.value() else {
3039 return CachedValue::NotSet;
3040 };
3041
3042 if value.has_expired() && self.auth_ctx().has_valid_access_token() {
3045 debug!("spawning task to refresh RTC transports cache");
3046
3047 let client = self.clone();
3048 self.task_monitor().spawn_finite_task("refresh RTC transports cache", async move {
3049 if let Err(error) = client.refresh_rtc_transports_cache().await {
3050 warn!("failed to refresh RTC transports cache: {error}");
3051 }
3052 });
3053 }
3054
3055 CachedValue::Cached(value.into_data())
3056 }
3057
3058 async fn refresh_rtc_transports_cache(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
3060 let cache = &self.inner.caches.rtc_transports;
3061
3062 let mut refresh_guard = match cache.refresh_lock.try_lock() {
3063 Ok(guard) => guard,
3064 Err(_) => {
3065 let guard = cache.refresh_lock.lock().await;
3068
3069 if let Err(error) = guard.as_ref() {
3070 return Err(HttpError::Cached(error.clone()));
3072 }
3073
3074 if let CachedValue::Cached(value) = cache.value()
3076 && !value.has_expired()
3077 {
3078 return Ok(value.into_data());
3079 }
3080
3081 guard
3084 }
3085 };
3086
3087 match self.fetch_rtc_transports().await {
3088 Ok(transports) => {
3089 *refresh_guard = Ok(());
3090 cache.set_value(TtlValue::new(Some(transports.clone())));
3091 Ok(Some(transports))
3092 }
3093 Err(error) if error.is_endpoint_not_implemented() => {
3094 debug!("homeserver does not implement the RTC transports endpoint");
3102 *refresh_guard = Ok(());
3103 cache.set_value(TtlValue::new(None));
3104 Ok(None)
3105 }
3106 Err(error) => {
3107 let error = Arc::new(error);
3108 *refresh_guard = Err(error.clone());
3109 Err(HttpError::Cached(error))
3110 }
3111 }
3112 }
3113
3114 pub async fn fetch_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
3117 let response = self
3118 .send(transports::msc4143::Request::new())
3119 .with_request_config(RequestConfig::short_retry())
3120 .await?;
3121 Ok(response.rtc_transports)
3122 }
3123
3124 pub fn reset_rtc_transports(&self) {
3129 self.inner.caches.rtc_transports.reset();
3130 }
3131
3132 pub async fn discover_rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
3161 if let Some(transports) = self.rtc_transports().await? {
3162 return Ok(Some(transports));
3163 }
3164
3165 Ok(self.well_known().await.map(|well_known| well_known.rtc_foci))
3170 }
3171
3172 pub async fn tile_server(&self) -> Option<TileServerInfo> {
3180 self.well_known().await.and_then(|well_known| well_known.tile_server).map(Into::into)
3181 }
3182
3183 pub async fn reset_well_known(&self) -> Result<()> {
3188 self.inner.caches.well_known.reset();
3190
3191 Ok(self.state_store().remove_kv_data(StateStoreDataKey::WellKnown).await?)
3193 }
3194
3195 pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> HttpResult<bool> {
3210 Ok(self.unstable_features().await?.contains(&FeatureFlag::from("org.matrix.msc4028")))
3211 }
3212
3213 #[cfg(feature = "unstable-msc4354")]
3218 pub async fn supports_sticky_events(&self) -> HttpResult<bool> {
3219 Ok(self.unstable_features().await?.contains(&FeatureFlag::from("org.matrix.msc4354")))
3220 }
3221
3222 #[instrument(skip_all)]
3244 pub async fn update_delayed_event(
3245 &self,
3246 delay_id: String,
3247 action: update_delayed_event::UpdateAction,
3248 request_config: Option<RequestConfig>,
3249 ) -> Result<update_delayed_event::msc4140_v2::Response> {
3250 let request = update_delayed_event::msc4140_v2::Request::new(delay_id, action);
3251
3252 let request_config = request_config.unwrap_or_else(|| self.request_config()).force_auth();
3256
3257 Ok(self.send(request).with_request_config(request_config).await?)
3258 }
3259
3260 pub async fn devices(&self) -> HttpResult<get_devices::v3::Response> {
3282 let request = get_devices::v3::Request::new();
3283
3284 self.send(request).await
3285 }
3286
3287 pub async fn get_retention_configuration(
3296 &self,
3297 ) -> HttpResult<get_retention_configuration::msc1763::Response> {
3298 self.send(get_retention_configuration::msc1763::Request::default()).await
3299 }
3300
3301 pub async fn delete_devices(
3348 &self,
3349 devices: &[OwnedDeviceId],
3350 auth_data: Option<uiaa::AuthData>,
3351 ) -> HttpResult<delete_devices::v3::Response> {
3352 let mut request = delete_devices::v3::Request::new(devices.to_owned());
3353 request.auth = auth_data;
3354
3355 self.send(request).await
3356 }
3357
3358 pub async fn rename_device(
3368 &self,
3369 device_id: &DeviceId,
3370 display_name: &str,
3371 ) -> HttpResult<update_device::v3::Response> {
3372 let mut request = update_device::v3::Request::new(device_id.to_owned());
3373 request.display_name = Some(display_name.to_owned());
3374
3375 self.send(request).await
3376 }
3377
3378 pub async fn device_exists(&self, device_id: OwnedDeviceId) -> Result<bool> {
3387 let request = device::get_device::v3::Request::new(device_id);
3388 match self.send(request).await {
3389 Ok(_) => Ok(true),
3390 Err(err) => {
3391 if let Some(error) = err.as_client_api_error()
3392 && error.status_code == 404
3393 {
3394 Ok(false)
3395 } else {
3396 Err(err.into())
3397 }
3398 }
3399 }
3400 }
3401
3402 #[instrument(skip(self))]
3498 pub async fn sync_once(
3499 &self,
3500 sync_settings: crate::config::SyncSettings,
3501 ) -> Result<SyncResponse> {
3502 #[cfg(feature = "e2e-encryption")]
3509 if let Err(e) = self.send_outgoing_requests().await {
3510 error!(error = ?e, "Error while sending outgoing E2EE requests");
3511 }
3512
3513 let token = match sync_settings.token {
3514 SyncToken::Specific(token) => Some(token),
3515 SyncToken::NoToken => None,
3516 SyncToken::ReusePrevious => self.sync_token().await,
3517 };
3518
3519 let request = assign!(sync_events::v3::Request::new(), {
3520 filter: sync_settings.filter.map(|f| *f),
3521 since: token,
3522 full_state: sync_settings.full_state,
3523 set_presence: sync_settings.set_presence.unwrap_or_else(|| self.sync_presence()),
3524 timeout: sync_settings.timeout,
3525 use_state_after: true,
3526 });
3527 let mut request_config = self.request_config();
3528 if let Some(timeout) = sync_settings.timeout {
3529 let base_timeout = request_config.timeout.unwrap_or(Duration::from_secs(30));
3530 request_config.timeout = Some(base_timeout + timeout);
3531 }
3532
3533 let response = self.send(request).with_request_config(request_config).await?;
3534 let next_batch = response.next_batch.clone();
3535 let response = self.process_sync(response).await?;
3536
3537 #[cfg(feature = "e2e-encryption")]
3538 if let Err(e) = self.send_outgoing_requests().await {
3539 error!(error = ?e, "Error while sending outgoing E2EE requests");
3540 }
3541
3542 self.inner.sync_beat.notify(usize::MAX);
3543
3544 Ok(SyncResponse::new(next_batch, response))
3545 }
3546
3547 pub async fn sync(&self, sync_settings: crate::config::SyncSettings) -> Result<(), Error> {
3607 self.sync_with_callback(sync_settings, |_| async { LoopCtrl::Continue }).await
3608 }
3609
3610 #[instrument(skip_all)]
3667 pub async fn sync_with_callback<C>(
3668 &self,
3669 sync_settings: crate::config::SyncSettings,
3670 callback: impl Fn(SyncResponse) -> C,
3671 ) -> Result<(), Error>
3672 where
3673 C: Future<Output = LoopCtrl>,
3674 {
3675 self.sync_with_result_callback(sync_settings, |result| async {
3676 Ok(callback(result?).await)
3677 })
3678 .await
3679 }
3680
3681 #[instrument(skip(self, callback))]
3747 pub async fn sync_with_result_callback<C>(
3748 &self,
3749 sync_settings: crate::config::SyncSettings,
3750 callback: impl Fn(Result<SyncResponse, Error>) -> C,
3751 ) -> Result<(), Error>
3752 where
3753 C: Future<Output = Result<LoopCtrl, Error>>,
3754 {
3755 let mut sync_stream = Box::pin(self.sync_stream(sync_settings).await);
3756
3757 while let Some(result) = sync_stream.next().await {
3758 trace!("Running callback");
3759 if callback(result).await? == LoopCtrl::Break {
3760 trace!("Callback told us to stop");
3761 break;
3762 }
3763 trace!("Done running callback");
3764 }
3765
3766 Ok(())
3767 }
3768
3769 #[allow(unknown_lints, clippy::let_with_type_underscore)] #[instrument(skip(self))]
3811 pub async fn sync_stream(
3812 &self,
3813 mut sync_settings: crate::config::SyncSettings,
3814 ) -> impl Stream<Item = Result<SyncResponse>> + '_ {
3815 let mut is_first_sync = true;
3816 let mut timeout = None;
3817 let mut last_sync_time: Option<Instant> = None;
3818
3819 let parent_span = Span::current();
3820
3821 async_stream::stream!({
3822 loop {
3823 trace!("Syncing");
3824
3825 if sync_settings.ignore_timeout_on_first_sync {
3826 if is_first_sync {
3827 timeout = sync_settings.timeout.take();
3828 } else if sync_settings.timeout.is_none() && timeout.is_some() {
3829 sync_settings.timeout = timeout.take();
3830 }
3831
3832 is_first_sync = false;
3833 }
3834
3835 yield self
3836 .sync_loop_helper(&mut sync_settings)
3837 .instrument(parent_span.clone())
3838 .await;
3839
3840 if sync_settings.throttle_sync_loop {
3841 Client::delay_sync(&mut last_sync_time).await
3842 }
3843 }
3844 })
3845 }
3846
3847 pub(crate) async fn sync_token(&self) -> Option<String> {
3850 self.inner.base_client.sync_token().await
3851 }
3852
3853 pub async fn wait_for_sync(&self) {
3863 self.inner.sync_beat.listen().await;
3864 }
3865
3866 pub async fn whoami(&self) -> HttpResult<whoami::v3::Response> {
3868 let request = whoami::v3::Request::new();
3869 self.send(request).await
3870 }
3871
3872 pub fn subscribe_to_session_changes(&self) -> broadcast::Receiver<SessionChange> {
3874 let broadcast = &self.auth_ctx().session_change_sender;
3875 broadcast.subscribe()
3876 }
3877
3878 pub fn set_session_callbacks(
3883 &self,
3884 reload_session_callback: Box<ReloadSessionCallback>,
3885 save_session_callback: Box<SaveSessionCallback>,
3886 ) -> Result<()> {
3887 self.inner
3888 .auth_ctx
3889 .reload_session_callback
3890 .set(reload_session_callback)
3891 .map_err(|_| Error::MultipleSessionCallbacks)?;
3892
3893 self.inner
3894 .auth_ctx
3895 .save_session_callback
3896 .set(save_session_callback)
3897 .map_err(|_| Error::MultipleSessionCallbacks)?;
3898
3899 Ok(())
3900 }
3901
3902 pub async fn notification_settings(&self) -> NotificationSettings {
3904 let ruleset = self.account().push_rules().await.unwrap_or_else(|_| Ruleset::new());
3905 NotificationSettings::new(self.clone(), ruleset)
3906 }
3907
3908 pub async fn notification_client(
3915 &self,
3916 cross_process_lock_config: CrossProcessLockConfig,
3917 ) -> Result<Client> {
3918 let client = Client {
3919 inner: ClientInner::new(
3920 self.inner.auth_ctx.clone(),
3921 self.server(),
3922 self.homeserver(),
3923 self.sliding_sync_version(),
3924 self.inner.sync_presence.clone(),
3925 self.inner.http_client.clone(),
3926 self.inner
3927 .base_client
3928 .clone_with_in_memory_state_store(cross_process_lock_config.clone(), false)
3929 .await?,
3930 self.inner.caches.supported_versions.value(),
3931 self.inner.caches.well_known.value(),
3932 self.inner.respect_login_well_known,
3933 self.well_known_lookup_disabled(),
3934 false,
3935 false,
3937 self.inner.send_queue_data.clone(),
3938 #[cfg(feature = "e2e-encryption")]
3939 self.inner.e2ee.encryption_settings,
3940 #[cfg(feature = "e2e-encryption")]
3941 self.inner.enable_share_history_on_invite,
3942 cross_process_lock_config,
3943 #[cfg(feature = "experimental-search")]
3944 self.inner.search_index.clone(),
3945 self.inner.thread_subscription_catchup.clone(),
3946 (*self.inner.media_fetcher.read().await).clone(),
3947 )
3948 .await,
3949 };
3950
3951 Ok(client)
3952 }
3953
3954 pub fn event_cache(&self) -> Option<&EventCache> {
3956 self.inner.event_cache.get()
3957 }
3958
3959 pub async fn latest_events(&self) -> Option<&LatestEvents> {
3962 if let Some(event_cache) = self.event_cache() {
3963 Some(
3964 self.inner
3965 .latest_events
3966 .get_or_init(|| async {
3967 LatestEvents::new(
3968 self.downgrade(),
3969 event_cache.clone(),
3970 SendQueue::new(self.clone()),
3971 self.room_info_notable_update_receiver(),
3972 )
3973 })
3974 .await,
3975 )
3976 } else {
3977 None
3978 }
3979 }
3980
3981 pub async fn await_room_remote_echo(&self, room_id: &RoomId) -> Room {
3987 loop {
3988 if let Some(room) = self.get_room(room_id) {
3989 if room.is_state_partially_or_fully_synced() {
3990 debug!("Found just created room!");
3991 return room;
3992 }
3993 debug!("Room wasn't partially synced, waiting for sync beat to try again");
3994 } else {
3995 debug!("Room wasn't found, waiting for sync beat to try again");
3996 }
3997 self.inner.sync_beat.listen().await;
3998 }
3999 }
4000
4001 pub async fn knock(
4004 &self,
4005 room_id_or_alias: OwnedRoomOrAliasId,
4006 reason: Option<String>,
4007 server_names: Vec<OwnedServerName>,
4008 ) -> Result<Room> {
4009 let request =
4010 assign!(knock_room::v3::Request::new(room_id_or_alias), { reason, via: server_names });
4011 let response = self.send(request).await?;
4012 let base_room = self.inner.base_client.room_knocked(&response.room_id).await?;
4013 Ok(Room::new(self.clone(), base_room))
4014 }
4015
4016 pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
4018 self.base_client().is_user_ignored(user_id).await
4019 }
4020
4021 pub async fn load_or_fetch_max_upload_size(&self) -> Result<UInt> {
4029 let max_upload_size_lock = self.inner.server_max_upload_size.lock().await;
4030 if let Some(data) = max_upload_size_lock.get() {
4031 return Ok(data.to_owned());
4032 }
4033
4034 let supported_versions = self.supported_versions().await?;
4036 let use_auth = authenticated_media::get_media_config::v1::Request::PATH_BUILDER
4037 .is_supported(&supported_versions);
4038
4039 let upload_size = if use_auth {
4040 self.send(authenticated_media::get_media_config::v1::Request::default())
4041 .await?
4042 .upload_size
4043 } else {
4044 #[allow(deprecated)]
4045 self.send(media::get_media_config::v3::Request::default()).await?.upload_size
4046 };
4047
4048 let Some(upload_size) = upload_size else {
4049 return Err(Error::Media(MediaError::FetchMaxUploadSizeFailed(
4050 "the homeserver does not advertise `m.upload.size`".to_owned(),
4051 )));
4052 };
4053
4054 match max_upload_size_lock.set(upload_size) {
4055 Ok(_) => Ok(upload_size),
4056 Err(error) => {
4057 Err(Error::Media(MediaError::FetchMaxUploadSizeFailed(error.to_string())))
4058 }
4059 }
4060 }
4061
4062 #[cfg(feature = "e2e-encryption")]
4064 pub fn decryption_settings(&self) -> &DecryptionSettings {
4065 &self.base_client().decryption_settings
4066 }
4067
4068 #[cfg(feature = "experimental-search")]
4070 pub fn search_index(&self) -> &SearchIndex {
4071 &self.inner.search_index
4072 }
4073
4074 pub async fn enabled_thread_subscriptions(&self) -> Result<bool> {
4085 match self.base_client().threading_support {
4088 ThreadingSupport::Enabled { with_subscriptions: false }
4089 | ThreadingSupport::Disabled => return Ok(false),
4090 ThreadingSupport::Enabled { with_subscriptions: true } => {}
4091 }
4092
4093 let server_enabled = self
4095 .supported_versions()
4096 .await?
4097 .features
4098 .contains(&FeatureFlag::from("org.matrix.msc4306"));
4099
4100 Ok(server_enabled)
4101 }
4102
4103 pub async fn is_global_profile_sync_enabled(&self) -> Result<bool> {
4109 if matches!(self.sliding_sync_version(), SlidingSyncVersion::None) {
4110 return Ok(false);
4111 }
4112
4113 Ok(self
4114 .supported_versions()
4115 .await?
4116 .features
4117 .contains(&FeatureFlag::from("org.matrix.msc4262")))
4118 }
4119
4120 pub async fn fetch_thread_subscriptions(
4126 &self,
4127 from: Option<String>,
4128 to: Option<String>,
4129 limit: Option<UInt>,
4130 ) -> Result<get_thread_subscriptions_changes::msc4308::Response> {
4131 let request = assign!(get_thread_subscriptions_changes::msc4308::Request::new(), {
4132 from,
4133 to,
4134 limit,
4135 });
4136 Ok(self.send(request).await?)
4137 }
4138
4139 pub(crate) fn thread_subscription_catchup(&self) -> &ThreadSubscriptionCatchup {
4140 self.inner.thread_subscription_catchup.get().unwrap()
4141 }
4142
4143 #[cfg(feature = "unstable-msc4536")]
4144 pub(crate) fn global_profile_fetcher(&self) -> &GlobalProfileFetcher {
4145 self.inner.global_profile_fetcher.get_or_init(|| GlobalProfileFetcher::new(self))
4146 }
4147
4148 pub async fn pause(&self) -> Result<()> {
4166 info!("Client::pause — releasing database resources");
4167
4168 self.send_queue().set_enabled(false).await;
4170
4171 self.base_client().close_stores().await?;
4173
4174 info!("Client::pause — complete, all database connections released");
4175 Ok(())
4176 }
4177
4178 pub async fn resume(&self) -> Result<()> {
4186 info!("Client::resume — re-acquiring database resources");
4187
4188 self.base_client().reopen_stores().await?;
4190
4191 self.send_queue().set_enabled(true).await;
4193
4194 info!("Client::resume — complete");
4195 Ok(())
4196 }
4197
4198 #[doc(hidden)]
4204 pub async fn optimize_stores(&self) -> Result<()> {
4205 trace!("Optimizing state store...");
4206 self.state_store().optimize().await?;
4207
4208 trace!("Optimizing event cache store...");
4209 if let Some(clean_lock) = self.event_cache_store().lock().await?.as_clean() {
4210 clean_lock.optimize().await?;
4211 }
4212
4213 trace!("Optimizing media store...");
4214 self.media_store().lock().await?.optimize().await?;
4215
4216 Ok(())
4217 }
4218
4219 pub async fn get_store_sizes(&self) -> Result<StoreSizes> {
4221 #[cfg(feature = "e2e-encryption")]
4222 let crypto_store_size = if let Some(olm_machine) = self.olm_machine().await.as_ref()
4223 && let Ok(Some(store_size)) = olm_machine.store().get_size().await
4224 {
4225 Some(store_size)
4226 } else {
4227 None
4228 };
4229 #[cfg(not(feature = "e2e-encryption"))]
4230 let crypto_store_size = None;
4231
4232 let state_store_size = self.state_store().get_size().await.ok().flatten();
4233
4234 let event_cache_store_size = if let Some(clean_lock) =
4235 self.event_cache_store().lock().await?.as_clean()
4236 && let Ok(Some(store_size)) = clean_lock.get_size().await
4237 {
4238 Some(store_size)
4239 } else {
4240 None
4241 };
4242
4243 let media_store_size = self.media_store().lock().await?.get_size().await.ok().flatten();
4244
4245 #[cfg(feature = "experimental-search")]
4246 let search_index_size = self.search_index().size();
4247 #[cfg(not(feature = "experimental-search"))]
4248 let search_index_size = None;
4249
4250 Ok(StoreSizes {
4251 crypto_store: crypto_store_size,
4252 state_store: state_store_size,
4253 event_cache_store: event_cache_store_size,
4254 media_store: media_store_size,
4255 search_index: search_index_size,
4256 })
4257 }
4258
4259 pub fn task_monitor(&self) -> &TaskMonitor {
4262 &self.inner.task_monitor
4263 }
4264
4265 #[cfg(feature = "e2e-encryption")]
4268 pub fn subscribe_to_duplicate_key_upload_errors(
4269 &self,
4270 ) -> broadcast::Receiver<Option<DuplicateOneTimeKeyErrorMessage>> {
4271 self.inner.duplicate_key_upload_error_sender.subscribe()
4272 }
4273
4274 #[cfg(feature = "e2e-encryption")]
4279 pub async fn get_pending_key_bundle_details_for_room(
4280 &self,
4281 room_id: &RoomId,
4282 ) -> Result<Option<RoomPendingKeyBundleDetails>> {
4283 Ok(self.base_client().get_pending_key_bundle_details_for_room(room_id).await?)
4284 }
4285
4286 pub fn dm_room_definition(&self) -> &DmRoomDefinition {
4289 &self.inner.base_client.dm_room_definition
4290 }
4291
4292 pub async fn set_media_fetcher(&self, media_fetcher: Arc<dyn MediaFetcher>) {
4295 *self.inner.media_fetcher.write().await = media_fetcher;
4296 }
4297
4298 pub async fn get_media_fetcher(&self) -> Arc<dyn MediaFetcher> {
4301 self.inner.media_fetcher.read().await.clone()
4302 }
4303}
4304
4305#[derive(Debug, Clone)]
4308pub struct StoreSizes {
4309 pub crypto_store: Option<usize>,
4311 pub state_store: Option<usize>,
4313 pub event_cache_store: Option<usize>,
4315 pub media_store: Option<usize>,
4317 pub search_index: Option<usize>,
4319}
4320
4321#[derive(Debug, Clone)]
4325pub struct ToDeviceMessage {
4326 pub raw: Raw<AnyToDeviceEvent>,
4328
4329 pub encryption_info: Option<EncryptionInfo>,
4335}
4336
4337pub(crate) fn is_internal_to_device_type(event_type: &ToDeviceEventType) -> bool {
4344 matches!(
4345 event_type.to_string().as_str(),
4346 "m.dummy"
4347 | "m.room_key"
4348 | "m.room_key_request"
4349 | "m.forwarded_room_key"
4350 | "m.key.verification.request"
4351 | "m.key.verification.ready"
4352 | "m.key.verification.start"
4353 | "m.key.verification.cancel"
4354 | "m.key.verification.accept"
4355 | "m.key.verification.key"
4356 | "m.key.verification.mac"
4357 | "m.key.verification.done"
4358 | "m.secret.request"
4359 | "m.secret.send"
4360 | "m.room.encrypted"
4361 )
4362}
4363
4364#[cfg(any(feature = "testing", test))]
4365impl Client {
4366 #[cfg(feature = "e2e-encryption")]
4368 pub async fn update_tracked_users_for_testing(
4369 &self,
4370 user_ids: impl IntoIterator<Item = &UserId>,
4371 ) {
4372 let olm = self.olm_machine().await;
4373 let olm = olm.as_ref().unwrap();
4374 olm.update_tracked_users(user_ids).await.unwrap();
4375 }
4376}
4377
4378#[derive(Clone, Debug)]
4386pub struct WeakClient {
4387 client: Weak<ClientInner>,
4388}
4389
4390impl WeakClient {
4391 pub(crate) fn from_inner(client: &Arc<ClientInner>) -> Self {
4393 Self { client: Arc::downgrade(client) }
4394 }
4395
4396 pub fn get(&self) -> Option<Client> {
4398 self.client.upgrade().map(|inner| Client { inner })
4399 }
4400
4401 pub fn strong_count(&self) -> usize {
4404 self.client.strong_count()
4405 }
4406}
4407
4408#[derive(Debug, Clone, Default)]
4410struct PreJoinRoomInfo {
4411 pub inviter: Option<RoomMember>,
4413}
4414
4415#[cfg(all(test, not(target_family = "wasm")))]
4417pub(crate) mod tests {
4418 use std::{
4419 pin::pin,
4420 sync::{
4421 Arc,
4422 atomic::{AtomicU32, Ordering},
4423 },
4424 time::Duration,
4425 };
4426
4427 use assert_matches::assert_matches;
4428 use eyeball::SharedObservable;
4429 use futures_util::{FutureExt, StreamExt};
4430 use js_int::{UInt, uint};
4431 use matrix_sdk_base::{
4432 RoomInfoNotableUpdateReasons, RoomState,
4433 read_receipts::ReadReceipts,
4434 store::{MemoryStore, StoreConfig},
4435 ttl::TtlValue,
4436 };
4437 use matrix_sdk_test::{
4438 JoinedRoomBuilder, SyncResponseBuilder, TestRoom, User, async_test,
4439 event_factory::EventFactory,
4440 };
4441 use strass::assert_let;
4442 #[cfg(target_family = "wasm")]
4443 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
4444
4445 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
4446 use ruma::{
4447 RoomId, ServerName, UserId,
4448 api::{
4449 FeatureFlag, MatrixVersion,
4450 client::{
4451 room::create_room::v3::Request as CreateRoomRequest,
4452 rtc::{LiveKitRtcTransport, RtcTransport},
4453 },
4454 },
4455 assign,
4456 events::{
4457 ignored_user_list::IgnoredUserListEventContent,
4458 invite_permission_config::{
4459 InvitePermissionAction, InvitePermissionConfigEventContent,
4460 },
4461 media_preview_config::{InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews},
4462 },
4463 owned_device_id,
4464 presence::PresenceState,
4465 room_alias_id,
4466 };
4467 use serde_json::json;
4468 use stream_assert::{assert_next_matches, assert_pending};
4469 use tokio::{
4470 spawn,
4471 time::{sleep, timeout},
4472 };
4473 use url::Url;
4474
4475 use super::Client;
4476 use crate::{
4477 Error, Result, TransmissionProgress, assert_next_eq_with_timeout,
4478 client::{caches::CachedValue, futures::SendMediaUploadRequest},
4479 config::{RequestConfig, SyncSettings},
4480 futures::SendRequest,
4481 media::MediaError,
4482 test_utils::{client::MockClientBuilder, mocks::MatrixMockServer},
4483 };
4484
4485 #[async_test]
4486 async fn test_sync_presence_is_shared_by_client_clones_and_notification_child() {
4487 let client = MockClientBuilder::new(None).build().await;
4488 let clone = client.clone();
4489 let notification_client =
4490 client.notification_client(CrossProcessLockConfig::SingleProcess).await.unwrap();
4491
4492 assert_eq!(client.sync_presence(), PresenceState::Online);
4493 assert_eq!(clone.sync_presence(), PresenceState::Online);
4494 assert_eq!(notification_client.sync_presence(), PresenceState::Online);
4495
4496 client
4497 .set_presence(PresenceState::Unavailable, None, false)
4498 .await
4499 .expect("presence should update");
4500
4501 assert_eq!(client.sync_presence(), PresenceState::Unavailable);
4502 assert_eq!(clone.sync_presence(), PresenceState::Unavailable);
4503 assert_eq!(notification_client.sync_presence(), PresenceState::Unavailable);
4504
4505 notification_client
4506 .set_presence(PresenceState::Offline, None, false)
4507 .await
4508 .expect("presence should update");
4509
4510 assert_eq!(client.sync_presence(), PresenceState::Offline);
4511 assert_eq!(clone.sync_presence(), PresenceState::Offline);
4512 assert_eq!(notification_client.sync_presence(), PresenceState::Offline);
4513 }
4514
4515 #[cfg(feature = "e2e-encryption")]
4518 #[async_test]
4519 async fn test_disabled_event_cache_is_inherited_by_notification_client() {
4520 let server = MatrixMockServer::new().await;
4521 let client = server
4522 .client_builder()
4523 .on_builder(|builder| builder.with_event_cache(false))
4524 .build()
4525 .await;
4526 let notification_client =
4527 client.notification_client(CrossProcessLockConfig::SingleProcess).await.unwrap();
4528 assert!(client.event_cache().is_none());
4529 assert!(notification_client.event_cache().is_none());
4530 }
4531
4532 #[async_test]
4533 async fn test_sync_once_uses_client_sync_presence_unless_overridden() {
4534 let server = MatrixMockServer::new().await;
4535 let client = server.client_builder().build().await;
4536
4537 {
4538 let _sync_guard = server
4539 .mock_sync()
4540 .set_presence_missing()
4541 .ok(|_| {})
4542 .expect(1)
4543 .mount_as_scoped()
4544 .await;
4545
4546 client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4547 }
4548
4549 client
4550 .set_presence(PresenceState::Offline, None, false)
4551 .await
4552 .expect("presence should update");
4553
4554 {
4555 let _sync_guard = server
4556 .mock_sync()
4557 .set_presence("offline")
4558 .ok(|_| {})
4559 .expect(1)
4560 .mount_as_scoped()
4561 .await;
4562
4563 client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4564 }
4565
4566 {
4567 let _sync_guard = server
4568 .mock_sync()
4569 .set_presence("unavailable")
4570 .ok(|_| {})
4571 .expect(1)
4572 .mount_as_scoped()
4573 .await;
4574
4575 client
4576 .sync_once(SyncSettings::new().set_presence(PresenceState::Unavailable))
4577 .await
4578 .expect("sync should succeed");
4579 }
4580 }
4581
4582 #[async_test]
4583 async fn test_set_presence_sends_presence_status_update() {
4584 let server = MatrixMockServer::new().await;
4585 let client = server.client_builder().build().await;
4586
4587 let state = PresenceState::Online;
4588 let status_msg = "Here".to_owned();
4589
4590 server.mock_update_presence(&state, Some(&status_msg)).ok().expect(1).mount().await;
4591
4592 client
4593 .set_presence(state.clone(), Some(status_msg), true)
4594 .await
4595 .expect("presence update should succeed");
4596
4597 assert_eq!(client.sync_presence(), state);
4598 }
4599
4600 #[async_test]
4601 async fn test_set_presence_requires_authentication() {
4602 let client = MockClientBuilder::new(None).unlogged().build().await;
4603
4604 assert_matches!(
4605 client.set_presence(PresenceState::Unavailable, None, true).await,
4606 Err(Error::AuthenticationRequired)
4607 );
4608 }
4609
4610 #[async_test]
4611 async fn test_set_presence_without_immediate_does_not_require_authentication() {
4612 let client = MockClientBuilder::new(None).unlogged().build().await;
4613
4614 client
4615 .set_presence(PresenceState::Offline, None, false)
4616 .await
4617 .expect("presence should update");
4618
4619 assert_eq!(client.sync_presence(), PresenceState::Offline);
4620 }
4621
4622 #[async_test]
4623 async fn test_account_data() {
4624 let server = MatrixMockServer::new().await;
4625 let client = server.client_builder().build().await;
4626
4627 let f = EventFactory::new();
4628 server
4629 .mock_sync()
4630 .ok_and_run(&client, |builder| {
4631 builder.add_global_account_data(f.ignored_user_list([User::Bob.into()]));
4632 })
4633 .await;
4634
4635 let content = client
4636 .account()
4637 .account_data::<IgnoredUserListEventContent>()
4638 .await
4639 .unwrap()
4640 .unwrap()
4641 .deserialize()
4642 .unwrap();
4643
4644 assert_eq!(content.ignored_users.len(), 1);
4645 }
4646
4647 #[async_test]
4648 async fn test_successful_discovery() {
4649 let server = MatrixMockServer::new().await;
4651 let server_url = server.uri();
4652
4653 let homeserver = MatrixMockServer::new().await;
4655 let homeserver_url = homeserver.uri();
4656
4657 let domain = server_url.strip_prefix("http://").unwrap();
4659 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4660
4661 server
4663 .mock_well_known()
4664 .ok_with_homeserver_url(&homeserver_url)
4665 .mock_once()
4666 .named("well-known")
4667 .mount()
4668 .await;
4669
4670 homeserver.mock_versions().ok().mock_once().named("versions").mount().await;
4673
4674 let client = Client::builder()
4675 .insecure_server_name_no_tls(alice.server_name())
4676 .build()
4677 .await
4678 .unwrap();
4679
4680 assert_eq!(client.server().unwrap(), Url::parse(&server_url).unwrap());
4681 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4682 client.server_versions().await.unwrap();
4683 }
4684
4685 #[async_test]
4686 async fn test_homeserver_swap_resets_server_field() {
4687 let homeserver = MatrixMockServer::new().await;
4688 let homeserver_url = homeserver.uri();
4689
4690 let domain = homeserver_url.strip_prefix("http://").unwrap();
4691 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4692
4693 homeserver.mock_well_known().ok().mock_once().named("well-known").mount().await;
4694
4695 let client = Client::builder()
4696 .insecure_server_name_no_tls(alice.server_name())
4697 .build()
4698 .await
4699 .unwrap();
4700
4701 assert_eq!(client.server().unwrap(), Url::parse(&homeserver_url).unwrap());
4702 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4703
4704 let new_server = Url::parse("http://example.org").unwrap();
4705 client.set_homeserver(new_server.clone());
4708
4709 assert_eq!(client.homeserver(), new_server);
4711 assert!(client.server().is_none())
4714 }
4715
4716 #[async_test]
4717 async fn test_discovery_broken_server() {
4718 let server = MatrixMockServer::new().await;
4719 let server_url = server.uri();
4720 let domain = server_url.strip_prefix("http://").unwrap();
4721 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4722
4723 server.mock_well_known().error404().mock_once().named("well-known").mount().await;
4724
4725 assert!(
4726 Client::builder()
4727 .insecure_server_name_no_tls(alice.server_name())
4728 .build()
4729 .await
4730 .is_err(),
4731 "Creating a client from a user ID should fail when the .well-known request fails."
4732 );
4733 }
4734
4735 #[async_test]
4736 async fn test_room_creation() {
4737 let server = MatrixMockServer::new().await;
4738 let client = server.client_builder().build().await;
4739
4740 let f = EventFactory::new().sender(User::Alice);
4741
4742 let room = server
4743 .sync_room(
4744 &client,
4745 JoinedRoomBuilder::default()
4746 .add_state_event(f.member(User::Alice).display_name("example"))
4747 .add_state_event(f.power_levels()),
4748 )
4749 .await;
4750
4751 assert_eq!(room.state(), RoomState::Joined);
4752 }
4753
4754 #[async_test]
4755 async fn test_retry_limit_http_requests() {
4756 let server = MatrixMockServer::new().await;
4757 let client = server
4758 .client_builder()
4759 .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(4)))
4760 .build()
4761 .await;
4762
4763 assert!(client.request_config().retry_limit.unwrap() == 4);
4764
4765 server.mock_who_am_i().error500().expect(4).mount().await;
4766
4767 client.whoami().await.unwrap_err();
4768 }
4769
4770 #[async_test]
4771 async fn test_retry_timeout_http_requests() {
4772 let retry_timeout = Duration::from_secs(5);
4774 let server = MatrixMockServer::new().await;
4775 let client = server
4776 .client_builder()
4777 .on_builder(|builder| {
4778 builder.request_config(RequestConfig::new().max_retry_time(retry_timeout))
4779 })
4780 .build()
4781 .await;
4782
4783 assert!(client.request_config().max_retry_time.unwrap() == retry_timeout);
4784
4785 server.mock_login().error500().expect(2..).mount().await;
4786
4787 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4788 }
4789
4790 #[async_test]
4791 async fn test_short_retry_initial_http_requests() {
4792 let server = MatrixMockServer::new().await;
4793 let client = server
4794 .client_builder()
4795 .on_builder(|builder| builder.request_config(RequestConfig::short_retry()))
4796 .build()
4797 .await;
4798
4799 server.mock_login().error500().expect(3..).mount().await;
4800
4801 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4802 }
4803
4804 #[async_test]
4805 async fn test_no_retry_http_requests() {
4806 let server = MatrixMockServer::new().await;
4807 let client = server.client_builder().build().await;
4808
4809 server.mock_devices().error500().mock_once().mount().await;
4810
4811 client.devices().await.unwrap_err();
4812 }
4813
4814 #[async_test]
4815 async fn test_set_homeserver() {
4816 let client = MockClientBuilder::new(None).build().await;
4817 assert_eq!(client.homeserver().as_ref(), "http://localhost/");
4818
4819 let homeserver = Url::parse("http://example.com/").unwrap();
4820 client.set_homeserver(homeserver.clone());
4821 assert_eq!(client.homeserver(), homeserver);
4822 }
4823
4824 #[async_test]
4825 async fn test_search_user_request() {
4826 let server = MatrixMockServer::new().await;
4827 let client = server.client_builder().build().await;
4828
4829 server.mock_user_directory().ok().mock_once().mount().await;
4830
4831 let response = client.search_users("test", 50).await.unwrap();
4832 assert_eq!(response.results.len(), 1);
4833 let result = &response.results[0];
4834 assert_eq!(result.user_id.to_string(), "@test:example.me");
4835 assert_eq!(result.display_name.clone().unwrap(), "Test");
4836 assert_eq!(result.avatar_url.clone().unwrap().to_string(), "mxc://example.me/someid");
4837 assert!(!response.limited);
4838 }
4839
4840 #[async_test]
4841 async fn test_request_unstable_features() {
4842 let server = MatrixMockServer::new().await;
4843 let client = server.client_builder().no_server_versions().build().await;
4844
4845 server
4846 .mock_versions()
4847 .with_feature("org.matrix.e2e_cross_signing", true)
4848 .ok()
4849 .mock_once()
4850 .mount()
4851 .await;
4852
4853 let unstable_features = client.unstable_features().await.unwrap();
4854 assert!(unstable_features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4855 assert!(!unstable_features.contains(&FeatureFlag::from("you.shall.pass")));
4856 }
4857
4858 #[async_test]
4859 async fn test_can_homeserver_push_encrypted_event_to_device() {
4860 let server = MatrixMockServer::new().await;
4861 let client = server.client_builder().no_server_versions().build().await;
4862
4863 server.mock_versions().with_push_encrypted_events().ok().mock_once().mount().await;
4864
4865 let msc4028_enabled = client.can_homeserver_push_encrypted_event_to_device().await.unwrap();
4866 assert!(msc4028_enabled);
4867 }
4868
4869 #[cfg(feature = "unstable-msc4354")]
4870 #[async_test]
4871 async fn test_supports_sticky_events() {
4872 let server = MatrixMockServer::new().await;
4874 let client = server.client_builder().no_server_versions().build().await;
4875
4876 server.mock_versions().ok().mock_once().mount().await;
4877
4878 assert!(!client.supports_sticky_events().await.unwrap());
4879
4880 let server = MatrixMockServer::new().await;
4882 let client = server.client_builder().no_server_versions().build().await;
4883
4884 server.mock_versions().with_sticky_events().ok().mock_once().mount().await;
4885
4886 assert!(client.supports_sticky_events().await.unwrap());
4887 }
4888
4889 #[async_test]
4890 async fn test_recently_visited_rooms() {
4891 let room_1 = TestRoom::Default;
4892 let room_2 = TestRoom::V12;
4893
4894 let client = MockClientBuilder::new(None).unlogged().build().await;
4896 assert_matches!(
4897 client.account().track_recently_visited_room(room_1.into()).await,
4898 Err(Error::AuthenticationRequired)
4899 );
4900
4901 let client = MockClientBuilder::new(None).build().await;
4902 let account = client.account();
4903
4904 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 0);
4906
4907 account.track_recently_visited_room(room_1.into()).await.unwrap();
4909 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), &[room_1]);
4910
4911 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), &[room_1]);
4913
4914 account.track_recently_visited_room(room_1.into()).await.unwrap();
4916 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), &[room_1]);
4917
4918 account.track_recently_visited_room(room_2.into()).await.unwrap();
4920 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), &[room_2, room_1]);
4921
4922 account.track_recently_visited_room(room_1.into()).await.unwrap();
4925 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), &[room_1, room_2]);
4926
4927 for n in 0..20 {
4929 account
4930 .track_recently_visited_room(RoomId::parse(format!("!{n}:localhost")).unwrap())
4931 .await
4932 .unwrap();
4933 }
4934
4935 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 20);
4936
4937 let rooms = account.get_recently_visited_rooms().await.unwrap();
4939 assert!(!rooms.contains(&room_1.into()));
4940 assert!(!rooms.contains(&room_2.into()));
4941
4942 assert_eq!(rooms.first().unwrap(), "!19:localhost");
4944 }
4945
4946 #[async_test]
4947 async fn test_client_no_cycle_with_event_cache() {
4948 let client = MockClientBuilder::new(None).build().await;
4949
4950 sleep(Duration::from_secs(1)).await;
4952
4953 let weak_client = client.downgrade();
4954 assert_eq!(weak_client.strong_count(), 1);
4955
4956 {
4957 let response = SyncResponseBuilder::default()
4959 .add_joined_room(JoinedRoomBuilder::default())
4960 .build_sync_response();
4961 client.inner.base_client.receive_sync_response(response).await.unwrap();
4962
4963 let (_room_event_cache, _drop_handles) =
4964 client.event_cache().unwrap().room(TestRoom::Default.into()).await.unwrap();
4965 }
4966
4967 drop(client);
4968
4969 sleep(Duration::from_secs(1)).await;
4971
4972 assert_eq!(weak_client.strong_count(), 0);
4974 let client = weak_client.get();
4975 assert!(
4976 client.is_none(),
4977 "too many strong references to the client: {}",
4978 Arc::strong_count(&client.unwrap().inner)
4979 );
4980 }
4981
4982 #[async_test]
4983 async fn test_supported_versions_caching() {
4984 let server = MatrixMockServer::new().await;
4985
4986 let versions_mock = server
4987 .mock_versions()
4988 .expect_default_access_token()
4989 .with_feature("org.matrix.e2e_cross_signing", true)
4990 .ok()
4991 .named("first versions mock")
4992 .expect(1)
4993 .mount_as_scoped()
4994 .await;
4995
4996 let memory_store = Arc::new(MemoryStore::new());
4997 let client = server
4998 .client_builder()
4999 .no_server_versions()
5000 .on_builder(|builder| {
5001 builder.store_config(
5002 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
5003 .state_store(memory_store.clone()),
5004 )
5005 })
5006 .build()
5007 .await;
5008
5009 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
5010
5011 assert_matches!(client.supported_versions_cached().await, Ok(Some(_)));
5013 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
5015
5016 drop(client);
5017
5018 let client = server
5019 .client_builder()
5020 .no_server_versions()
5021 .on_builder(|builder| {
5022 builder.store_config(
5023 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
5024 .state_store(memory_store.clone()),
5025 )
5026 })
5027 .build()
5028 .await;
5029
5030 assert!(
5032 client
5033 .unstable_features()
5034 .await
5035 .unwrap()
5036 .contains(&FeatureFlag::from("org.matrix.e2e_cross_signing"))
5037 );
5038
5039 let supported = client.supported_versions().await.unwrap();
5040 assert!(supported.versions.contains(&MatrixVersion::V1_0));
5041 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
5042
5043 let supported = client.supported_versions().await.unwrap();
5045 assert!(supported.versions.contains(&MatrixVersion::V1_0));
5046 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
5047
5048 drop(versions_mock);
5049
5050 client.reset_supported_versions().await.unwrap();
5053
5054 server.mock_versions().ok().expect(2).named("second versions mock").mount().await;
5055
5056 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
5058 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
5060 assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
5061
5062 let supported_versions = client.supported_versions_cached().await.unwrap().unwrap();
5064 let mut ttl_value = TtlValue::new(supported_versions);
5065 ttl_value.expire();
5066 client.inner.caches.supported_versions.set_value(ttl_value);
5067
5068 client.supported_versions_cached().await.unwrap().unwrap();
5070
5071 sleep(Duration::from_secs(1)).await;
5074 assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
5075 }
5076
5077 #[async_test]
5078 async fn test_well_known_caching() {
5079 let server = MatrixMockServer::new().await;
5080 let server_url = server.uri();
5081 let domain = server_url.strip_prefix("http://").unwrap();
5082 let server_name = <&ServerName>::try_from(domain).unwrap();
5083 let rtc_foci = vec![RtcTransport::LiveKit(LiveKitRtcTransport::new(
5084 "wss://livekit.example.com".to_owned(),
5085 ))];
5086
5087 let well_known_mock = server
5088 .mock_well_known()
5089 .ok()
5090 .named("well known mock")
5091 .expect(2) .mount_as_scoped()
5093 .await;
5094
5095 let memory_store = Arc::new(MemoryStore::new());
5096 let client = Client::builder()
5097 .insecure_server_name_no_tls(server_name)
5098 .store_config(
5099 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
5100 .state_store(memory_store.clone()),
5101 )
5102 .build()
5103 .await
5104 .unwrap();
5105
5106 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5107
5108 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5110
5111 drop(client);
5112
5113 let client = server
5114 .client_builder()
5115 .no_server_versions()
5116 .on_builder(|builder| {
5117 builder.store_config(
5118 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
5119 .state_store(memory_store.clone()),
5120 )
5121 })
5122 .build()
5123 .await;
5124
5125 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5127
5128 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5130
5131 drop(well_known_mock);
5132
5133 client.reset_well_known().await.unwrap();
5136
5137 server.mock_well_known().ok().named("second well known mock").expect(2).mount().await;
5138
5139 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5141 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5143
5144 let well_known = client.well_known().await;
5146 let mut ttl_value = TtlValue::new(well_known);
5147 ttl_value.expire();
5148 client.inner.caches.well_known.set_value(ttl_value);
5149
5150 client.well_known().await;
5152
5153 sleep(Duration::from_secs(5)).await;
5158 assert_matches!(client.inner.caches.well_known.value(), CachedValue::Cached(value) if !value.has_expired());
5159 }
5160
5161 #[async_test]
5162 async fn test_rtc_transports_caching() {
5163 let server = MatrixMockServer::new().await;
5164 let transports = vec![RtcTransport::LiveKit(LiveKitRtcTransport::new(
5165 "wss://livekit.example.com".to_owned(),
5166 ))];
5167
5168 let transports_mock = server
5169 .mock_get_rtc_transports()
5170 .ok()
5171 .named("first transports mock")
5172 .expect(1)
5173 .mount_as_scoped()
5174 .await;
5175
5176 let client = server.client_builder().build().await;
5177
5178 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
5180 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
5182 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
5183
5184 drop(transports_mock);
5185
5186 client.reset_rtc_transports();
5188
5189 server
5190 .mock_get_rtc_transports()
5191 .ok()
5192 .named("second transports mock")
5193 .expect(2)
5194 .mount()
5195 .await;
5196
5197 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
5199 assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
5201
5202 let mut ttl_value = TtlValue::new(Some(transports.clone()));
5204 ttl_value.expire();
5205 client.inner.caches.rtc_transports.set_value(ttl_value);
5206
5207 client.rtc_transports().await.unwrap();
5209
5210 sleep(Duration::from_secs(1)).await;
5213 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
5214 }
5215
5216 #[async_test]
5217 async fn test_rtc_transports_unsupported_caching() {
5218 let server = MatrixMockServer::new().await;
5219
5220 server
5225 .mock_get_rtc_transports()
5226 .error_unrecognized()
5227 .named("unrecognized transports mock")
5228 .expect(1)
5229 .mount()
5230 .await;
5231
5232 let client = server.client_builder().build().await;
5233
5234 assert_eq!(client.rtc_transports().await.unwrap(), None);
5237 assert_eq!(client.rtc_transports().await.unwrap(), None);
5240 assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
5241 }
5242
5243 async fn mock_rtc_transports_endpoint(
5247 server: &MatrixMockServer,
5248 supported: bool,
5249 ) -> wiremock::MockGuard {
5250 let endpoint = server.mock_get_rtc_transports();
5251
5252 let mock = if supported { endpoint.ok() } else { endpoint.error_unrecognized() };
5253
5254 mock.named("transports mock").expect(1).mount_as_scoped().await
5255 }
5256
5257 #[async_test]
5258 async fn test_discover_rtc_transports_prefers_the_endpoint() {
5259 let server = MatrixMockServer::new().await;
5260 let transports = vec![RtcTransport::LiveKit(LiveKitRtcTransport::new(
5261 "wss://livekit.example.com".to_owned(),
5262 ))];
5263
5264 let _transports_mock = mock_rtc_transports_endpoint(&server, true).await;
5265
5266 let _well_known_mock = server
5269 .mock_well_known()
5270 .ok()
5271 .named("well-known mock")
5272 .expect(0)
5273 .mount_as_scoped()
5274 .await;
5275
5276 let client = server.client_builder().build().await;
5277
5278 assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(transports));
5279 }
5280
5281 #[async_test]
5282 async fn test_discover_rtc_transports_falls_back_to_well_known() {
5283 let server = MatrixMockServer::new().await;
5284 let rtc_foci = vec![RtcTransport::LiveKit(LiveKitRtcTransport::new(
5286 "wss://livekit.example.com".to_owned(),
5287 ))];
5288
5289 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
5290
5291 let _well_known_mock = server
5292 .mock_well_known()
5293 .ok()
5294 .named("well-known mock")
5295 .expect(1)
5296 .mount_as_scoped()
5297 .await;
5298
5299 let client = server.client_builder().build().await;
5300
5301 assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(rtc_foci));
5304 }
5305
5306 async fn mock_well_known_never_called(server: &MatrixMockServer) -> wiremock::MockGuard {
5308 server.mock_well_known().ok().named("well-known mock").expect(0).mount_as_scoped().await
5309 }
5310
5311 #[async_test]
5312 async fn test_well_known_lookup_disabled() {
5313 let server = MatrixMockServer::new().await;
5314
5315 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
5316
5317 let _well_known_mock = mock_well_known_never_called(&server).await;
5319
5320 let client = server
5322 .client_builder()
5323 .on_builder(|builder| builder.disable_well_known_lookup(true))
5324 .build()
5325 .await;
5326
5327 assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
5330 assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
5332 assert!(client.tile_server().await.is_none());
5333 assert!(client.fetch_client_well_known().await.is_none());
5334 }
5335
5336 #[async_test]
5337 async fn test_well_known_lookup_disabled_after_build() {
5338 let server = MatrixMockServer::new().await;
5339
5340 let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
5341
5342 let _well_known_mock = mock_well_known_never_called(&server).await;
5344
5345 let client = server.client_builder().build().await;
5347 client.disable_well_known_lookup(true);
5348
5349 assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
5352 assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
5354 assert!(client.tile_server().await.is_none());
5355 assert!(client.fetch_client_well_known().await.is_none());
5356 }
5357
5358 #[async_test]
5359 async fn test_missing_well_known_caching() {
5360 let server = MatrixMockServer::new().await;
5361 let rtc_foci: Vec<RtcTransport> = vec![];
5362
5363 let well_known_mock = server
5364 .mock_well_known()
5365 .error_unrecognized()
5366 .named("first well-known mock")
5367 .expect(1)
5368 .mount_as_scoped()
5369 .await;
5370
5371 let memory_store = Arc::new(MemoryStore::new());
5372 let client = server
5373 .client_builder()
5374 .on_builder(|builder| {
5375 builder.store_config(
5376 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
5377 .state_store(memory_store.clone()),
5378 )
5379 })
5380 .build()
5381 .await;
5382
5383 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5384
5385 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5387
5388 drop(client);
5389
5390 let client = server
5391 .client_builder()
5392 .on_builder(|builder| {
5393 builder.store_config(
5394 StoreConfig::new(CrossProcessLockConfig::SingleProcess)
5395 .state_store(memory_store.clone()),
5396 )
5397 })
5398 .build()
5399 .await;
5400
5401 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5403
5404 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5406
5407 drop(well_known_mock);
5408
5409 client.reset_well_known().await.unwrap();
5412
5413 server
5414 .mock_well_known()
5415 .error_unrecognized()
5416 .expect(1)
5417 .named("second well-known mock")
5418 .mount()
5419 .await;
5420
5421 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5423 assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
5425 }
5426
5427 #[async_test]
5428 async fn test_no_network_doesnt_cause_infinite_retries() {
5429 let client = MockClientBuilder::new(None)
5431 .on_builder(|builder| builder.request_config(RequestConfig::new()))
5432 .build()
5433 .await;
5434
5435 client.whoami().await.unwrap_err();
5438 }
5439
5440 #[async_test]
5441 async fn test_await_room_remote_echo_returns_the_room_if_it_was_already_synced() {
5442 let server = MatrixMockServer::new().await;
5443 let client = server.client_builder().build().await;
5444
5445 let room_id = TestRoom::Default;
5446
5447 server
5448 .mock_sync()
5449 .ok_and_run(&client, |builder| {
5450 builder.add_joined_room(JoinedRoomBuilder::default());
5451 })
5452 .await;
5453
5454 let room = client.await_room_remote_echo(room_id.into()).now_or_never().unwrap();
5455 assert_eq!(room.room_id(), room_id);
5456 }
5457
5458 #[async_test]
5459 async fn test_await_room_remote_echo_returns_the_room_when_it_is_ready() {
5460 let server = MatrixMockServer::new().await;
5461 let client = server.client_builder().build().await;
5462
5463 let room_id = TestRoom::Default;
5464
5465 let client = Arc::new(client);
5466
5467 spawn({
5470 let client = client.clone();
5471 async move {
5472 sleep(Duration::from_millis(100)).await;
5473
5474 server
5475 .mock_sync()
5476 .ok_and_run(&client, |builder| {
5477 builder.add_joined_room(JoinedRoomBuilder::default());
5478 })
5479 .await;
5480 }
5481 });
5482
5483 let room = timeout(Duration::from_secs(10), client.await_room_remote_echo(room_id.into()))
5484 .await
5485 .unwrap();
5486 assert_eq!(room.room_id(), room_id);
5487 }
5488
5489 #[async_test]
5490 async fn test_await_room_remote_echo_will_timeout_if_no_room_is_found() {
5491 let client = MockClientBuilder::new(None).build().await;
5492
5493 timeout(Duration::from_secs(1), client.await_room_remote_echo(TestRoom::Default.into()))
5496 .await
5497 .unwrap_err();
5498 }
5499
5500 #[async_test]
5501 async fn test_await_room_remote_echo_will_timeout_if_room_is_found_but_not_synced() {
5502 let server = MatrixMockServer::new().await;
5503 let client = server.client_builder().build().await;
5504
5505 server.mock_create_room().ok().mount().await;
5506
5507 let room = client
5509 .create_room(assign!(CreateRoomRequest::new(), {
5510 invite: vec![],
5511 is_direct: false,
5512 }))
5513 .await
5514 .unwrap();
5515
5516 timeout(Duration::from_secs(1), client.await_room_remote_echo(room.room_id()))
5518 .await
5519 .unwrap_err();
5520 }
5521
5522 #[async_test]
5523 async fn test_is_room_alias_available_if_alias_is_not_resolved() {
5524 let server = MatrixMockServer::new().await;
5525 let client = server.client_builder().build().await;
5526
5527 server.mock_room_directory_resolve_alias().not_found().expect(1).mount().await;
5528
5529 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5530 assert_matches!(ret, Ok(true));
5531 }
5532
5533 #[async_test]
5534 async fn test_is_room_alias_available_if_alias_is_resolved() {
5535 let server = MatrixMockServer::new().await;
5536 let client = server.client_builder().build().await;
5537
5538 server
5539 .mock_room_directory_resolve_alias()
5540 .ok("!some_room_id:matrix.org", Vec::new())
5541 .expect(1)
5542 .mount()
5543 .await;
5544
5545 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5546 assert_matches!(ret, Ok(false));
5547 }
5548
5549 #[async_test]
5550 async fn test_is_room_alias_available_if_error_found() {
5551 let server = MatrixMockServer::new().await;
5552 let client = server.client_builder().build().await;
5553
5554 server.mock_room_directory_resolve_alias().error500().expect(1).mount().await;
5555
5556 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5557 assert_matches!(ret, Err(_));
5558 }
5559
5560 #[async_test]
5561 async fn test_create_room_alias() {
5562 let server = MatrixMockServer::new().await;
5563 let client = server.client_builder().build().await;
5564
5565 server.mock_room_directory_create_room_alias().ok().expect(1).mount().await;
5566
5567 let ret = client
5568 .create_room_alias(room_alias_id!("#some_alias:matrix.org"), TestRoom::Default.into())
5569 .await;
5570 assert_matches!(ret, Ok(()));
5571 }
5572
5573 #[async_test]
5574 async fn test_join_room_by_id_or_alias() {
5575 let server = MatrixMockServer::new().await;
5576 let client = server.client_builder().build().await;
5577
5578 let target_room_id = TestRoom::Default;
5579 let target_alias = room_alias_id!("#some_alias:matrix.org");
5580
5581 server
5582 .mock_room_join_by_id_or_alias(target_room_id)
5583 .ok(target_room_id)
5584 .expect(1)
5585 .mount()
5586 .await;
5587
5588 server
5589 .mock_room_join_by_id_or_alias(target_alias)
5590 .ok(target_room_id)
5591 .expect(1)
5592 .mount()
5593 .await;
5594
5595 server
5596 .mock_room_directory_resolve_alias()
5597 .ok(target_room_id.as_str(), Vec::new())
5598 .mount()
5599 .await;
5600
5601 let ret = client.join_room_by_id_or_alias(target_alias.into(), &[]).await;
5602 assert!(ret.is_ok());
5603
5604 let ret = client.join_room_by_id_or_alias(target_room_id.into(), &[]).await;
5605 assert!(ret.is_ok());
5606 }
5607
5608 #[async_test]
5609 async fn test_room_preview_for_invited_room_hits_summary_endpoint() {
5610 let server = MatrixMockServer::new().await;
5611 let client = server.client_builder().build().await;
5612
5613 let room_id = TestRoom::Default;
5614
5615 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5617
5618 let invited_room =
5620 client.inner.base_client.get_or_create_room(room_id.into(), RoomState::Invited);
5621
5622 let preview = client
5624 .get_room_preview(room_id.into(), Vec::new())
5625 .await
5626 .expect("Room preview should be retrieved");
5627
5628 assert_eq!(invited_room.room_id(), preview.room_id);
5629 }
5630
5631 #[async_test]
5632 async fn test_room_preview_for_left_room_hits_summary_endpoint() {
5633 let server = MatrixMockServer::new().await;
5634 let client = server.client_builder().build().await;
5635
5636 let room_id = TestRoom::Default;
5637
5638 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5640
5641 let left_room =
5643 client.inner.base_client.get_or_create_room(room_id.into(), RoomState::Left);
5644
5645 let preview = client
5647 .get_room_preview(room_id.into(), Vec::new())
5648 .await
5649 .expect("Room preview should be retrieved");
5650
5651 assert_eq!(left_room.room_id(), preview.room_id);
5652 }
5653
5654 #[async_test]
5655 async fn test_room_preview_for_knocked_room_hits_summary_endpoint() {
5656 let server = MatrixMockServer::new().await;
5657 let client = server.client_builder().build().await;
5658
5659 let room_id = TestRoom::Default;
5660
5661 server.mock_room_summary().ok(room_id).mock_once().mount().await;
5663
5664 let knocked_room =
5666 client.inner.base_client.get_or_create_room(room_id.into(), RoomState::Knocked);
5667
5668 let preview = client
5670 .get_room_preview(room_id.into(), Vec::new())
5671 .await
5672 .expect("Room preview should be retrieved");
5673
5674 assert_eq!(knocked_room.room_id(), preview.room_id);
5675 }
5676
5677 #[async_test]
5678 async fn test_room_preview_for_joined_room_retrieves_local_room_info() {
5679 let server = MatrixMockServer::new().await;
5680 let client = server.client_builder().build().await;
5681
5682 let room_id = TestRoom::Default;
5683
5684 server.mock_room_summary().ok(room_id).never().mount().await;
5686
5687 let joined_room =
5689 client.inner.base_client.get_or_create_room(room_id.into(), RoomState::Joined);
5690
5691 let preview = client
5693 .get_room_preview(room_id.into(), Vec::new())
5694 .await
5695 .expect("Room preview should be retrieved");
5696
5697 assert_eq!(joined_room.room_id(), preview.room_id);
5698 }
5699
5700 #[async_test]
5701 async fn test_media_preview_config() {
5702 let server = MatrixMockServer::new().await;
5703 let client = server.client_builder().build().await;
5704
5705 server
5706 .mock_sync()
5707 .ok_and_run(&client, |builder| {
5708 builder.add_custom_global_account_data(json!({
5709 "content": {
5710 "media_previews": "private",
5711 "invite_avatars": "off"
5712 },
5713 "type": "m.media_preview_config"
5714 }));
5715 })
5716 .await;
5717
5718 let (initial_value, stream) =
5719 client.account().observe_media_preview_config().await.unwrap();
5720
5721 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5722 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5723 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5724 let mut stream = pin!(stream);
5725 assert_pending!(stream);
5726
5727 server
5728 .mock_sync()
5729 .ok_and_run(&client, |builder| {
5730 builder.add_custom_global_account_data(json!({
5731 "content": {
5732 "media_previews": "off",
5733 "invite_avatars": "on"
5734 },
5735 "type": "m.media_preview_config"
5736 }));
5737 })
5738 .await;
5739
5740 assert_next_matches!(
5741 stream,
5742 MediaPreviewConfigEventContent {
5743 media_previews: Some(MediaPreviews::Off),
5744 invite_avatars: Some(InviteAvatars::On),
5745 ..
5746 }
5747 );
5748 assert_pending!(stream);
5749 }
5750
5751 #[async_test]
5752 async fn test_unstable_media_preview_config() {
5753 let server = MatrixMockServer::new().await;
5754 let client = server.client_builder().build().await;
5755
5756 server
5757 .mock_sync()
5758 .ok_and_run(&client, |builder| {
5759 builder.add_custom_global_account_data(json!({
5760 "content": {
5761 "media_previews": "private",
5762 "invite_avatars": "off"
5763 },
5764 "type": "io.element.msc4278.media_preview_config"
5765 }));
5766 })
5767 .await;
5768
5769 let (initial_value, stream) =
5770 client.account().observe_media_preview_config().await.unwrap();
5771
5772 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5773 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5774 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5775 let mut stream = pin!(stream);
5776 assert_pending!(stream);
5777
5778 server
5779 .mock_sync()
5780 .ok_and_run(&client, |builder| {
5781 builder.add_custom_global_account_data(json!({
5782 "content": {
5783 "media_previews": "off",
5784 "invite_avatars": "on"
5785 },
5786 "type": "io.element.msc4278.media_preview_config"
5787 }));
5788 })
5789 .await;
5790
5791 assert_next_matches!(
5792 stream,
5793 MediaPreviewConfigEventContent {
5794 media_previews: Some(MediaPreviews::Off),
5795 invite_avatars: Some(InviteAvatars::On),
5796 ..
5797 }
5798 );
5799 assert_pending!(stream);
5800 }
5801
5802 #[async_test]
5803 async fn test_media_preview_config_not_found() {
5804 let server = MatrixMockServer::new().await;
5805 let client = server.client_builder().build().await;
5806
5807 let (initial_value, _) = client.account().observe_media_preview_config().await.unwrap();
5808
5809 assert!(initial_value.is_none());
5810 }
5811
5812 #[async_test]
5813 async fn test_load_or_fetch_max_upload_size_with_auth_matrix_version() {
5814 let server = MatrixMockServer::new().await;
5817 let client = server.client_builder().build().await;
5818
5819 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5820
5821 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5822 client.load_or_fetch_max_upload_size().await.unwrap();
5823
5824 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5825 }
5826
5827 #[async_test]
5828 async fn test_load_or_fetch_max_upload_size_with_auth_stable_feature() {
5829 let server = MatrixMockServer::new().await;
5832 let client = server.client_builder().no_server_versions().build().await;
5833
5834 server
5835 .mock_versions()
5836 .with_versions(vec!["v1.7", "v1.8", "v1.9", "v1.10"])
5837 .with_feature("org.matrix.msc3916.stable", true)
5838 .ok()
5839 .named("versions")
5840 .expect(1)
5841 .mount()
5842 .await;
5843
5844 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5845
5846 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5847 client.load_or_fetch_max_upload_size().await.unwrap();
5848
5849 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5850 }
5851
5852 #[async_test]
5853 async fn test_load_or_fetch_max_upload_size_no_auth() {
5854 let server = MatrixMockServer::new().await;
5857 let client = server.client_builder().no_server_versions().build().await;
5858
5859 server
5860 .mock_versions()
5861 .with_versions(vec!["v1.1"])
5862 .ok()
5863 .named("versions")
5864 .expect(1)
5865 .mount()
5866 .await;
5867
5868 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5869
5870 server.mock_media_config().ok(uint!(2)).mock_once().mount().await;
5871 client.load_or_fetch_max_upload_size().await.unwrap();
5872
5873 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5874 }
5875
5876 #[async_test]
5877 async fn test_uploading_a_too_large_media_file() {
5878 let server = MatrixMockServer::new().await;
5879 let client = server.client_builder().build().await;
5880
5881 server.mock_authenticated_media_config().ok(uint!(1)).mock_once().mount().await;
5882 client.load_or_fetch_max_upload_size().await.unwrap();
5883 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(1));
5884
5885 let data = vec![1, 2];
5886 let upload_request =
5887 ruma::api::client::media::create_content::v3::Request::new(data.clone());
5888 let request = SendRequest {
5889 client: client.clone(),
5890 request: upload_request,
5891 config: None,
5892 send_progress: SharedObservable::new(TransmissionProgress::default()),
5893 };
5894 let media_request = SendMediaUploadRequest::new(request);
5895
5896 let error = media_request.await.err();
5897 assert_let!(Some(Error::Media(MediaError::MediaTooLargeToUpload { max, current })) = error);
5898 assert_eq!(max, uint!(1));
5899 assert_eq!(current, UInt::new_wrapping(data.len() as u64));
5900 }
5901
5902 #[async_test]
5903 async fn test_dont_ignore_timeout_on_first_sync() {
5904 let server = MatrixMockServer::new().await;
5905 let client = server.client_builder().build().await;
5906
5907 server
5908 .mock_sync()
5909 .timeout(Some(Duration::from_secs(30)))
5910 .ok(|_| {})
5911 .mock_once()
5912 .named("sync_with_timeout")
5913 .mount()
5914 .await;
5915
5916 let mut stream = Box::pin(client.sync_stream(SyncSettings::new()).await);
5918
5919 timeout(Duration::from_secs(1), async {
5920 stream.next().await.unwrap().unwrap();
5921 })
5922 .await
5923 .unwrap();
5924 }
5925
5926 #[async_test]
5927 async fn test_ignore_timeout_on_first_sync() {
5928 let server = MatrixMockServer::new().await;
5929 let client = server.client_builder().build().await;
5930
5931 server
5932 .mock_sync()
5933 .timeout(None)
5934 .ok(|_| {})
5935 .mock_once()
5936 .named("sync_no_timeout")
5937 .mount()
5938 .await;
5939 server
5940 .mock_sync()
5941 .timeout(Some(Duration::from_secs(30)))
5942 .ok(|_| {})
5943 .mock_once()
5944 .named("sync_with_timeout")
5945 .mount()
5946 .await;
5947
5948 let mut stream = Box::pin(
5950 client.sync_stream(SyncSettings::new().ignore_timeout_on_first_sync(true)).await,
5951 );
5952
5953 timeout(Duration::from_secs(1), async {
5954 stream.next().await.unwrap().unwrap();
5955 stream.next().await.unwrap().unwrap();
5956 })
5957 .await
5958 .unwrap();
5959 }
5960
5961 #[async_test]
5962 async fn test_disable_room_invites() {
5963 use wiremock::{
5964 Mock, ResponseTemplate,
5965 matchers::{body_json, method, path},
5966 };
5967
5968 let server = MatrixMockServer::new().await;
5969 let client = server.client_builder().build().await;
5970 let user_id = client.user_id().unwrap();
5971
5972 Mock::given(method("PUT"))
5973 .and(path(format!(
5974 "/_matrix/client/v3/user/{user_id}/account_data/m.invite_permission_config"
5975 )))
5976 .and(body_json(json!({
5977 "default_action": "block"
5978 })))
5979 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
5980 .expect(1)
5981 .mount(server.server())
5982 .await;
5983
5984 let mut config = InvitePermissionConfigEventContent::new();
5985 config.default_action = Some(InvitePermissionAction::Block);
5987
5988 client.account().set_invite_permission_config(config).await.unwrap();
5990 }
5991
5992 #[async_test]
5993 async fn test_enable_room_invites() {
5994 use wiremock::{
5995 Mock, ResponseTemplate,
5996 matchers::{body_json, method, path},
5997 };
5998
5999 let server = MatrixMockServer::new().await;
6000 let client = server.client_builder().build().await;
6001 let user_id = client.user_id().unwrap();
6002
6003 Mock::given(method("PUT"))
6004 .and(path(format!(
6005 "/_matrix/client/v3/user/{user_id}/account_data/m.invite_permission_config"
6006 )))
6007 .and(body_json(json!({})))
6008 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
6009 .expect(1)
6010 .mount(server.server())
6011 .await;
6012 let mut config = InvitePermissionConfigEventContent::new();
6013 config.default_action = None;
6015
6016 client.account().set_invite_permission_config(config).await.unwrap();
6018 }
6019
6020 #[async_test]
6021 async fn test_fetch_disabled_invite_permission_config() {
6022 use wiremock::{
6023 Mock, ResponseTemplate,
6024 matchers::{method, path},
6025 };
6026
6027 let server = MatrixMockServer::new().await;
6028 let client = server.client_builder().build().await;
6029 let user_id = client.user_id().unwrap();
6030
6031 Mock::given(method("GET"))
6032 .and(path(format!(
6033 "/_matrix/client/v3/user/{user_id}/account_data/m.invite_permission_config"
6034 )))
6035 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
6036 "default_action": "block"
6037 })))
6038 .expect(1)
6039 .mount(server.server())
6040 .await;
6041
6042 let config = client.account().fetch_invite_permission_config().await.unwrap();
6043 assert_eq!(config.unwrap().default_action, Some(InvitePermissionAction::Block));
6045 }
6046
6047 #[async_test]
6048 async fn test_fetch_enabled_invite_permission_config() {
6049 use wiremock::{
6050 Mock, ResponseTemplate,
6051 matchers::{method, path},
6052 };
6053
6054 let server = MatrixMockServer::new().await;
6055 let client = server.client_builder().build().await;
6056 let user_id = client.user_id().unwrap();
6057
6058 Mock::given(method("GET"))
6059 .and(path(format!(
6060 "/_matrix/client/v3/user/{user_id}/account_data/m.invite_permission_config"
6061 )))
6062 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
6063 .expect(1)
6064 .mount(server.server())
6065 .await;
6066
6067 let config = client.account().fetch_invite_permission_config().await.unwrap();
6068 assert_eq!(config.unwrap().default_action, None);
6070 }
6071
6072 #[async_test]
6073 async fn test_fetch_missing_invite_permission_config() {
6074 use wiremock::{
6075 Mock, ResponseTemplate,
6076 matchers::{method, path},
6077 };
6078
6079 let server = MatrixMockServer::new().await;
6080 let client = server.client_builder().build().await;
6081 let user_id = client.user_id().unwrap();
6082
6083 Mock::given(method("GET"))
6084 .and(path(format!(
6085 "/_matrix/client/v3/user/{user_id}/account_data/m.invite_permission_config"
6086 )))
6087 .respond_with(ResponseTemplate::new(404).set_body_json(json!({
6088 "errcode": "M_NOT_FOUND",
6089 "error": "Account data not found"
6090 })))
6091 .expect(1)
6092 .mount(server.server())
6093 .await;
6094
6095 let config = client.account().fetch_invite_permission_config().await.unwrap();
6096
6097 assert!(config.is_none());
6099 }
6100
6101 #[async_test]
6102 async fn test_get_disabled_invite_permission_config() {
6103 let server = MatrixMockServer::new().await;
6104 let client = server.client_builder().build().await;
6105
6106 server
6107 .mock_sync()
6108 .ok_and_run(&client, |builder| {
6109 builder.add_custom_global_account_data(json!({
6110 "content": {
6111 "default_action": "block"
6112 },
6113 "type": "m.invite_permission_config"
6114 }));
6115 })
6116 .await;
6117
6118 let config = client.account().get_invite_permission_config().await.unwrap();
6119
6120 assert_eq!(config.unwrap().default_action, Some(InvitePermissionAction::Block));
6122 }
6123
6124 #[async_test]
6125 async fn test_get_enabled_invite_permission_config() {
6126 let server = MatrixMockServer::new().await;
6127 let client = server.client_builder().build().await;
6128
6129 server
6130 .mock_sync()
6131 .ok_and_run(&client, |builder| {
6132 builder.add_custom_global_account_data(json!({
6133 "content": {},
6134 "type": "m.invite_permission_config"
6135 }));
6136 })
6137 .await;
6138
6139 let config = client.account().get_invite_permission_config().await.unwrap();
6140
6141 assert!(config.is_some());
6142 assert_eq!(config.unwrap().default_action, None);
6144 }
6145
6146 #[async_test]
6147 async fn test_get_invite_permission_config_not_found() {
6148 let server = MatrixMockServer::new().await;
6149 let client = server.client_builder().build().await;
6150
6151 let config = client.account().get_invite_permission_config().await.unwrap();
6152
6153 assert!(config.is_none());
6155 }
6156
6157 #[async_test]
6158 async fn test_observe_invite_permission_config() {
6159 let server = MatrixMockServer::new().await;
6160 let client = server.client_builder().build().await;
6161
6162 server
6164 .mock_sync()
6165 .ok_and_run(&client, |builder| {
6166 builder.add_custom_global_account_data(json!({
6167 "content": {
6168 "default_action": "block"
6169 },
6170 "type": "m.invite_permission_config"
6171 }));
6172 })
6173 .await;
6174
6175 let (initial_value, stream) =
6176 client.account().observe_invite_permission_config().await.unwrap();
6177
6178 let initial_value = initial_value.unwrap();
6179 assert_eq!(initial_value.default_action, Some(InvitePermissionAction::Block));
6180
6181 let mut stream = pin!(stream);
6182 assert_pending!(stream);
6183
6184 server
6186 .mock_sync()
6187 .ok_and_run(&client, |builder| {
6188 builder.add_custom_global_account_data(json!({
6189 "content": {},
6190 "type": "m.invite_permission_config"
6191 }));
6192 })
6193 .await;
6194
6195 assert_next_matches!(
6196 stream,
6197 InvitePermissionConfigEventContent { default_action: None, .. }
6198 );
6199 assert_pending!(stream);
6200 }
6201
6202 #[async_test]
6203 async fn test_observe_invite_permission_config_after_initial_subscription() {
6204 let server = MatrixMockServer::new().await;
6205 let client = server.client_builder().build().await;
6206
6207 let (initial_value, stream) =
6208 client.account().observe_invite_permission_config().await.unwrap();
6209
6210 assert!(initial_value.is_none());
6212
6213 let mut stream = pin!(stream);
6214 assert_pending!(stream);
6215
6216 server
6218 .mock_sync()
6219 .ok_and_run(&client, |builder| {
6220 builder.add_custom_global_account_data(json!({
6221 "content": {
6222 "default_action": "block"
6223 },
6224 "type": "m.invite_permission_config"
6225 }));
6226 })
6227 .await;
6228
6229 assert_next_matches!(
6230 stream,
6231 InvitePermissionConfigEventContent {
6232 default_action: Some(InvitePermissionAction::Block),
6233 ..
6234 }
6235 );
6236 assert_pending!(stream);
6237 }
6238
6239 #[async_test]
6240 async fn test_observe_invite_permission_config_not_found() {
6241 let server = MatrixMockServer::new().await;
6242 let client = server.client_builder().build().await;
6243
6244 let (initial_value, _) = client.account().observe_invite_permission_config().await.unwrap();
6245
6246 assert!(initial_value.is_none());
6248 }
6249
6250 #[async_test]
6251 async fn test_get_dm_room_returns_the_room_we_have_with_this_user() {
6252 let server = MatrixMockServer::new().await;
6253 let client = server.client_builder().build().await;
6254 let user = User::Carol;
6255
6256 let f = EventFactory::new();
6258 server
6259 .mock_sync()
6260 .ok_and_run(&client, |builder| {
6261 builder
6262 .add_joined_room(JoinedRoomBuilder::default().add_state_event(f.member(user)))
6263 .add_global_account_data(f.direct().add_user(user, TestRoom::Default));
6264 })
6265 .await;
6266
6267 let found_room = client.get_dm_room(user.into()).expect("DM not found!");
6269 assert!(found_room.get_member_no_sync(user.into()).await.unwrap().is_some());
6270 }
6271
6272 #[async_test]
6273 async fn test_get_dm_room_still_finds_room_where_participant_is_only_invited() {
6274 let server = MatrixMockServer::new().await;
6275 let client = server.client_builder().build().await;
6276 let user = User::Carol;
6278
6279 let f = EventFactory::new().sender(User::Alice);
6281 server
6282 .mock_sync()
6283 .ok_and_run(&client, |builder| {
6284 builder
6285 .add_joined_room(
6286 JoinedRoomBuilder::default()
6287 .add_state_event(f.member(user).invited(user).display_name("example")),
6288 )
6289 .add_global_account_data(f.direct().add_user(user, TestRoom::Default));
6290 })
6291 .await;
6292
6293 let found_room = client.get_dm_room(user.into()).expect("DM not found!");
6295 assert!(found_room.get_member_no_sync(user.into()).await.unwrap().is_some());
6296 }
6297
6298 #[async_test]
6299 async fn test_get_dm_room_still_finds_left_room() {
6300 let server = MatrixMockServer::new().await;
6306 let client = server.client_builder().build().await;
6307 let user = User::Carol;
6308
6309 let f = EventFactory::new().sender(user);
6311 server
6312 .mock_sync()
6313 .ok_and_run(&client, |builder| {
6314 builder
6315 .add_joined_room(
6316 JoinedRoomBuilder::default().add_state_event(f.member(user).leave()),
6317 )
6318 .add_global_account_data(f.direct().add_user(user, TestRoom::Default));
6319 })
6320 .await;
6321
6322 let found_room = client.get_dm_room(user.into()).expect("DM not found!");
6324 assert!(found_room.get_member_no_sync(user.into()).await.unwrap().is_some());
6325 }
6326
6327 #[async_test]
6328 async fn test_device_exists() {
6329 let server = MatrixMockServer::new().await;
6330 let client = server.client_builder().build().await;
6331
6332 server.mock_get_device().ok().expect(1).mount().await;
6333
6334 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(true));
6335 }
6336
6337 #[async_test]
6338 async fn test_device_exists_404() {
6339 let server = MatrixMockServer::new().await;
6340 let client = server.client_builder().build().await;
6341
6342 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(false));
6343 }
6344
6345 #[async_test]
6346 async fn test_device_exists_500() {
6347 let server = MatrixMockServer::new().await;
6348 let client = server.client_builder().build().await;
6349
6350 server.mock_get_device().error500().expect(1).mount().await;
6351
6352 assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Err(_));
6353 }
6354
6355 #[async_test]
6356 async fn test_fetching_well_known_with_homeserver_url() {
6357 let server = MatrixMockServer::new().await;
6358 let client = server.client_builder().build().await;
6359 server.mock_well_known().ok().mount().await;
6360
6361 assert_matches!(client.fetch_client_well_known().await, Some(_));
6362 }
6363
6364 #[async_test]
6365 async fn test_fetching_well_known_with_server_name() {
6366 let server = MatrixMockServer::new().await;
6367 let server_name = ServerName::parse(server.server().address().to_string()).unwrap();
6368
6369 server.mock_well_known().ok().mount().await;
6370
6371 let client = MockClientBuilder::new(None)
6372 .on_builder(|builder| builder.insecure_server_name_no_tls(&server_name))
6373 .build()
6374 .await;
6375
6376 assert_matches!(client.fetch_client_well_known().await, Some(_));
6377 }
6378
6379 #[async_test]
6380 async fn test_fetching_well_known_with_domain_part_of_user_id() {
6381 let server = MatrixMockServer::new().await;
6382 server.mock_well_known().ok().mount().await;
6383
6384 let user_id =
6385 UserId::parse(format!("@user:{}", server.server().address())).expect("Invalid user id");
6386 let client = MockClientBuilder::new(None)
6387 .logged_in_with_token("A_TOKEN".to_owned(), user_id, owned_device_id!("ABCDEF"))
6388 .build()
6389 .await;
6390
6391 assert_matches!(client.fetch_client_well_known().await, Some(_));
6392 }
6393
6394 #[cfg(feature = "e2e-encryption")]
6395 #[async_test]
6396 async fn test_syncing_one_time_key_counts_updates() -> Result<()> {
6397 use wiremock::ResponseTemplate;
6398
6399 macro_rules! assert_key_count {
6400 ($client: ident, $count:literal) => {{
6401 let machine = $client.olm_machine().await;
6402 let uploaded_key_counts =
6403 machine.as_ref().unwrap().uploaded_key_count().await.unwrap();
6404 assert_eq!(uploaded_key_counts, $count)
6405 }};
6406 }
6407
6408 macro_rules! sync_with_key_count {
6409 ($client: ident, $server:ident, $count:literal) => {
6410 let count = Some($count);
6411 sync_with_key_count!($client, $server, count);
6412 };
6413 ($client: ident, $server:ident, $count:ident) => {{
6414 use rand::RngExt as _;
6415
6416 let next_batch: String = rand::rng()
6417 .sample_iter(&rand::distr::Alphanumeric)
6418 .take(16)
6419 .map(char::from)
6420 .collect();
6421
6422 let count: Option<u32> = $count;
6423
6424 let template = if let Some(count) = count {
6425 ResponseTemplate::new(200).set_body_json(json!({
6426 "next_batch": next_batch,
6427 "rooms": {"leave": {}, "join": {}, "invite": {}},
6428 "device_lists": {
6429 "changed": [],
6430 "left": [],
6431 },
6432 "device_one_time_keys_count": {
6433 "signed_curve25519": count
6434 },
6435 }))
6436 } else {
6437 ResponseTemplate::new(200).set_body_json(json!({
6438 "next_batch": next_batch,
6439 "rooms": {"leave": {}, "join": {}, "invite": {}},
6440 "device_lists": {
6441 "changed": [],
6442 "left": [],
6443 },
6444 "device_one_time_keys_count": {},
6445 }))
6446 };
6447
6448 let _sync_mock_guard = $server.mock_sync().respond_with(template).mount_as_scoped().await;
6449 $client.sync_once(Default::default()).await?;
6450 }}
6451 }
6452
6453 let server = MatrixMockServer::new().await;
6454 let client = server.client_builder().build().await;
6455
6456 server.mock_upload_keys().ok_with_signed_curve_key_count(50).mock_once().mount().await;
6457
6458 assert_key_count!(client, 0);
6460
6461 sync_with_key_count!(client, server, 50);
6463 assert_key_count!(client, 50);
6464
6465 sync_with_key_count!(client, server, 10);
6467 assert_key_count!(client, 10);
6468
6469 sync_with_key_count!(client, server, None);
6471 assert_key_count!(client, 0);
6472
6473 Ok(())
6474 }
6475
6476 #[async_test]
6477 async fn test_get_retention_configuration() {
6478 use wiremock::{
6479 Mock, ResponseTemplate,
6480 matchers::{method, path},
6481 };
6482
6483 let server = MatrixMockServer::new().await;
6484 let client = server.client_builder().build().await;
6485
6486 Mock::given(method("GET"))
6487 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
6488 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
6489 "policies": {},
6490 "limits": {},
6491 })))
6492 .expect(1)
6493 .mount(server.server())
6494 .await;
6495
6496 let response = client.get_retention_configuration().await;
6497 assert!(response.is_ok());
6498 let response = response.unwrap();
6499 assert!(response.policies.is_empty());
6500 assert!(response.limits.max_lifetime.is_none());
6501 assert!(response.limits.min_lifetime.is_none());
6502 }
6503
6504 #[async_test]
6505 async fn test_wait_for_sync() {
6506 let server = MatrixMockServer::new().await;
6507 let client = server.client_builder().build().await;
6508
6509 let num_syncs = AtomicU32::new(0);
6510
6511 assert_eq!(num_syncs.load(Ordering::SeqCst), 0);
6512
6513 let client_clone = client.clone();
6515 let task = spawn(async move {
6516 client_clone.wait_for_sync().await;
6517 client_clone.wait_for_sync().await;
6518 });
6519
6520 server
6522 .mock_sync()
6523 .ok_and_run(&client, |_| {
6524 num_syncs.fetch_add(1, Ordering::SeqCst);
6525 })
6526 .await;
6527
6528 assert_eq!(num_syncs.load(Ordering::SeqCst), 1);
6529
6530 server
6532 .mock_sync()
6533 .ok_and_run(&client, |_| {
6534 num_syncs.fetch_add(1, Ordering::SeqCst);
6535 })
6536 .await;
6537
6538 assert_eq!(num_syncs.load(Ordering::SeqCst), 2);
6539
6540 timeout(Duration::from_secs(1), task)
6542 .await
6543 .expect("no timeout")
6544 .expect("task didn't panick");
6545 }
6546
6547 #[async_test]
6548 async fn test_observe_total_unread_notifications() {
6549 let server = MatrixMockServer::new().await;
6550 let client = server.client_builder().build().await;
6551 let f = EventFactory::new();
6552
6553 let set_num_notifications = async |room: &crate::Room, num: u64| {
6554 room.update_room_info(|mut room_info| {
6555 room_info.set_read_receipts(ReadReceipts {
6556 num_notifications: num,
6557 ..Default::default()
6558 });
6559 (room_info, RoomInfoNotableUpdateReasons::READ_RECEIPT)
6560 })
6561 .await;
6562 };
6563
6564 let room = server.sync_default_joined_room(&client).await;
6565 set_num_notifications(&room, 1).await;
6566
6567 let min_interval = Duration::from_millis(100);
6568 let mut stream = pin!(client.observe_total_unread_notifications(min_interval));
6569
6570 assert_next_eq_with_timeout!(stream, 1);
6572 assert_pending!(stream);
6573
6574 set_num_notifications(&room, 3).await;
6577 set_num_notifications(&room, 5).await;
6578
6579 assert_next_eq_with_timeout!(stream, 5, 300 ms);
6580 assert_pending!(stream);
6581
6582 set_num_notifications(&room, 5).await;
6584 assert!(timeout(min_interval * 3, stream.next()).await.is_err());
6585
6586 server
6588 .sync_room(
6589 &client,
6590 JoinedRoomBuilder::new(TestRoom::V12).add_account_data(f.marked_unread(true)),
6591 )
6592 .await;
6593
6594 assert_next_eq_with_timeout!(stream, 6, 300 ms);
6595 assert_pending!(stream);
6596 }
6597}