matrix_sdk/client/mod.rs
1// Copyright 2020 Damir Jelić
2// Copyright 2020 The Matrix.org Foundation C.I.C.
3// Copyright 2022 Famedly GmbH
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17use std::{
18 collections::{btree_map, BTreeMap, BTreeSet},
19 fmt::{self, Debug},
20 future::{ready, Future},
21 pin::Pin,
22 sync::{Arc, Mutex as StdMutex, RwLock as StdRwLock, Weak},
23 time::Duration,
24};
25
26use caches::ClientCaches;
27use eyeball::{SharedObservable, Subscriber};
28use eyeball_im::{Vector, VectorDiff};
29use futures_core::Stream;
30use futures_util::StreamExt;
31#[cfg(feature = "e2e-encryption")]
32use matrix_sdk_base::crypto::{store::LockableCryptoStore, DecryptionSettings};
33use matrix_sdk_base::{
34 event_cache::store::EventCacheStoreLock,
35 store::{DynStateStore, RoomLoadSettings, ServerInfo, WellKnownResponse},
36 sync::{Notification, RoomUpdates},
37 BaseClient, RoomInfoNotableUpdate, RoomState, RoomStateFilter, SendOutsideWasm, SessionMeta,
38 StateStoreDataKey, StateStoreDataValue, SyncOutsideWasm, ThreadingSupport,
39};
40use matrix_sdk_common::ttl_cache::TtlCache;
41#[cfg(feature = "e2e-encryption")]
42use ruma::events::{room::encryption::RoomEncryptionEventContent, InitialStateEvent};
43use ruma::{
44 api::{
45 client::{
46 account::whoami,
47 alias::{create_alias, delete_alias, get_alias},
48 authenticated_media,
49 device::{delete_devices, get_devices, update_device},
50 directory::{get_public_rooms, get_public_rooms_filtered},
51 discovery::{
52 discover_homeserver::{self, RtcFocusInfo},
53 get_capabilities::{self, v3::Capabilities},
54 get_supported_versions,
55 },
56 error::ErrorKind,
57 filter::{create_filter::v3::Request as FilterUploadRequest, FilterDefinition},
58 knock::knock_room,
59 media,
60 membership::{join_room_by_id, join_room_by_id_or_alias},
61 room::create_room,
62 session::login::v3::DiscoveryInfo,
63 sync::sync_events,
64 uiaa,
65 user_directory::search_users,
66 },
67 error::FromHttpResponseError,
68 FeatureFlag, MatrixVersion, OutgoingRequest, SupportedVersions,
69 },
70 assign,
71 push::Ruleset,
72 time::Instant,
73 DeviceId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
74 RoomAliasId, RoomId, RoomOrAliasId, ServerName, UInt, UserId,
75};
76use serde::de::DeserializeOwned;
77use tokio::sync::{broadcast, Mutex, OnceCell, RwLock, RwLockReadGuard};
78use tracing::{debug, error, instrument, trace, warn, Instrument, Span};
79use url::Url;
80
81use self::futures::SendRequest;
82use crate::{
83 authentication::{
84 matrix::MatrixAuth, oauth::OAuth, AuthCtx, AuthData, ReloadSessionCallback,
85 SaveSessionCallback,
86 },
87 config::RequestConfig,
88 deduplicating_handler::DeduplicatingHandler,
89 error::HttpResult,
90 event_cache::EventCache,
91 event_handler::{
92 EventHandler, EventHandlerContext, EventHandlerDropGuard, EventHandlerHandle,
93 EventHandlerStore, ObservableEventHandler, SyncEvent,
94 },
95 http_client::HttpClient,
96 latest_events::LatestEvents,
97 media::MediaError,
98 notification_settings::NotificationSettings,
99 room::RoomMember,
100 room_preview::RoomPreview,
101 send_queue::{SendQueue, SendQueueData},
102 sliding_sync::Version as SlidingSyncVersion,
103 sync::{RoomUpdate, SyncResponse},
104 Account, AuthApi, AuthSession, Error, HttpError, Media, Pusher, RefreshTokenError, Result,
105 Room, SessionTokens, TransmissionProgress,
106};
107#[cfg(feature = "e2e-encryption")]
108use crate::{
109 encryption::{Encryption, EncryptionData, EncryptionSettings, VerificationState},
110 store_locks::CrossProcessStoreLock,
111};
112
113mod builder;
114pub(crate) mod caches;
115pub(crate) mod futures;
116
117pub use self::builder::{sanitize_server_name, ClientBuildError, ClientBuilder};
118
119#[cfg(not(target_family = "wasm"))]
120type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()> + Send>>;
121#[cfg(target_family = "wasm")]
122type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()>>>;
123
124#[cfg(not(target_family = "wasm"))]
125type NotificationHandlerFn =
126 Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut + Send + Sync>;
127#[cfg(target_family = "wasm")]
128type NotificationHandlerFn = Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut>;
129
130/// Enum controlling if a loop running callbacks should continue or abort.
131///
132/// This is mainly used in the [`sync_with_callback`] method, the return value
133/// of the provided callback controls if the sync loop should be exited.
134///
135/// [`sync_with_callback`]: #method.sync_with_callback
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub enum LoopCtrl {
138 /// Continue running the loop.
139 Continue,
140 /// Break out of the loop.
141 Break,
142}
143
144/// Represents changes that can occur to a `Client`s `Session`.
145#[derive(Debug, Clone, PartialEq)]
146pub enum SessionChange {
147 /// The session's token is no longer valid.
148 UnknownToken {
149 /// Whether or not the session was soft logged out
150 soft_logout: bool,
151 },
152 /// The session's tokens have been refreshed.
153 TokensRefreshed,
154}
155
156/// An async/await enabled Matrix client.
157///
158/// All of the state is held in an `Arc` so the `Client` can be cloned freely.
159#[derive(Clone)]
160pub struct Client {
161 pub(crate) inner: Arc<ClientInner>,
162}
163
164#[derive(Default)]
165pub(crate) struct ClientLocks {
166 /// Lock ensuring that only a single room may be marked as a DM at once.
167 /// Look at the [`Account::mark_as_dm()`] method for a more detailed
168 /// explanation.
169 pub(crate) mark_as_dm_lock: Mutex<()>,
170
171 /// Lock ensuring that only a single secret store is getting opened at the
172 /// same time.
173 ///
174 /// This is important so we don't accidentally create multiple different new
175 /// default secret storage keys.
176 #[cfg(feature = "e2e-encryption")]
177 pub(crate) open_secret_store_lock: Mutex<()>,
178
179 /// Lock ensuring that we're only storing a single secret at a time.
180 ///
181 /// Take a look at the [`SecretStore::put_secret`] method for a more
182 /// detailed explanation.
183 ///
184 /// [`SecretStore::put_secret`]: crate::encryption::secret_storage::SecretStore::put_secret
185 #[cfg(feature = "e2e-encryption")]
186 pub(crate) store_secret_lock: Mutex<()>,
187
188 /// Lock ensuring that only one method at a time might modify our backup.
189 #[cfg(feature = "e2e-encryption")]
190 pub(crate) backup_modify_lock: Mutex<()>,
191
192 /// Lock ensuring that we're going to attempt to upload backups for a single
193 /// requester.
194 #[cfg(feature = "e2e-encryption")]
195 pub(crate) backup_upload_lock: Mutex<()>,
196
197 /// Handler making sure we only have one group session sharing request in
198 /// flight per room.
199 #[cfg(feature = "e2e-encryption")]
200 pub(crate) group_session_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
201
202 /// Lock making sure we're only doing one key claim request at a time.
203 #[cfg(feature = "e2e-encryption")]
204 pub(crate) key_claim_lock: Mutex<()>,
205
206 /// Handler to ensure that only one members request is running at a time,
207 /// given a room.
208 pub(crate) members_request_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
209
210 /// Handler to ensure that only one encryption state request is running at a
211 /// time, given a room.
212 pub(crate) encryption_state_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
213
214 /// Deduplicating handler for sending read receipts. The string is an
215 /// internal implementation detail, see [`Self::send_single_receipt`].
216 pub(crate) read_receipt_deduplicated_handler: DeduplicatingHandler<(String, OwnedEventId)>,
217
218 #[cfg(feature = "e2e-encryption")]
219 pub(crate) cross_process_crypto_store_lock:
220 OnceCell<CrossProcessStoreLock<LockableCryptoStore>>,
221
222 /// Latest "generation" of data known by the crypto store.
223 ///
224 /// This is a counter that only increments, set in the database (and can
225 /// wrap). It's incremented whenever some process acquires a lock for the
226 /// first time. *This assumes the crypto store lock is being held, to
227 /// avoid data races on writing to this value in the store*.
228 ///
229 /// The current process will maintain this value in local memory and in the
230 /// DB over time. Observing a different value than the one read in
231 /// memory, when reading from the store indicates that somebody else has
232 /// written into the database under our feet.
233 ///
234 /// TODO: this should live in the `OlmMachine`, since it's information
235 /// related to the lock. As of today (2023-07-28), we blow up the entire
236 /// olm machine when there's a generation mismatch. So storing the
237 /// generation in the olm machine would make the client think there's
238 /// *always* a mismatch, and that's why we need to store the generation
239 /// outside the `OlmMachine`.
240 #[cfg(feature = "e2e-encryption")]
241 pub(crate) crypto_store_generation: Arc<Mutex<Option<u64>>>,
242}
243
244pub(crate) struct ClientInner {
245 /// All the data related to authentication and authorization.
246 pub(crate) auth_ctx: Arc<AuthCtx>,
247
248 /// The URL of the server.
249 ///
250 /// Not to be confused with the `Self::homeserver`. `server` is usually
251 /// the server part in a user ID, e.g. with `@mnt_io:matrix.org`, here
252 /// `matrix.org` is the server, whilst `matrix-client.matrix.org` is the
253 /// homeserver (at the time of writing — 2024-08-28).
254 ///
255 /// This value is optional depending on how the `Client` has been built.
256 /// If it's been built from a homeserver URL directly, we don't know the
257 /// server. However, if the `Client` has been built from a server URL or
258 /// name, then the homeserver has been discovered, and we know both.
259 server: Option<Url>,
260
261 /// The URL of the homeserver to connect to.
262 ///
263 /// This is the URL for the client-server Matrix API.
264 homeserver: StdRwLock<Url>,
265
266 /// The sliding sync version.
267 sliding_sync_version: StdRwLock<SlidingSyncVersion>,
268
269 /// The underlying HTTP client.
270 pub(crate) http_client: HttpClient,
271
272 /// User session data.
273 pub(super) base_client: BaseClient,
274
275 /// Collection of in-memory caches for the [`Client`].
276 pub(crate) caches: ClientCaches,
277
278 /// Collection of locks individual client methods might want to use, either
279 /// to ensure that only a single call to a method happens at once or to
280 /// deduplicate multiple calls to a method.
281 pub(crate) locks: ClientLocks,
282
283 /// The cross-process store locks holder name.
284 ///
285 /// The SDK provides cross-process store locks (see
286 /// [`matrix_sdk_common::store_locks::CrossProcessStoreLock`]). The
287 /// `holder_name` is the value used for all cross-process store locks
288 /// used by this `Client`.
289 ///
290 /// If multiple `Client`s are running in different processes, this
291 /// value MUST be different for each `Client`.
292 cross_process_store_locks_holder_name: String,
293
294 /// A mapping of the times at which the current user sent typing notices,
295 /// keyed by room.
296 pub(crate) typing_notice_times: StdRwLock<BTreeMap<OwnedRoomId, Instant>>,
297
298 /// Event handlers. See `add_event_handler`.
299 pub(crate) event_handlers: EventHandlerStore,
300
301 /// Notification handlers. See `register_notification_handler`.
302 notification_handlers: RwLock<Vec<NotificationHandlerFn>>,
303
304 /// The sender-side of channels used to receive room updates.
305 pub(crate) room_update_channels: StdMutex<BTreeMap<OwnedRoomId, broadcast::Sender<RoomUpdate>>>,
306
307 /// The sender-side of a channel used to observe all the room updates of a
308 /// sync response.
309 pub(crate) room_updates_sender: broadcast::Sender<RoomUpdates>,
310
311 /// Whether the client should update its homeserver URL with the discovery
312 /// information present in the login response.
313 respect_login_well_known: bool,
314
315 /// An event that can be listened on to wait for a successful sync. The
316 /// event will only be fired if a sync loop is running. Can be used for
317 /// synchronization, e.g. if we send out a request to create a room, we can
318 /// wait for the sync to get the data to fetch a room object from the state
319 /// store.
320 pub(crate) sync_beat: event_listener::Event,
321
322 /// A central cache for events, inactive first.
323 ///
324 /// It becomes active when [`EventCache::subscribe`] is called.
325 pub(crate) event_cache: OnceCell<EventCache>,
326
327 /// End-to-end encryption related state.
328 #[cfg(feature = "e2e-encryption")]
329 pub(crate) e2ee: EncryptionData,
330
331 /// The verification state of our own device.
332 #[cfg(feature = "e2e-encryption")]
333 pub(crate) verification_state: SharedObservable<VerificationState>,
334
335 /// Whether to enable the experimental support for sending and receiving
336 /// encrypted room history on invite, per [MSC4268].
337 ///
338 /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
339 #[cfg(feature = "e2e-encryption")]
340 pub(crate) enable_share_history_on_invite: bool,
341
342 /// Data related to the [`SendQueue`].
343 ///
344 /// [`SendQueue`]: crate::send_queue::SendQueue
345 pub(crate) send_queue_data: Arc<SendQueueData>,
346
347 /// The `max_upload_size` value of the homeserver, it contains the max
348 /// request size you can send.
349 pub(crate) server_max_upload_size: Mutex<OnceCell<UInt>>,
350
351 /// The entry point to get the [`LatestEvent`] of rooms and threads.
352 ///
353 /// [`LatestEvent`]: crate::latest_event::LatestEvent
354 latest_events: OnceCell<LatestEvents>,
355}
356
357impl ClientInner {
358 /// Create a new `ClientInner`.
359 ///
360 /// All the fields passed as parameters here are those that must be cloned
361 /// upon instantiation of a sub-client, e.g. a client specialized for
362 /// notifications.
363 #[allow(clippy::too_many_arguments)]
364 async fn new(
365 auth_ctx: Arc<AuthCtx>,
366 server: Option<Url>,
367 homeserver: Url,
368 sliding_sync_version: SlidingSyncVersion,
369 http_client: HttpClient,
370 base_client: BaseClient,
371 server_info: ClientServerInfo,
372 respect_login_well_known: bool,
373 event_cache: OnceCell<EventCache>,
374 send_queue: Arc<SendQueueData>,
375 latest_events: OnceCell<LatestEvents>,
376 #[cfg(feature = "e2e-encryption")] encryption_settings: EncryptionSettings,
377 #[cfg(feature = "e2e-encryption")] enable_share_history_on_invite: bool,
378 cross_process_store_locks_holder_name: String,
379 ) -> Arc<Self> {
380 let caches = ClientCaches {
381 server_info: server_info.into(),
382 server_metadata: Mutex::new(TtlCache::new()),
383 };
384
385 let client = Self {
386 server,
387 homeserver: StdRwLock::new(homeserver),
388 auth_ctx,
389 sliding_sync_version: StdRwLock::new(sliding_sync_version),
390 http_client,
391 base_client,
392 caches,
393 locks: Default::default(),
394 cross_process_store_locks_holder_name,
395 typing_notice_times: Default::default(),
396 event_handlers: Default::default(),
397 notification_handlers: Default::default(),
398 room_update_channels: Default::default(),
399 // A single `RoomUpdates` is sent once per sync, so we assume that 32 is sufficient
400 // ballast for all observers to catch up.
401 room_updates_sender: broadcast::Sender::new(32),
402 respect_login_well_known,
403 sync_beat: event_listener::Event::new(),
404 event_cache,
405 send_queue_data: send_queue,
406 latest_events,
407 #[cfg(feature = "e2e-encryption")]
408 e2ee: EncryptionData::new(encryption_settings),
409 #[cfg(feature = "e2e-encryption")]
410 verification_state: SharedObservable::new(VerificationState::Unknown),
411 #[cfg(feature = "e2e-encryption")]
412 enable_share_history_on_invite,
413 server_max_upload_size: Mutex::new(OnceCell::new()),
414 };
415
416 #[allow(clippy::let_and_return)]
417 let client = Arc::new(client);
418
419 #[cfg(feature = "e2e-encryption")]
420 client.e2ee.initialize_tasks(&client);
421
422 let _ = client
423 .event_cache
424 .get_or_init(|| async {
425 EventCache::new(
426 WeakClient::from_inner(&client),
427 client.base_client.event_cache_store().clone(),
428 )
429 })
430 .await;
431
432 client
433 }
434}
435
436#[cfg(not(tarpaulin_include))]
437impl Debug for Client {
438 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
439 write!(fmt, "Client")
440 }
441}
442
443impl Client {
444 /// Create a new [`Client`] that will use the given homeserver.
445 ///
446 /// # Arguments
447 ///
448 /// * `homeserver_url` - The homeserver that the client should connect to.
449 pub async fn new(homeserver_url: Url) -> Result<Self, ClientBuildError> {
450 Self::builder().homeserver_url(homeserver_url).build().await
451 }
452
453 /// Returns a subscriber that publishes an event every time the ignore user
454 /// list changes.
455 pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<Vec<String>> {
456 self.inner.base_client.subscribe_to_ignore_user_list_changes()
457 }
458
459 /// Create a new [`ClientBuilder`].
460 pub fn builder() -> ClientBuilder {
461 ClientBuilder::new()
462 }
463
464 pub(crate) fn base_client(&self) -> &BaseClient {
465 &self.inner.base_client
466 }
467
468 /// The underlying HTTP client.
469 pub fn http_client(&self) -> &reqwest::Client {
470 &self.inner.http_client.inner
471 }
472
473 pub(crate) fn locks(&self) -> &ClientLocks {
474 &self.inner.locks
475 }
476
477 pub(crate) fn auth_ctx(&self) -> &AuthCtx {
478 &self.inner.auth_ctx
479 }
480
481 /// The cross-process store locks holder name.
482 ///
483 /// The SDK provides cross-process store locks (see
484 /// [`matrix_sdk_common::store_locks::CrossProcessStoreLock`]). The
485 /// `holder_name` is the value used for all cross-process store locks
486 /// used by this `Client`.
487 pub fn cross_process_store_locks_holder_name(&self) -> &str {
488 &self.inner.cross_process_store_locks_holder_name
489 }
490
491 /// Change the homeserver URL used by this client.
492 ///
493 /// # Arguments
494 ///
495 /// * `homeserver_url` - The new URL to use.
496 fn set_homeserver(&self, homeserver_url: Url) {
497 *self.inner.homeserver.write().unwrap() = homeserver_url;
498 }
499
500 /// Get the capabilities of the homeserver.
501 ///
502 /// This method should be used to check what features are supported by the
503 /// homeserver.
504 ///
505 /// # Examples
506 ///
507 /// ```no_run
508 /// # use matrix_sdk::Client;
509 /// # use url::Url;
510 /// # async {
511 /// # let homeserver = Url::parse("http://example.com")?;
512 /// let client = Client::new(homeserver).await?;
513 ///
514 /// let capabilities = client.get_capabilities().await?;
515 ///
516 /// if capabilities.change_password.enabled {
517 /// // Change password
518 /// }
519 /// # anyhow::Ok(()) };
520 /// ```
521 pub async fn get_capabilities(&self) -> HttpResult<Capabilities> {
522 let res = self.send(get_capabilities::v3::Request::new()).await?;
523 Ok(res.capabilities)
524 }
525
526 /// Get a copy of the default request config.
527 ///
528 /// The default request config is what's used when sending requests if no
529 /// `RequestConfig` is explicitly passed to [`send`][Self::send] or another
530 /// function with such a parameter.
531 ///
532 /// If the default request config was not customized through
533 /// [`ClientBuilder`] when creating this `Client`, the returned value will
534 /// be equivalent to [`RequestConfig::default()`].
535 pub fn request_config(&self) -> RequestConfig {
536 self.inner.http_client.request_config
537 }
538
539 /// Check whether the client has been activated.
540 ///
541 /// A client is considered active when:
542 ///
543 /// 1. It has a `SessionMeta` (user ID, device ID and access token), i.e. it
544 /// is logged in,
545 /// 2. Has loaded cached data from storage,
546 /// 3. If encryption is enabled, it also initialized or restored its
547 /// `OlmMachine`.
548 pub fn is_active(&self) -> bool {
549 self.inner.base_client.is_active()
550 }
551
552 /// The server used by the client.
553 ///
554 /// See `Self::server` to learn more.
555 pub fn server(&self) -> Option<&Url> {
556 self.inner.server.as_ref()
557 }
558
559 /// The homeserver of the client.
560 pub fn homeserver(&self) -> Url {
561 self.inner.homeserver.read().unwrap().clone()
562 }
563
564 /// Get the sliding sync version.
565 pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
566 self.inner.sliding_sync_version.read().unwrap().clone()
567 }
568
569 /// Override the sliding sync version.
570 pub fn set_sliding_sync_version(&self, version: SlidingSyncVersion) {
571 let mut lock = self.inner.sliding_sync_version.write().unwrap();
572 *lock = version;
573 }
574
575 /// Get the Matrix user session meta information.
576 ///
577 /// If the client is currently logged in, this will return a
578 /// [`SessionMeta`] object which contains the user ID and device ID.
579 /// Otherwise it returns `None`.
580 pub fn session_meta(&self) -> Option<&SessionMeta> {
581 self.base_client().session_meta()
582 }
583
584 /// Returns a receiver that gets events for each room info update. To watch
585 /// for new events, use `receiver.resubscribe()`. Each event contains the
586 /// room and a boolean whether this event should trigger a room list update.
587 pub fn room_info_notable_update_receiver(&self) -> broadcast::Receiver<RoomInfoNotableUpdate> {
588 self.base_client().room_info_notable_update_receiver()
589 }
590
591 /// Performs a search for users.
592 /// The search is performed case-insensitively on user IDs and display names
593 ///
594 /// # Arguments
595 ///
596 /// * `search_term` - The search term for the search
597 /// * `limit` - The maximum number of results to return. Defaults to 10.
598 ///
599 /// [user directory]: https://spec.matrix.org/v1.6/client-server-api/#user-directory
600 pub async fn search_users(
601 &self,
602 search_term: &str,
603 limit: u64,
604 ) -> HttpResult<search_users::v3::Response> {
605 let mut request = search_users::v3::Request::new(search_term.to_owned());
606
607 if let Some(limit) = UInt::new(limit) {
608 request.limit = limit;
609 }
610
611 self.send(request).await
612 }
613
614 /// Get the user id of the current owner of the client.
615 pub fn user_id(&self) -> Option<&UserId> {
616 self.session_meta().map(|s| s.user_id.as_ref())
617 }
618
619 /// Get the device ID that identifies the current session.
620 pub fn device_id(&self) -> Option<&DeviceId> {
621 self.session_meta().map(|s| s.device_id.as_ref())
622 }
623
624 /// Get the current access token for this session.
625 ///
626 /// Will be `None` if the client has not been logged in.
627 pub fn access_token(&self) -> Option<String> {
628 self.auth_ctx().access_token()
629 }
630
631 /// Get the current tokens for this session.
632 ///
633 /// To be notified of changes in the session tokens, use
634 /// [`Client::subscribe_to_session_changes()`] or
635 /// [`Client::set_session_callbacks()`].
636 ///
637 /// Returns `None` if the client has not been logged in.
638 pub fn session_tokens(&self) -> Option<SessionTokens> {
639 self.auth_ctx().session_tokens()
640 }
641
642 /// Access the authentication API used to log in this client.
643 ///
644 /// Will be `None` if the client has not been logged in.
645 pub fn auth_api(&self) -> Option<AuthApi> {
646 match self.auth_ctx().auth_data.get()? {
647 AuthData::Matrix => Some(AuthApi::Matrix(self.matrix_auth())),
648 AuthData::OAuth(_) => Some(AuthApi::OAuth(self.oauth())),
649 }
650 }
651
652 /// Get the whole session info of this client.
653 ///
654 /// Will be `None` if the client has not been logged in.
655 ///
656 /// Can be used with [`Client::restore_session`] to restore a previously
657 /// logged-in session.
658 pub fn session(&self) -> Option<AuthSession> {
659 match self.auth_api()? {
660 AuthApi::Matrix(api) => api.session().map(Into::into),
661 AuthApi::OAuth(api) => api.full_session().map(Into::into),
662 }
663 }
664
665 /// Get a reference to the state store.
666 pub fn state_store(&self) -> &DynStateStore {
667 self.base_client().state_store()
668 }
669
670 /// Get a reference to the event cache store.
671 pub fn event_cache_store(&self) -> &EventCacheStoreLock {
672 self.base_client().event_cache_store()
673 }
674
675 /// Access the native Matrix authentication API with this client.
676 pub fn matrix_auth(&self) -> MatrixAuth {
677 MatrixAuth::new(self.clone())
678 }
679
680 /// Get the account of the current owner of the client.
681 pub fn account(&self) -> Account {
682 Account::new(self.clone())
683 }
684
685 /// Get the encryption manager of the client.
686 #[cfg(feature = "e2e-encryption")]
687 pub fn encryption(&self) -> Encryption {
688 Encryption::new(self.clone())
689 }
690
691 /// Get the media manager of the client.
692 pub fn media(&self) -> Media {
693 Media::new(self.clone())
694 }
695
696 /// Get the pusher manager of the client.
697 pub fn pusher(&self) -> Pusher {
698 Pusher::new(self.clone())
699 }
700
701 /// Access the OAuth 2.0 API of the client.
702 pub fn oauth(&self) -> OAuth {
703 OAuth::new(self.clone())
704 }
705
706 /// Register a handler for a specific event type.
707 ///
708 /// The handler is a function or closure with one or more arguments. The
709 /// first argument is the event itself. All additional arguments are
710 /// "context" arguments: They have to implement [`EventHandlerContext`].
711 /// This trait is named that way because most of the types implementing it
712 /// give additional context about an event: The room it was in, its raw form
713 /// and other similar things. As two exceptions to this,
714 /// [`Client`] and [`EventHandlerHandle`] also implement the
715 /// `EventHandlerContext` trait so you don't have to clone your client
716 /// into the event handler manually and a handler can decide to remove
717 /// itself.
718 ///
719 /// Some context arguments are not universally applicable. A context
720 /// argument that isn't available for the given event type will result in
721 /// the event handler being skipped and an error being logged. The following
722 /// context argument types are only available for a subset of event types:
723 ///
724 /// * [`Room`] is only available for room-specific events, i.e. not for
725 /// events like global account data events or presence events.
726 ///
727 /// You can provide custom context via
728 /// [`add_event_handler_context`](Client::add_event_handler_context) and
729 /// then use [`Ctx<T>`](crate::event_handler::Ctx) to extract the context
730 /// into the event handler.
731 ///
732 /// [`EventHandlerContext`]: crate::event_handler::EventHandlerContext
733 ///
734 /// # Examples
735 ///
736 /// ```no_run
737 /// use matrix_sdk::{
738 /// deserialized_responses::EncryptionInfo,
739 /// event_handler::Ctx,
740 /// ruma::{
741 /// events::{
742 /// macros::EventContent,
743 /// push_rules::PushRulesEvent,
744 /// room::{message::SyncRoomMessageEvent, topic::SyncRoomTopicEvent},
745 /// },
746 /// push::Action,
747 /// Int, MilliSecondsSinceUnixEpoch,
748 /// },
749 /// Client, Room,
750 /// };
751 /// use serde::{Deserialize, Serialize};
752 ///
753 /// # async fn example(client: Client) {
754 /// client.add_event_handler(
755 /// |ev: SyncRoomMessageEvent, room: Room, client: Client| async move {
756 /// // Common usage: Room event plus room and client.
757 /// },
758 /// );
759 /// client.add_event_handler(
760 /// |ev: SyncRoomMessageEvent, room: Room, encryption_info: Option<EncryptionInfo>| {
761 /// async move {
762 /// // An `Option<EncryptionInfo>` parameter lets you distinguish between
763 /// // unencrypted events and events that were decrypted by the SDK.
764 /// }
765 /// },
766 /// );
767 /// client.add_event_handler(
768 /// |ev: SyncRoomMessageEvent, room: Room, push_actions: Vec<Action>| {
769 /// async move {
770 /// // A `Vec<Action>` parameter allows you to know which push actions
771 /// // are applicable for an event. For example, an event with
772 /// // `Action::SetTweak(Tweak::Highlight(true))` should be highlighted
773 /// // in the timeline.
774 /// }
775 /// },
776 /// );
777 /// client.add_event_handler(|ev: SyncRoomTopicEvent| async move {
778 /// // You can omit any or all arguments after the first.
779 /// });
780 ///
781 /// // Registering a temporary event handler:
782 /// let handle = client.add_event_handler(|ev: SyncRoomMessageEvent| async move {
783 /// /* Event handler */
784 /// });
785 /// client.remove_event_handler(handle);
786 ///
787 /// // Registering custom event handler context:
788 /// #[derive(Debug, Clone)] // The context will be cloned for event handler.
789 /// struct MyContext {
790 /// number: usize,
791 /// }
792 /// client.add_event_handler_context(MyContext { number: 5 });
793 /// client.add_event_handler(|ev: SyncRoomMessageEvent, context: Ctx<MyContext>| async move {
794 /// // Use the context
795 /// });
796 ///
797 /// // Custom events work exactly the same way, you just need to declare
798 /// // the content struct and use the EventContent derive macro on it.
799 /// #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
800 /// #[ruma_event(type = "org.shiny_new_2fa.token", kind = MessageLike)]
801 /// struct TokenEventContent {
802 /// token: String,
803 /// #[serde(rename = "exp")]
804 /// expires_at: MilliSecondsSinceUnixEpoch,
805 /// }
806 ///
807 /// client.add_event_handler(|ev: SyncTokenEvent, room: Room| async move {
808 /// todo!("Display the token");
809 /// });
810 ///
811 /// // Event handler closures can also capture local variables.
812 /// // Make sure they are cheap to clone though, because they will be cloned
813 /// // every time the closure is called.
814 /// let data: std::sync::Arc<str> = "MyCustomIdentifier".into();
815 ///
816 /// client.add_event_handler(move |ev: SyncRoomMessageEvent | async move {
817 /// println!("Calling the handler with identifier {data}");
818 /// });
819 /// # }
820 /// ```
821 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
822 where
823 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
824 H: EventHandler<Ev, Ctx>,
825 {
826 self.add_event_handler_impl(handler, None)
827 }
828
829 /// Register a handler for a specific room, and event type.
830 ///
831 /// This method works the same way as
832 /// [`add_event_handler`][Self::add_event_handler], except that the handler
833 /// will only be called for events in the room with the specified ID. See
834 /// that method for more details on event handler functions.
835 ///
836 /// `client.add_room_event_handler(room_id, hdl)` is equivalent to
837 /// `room.add_event_handler(hdl)`. Use whichever one is more convenient in
838 /// your use case.
839 pub fn add_room_event_handler<Ev, Ctx, H>(
840 &self,
841 room_id: &RoomId,
842 handler: H,
843 ) -> EventHandlerHandle
844 where
845 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
846 H: EventHandler<Ev, Ctx>,
847 {
848 self.add_event_handler_impl(handler, Some(room_id.to_owned()))
849 }
850
851 /// Observe a specific event type.
852 ///
853 /// `Ev` represents the kind of event that will be observed. `Ctx`
854 /// represents the context that will come with the event. It relies on the
855 /// same mechanism as [`Client::add_event_handler`]. The main difference is
856 /// that it returns an [`ObservableEventHandler`] and doesn't require a
857 /// user-defined closure. It is possible to subscribe to the
858 /// [`ObservableEventHandler`] to get an [`EventHandlerSubscriber`], which
859 /// implements a [`Stream`]. The `Stream::Item` will be of type `(Ev,
860 /// Ctx)`.
861 ///
862 /// Be careful that only the most recent value can be observed. Subscribers
863 /// are notified when a new value is sent, but there is no guarantee
864 /// that they will see all values.
865 ///
866 /// # Example
867 ///
868 /// Let's see a classical usage:
869 ///
870 /// ```
871 /// use futures_util::StreamExt as _;
872 /// use matrix_sdk::{
873 /// ruma::{events::room::message::SyncRoomMessageEvent, push::Action},
874 /// Client, Room,
875 /// };
876 ///
877 /// # async fn example(client: Client) -> Option<()> {
878 /// let observer =
879 /// client.observe_events::<SyncRoomMessageEvent, (Room, Vec<Action>)>();
880 ///
881 /// let mut subscriber = observer.subscribe();
882 ///
883 /// let (event, (room, push_actions)) = subscriber.next().await?;
884 /// # Some(())
885 /// # }
886 /// ```
887 ///
888 /// Now let's see how to get several contexts that can be useful for you:
889 ///
890 /// ```
891 /// use matrix_sdk::{
892 /// deserialized_responses::EncryptionInfo,
893 /// ruma::{
894 /// events::room::{
895 /// message::SyncRoomMessageEvent, topic::SyncRoomTopicEvent,
896 /// },
897 /// push::Action,
898 /// },
899 /// Client, Room,
900 /// };
901 ///
902 /// # async fn example(client: Client) {
903 /// // Observe `SyncRoomMessageEvent` and fetch `Room` + `Client`.
904 /// let _ = client.observe_events::<SyncRoomMessageEvent, (Room, Client)>();
905 ///
906 /// // Observe `SyncRoomMessageEvent` and fetch `Room` + `EncryptionInfo`
907 /// // to distinguish between unencrypted events and events that were decrypted
908 /// // by the SDK.
909 /// let _ = client
910 /// .observe_events::<SyncRoomMessageEvent, (Room, Option<EncryptionInfo>)>(
911 /// );
912 ///
913 /// // Observe `SyncRoomMessageEvent` and fetch `Room` + push actions.
914 /// // For example, an event with `Action::SetTweak(Tweak::Highlight(true))`
915 /// // should be highlighted in the timeline.
916 /// let _ =
917 /// client.observe_events::<SyncRoomMessageEvent, (Room, Vec<Action>)>();
918 ///
919 /// // Observe `SyncRoomTopicEvent` and fetch nothing else.
920 /// let _ = client.observe_events::<SyncRoomTopicEvent, ()>();
921 /// # }
922 /// ```
923 ///
924 /// [`EventHandlerSubscriber`]: crate::event_handler::EventHandlerSubscriber
925 pub fn observe_events<Ev, Ctx>(&self) -> ObservableEventHandler<(Ev, Ctx)>
926 where
927 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
928 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
929 {
930 self.observe_room_events_impl(None)
931 }
932
933 /// Observe a specific room, and event type.
934 ///
935 /// This method works the same way as [`Client::observe_events`], except
936 /// that the observability will only be applied for events in the room with
937 /// the specified ID. See that method for more details.
938 ///
939 /// Be careful that only the most recent value can be observed. Subscribers
940 /// are notified when a new value is sent, but there is no guarantee
941 /// that they will see all values.
942 pub fn observe_room_events<Ev, Ctx>(
943 &self,
944 room_id: &RoomId,
945 ) -> ObservableEventHandler<(Ev, Ctx)>
946 where
947 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
948 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
949 {
950 self.observe_room_events_impl(Some(room_id.to_owned()))
951 }
952
953 /// Shared implementation for `Client::observe_events` and
954 /// `Client::observe_room_events`.
955 fn observe_room_events_impl<Ev, Ctx>(
956 &self,
957 room_id: Option<OwnedRoomId>,
958 ) -> ObservableEventHandler<(Ev, Ctx)>
959 where
960 Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
961 Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
962 {
963 // The default value is `None`. It becomes `Some((Ev, Ctx))` once it has a
964 // new value.
965 let shared_observable = SharedObservable::new(None);
966
967 ObservableEventHandler::new(
968 shared_observable.clone(),
969 self.event_handler_drop_guard(self.add_event_handler_impl(
970 move |event: Ev, context: Ctx| {
971 shared_observable.set(Some((event, context)));
972
973 ready(())
974 },
975 room_id,
976 )),
977 )
978 }
979
980 /// Remove the event handler associated with the handle.
981 ///
982 /// Note that you **must not** call `remove_event_handler` from the
983 /// non-async part of an event handler, that is:
984 ///
985 /// ```ignore
986 /// client.add_event_handler(|ev: SomeEvent, client: Client, handle: EventHandlerHandle| {
987 /// // ⚠ this will cause a deadlock ⚠
988 /// client.remove_event_handler(handle);
989 ///
990 /// async move {
991 /// // removing the event handler here is fine
992 /// client.remove_event_handler(handle);
993 /// }
994 /// })
995 /// ```
996 ///
997 /// Note also that handlers that remove themselves will still execute with
998 /// events received in the same sync cycle.
999 ///
1000 /// # Arguments
1001 ///
1002 /// `handle` - The [`EventHandlerHandle`] that is returned when
1003 /// registering the event handler with [`Client::add_event_handler`].
1004 ///
1005 /// # Examples
1006 ///
1007 /// ```no_run
1008 /// # use url::Url;
1009 /// # use tokio::sync::mpsc;
1010 /// #
1011 /// # let homeserver = Url::parse("http://localhost:8080").unwrap();
1012 /// #
1013 /// use matrix_sdk::{
1014 /// event_handler::EventHandlerHandle,
1015 /// ruma::events::room::member::SyncRoomMemberEvent, Client,
1016 /// };
1017 /// #
1018 /// # futures_executor::block_on(async {
1019 /// # let client = matrix_sdk::Client::builder()
1020 /// # .homeserver_url(homeserver)
1021 /// # .server_versions([ruma::api::MatrixVersion::V1_0])
1022 /// # .build()
1023 /// # .await
1024 /// # .unwrap();
1025 ///
1026 /// client.add_event_handler(
1027 /// |ev: SyncRoomMemberEvent,
1028 /// client: Client,
1029 /// handle: EventHandlerHandle| async move {
1030 /// // Common usage: Check arriving Event is the expected one
1031 /// println!("Expected RoomMemberEvent received!");
1032 /// client.remove_event_handler(handle);
1033 /// },
1034 /// );
1035 /// # });
1036 /// ```
1037 pub fn remove_event_handler(&self, handle: EventHandlerHandle) {
1038 self.inner.event_handlers.remove(handle);
1039 }
1040
1041 /// Create an [`EventHandlerDropGuard`] for the event handler identified by
1042 /// the given handle.
1043 ///
1044 /// When the returned value is dropped, the event handler will be removed.
1045 pub fn event_handler_drop_guard(&self, handle: EventHandlerHandle) -> EventHandlerDropGuard {
1046 EventHandlerDropGuard::new(handle, self.clone())
1047 }
1048
1049 /// Add an arbitrary value for use as event handler context.
1050 ///
1051 /// The value can be obtained in an event handler by adding an argument of
1052 /// the type [`Ctx<T>`][crate::event_handler::Ctx].
1053 ///
1054 /// If a value of the same type has been added before, it will be
1055 /// overwritten.
1056 ///
1057 /// # Examples
1058 ///
1059 /// ```no_run
1060 /// use matrix_sdk::{
1061 /// event_handler::Ctx, ruma::events::room::message::SyncRoomMessageEvent,
1062 /// Room,
1063 /// };
1064 /// # #[derive(Clone)]
1065 /// # struct SomeType;
1066 /// # fn obtain_gui_handle() -> SomeType { SomeType }
1067 /// # let homeserver = url::Url::parse("http://localhost:8080").unwrap();
1068 /// # futures_executor::block_on(async {
1069 /// # let client = matrix_sdk::Client::builder()
1070 /// # .homeserver_url(homeserver)
1071 /// # .server_versions([ruma::api::MatrixVersion::V1_0])
1072 /// # .build()
1073 /// # .await
1074 /// # .unwrap();
1075 ///
1076 /// // Handle used to send messages to the UI part of the app
1077 /// let my_gui_handle: SomeType = obtain_gui_handle();
1078 ///
1079 /// client.add_event_handler_context(my_gui_handle.clone());
1080 /// client.add_event_handler(
1081 /// |ev: SyncRoomMessageEvent, room: Room, gui_handle: Ctx<SomeType>| {
1082 /// async move {
1083 /// // gui_handle.send(DisplayMessage { message: ev });
1084 /// }
1085 /// },
1086 /// );
1087 /// # });
1088 /// ```
1089 pub fn add_event_handler_context<T>(&self, ctx: T)
1090 where
1091 T: Clone + Send + Sync + 'static,
1092 {
1093 self.inner.event_handlers.add_context(ctx);
1094 }
1095
1096 /// Register a handler for a notification.
1097 ///
1098 /// Similar to [`Client::add_event_handler`], but only allows functions
1099 /// or closures with exactly the three arguments [`Notification`], [`Room`],
1100 /// [`Client`] for now.
1101 pub async fn register_notification_handler<H, Fut>(&self, handler: H) -> &Self
1102 where
1103 H: Fn(Notification, Room, Client) -> Fut + SendOutsideWasm + SyncOutsideWasm + 'static,
1104 Fut: Future<Output = ()> + SendOutsideWasm + 'static,
1105 {
1106 self.inner.notification_handlers.write().await.push(Box::new(
1107 move |notification, room, client| Box::pin((handler)(notification, room, client)),
1108 ));
1109
1110 self
1111 }
1112
1113 /// Subscribe to all updates for the room with the given ID.
1114 ///
1115 /// The returned receiver will receive a new message for each sync response
1116 /// that contains updates for that room.
1117 pub fn subscribe_to_room_updates(&self, room_id: &RoomId) -> broadcast::Receiver<RoomUpdate> {
1118 match self.inner.room_update_channels.lock().unwrap().entry(room_id.to_owned()) {
1119 btree_map::Entry::Vacant(entry) => {
1120 let (tx, rx) = broadcast::channel(8);
1121 entry.insert(tx);
1122 rx
1123 }
1124 btree_map::Entry::Occupied(entry) => entry.get().subscribe(),
1125 }
1126 }
1127
1128 /// Subscribe to all updates to all rooms, whenever any has been received in
1129 /// a sync response.
1130 pub fn subscribe_to_all_room_updates(&self) -> broadcast::Receiver<RoomUpdates> {
1131 self.inner.room_updates_sender.subscribe()
1132 }
1133
1134 pub(crate) async fn notification_handlers(
1135 &self,
1136 ) -> RwLockReadGuard<'_, Vec<NotificationHandlerFn>> {
1137 self.inner.notification_handlers.read().await
1138 }
1139
1140 /// Get all the rooms the client knows about.
1141 ///
1142 /// This will return the list of joined, invited, and left rooms.
1143 pub fn rooms(&self) -> Vec<Room> {
1144 self.base_client().rooms().into_iter().map(|room| Room::new(self.clone(), room)).collect()
1145 }
1146
1147 /// Get all the rooms the client knows about, filtered by room state.
1148 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
1149 self.base_client()
1150 .rooms_filtered(filter)
1151 .into_iter()
1152 .map(|room| Room::new(self.clone(), room))
1153 .collect()
1154 }
1155
1156 /// Get a stream of all the rooms, in addition to the existing rooms.
1157 pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + '_) {
1158 let (rooms, stream) = self.base_client().rooms_stream();
1159
1160 let map_room = |room| Room::new(self.clone(), room);
1161
1162 (
1163 rooms.into_iter().map(map_room).collect(),
1164 stream.map(move |diffs| diffs.into_iter().map(|diff| diff.map(map_room)).collect()),
1165 )
1166 }
1167
1168 /// Returns the joined rooms this client knows about.
1169 pub fn joined_rooms(&self) -> Vec<Room> {
1170 self.base_client()
1171 .rooms_filtered(RoomStateFilter::JOINED)
1172 .into_iter()
1173 .map(|room| Room::new(self.clone(), room))
1174 .collect()
1175 }
1176
1177 /// Returns the invited rooms this client knows about.
1178 pub fn invited_rooms(&self) -> Vec<Room> {
1179 self.base_client()
1180 .rooms_filtered(RoomStateFilter::INVITED)
1181 .into_iter()
1182 .map(|room| Room::new(self.clone(), room))
1183 .collect()
1184 }
1185
1186 /// Returns the left rooms this client knows about.
1187 pub fn left_rooms(&self) -> Vec<Room> {
1188 self.base_client()
1189 .rooms_filtered(RoomStateFilter::LEFT)
1190 .into_iter()
1191 .map(|room| Room::new(self.clone(), room))
1192 .collect()
1193 }
1194
1195 /// Get a room with the given room id.
1196 ///
1197 /// # Arguments
1198 ///
1199 /// `room_id` - The unique id of the room that should be fetched.
1200 pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1201 self.base_client().get_room(room_id).map(|room| Room::new(self.clone(), room))
1202 }
1203
1204 /// Gets the preview of a room, whether the current user has joined it or
1205 /// not.
1206 pub async fn get_room_preview(
1207 &self,
1208 room_or_alias_id: &RoomOrAliasId,
1209 via: Vec<OwnedServerName>,
1210 ) -> Result<RoomPreview> {
1211 let room_id = match <&RoomId>::try_from(room_or_alias_id) {
1212 Ok(room_id) => room_id.to_owned(),
1213 Err(alias) => self.resolve_room_alias(alias).await?.room_id,
1214 };
1215
1216 if let Some(room) = self.get_room(&room_id) {
1217 // The cached data can only be trusted if the room state is joined or
1218 // banned: for invite and knock rooms, no updates will be received
1219 // for the rooms after the invite/knock action took place so we may
1220 // have very out to date data for important fields such as
1221 // `join_rule`. For left rooms, the homeserver should return the latest info.
1222 match room.state() {
1223 RoomState::Joined | RoomState::Banned => {
1224 return Ok(RoomPreview::from_known_room(&room).await);
1225 }
1226 RoomState::Left | RoomState::Invited | RoomState::Knocked => {}
1227 }
1228 }
1229
1230 RoomPreview::from_remote_room(self, room_id, room_or_alias_id, via).await
1231 }
1232
1233 /// Resolve a room alias to a room id and a list of servers which know
1234 /// about it.
1235 ///
1236 /// # Arguments
1237 ///
1238 /// `room_alias` - The room alias to be resolved.
1239 pub async fn resolve_room_alias(
1240 &self,
1241 room_alias: &RoomAliasId,
1242 ) -> HttpResult<get_alias::v3::Response> {
1243 let request = get_alias::v3::Request::new(room_alias.to_owned());
1244 self.send(request).await
1245 }
1246
1247 /// Checks if a room alias is not in use yet.
1248 ///
1249 /// Returns:
1250 /// - `Ok(true)` if the room alias is available.
1251 /// - `Ok(false)` if it's not (the resolve alias request returned a `404`
1252 /// status code).
1253 /// - An `Err` otherwise.
1254 pub async fn is_room_alias_available(&self, alias: &RoomAliasId) -> HttpResult<bool> {
1255 match self.resolve_room_alias(alias).await {
1256 // The room alias was resolved, so it's already in use.
1257 Ok(_) => Ok(false),
1258 Err(error) => {
1259 match error.client_api_error_kind() {
1260 // The room alias wasn't found, so it's available.
1261 Some(ErrorKind::NotFound) => Ok(true),
1262 _ => Err(error),
1263 }
1264 }
1265 }
1266 }
1267
1268 /// Adds a new room alias associated with a room to the room directory.
1269 pub async fn create_room_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> HttpResult<()> {
1270 let request = create_alias::v3::Request::new(alias.to_owned(), room_id.to_owned());
1271 self.send(request).await?;
1272 Ok(())
1273 }
1274
1275 /// Removes a room alias from the room directory.
1276 pub async fn remove_room_alias(&self, alias: &RoomAliasId) -> HttpResult<()> {
1277 let request = delete_alias::v3::Request::new(alias.to_owned());
1278 self.send(request).await?;
1279 Ok(())
1280 }
1281
1282 /// Update the homeserver from the login response well-known if needed.
1283 ///
1284 /// # Arguments
1285 ///
1286 /// * `login_well_known` - The `well_known` field from a successful login
1287 /// response.
1288 pub(crate) fn maybe_update_login_well_known(&self, login_well_known: Option<&DiscoveryInfo>) {
1289 if self.inner.respect_login_well_known {
1290 if let Some(well_known) = login_well_known {
1291 if let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url) {
1292 self.set_homeserver(homeserver);
1293 }
1294 }
1295 }
1296 }
1297
1298 /// Similar to [`Client::restore_session_with`], with
1299 /// [`RoomLoadSettings::default()`].
1300 ///
1301 /// # Panics
1302 ///
1303 /// Panics if a session was already restored or logged in.
1304 #[instrument(skip_all)]
1305 pub async fn restore_session(&self, session: impl Into<AuthSession>) -> Result<()> {
1306 self.restore_session_with(session, RoomLoadSettings::default()).await
1307 }
1308
1309 /// Restore a session previously logged-in using one of the available
1310 /// authentication APIs. The number of rooms to restore is controlled by
1311 /// [`RoomLoadSettings`].
1312 ///
1313 /// See the documentation of the corresponding authentication API's
1314 /// `restore_session` method for more information.
1315 ///
1316 /// # Panics
1317 ///
1318 /// Panics if a session was already restored or logged in.
1319 #[instrument(skip_all)]
1320 pub async fn restore_session_with(
1321 &self,
1322 session: impl Into<AuthSession>,
1323 room_load_settings: RoomLoadSettings,
1324 ) -> Result<()> {
1325 let session = session.into();
1326 match session {
1327 AuthSession::Matrix(session) => {
1328 Box::pin(self.matrix_auth().restore_session(session, room_load_settings)).await
1329 }
1330 AuthSession::OAuth(session) => {
1331 Box::pin(self.oauth().restore_session(*session, room_load_settings)).await
1332 }
1333 }
1334 }
1335
1336 /// Refresh the access token using the authentication API used to log into
1337 /// this session.
1338 ///
1339 /// See the documentation of the authentication API's `refresh_access_token`
1340 /// method for more information.
1341 pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1342 let Some(auth_api) = self.auth_api() else {
1343 return Err(RefreshTokenError::RefreshTokenRequired);
1344 };
1345
1346 match auth_api {
1347 AuthApi::Matrix(api) => {
1348 trace!("Token refresh: Using the homeserver.");
1349 Box::pin(api.refresh_access_token()).await?;
1350 }
1351 AuthApi::OAuth(api) => {
1352 trace!("Token refresh: Using OAuth 2.0.");
1353 Box::pin(api.refresh_access_token()).await?;
1354 }
1355 }
1356
1357 Ok(())
1358 }
1359
1360 /// Log out the current session using the proper authentication API.
1361 ///
1362 /// # Errors
1363 ///
1364 /// Returns an error if the session is not authenticated or if an error
1365 /// occurred while making the request to the server.
1366 pub async fn logout(&self) -> Result<(), Error> {
1367 let auth_api = self.auth_api().ok_or(Error::AuthenticationRequired)?;
1368 match auth_api {
1369 AuthApi::Matrix(matrix_auth) => {
1370 matrix_auth.logout().await?;
1371 Ok(())
1372 }
1373 AuthApi::OAuth(oauth) => Ok(oauth.logout().await?),
1374 }
1375 }
1376
1377 /// Get or upload a sync filter.
1378 ///
1379 /// This method will either get a filter ID from the store or upload the
1380 /// filter definition to the homeserver and return the new filter ID.
1381 ///
1382 /// # Arguments
1383 ///
1384 /// * `filter_name` - The unique name of the filter, this name will be used
1385 /// locally to store and identify the filter ID returned by the server.
1386 ///
1387 /// * `definition` - The filter definition that should be uploaded to the
1388 /// server if no filter ID can be found in the store.
1389 ///
1390 /// # Examples
1391 ///
1392 /// ```no_run
1393 /// # use matrix_sdk::{
1394 /// # Client, config::SyncSettings,
1395 /// # ruma::api::client::{
1396 /// # filter::{
1397 /// # FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter,
1398 /// # },
1399 /// # sync::sync_events::v3::Filter,
1400 /// # }
1401 /// # };
1402 /// # use url::Url;
1403 /// # async {
1404 /// # let homeserver = Url::parse("http://example.com").unwrap();
1405 /// # let client = Client::new(homeserver).await.unwrap();
1406 /// let mut filter = FilterDefinition::default();
1407 ///
1408 /// // Let's enable member lazy loading.
1409 /// filter.room.state.lazy_load_options =
1410 /// LazyLoadOptions::Enabled { include_redundant_members: false };
1411 ///
1412 /// let filter_id = client
1413 /// .get_or_upload_filter("sync", filter)
1414 /// .await
1415 /// .unwrap();
1416 ///
1417 /// let sync_settings = SyncSettings::new()
1418 /// .filter(Filter::FilterId(filter_id));
1419 ///
1420 /// let response = client.sync_once(sync_settings).await.unwrap();
1421 /// # };
1422 #[instrument(skip(self, definition))]
1423 pub async fn get_or_upload_filter(
1424 &self,
1425 filter_name: &str,
1426 definition: FilterDefinition,
1427 ) -> Result<String> {
1428 if let Some(filter) = self.inner.base_client.get_filter(filter_name).await? {
1429 debug!("Found filter locally");
1430 Ok(filter)
1431 } else {
1432 debug!("Didn't find filter locally");
1433 let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?;
1434 let request = FilterUploadRequest::new(user_id.to_owned(), definition);
1435 let response = self.send(request).await?;
1436
1437 self.inner.base_client.receive_filter_upload(filter_name, &response).await?;
1438
1439 Ok(response.filter_id)
1440 }
1441 }
1442
1443 /// Prepare to join a room by ID, by getting the current details about it
1444 async fn prepare_join_room_by_id(&self, room_id: &RoomId) -> Option<PreJoinRoomInfo> {
1445 let room = self.get_room(room_id)?;
1446
1447 let inviter = match room.invite_details().await {
1448 Ok(details) => details.inviter,
1449 Err(Error::WrongRoomState(_)) => None,
1450 Err(e) => {
1451 warn!("Error fetching invite details for room: {e:?}");
1452 None
1453 }
1454 };
1455
1456 Some(PreJoinRoomInfo { inviter })
1457 }
1458
1459 /// Finish joining a room.
1460 ///
1461 /// If the room was an invite that should be marked as a DM, will include it
1462 /// in the DM event after creating the joined room.
1463 ///
1464 /// If encrypted history sharing is enabled, will check to see if we have a
1465 /// key bundle, and import it if so.
1466 ///
1467 /// # Arguments
1468 ///
1469 /// * `room_id` - The `RoomId` of the room that was joined.
1470 /// * `pre_join_room_info` - Information about the room before we joined.
1471 async fn finish_join_room(
1472 &self,
1473 room_id: &RoomId,
1474 pre_join_room_info: Option<PreJoinRoomInfo>,
1475 ) -> Result<Room> {
1476 let mark_as_dm = if let Some(room) = self.get_room(room_id) {
1477 room.state() == RoomState::Invited
1478 && room.is_direct().await.unwrap_or_else(|e| {
1479 warn!(%room_id, "is_direct() failed: {e}");
1480 false
1481 })
1482 } else {
1483 false
1484 };
1485
1486 let base_room = self
1487 .base_client()
1488 .room_joined(
1489 room_id,
1490 pre_join_room_info
1491 .as_ref()
1492 .and_then(|info| info.inviter.as_ref())
1493 .map(|i| i.user_id().to_owned()),
1494 )
1495 .await?;
1496 let room = Room::new(self.clone(), base_room);
1497
1498 if mark_as_dm {
1499 room.set_is_direct(true).await?;
1500 }
1501
1502 #[cfg(feature = "e2e-encryption")]
1503 if self.inner.enable_share_history_on_invite {
1504 if let Some(inviter) =
1505 pre_join_room_info.as_ref().and_then(|info| info.inviter.as_ref())
1506 {
1507 crate::room::shared_room_history::maybe_accept_key_bundle(&room, inviter.user_id())
1508 .await?;
1509 }
1510 }
1511
1512 // Suppress "unused variable" and "unused field" lints
1513 #[cfg(not(feature = "e2e-encryption"))]
1514 let _ = pre_join_room_info.map(|i| i.inviter);
1515
1516 Ok(room)
1517 }
1518
1519 /// Join a room by `RoomId`.
1520 ///
1521 /// Returns the `Room` in the joined state.
1522 ///
1523 /// # Arguments
1524 ///
1525 /// * `room_id` - The `RoomId` of the room to be joined.
1526 #[instrument(skip(self))]
1527 pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result<Room> {
1528 // See who invited us to this room, if anyone. Note we have to do this before
1529 // making the `/join` request, otherwise we could race against the sync.
1530 let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1531
1532 let request = join_room_by_id::v3::Request::new(room_id.to_owned());
1533 let response = self.send(request).await?;
1534 self.finish_join_room(&response.room_id, pre_join_info).await
1535 }
1536
1537 /// Join a room by `RoomOrAliasId`.
1538 ///
1539 /// Returns the `Room` in the joined state.
1540 ///
1541 /// # Arguments
1542 ///
1543 /// * `alias` - The `RoomId` or `RoomAliasId` of the room to be joined. An
1544 /// alias looks like `#name:example.com`.
1545 /// * `server_names` - The server names to be used for resolving the alias,
1546 /// if needs be.
1547 pub async fn join_room_by_id_or_alias(
1548 &self,
1549 alias: &RoomOrAliasId,
1550 server_names: &[OwnedServerName],
1551 ) -> Result<Room> {
1552 let pre_join_info = {
1553 match alias.try_into() {
1554 Ok(room_id) => self.prepare_join_room_by_id(room_id).await,
1555 Err(_) => {
1556 // The id is a room alias. We assume (possibly incorrectly?) that we are not
1557 // responding to an invitation to the room, and therefore don't need to handle
1558 // things that happen as a result of invites.
1559 None
1560 }
1561 }
1562 };
1563 let request = assign!(join_room_by_id_or_alias::v3::Request::new(alias.to_owned()), {
1564 via: server_names.to_owned(),
1565 });
1566 let response = self.send(request).await?;
1567 self.finish_join_room(&response.room_id, pre_join_info).await
1568 }
1569
1570 /// Search the homeserver's directory of public rooms.
1571 ///
1572 /// Sends a request to "_matrix/client/r0/publicRooms", returns
1573 /// a `get_public_rooms::Response`.
1574 ///
1575 /// # Arguments
1576 ///
1577 /// * `limit` - The number of `PublicRoomsChunk`s in each response.
1578 ///
1579 /// * `since` - Pagination token from a previous request.
1580 ///
1581 /// * `server` - The name of the server, if `None` the requested server is
1582 /// used.
1583 ///
1584 /// # Examples
1585 /// ```no_run
1586 /// use matrix_sdk::Client;
1587 /// # use url::Url;
1588 /// # let homeserver = Url::parse("http://example.com").unwrap();
1589 /// # let limit = Some(10);
1590 /// # let since = Some("since token");
1591 /// # let server = Some("servername.com".try_into().unwrap());
1592 /// # async {
1593 /// let mut client = Client::new(homeserver).await.unwrap();
1594 ///
1595 /// client.public_rooms(limit, since, server).await;
1596 /// # };
1597 /// ```
1598 #[cfg_attr(not(target_family = "wasm"), deny(clippy::future_not_send))]
1599 pub async fn public_rooms(
1600 &self,
1601 limit: Option<u32>,
1602 since: Option<&str>,
1603 server: Option<&ServerName>,
1604 ) -> HttpResult<get_public_rooms::v3::Response> {
1605 let limit = limit.map(UInt::from);
1606
1607 let request = assign!(get_public_rooms::v3::Request::new(), {
1608 limit,
1609 since: since.map(ToOwned::to_owned),
1610 server: server.map(ToOwned::to_owned),
1611 });
1612 self.send(request).await
1613 }
1614
1615 /// Create a room with the given parameters.
1616 ///
1617 /// Sends a request to `/_matrix/client/r0/createRoom` and returns the
1618 /// created room.
1619 ///
1620 /// If you want to create a direct message with one specific user, you can
1621 /// use [`create_dm`][Self::create_dm], which is more convenient than
1622 /// assembling the [`create_room::v3::Request`] yourself.
1623 ///
1624 /// If the `is_direct` field of the request is set to `true` and at least
1625 /// one user is invited, the room will be automatically added to the direct
1626 /// rooms in the account data.
1627 ///
1628 /// # Examples
1629 ///
1630 /// ```no_run
1631 /// use matrix_sdk::{
1632 /// ruma::api::client::room::create_room::v3::Request as CreateRoomRequest,
1633 /// Client,
1634 /// };
1635 /// # use url::Url;
1636 /// #
1637 /// # async {
1638 /// # let homeserver = Url::parse("http://example.com").unwrap();
1639 /// let request = CreateRoomRequest::new();
1640 /// let client = Client::new(homeserver).await.unwrap();
1641 /// assert!(client.create_room(request).await.is_ok());
1642 /// # };
1643 /// ```
1644 pub async fn create_room(&self, request: create_room::v3::Request) -> Result<Room> {
1645 let invite = request.invite.clone();
1646 let is_direct_room = request.is_direct;
1647 let response = self.send(request).await?;
1648
1649 let base_room = self.base_client().get_or_create_room(&response.room_id, RoomState::Joined);
1650
1651 let joined_room = Room::new(self.clone(), base_room);
1652
1653 if is_direct_room && !invite.is_empty() {
1654 if let Err(error) =
1655 self.account().mark_as_dm(joined_room.room_id(), invite.as_slice()).await
1656 {
1657 // FIXME: Retry in the background
1658 error!("Failed to mark room as DM: {error}");
1659 }
1660 }
1661
1662 Ok(joined_room)
1663 }
1664
1665 /// Create a DM room.
1666 ///
1667 /// Convenience shorthand for [`create_room`][Self::create_room] with the
1668 /// given user being invited, the room marked `is_direct` and both the
1669 /// creator and invitee getting the default maximum power level.
1670 ///
1671 /// If the `e2e-encryption` feature is enabled, the room will also be
1672 /// encrypted.
1673 ///
1674 /// # Arguments
1675 ///
1676 /// * `user_id` - The ID of the user to create a DM for.
1677 pub async fn create_dm(&self, user_id: &UserId) -> Result<Room> {
1678 #[cfg(feature = "e2e-encryption")]
1679 let initial_state =
1680 vec![InitialStateEvent::new(RoomEncryptionEventContent::with_recommended_defaults())
1681 .to_raw_any()];
1682
1683 #[cfg(not(feature = "e2e-encryption"))]
1684 let initial_state = vec![];
1685
1686 let request = assign!(create_room::v3::Request::new(), {
1687 invite: vec![user_id.to_owned()],
1688 is_direct: true,
1689 preset: Some(create_room::v3::RoomPreset::TrustedPrivateChat),
1690 initial_state,
1691 });
1692
1693 self.create_room(request).await
1694 }
1695
1696 /// Search the homeserver's directory for public rooms with a filter.
1697 ///
1698 /// # Arguments
1699 ///
1700 /// * `room_search` - The easiest way to create this request is using the
1701 /// `get_public_rooms_filtered::Request` itself.
1702 ///
1703 /// # Examples
1704 ///
1705 /// ```no_run
1706 /// # use url::Url;
1707 /// # use matrix_sdk::Client;
1708 /// # async {
1709 /// # let homeserver = Url::parse("http://example.com")?;
1710 /// use matrix_sdk::ruma::{
1711 /// api::client::directory::get_public_rooms_filtered, directory::Filter,
1712 /// };
1713 /// # let mut client = Client::new(homeserver).await?;
1714 ///
1715 /// let mut filter = Filter::new();
1716 /// filter.generic_search_term = Some("rust".to_owned());
1717 /// let mut request = get_public_rooms_filtered::v3::Request::new();
1718 /// request.filter = filter;
1719 ///
1720 /// let response = client.public_rooms_filtered(request).await?;
1721 ///
1722 /// for room in response.chunk {
1723 /// println!("Found room {room:?}");
1724 /// }
1725 /// # anyhow::Ok(()) };
1726 /// ```
1727 pub async fn public_rooms_filtered(
1728 &self,
1729 request: get_public_rooms_filtered::v3::Request,
1730 ) -> HttpResult<get_public_rooms_filtered::v3::Response> {
1731 self.send(request).await
1732 }
1733
1734 /// Send an arbitrary request to the server, without updating client state.
1735 ///
1736 /// **Warning:** Because this method *does not* update the client state, it
1737 /// is important to make sure that you account for this yourself, and
1738 /// use wrapper methods where available. This method should *only* be
1739 /// used if a wrapper method for the endpoint you'd like to use is not
1740 /// available.
1741 ///
1742 /// # Arguments
1743 ///
1744 /// * `request` - A filled out and valid request for the endpoint to be hit
1745 ///
1746 /// * `timeout` - An optional request timeout setting, this overrides the
1747 /// default request setting if one was set.
1748 ///
1749 /// # Examples
1750 ///
1751 /// ```no_run
1752 /// # use matrix_sdk::{Client, config::SyncSettings};
1753 /// # use url::Url;
1754 /// # async {
1755 /// # let homeserver = Url::parse("http://localhost:8080")?;
1756 /// # let mut client = Client::new(homeserver).await?;
1757 /// use matrix_sdk::ruma::{api::client::profile, user_id};
1758 ///
1759 /// // First construct the request you want to make
1760 /// // See https://docs.rs/ruma-client-api/latest/ruma_client_api/index.html
1761 /// // for all available Endpoints
1762 /// let user_id = user_id!("@example:localhost").to_owned();
1763 /// let request = profile::get_profile::v3::Request::new(user_id);
1764 ///
1765 /// // Start the request using Client::send()
1766 /// let response = client.send(request).await?;
1767 ///
1768 /// // Check the corresponding Response struct to find out what types are
1769 /// // returned
1770 /// # anyhow::Ok(()) };
1771 /// ```
1772 pub fn send<Request>(&self, request: Request) -> SendRequest<Request>
1773 where
1774 Request: OutgoingRequest + Clone + Debug,
1775 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
1776 {
1777 SendRequest {
1778 client: self.clone(),
1779 request,
1780 config: None,
1781 send_progress: Default::default(),
1782 }
1783 }
1784
1785 pub(crate) async fn send_inner<Request>(
1786 &self,
1787 request: Request,
1788 config: Option<RequestConfig>,
1789 send_progress: SharedObservable<TransmissionProgress>,
1790 ) -> HttpResult<Request::IncomingResponse>
1791 where
1792 Request: OutgoingRequest + Debug,
1793 HttpError: From<FromHttpResponseError<Request::EndpointError>>,
1794 {
1795 let homeserver = self.homeserver().to_string();
1796 let access_token = self.access_token();
1797
1798 self.inner
1799 .http_client
1800 .send(
1801 request,
1802 config,
1803 homeserver,
1804 access_token.as_deref(),
1805 &self.supported_versions().await?,
1806 send_progress,
1807 )
1808 .await
1809 }
1810
1811 fn broadcast_unknown_token(&self, soft_logout: &bool) {
1812 _ = self
1813 .inner
1814 .auth_ctx
1815 .session_change_sender
1816 .send(SessionChange::UnknownToken { soft_logout: *soft_logout });
1817 }
1818
1819 /// Fetches server versions from network; no caching.
1820 pub async fn fetch_server_versions(
1821 &self,
1822 request_config: Option<RequestConfig>,
1823 ) -> HttpResult<get_supported_versions::Response> {
1824 let server_versions = self
1825 .inner
1826 .http_client
1827 .send(
1828 get_supported_versions::Request::new(),
1829 request_config,
1830 self.homeserver().to_string(),
1831 None,
1832 &SupportedVersions {
1833 versions: [MatrixVersion::V1_0].into(),
1834 features: Default::default(),
1835 },
1836 Default::default(),
1837 )
1838 .await?;
1839
1840 Ok(server_versions)
1841 }
1842
1843 /// Fetches client well_known from network; no caching.
1844 pub async fn fetch_client_well_known(&self) -> Option<discover_homeserver::Response> {
1845 let server_url_string = self
1846 .server()
1847 .unwrap_or(
1848 // Sometimes people configure their well-known directly on the homeserver so use
1849 // this as a fallback when the server name is unknown.
1850 &self.homeserver(),
1851 )
1852 .to_string();
1853
1854 let well_known = self
1855 .inner
1856 .http_client
1857 .send(
1858 discover_homeserver::Request::new(),
1859 Some(RequestConfig::short_retry()),
1860 server_url_string,
1861 None,
1862 &SupportedVersions {
1863 versions: [MatrixVersion::V1_0].into(),
1864 features: Default::default(),
1865 },
1866 Default::default(),
1867 )
1868 .await;
1869
1870 match well_known {
1871 Ok(well_known) => Some(well_known),
1872 Err(http_error) => {
1873 // It is perfectly valid to not have a well-known file.
1874 // Maybe we should check for a specific error code to be sure?
1875 warn!("Failed to fetch client well-known: {http_error}");
1876 None
1877 }
1878 }
1879 }
1880
1881 /// Load server info from storage, or fetch them from network and cache
1882 /// them.
1883 async fn load_or_fetch_server_info(&self) -> HttpResult<ServerInfo> {
1884 match self.state_store().get_kv_data(StateStoreDataKey::ServerInfo).await {
1885 Ok(Some(stored)) => {
1886 if let Some(server_info) =
1887 stored.into_server_info().and_then(|info| info.maybe_decode())
1888 {
1889 return Ok(server_info);
1890 }
1891 }
1892 Ok(None) => {
1893 // fallthrough: cache is empty
1894 }
1895 Err(err) => {
1896 warn!("error when loading cached server info: {err}");
1897 // fallthrough to network.
1898 }
1899 }
1900
1901 let server_versions = self.fetch_server_versions(None).await?;
1902 let well_known = self.fetch_client_well_known().await;
1903 let server_info = ServerInfo::new(
1904 server_versions.versions.clone(),
1905 server_versions.unstable_features.clone(),
1906 well_known.map(Into::into),
1907 );
1908
1909 // Attempt to cache the result in storage.
1910 {
1911 if let Err(err) = self
1912 .state_store()
1913 .set_kv_data(
1914 StateStoreDataKey::ServerInfo,
1915 StateStoreDataValue::ServerInfo(server_info.clone()),
1916 )
1917 .await
1918 {
1919 warn!("error when caching server info: {err}");
1920 }
1921 }
1922
1923 Ok(server_info)
1924 }
1925
1926 async fn get_or_load_and_cache_server_info<
1927 Value,
1928 MapFunction: Fn(&ClientServerInfo) -> CachedValue<Value>,
1929 >(
1930 &self,
1931 map: MapFunction,
1932 ) -> HttpResult<Value> {
1933 let server_info = &self.inner.caches.server_info;
1934 if let CachedValue::Cached(val) = map(&*server_info.read().await) {
1935 return Ok(val);
1936 }
1937
1938 let mut guarded_server_info = server_info.write().await;
1939 if let CachedValue::Cached(val) = map(&guarded_server_info) {
1940 return Ok(val);
1941 }
1942
1943 let server_info = self.load_or_fetch_server_info().await?;
1944
1945 // Fill both unstable features and server versions at once.
1946 let mut supported = server_info.supported_versions();
1947 if supported.versions.is_empty() {
1948 supported.versions = [MatrixVersion::V1_0].into();
1949 }
1950
1951 guarded_server_info.supported_versions = CachedValue::Cached(supported);
1952 guarded_server_info.well_known = CachedValue::Cached(server_info.well_known);
1953
1954 // SAFETY: all fields were set above, so (assuming the caller doesn't attempt to
1955 // fetch an optional property), the function will always return some.
1956 Ok(map(&guarded_server_info).unwrap_cached_value())
1957 }
1958
1959 /// Get the Matrix versions and features supported by the homeserver by
1960 /// fetching them from the server or the cache.
1961 ///
1962 /// This is equivalent to calling both [`Client::server_versions()`] and
1963 /// [`Client::unstable_features()`]. To always fetch the result from the
1964 /// homeserver, you can call [`Client::fetch_server_versions()`] instead,
1965 /// and then `.as_supported_versions()` on the response.
1966 ///
1967 /// # Examples
1968 ///
1969 /// ```no_run
1970 /// use ruma::api::{FeatureFlag, MatrixVersion};
1971 /// # use matrix_sdk::{Client, config::SyncSettings};
1972 /// # use url::Url;
1973 /// # async {
1974 /// # let homeserver = Url::parse("http://localhost:8080")?;
1975 /// # let mut client = Client::new(homeserver).await?;
1976 ///
1977 /// let supported = client.supported_versions().await?;
1978 /// let supports_1_1 = supported.versions.contains(&MatrixVersion::V1_1);
1979 /// println!("The homeserver supports Matrix 1.1: {supports_1_1:?}");
1980 ///
1981 /// let msc_x_feature = FeatureFlag::from("msc_x");
1982 /// let supports_msc_x = supported.features.contains(&msc_x_feature);
1983 /// println!("The homeserver supports msc X: {supports_msc_x:?}");
1984 /// # anyhow::Ok(()) };
1985 /// ```
1986 pub async fn supported_versions(&self) -> HttpResult<SupportedVersions> {
1987 self.get_or_load_and_cache_server_info(|server_info| server_info.supported_versions.clone())
1988 .await
1989 }
1990
1991 /// Get the Matrix versions supported by the homeserver by fetching them
1992 /// from the server or the cache.
1993 ///
1994 /// # Examples
1995 ///
1996 /// ```no_run
1997 /// use ruma::api::MatrixVersion;
1998 /// # use matrix_sdk::{Client, config::SyncSettings};
1999 /// # use url::Url;
2000 /// # async {
2001 /// # let homeserver = Url::parse("http://localhost:8080")?;
2002 /// # let mut client = Client::new(homeserver).await?;
2003 ///
2004 /// let server_versions = client.server_versions().await?;
2005 /// let supports_1_1 = server_versions.contains(&MatrixVersion::V1_1);
2006 /// println!("The homeserver supports Matrix 1.1: {supports_1_1:?}");
2007 /// # anyhow::Ok(()) };
2008 /// ```
2009 pub async fn server_versions(&self) -> HttpResult<BTreeSet<MatrixVersion>> {
2010 self.get_or_load_and_cache_server_info(|server_info| {
2011 server_info.supported_versions.as_ref().map(|supported| supported.versions.clone())
2012 })
2013 .await
2014 }
2015
2016 /// Get the unstable features supported by the homeserver by fetching them
2017 /// from the server or the cache.
2018 ///
2019 /// # Examples
2020 ///
2021 /// ```no_run
2022 /// use matrix_sdk::ruma::api::FeatureFlag;
2023 /// # use matrix_sdk::{Client, config::SyncSettings};
2024 /// # use url::Url;
2025 /// # async {
2026 /// # let homeserver = Url::parse("http://localhost:8080")?;
2027 /// # let mut client = Client::new(homeserver).await?;
2028 ///
2029 /// let msc_x_feature = FeatureFlag::from("msc_x");
2030 /// let unstable_features = client.unstable_features().await?;
2031 /// let supports_msc_x = unstable_features.contains(&msc_x_feature);
2032 /// println!("The homeserver supports msc X: {supports_msc_x:?}");
2033 /// # anyhow::Ok(()) };
2034 /// ```
2035 pub async fn unstable_features(&self) -> HttpResult<BTreeSet<FeatureFlag>> {
2036 self.get_or_load_and_cache_server_info(|server_info| {
2037 server_info.supported_versions.as_ref().map(|supported| supported.features.clone())
2038 })
2039 .await
2040 }
2041
2042 /// Get information about the homeserver's advertised RTC foci by fetching
2043 /// the well-known file from the server or the cache.
2044 ///
2045 /// # Examples
2046 /// ```no_run
2047 /// # use matrix_sdk::{Client, config::SyncSettings, ruma::api::client::discovery::discover_homeserver::RtcFocusInfo};
2048 /// # use url::Url;
2049 /// # async {
2050 /// # let homeserver = Url::parse("http://localhost:8080")?;
2051 /// # let mut client = Client::new(homeserver).await?;
2052 /// let rtc_foci = client.rtc_foci().await?;
2053 /// let default_livekit_focus_info = rtc_foci.iter().find_map(|focus| match focus {
2054 /// RtcFocusInfo::LiveKit(info) => Some(info),
2055 /// _ => None,
2056 /// });
2057 /// if let Some(info) = default_livekit_focus_info {
2058 /// println!("Default LiveKit service URL: {}", info.service_url);
2059 /// }
2060 /// # anyhow::Ok(()) };
2061 /// ```
2062 pub async fn rtc_foci(&self) -> HttpResult<Vec<RtcFocusInfo>> {
2063 let well_known = self
2064 .get_or_load_and_cache_server_info(|server_info| server_info.well_known.clone())
2065 .await?;
2066
2067 Ok(well_known.map(|well_known| well_known.rtc_foci).unwrap_or_default())
2068 }
2069
2070 /// Empty the server version and unstable features cache.
2071 ///
2072 /// Since the SDK caches server info (versions, unstable features,
2073 /// well-known etc), it's possible to have a stale entry in the cache. This
2074 /// functions makes it possible to force reset it.
2075 pub async fn reset_server_info(&self) -> Result<()> {
2076 // Empty the in-memory caches.
2077 let mut guard = self.inner.caches.server_info.write().await;
2078 guard.supported_versions = CachedValue::NotSet;
2079
2080 // Empty the store cache.
2081 Ok(self.state_store().remove_kv_data(StateStoreDataKey::ServerInfo).await?)
2082 }
2083
2084 /// Check whether MSC 4028 is enabled on the homeserver.
2085 ///
2086 /// # Examples
2087 ///
2088 /// ```no_run
2089 /// # use matrix_sdk::{Client, config::SyncSettings};
2090 /// # use url::Url;
2091 /// # async {
2092 /// # let homeserver = Url::parse("http://localhost:8080")?;
2093 /// # let mut client = Client::new(homeserver).await?;
2094 /// let msc4028_enabled =
2095 /// client.can_homeserver_push_encrypted_event_to_device().await?;
2096 /// # anyhow::Ok(()) };
2097 /// ```
2098 pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> HttpResult<bool> {
2099 Ok(self.unstable_features().await?.contains(&FeatureFlag::from("org.matrix.msc4028")))
2100 }
2101
2102 /// Get information of all our own devices.
2103 ///
2104 /// # Examples
2105 ///
2106 /// ```no_run
2107 /// # use matrix_sdk::{Client, config::SyncSettings};
2108 /// # use url::Url;
2109 /// # async {
2110 /// # let homeserver = Url::parse("http://localhost:8080")?;
2111 /// # let mut client = Client::new(homeserver).await?;
2112 /// let response = client.devices().await?;
2113 ///
2114 /// for device in response.devices {
2115 /// println!(
2116 /// "Device: {} {}",
2117 /// device.device_id,
2118 /// device.display_name.as_deref().unwrap_or("")
2119 /// );
2120 /// }
2121 /// # anyhow::Ok(()) };
2122 /// ```
2123 pub async fn devices(&self) -> HttpResult<get_devices::v3::Response> {
2124 let request = get_devices::v3::Request::new();
2125
2126 self.send(request).await
2127 }
2128
2129 /// Delete the given devices from the server.
2130 ///
2131 /// # Arguments
2132 ///
2133 /// * `devices` - The list of devices that should be deleted from the
2134 /// server.
2135 ///
2136 /// * `auth_data` - This request requires user interactive auth, the first
2137 /// request needs to set this to `None` and will always fail with an
2138 /// `UiaaResponse`. The response will contain information for the
2139 /// interactive auth and the same request needs to be made but this time
2140 /// with some `auth_data` provided.
2141 ///
2142 /// ```no_run
2143 /// # use matrix_sdk::{
2144 /// # ruma::{api::client::uiaa, device_id},
2145 /// # Client, Error, config::SyncSettings,
2146 /// # };
2147 /// # use serde_json::json;
2148 /// # use url::Url;
2149 /// # use std::collections::BTreeMap;
2150 /// # async {
2151 /// # let homeserver = Url::parse("http://localhost:8080")?;
2152 /// # let mut client = Client::new(homeserver).await?;
2153 /// let devices = &[device_id!("DEVICEID").to_owned()];
2154 ///
2155 /// if let Err(e) = client.delete_devices(devices, None).await {
2156 /// if let Some(info) = e.as_uiaa_response() {
2157 /// let mut password = uiaa::Password::new(
2158 /// uiaa::UserIdentifier::UserIdOrLocalpart("example".to_owned()),
2159 /// "wordpass".to_owned(),
2160 /// );
2161 /// password.session = info.session.clone();
2162 ///
2163 /// client
2164 /// .delete_devices(devices, Some(uiaa::AuthData::Password(password)))
2165 /// .await?;
2166 /// }
2167 /// }
2168 /// # anyhow::Ok(()) };
2169 pub async fn delete_devices(
2170 &self,
2171 devices: &[OwnedDeviceId],
2172 auth_data: Option<uiaa::AuthData>,
2173 ) -> HttpResult<delete_devices::v3::Response> {
2174 let mut request = delete_devices::v3::Request::new(devices.to_owned());
2175 request.auth = auth_data;
2176
2177 self.send(request).await
2178 }
2179
2180 /// Change the display name of a device owned by the current user.
2181 ///
2182 /// Returns a `update_device::Response` which specifies the result
2183 /// of the operation.
2184 ///
2185 /// # Arguments
2186 ///
2187 /// * `device_id` - The ID of the device to change the display name of.
2188 /// * `display_name` - The new display name to set.
2189 pub async fn rename_device(
2190 &self,
2191 device_id: &DeviceId,
2192 display_name: &str,
2193 ) -> HttpResult<update_device::v3::Response> {
2194 let mut request = update_device::v3::Request::new(device_id.to_owned());
2195 request.display_name = Some(display_name.to_owned());
2196
2197 self.send(request).await
2198 }
2199
2200 /// Synchronize the client's state with the latest state on the server.
2201 ///
2202 /// ## Syncing Events
2203 ///
2204 /// Messages or any other type of event need to be periodically fetched from
2205 /// the server, this is achieved by sending a `/sync` request to the server.
2206 ///
2207 /// The first sync is sent out without a [`token`]. The response of the
2208 /// first sync will contain a [`next_batch`] field which should then be
2209 /// used in the subsequent sync calls as the [`token`]. This ensures that we
2210 /// don't receive the same events multiple times.
2211 ///
2212 /// ## Long Polling
2213 ///
2214 /// A sync should in the usual case always be in flight. The
2215 /// [`SyncSettings`] have a [`timeout`] option, which controls how
2216 /// long the server will wait for new events before it will respond.
2217 /// The server will respond immediately if some new events arrive before the
2218 /// timeout has expired. If no changes arrive and the timeout expires an
2219 /// empty sync response will be sent to the client.
2220 ///
2221 /// This method of sending a request that may not receive a response
2222 /// immediately is called long polling.
2223 ///
2224 /// ## Filtering Events
2225 ///
2226 /// The number or type of messages and events that the client should receive
2227 /// from the server can be altered using a [`Filter`].
2228 ///
2229 /// Filters can be non-trivial and, since they will be sent with every sync
2230 /// request, they may take up a bunch of unnecessary bandwidth.
2231 ///
2232 /// Luckily filters can be uploaded to the server and reused using an unique
2233 /// identifier, this can be achieved using the [`get_or_upload_filter()`]
2234 /// method.
2235 ///
2236 /// # Arguments
2237 ///
2238 /// * `sync_settings` - Settings for the sync call, this allows us to set
2239 /// various options to configure the sync:
2240 /// * [`filter`] - To configure which events we receive and which get
2241 /// [filtered] by the server
2242 /// * [`timeout`] - To configure our [long polling] setup.
2243 /// * [`token`] - To tell the server which events we already received
2244 /// and where we wish to continue syncing.
2245 /// * [`full_state`] - To tell the server that we wish to receive all
2246 /// state events, regardless of our configured [`token`].
2247 /// * [`set_presence`] - To tell the server to set the presence and to
2248 /// which state.
2249 ///
2250 /// # Examples
2251 ///
2252 /// ```no_run
2253 /// # use url::Url;
2254 /// # async {
2255 /// # let homeserver = Url::parse("http://localhost:8080")?;
2256 /// # let username = "";
2257 /// # let password = "";
2258 /// use matrix_sdk::{
2259 /// config::SyncSettings,
2260 /// ruma::events::room::message::OriginalSyncRoomMessageEvent, Client,
2261 /// };
2262 ///
2263 /// let client = Client::new(homeserver).await?;
2264 /// client.matrix_auth().login_username(username, password).send().await?;
2265 ///
2266 /// // Sync once so we receive the client state and old messages.
2267 /// client.sync_once(SyncSettings::default()).await?;
2268 ///
2269 /// // Register our handler so we start responding once we receive a new
2270 /// // event.
2271 /// client.add_event_handler(|ev: OriginalSyncRoomMessageEvent| async move {
2272 /// println!("Received event {}: {:?}", ev.sender, ev.content);
2273 /// });
2274 ///
2275 /// // Now keep on syncing forever. `sync()` will use the stored sync token
2276 /// // from our `sync_once()` call automatically.
2277 /// client.sync(SyncSettings::default()).await;
2278 /// # anyhow::Ok(()) };
2279 /// ```
2280 ///
2281 /// [`sync`]: #method.sync
2282 /// [`SyncSettings`]: crate::config::SyncSettings
2283 /// [`token`]: crate::config::SyncSettings#method.token
2284 /// [`timeout`]: crate::config::SyncSettings#method.timeout
2285 /// [`full_state`]: crate::config::SyncSettings#method.full_state
2286 /// [`set_presence`]: ruma::presence::PresenceState
2287 /// [`filter`]: crate::config::SyncSettings#method.filter
2288 /// [`Filter`]: ruma::api::client::sync::sync_events::v3::Filter
2289 /// [`next_batch`]: SyncResponse#structfield.next_batch
2290 /// [`get_or_upload_filter()`]: #method.get_or_upload_filter
2291 /// [long polling]: #long-polling
2292 /// [filtered]: #filtering-events
2293 #[instrument(skip(self))]
2294 pub async fn sync_once(
2295 &self,
2296 sync_settings: crate::config::SyncSettings,
2297 ) -> Result<SyncResponse> {
2298 // The sync might not return for quite a while due to the timeout.
2299 // We'll see if there's anything crypto related to send out before we
2300 // sync, i.e. if we closed our client after a sync but before the
2301 // crypto requests were sent out.
2302 //
2303 // This will mostly be a no-op.
2304 #[cfg(feature = "e2e-encryption")]
2305 if let Err(e) = self.send_outgoing_requests().await {
2306 error!(error = ?e, "Error while sending outgoing E2EE requests");
2307 }
2308
2309 let request = assign!(sync_events::v3::Request::new(), {
2310 filter: sync_settings.filter.map(|f| *f),
2311 since: sync_settings.token,
2312 full_state: sync_settings.full_state,
2313 set_presence: sync_settings.set_presence,
2314 timeout: sync_settings.timeout,
2315 use_state_after: true,
2316 });
2317 let mut request_config = self.request_config();
2318 if let Some(timeout) = sync_settings.timeout {
2319 let base_timeout = request_config.timeout.unwrap_or(Duration::from_secs(30));
2320 request_config.timeout = Some(base_timeout + timeout);
2321 }
2322
2323 let response = self.send(request).with_request_config(request_config).await?;
2324 let next_batch = response.next_batch.clone();
2325 let response = self.process_sync(response).await?;
2326
2327 #[cfg(feature = "e2e-encryption")]
2328 if let Err(e) = self.send_outgoing_requests().await {
2329 error!(error = ?e, "Error while sending outgoing E2EE requests");
2330 }
2331
2332 self.inner.sync_beat.notify(usize::MAX);
2333
2334 Ok(SyncResponse::new(next_batch, response))
2335 }
2336
2337 /// Repeatedly synchronize the client state with the server.
2338 ///
2339 /// This method will only return on error, if cancellation is needed
2340 /// the method should be wrapped in a cancelable task or the
2341 /// [`Client::sync_with_callback`] method can be used or
2342 /// [`Client::sync_with_result_callback`] if you want to handle error
2343 /// cases in the loop, too.
2344 ///
2345 /// This method will internally call [`Client::sync_once`] in a loop.
2346 ///
2347 /// This method can be used with the [`Client::add_event_handler`]
2348 /// method to react to individual events. If you instead wish to handle
2349 /// events in a bulk manner the [`Client::sync_with_callback`],
2350 /// [`Client::sync_with_result_callback`] and
2351 /// [`Client::sync_stream`] methods can be used instead. Those methods
2352 /// repeatedly return the whole sync response.
2353 ///
2354 /// # Arguments
2355 ///
2356 /// * `sync_settings` - Settings for the sync call. *Note* that those
2357 /// settings will be only used for the first sync call. See the argument
2358 /// docs for [`Client::sync_once`] for more info.
2359 ///
2360 /// # Return
2361 /// The sync runs until an error occurs, returning with `Err(Error)`. It is
2362 /// up to the user of the API to check the error and decide whether the sync
2363 /// should continue or not.
2364 ///
2365 /// # Examples
2366 ///
2367 /// ```no_run
2368 /// # use url::Url;
2369 /// # async {
2370 /// # let homeserver = Url::parse("http://localhost:8080")?;
2371 /// # let username = "";
2372 /// # let password = "";
2373 /// use matrix_sdk::{
2374 /// config::SyncSettings,
2375 /// ruma::events::room::message::OriginalSyncRoomMessageEvent, Client,
2376 /// };
2377 ///
2378 /// let client = Client::new(homeserver).await?;
2379 /// client.matrix_auth().login_username(&username, &password).send().await?;
2380 ///
2381 /// // Register our handler so we start responding once we receive a new
2382 /// // event.
2383 /// client.add_event_handler(|ev: OriginalSyncRoomMessageEvent| async move {
2384 /// println!("Received event {}: {:?}", ev.sender, ev.content);
2385 /// });
2386 ///
2387 /// // Now keep on syncing forever. `sync()` will use the latest sync token
2388 /// // automatically.
2389 /// client.sync(SyncSettings::default()).await?;
2390 /// # anyhow::Ok(()) };
2391 /// ```
2392 ///
2393 /// [argument docs]: #method.sync_once
2394 /// [`sync_with_callback`]: #method.sync_with_callback
2395 pub async fn sync(&self, sync_settings: crate::config::SyncSettings) -> Result<(), Error> {
2396 self.sync_with_callback(sync_settings, |_| async { LoopCtrl::Continue }).await
2397 }
2398
2399 /// Repeatedly call sync to synchronize the client state with the server.
2400 ///
2401 /// # Arguments
2402 ///
2403 /// * `sync_settings` - Settings for the sync call. *Note* that those
2404 /// settings will be only used for the first sync call. See the argument
2405 /// docs for [`Client::sync_once`] for more info.
2406 ///
2407 /// * `callback` - A callback that will be called every time a successful
2408 /// response has been fetched from the server. The callback must return a
2409 /// boolean which signalizes if the method should stop syncing. If the
2410 /// callback returns `LoopCtrl::Continue` the sync will continue, if the
2411 /// callback returns `LoopCtrl::Break` the sync will be stopped.
2412 ///
2413 /// # Return
2414 /// The sync runs until an error occurs or the
2415 /// callback indicates that the Loop should stop. If the callback asked for
2416 /// a regular stop, the result will be `Ok(())` otherwise the
2417 /// `Err(Error)` is returned.
2418 ///
2419 /// # Examples
2420 ///
2421 /// The following example demonstrates how to sync forever while sending all
2422 /// the interesting events through a mpsc channel to another thread e.g. a
2423 /// UI thread.
2424 ///
2425 /// ```no_run
2426 /// # use std::time::Duration;
2427 /// # use matrix_sdk::{Client, config::SyncSettings, LoopCtrl};
2428 /// # use url::Url;
2429 /// # async {
2430 /// # let homeserver = Url::parse("http://localhost:8080").unwrap();
2431 /// # let mut client = Client::new(homeserver).await.unwrap();
2432 ///
2433 /// use tokio::sync::mpsc::channel;
2434 ///
2435 /// let (tx, rx) = channel(100);
2436 ///
2437 /// let sync_channel = &tx;
2438 /// let sync_settings = SyncSettings::new()
2439 /// .timeout(Duration::from_secs(30));
2440 ///
2441 /// client
2442 /// .sync_with_callback(sync_settings, |response| async move {
2443 /// let channel = sync_channel;
2444 /// for (room_id, room) in response.rooms.joined {
2445 /// for event in room.timeline.events {
2446 /// channel.send(event).await.unwrap();
2447 /// }
2448 /// }
2449 ///
2450 /// LoopCtrl::Continue
2451 /// })
2452 /// .await;
2453 /// };
2454 /// ```
2455 #[instrument(skip_all)]
2456 pub async fn sync_with_callback<C>(
2457 &self,
2458 sync_settings: crate::config::SyncSettings,
2459 callback: impl Fn(SyncResponse) -> C,
2460 ) -> Result<(), Error>
2461 where
2462 C: Future<Output = LoopCtrl>,
2463 {
2464 self.sync_with_result_callback(sync_settings, |result| async {
2465 Ok(callback(result?).await)
2466 })
2467 .await
2468 }
2469
2470 /// Repeatedly call sync to synchronize the client state with the server.
2471 ///
2472 /// # Arguments
2473 ///
2474 /// * `sync_settings` - Settings for the sync call. *Note* that those
2475 /// settings will be only used for the first sync call. See the argument
2476 /// docs for [`Client::sync_once`] for more info.
2477 ///
2478 /// * `callback` - A callback that will be called every time after a
2479 /// response has been received, failure or not. The callback returns a
2480 /// `Result<LoopCtrl, Error>`, too. When returning
2481 /// `Ok(LoopCtrl::Continue)` the sync will continue, if the callback
2482 /// returns `Ok(LoopCtrl::Break)` the sync will be stopped and the
2483 /// function returns `Ok(())`. In case the callback can't handle the
2484 /// `Error` or has a different malfunction, it can return an `Err(Error)`,
2485 /// which results in the sync ending and the `Err(Error)` being returned.
2486 ///
2487 /// # Return
2488 /// The sync runs until an error occurs that the callback can't handle or
2489 /// the callback indicates that the Loop should stop. If the callback
2490 /// asked for a regular stop, the result will be `Ok(())` otherwise the
2491 /// `Err(Error)` is returned.
2492 ///
2493 /// _Note_: Lower-level configuration (e.g. for retries) are not changed by
2494 /// this, and are handled first without sending the result to the
2495 /// callback. Only after they have exceeded is the `Result` handed to
2496 /// the callback.
2497 ///
2498 /// # Examples
2499 ///
2500 /// The following example demonstrates how to sync forever while sending all
2501 /// the interesting events through a mpsc channel to another thread e.g. a
2502 /// UI thread.
2503 ///
2504 /// ```no_run
2505 /// # use std::time::Duration;
2506 /// # use matrix_sdk::{Client, config::SyncSettings, LoopCtrl};
2507 /// # use url::Url;
2508 /// # async {
2509 /// # let homeserver = Url::parse("http://localhost:8080").unwrap();
2510 /// # let mut client = Client::new(homeserver).await.unwrap();
2511 /// #
2512 /// use tokio::sync::mpsc::channel;
2513 ///
2514 /// let (tx, rx) = channel(100);
2515 ///
2516 /// let sync_channel = &tx;
2517 /// let sync_settings = SyncSettings::new()
2518 /// .timeout(Duration::from_secs(30));
2519 ///
2520 /// client
2521 /// .sync_with_result_callback(sync_settings, |response| async move {
2522 /// let channel = sync_channel;
2523 /// let sync_response = response?;
2524 /// for (room_id, room) in sync_response.rooms.joined {
2525 /// for event in room.timeline.events {
2526 /// channel.send(event).await.unwrap();
2527 /// }
2528 /// }
2529 ///
2530 /// Ok(LoopCtrl::Continue)
2531 /// })
2532 /// .await;
2533 /// };
2534 /// ```
2535 #[instrument(skip(self, callback))]
2536 pub async fn sync_with_result_callback<C>(
2537 &self,
2538 sync_settings: crate::config::SyncSettings,
2539 callback: impl Fn(Result<SyncResponse, Error>) -> C,
2540 ) -> Result<(), Error>
2541 where
2542 C: Future<Output = Result<LoopCtrl, Error>>,
2543 {
2544 let mut sync_stream = Box::pin(self.sync_stream(sync_settings).await);
2545
2546 while let Some(result) = sync_stream.next().await {
2547 trace!("Running callback");
2548 if callback(result).await? == LoopCtrl::Break {
2549 trace!("Callback told us to stop");
2550 break;
2551 }
2552 trace!("Done running callback");
2553 }
2554
2555 Ok(())
2556 }
2557
2558 //// Repeatedly synchronize the client state with the server.
2559 ///
2560 /// This method will internally call [`Client::sync_once`] in a loop and is
2561 /// equivalent to the [`Client::sync`] method but the responses are provided
2562 /// as an async stream.
2563 ///
2564 /// # Arguments
2565 ///
2566 /// * `sync_settings` - Settings for the sync call. *Note* that those
2567 /// settings will be only used for the first sync call. See the argument
2568 /// docs for [`Client::sync_once`] for more info.
2569 ///
2570 /// # Examples
2571 ///
2572 /// ```no_run
2573 /// # use url::Url;
2574 /// # async {
2575 /// # let homeserver = Url::parse("http://localhost:8080")?;
2576 /// # let username = "";
2577 /// # let password = "";
2578 /// use futures_util::StreamExt;
2579 /// use matrix_sdk::{config::SyncSettings, Client};
2580 ///
2581 /// let client = Client::new(homeserver).await?;
2582 /// client.matrix_auth().login_username(&username, &password).send().await?;
2583 ///
2584 /// let mut sync_stream =
2585 /// Box::pin(client.sync_stream(SyncSettings::default()).await);
2586 ///
2587 /// while let Some(Ok(response)) = sync_stream.next().await {
2588 /// for room in response.rooms.joined.values() {
2589 /// for e in &room.timeline.events {
2590 /// if let Ok(event) = e.raw().deserialize() {
2591 /// println!("Received event {:?}", event);
2592 /// }
2593 /// }
2594 /// }
2595 /// }
2596 ///
2597 /// # anyhow::Ok(()) };
2598 /// ```
2599 #[allow(unknown_lints, clippy::let_with_type_underscore)] // triggered by instrument macro
2600 #[instrument(skip(self))]
2601 pub async fn sync_stream(
2602 &self,
2603 mut sync_settings: crate::config::SyncSettings,
2604 ) -> impl Stream<Item = Result<SyncResponse>> + '_ {
2605 let mut is_first_sync = true;
2606 let mut timeout = None;
2607 let mut last_sync_time: Option<Instant> = None;
2608
2609 if sync_settings.token.is_none() {
2610 sync_settings.token = self.sync_token().await;
2611 }
2612
2613 let parent_span = Span::current();
2614
2615 async_stream::stream!({
2616 loop {
2617 trace!("Syncing");
2618
2619 if sync_settings.ignore_timeout_on_first_sync {
2620 if is_first_sync {
2621 timeout = sync_settings.timeout.take();
2622 } else if sync_settings.timeout.is_none() && timeout.is_some() {
2623 sync_settings.timeout = timeout.take();
2624 }
2625
2626 is_first_sync = false;
2627 }
2628
2629 yield self
2630 .sync_loop_helper(&mut sync_settings)
2631 .instrument(parent_span.clone())
2632 .await;
2633
2634 Client::delay_sync(&mut last_sync_time).await
2635 }
2636 })
2637 }
2638
2639 /// Get the current, if any, sync token of the client.
2640 /// This will be None if the client didn't sync at least once.
2641 pub(crate) async fn sync_token(&self) -> Option<String> {
2642 self.inner.base_client.sync_token().await
2643 }
2644
2645 /// Gets information about the owner of a given access token.
2646 pub async fn whoami(&self) -> HttpResult<whoami::v3::Response> {
2647 let request = whoami::v3::Request::new();
2648 self.send(request).await
2649 }
2650
2651 /// Subscribes a new receiver to client SessionChange broadcasts.
2652 pub fn subscribe_to_session_changes(&self) -> broadcast::Receiver<SessionChange> {
2653 let broadcast = &self.auth_ctx().session_change_sender;
2654 broadcast.subscribe()
2655 }
2656
2657 /// Sets the save/restore session callbacks.
2658 ///
2659 /// This is another mechanism to get synchronous updates to session tokens,
2660 /// while [`Self::subscribe_to_session_changes`] provides an async update.
2661 pub fn set_session_callbacks(
2662 &self,
2663 reload_session_callback: Box<ReloadSessionCallback>,
2664 save_session_callback: Box<SaveSessionCallback>,
2665 ) -> Result<()> {
2666 self.inner
2667 .auth_ctx
2668 .reload_session_callback
2669 .set(reload_session_callback)
2670 .map_err(|_| Error::MultipleSessionCallbacks)?;
2671
2672 self.inner
2673 .auth_ctx
2674 .save_session_callback
2675 .set(save_session_callback)
2676 .map_err(|_| Error::MultipleSessionCallbacks)?;
2677
2678 Ok(())
2679 }
2680
2681 /// Get the notification settings of the current owner of the client.
2682 pub async fn notification_settings(&self) -> NotificationSettings {
2683 let ruleset = self.account().push_rules().await.unwrap_or_else(|_| Ruleset::new());
2684 NotificationSettings::new(self.clone(), ruleset)
2685 }
2686
2687 /// Create a new specialized `Client` that can process notifications.
2688 ///
2689 /// See [`CrossProcessStoreLock::new`] to learn more about
2690 /// `cross_process_store_locks_holder_name`.
2691 ///
2692 /// [`CrossProcessStoreLock::new`]: matrix_sdk_common::store_locks::CrossProcessStoreLock::new
2693 pub async fn notification_client(
2694 &self,
2695 cross_process_store_locks_holder_name: String,
2696 ) -> Result<Client> {
2697 let client = Client {
2698 inner: ClientInner::new(
2699 self.inner.auth_ctx.clone(),
2700 self.server().cloned(),
2701 self.homeserver(),
2702 self.sliding_sync_version(),
2703 self.inner.http_client.clone(),
2704 self.inner
2705 .base_client
2706 .clone_with_in_memory_state_store(&cross_process_store_locks_holder_name, false)
2707 .await?,
2708 self.inner.caches.server_info.read().await.clone(),
2709 self.inner.respect_login_well_known,
2710 self.inner.event_cache.clone(),
2711 self.inner.send_queue_data.clone(),
2712 self.inner.latest_events.clone(),
2713 #[cfg(feature = "e2e-encryption")]
2714 self.inner.e2ee.encryption_settings,
2715 #[cfg(feature = "e2e-encryption")]
2716 self.inner.enable_share_history_on_invite,
2717 cross_process_store_locks_holder_name,
2718 )
2719 .await,
2720 };
2721
2722 Ok(client)
2723 }
2724
2725 /// The [`EventCache`] instance for this [`Client`].
2726 pub fn event_cache(&self) -> &EventCache {
2727 // SAFETY: always initialized in the `Client` ctor.
2728 self.inner.event_cache.get().unwrap()
2729 }
2730
2731 /// The [`LatestEvents`] instance for this [`Client`].
2732 pub async fn latest_events(&self) -> &LatestEvents {
2733 self.inner
2734 .latest_events
2735 .get_or_init(|| async {
2736 LatestEvents::new(
2737 WeakClient::from_client(self),
2738 self.event_cache().clone(),
2739 SendQueue::new(self.clone()),
2740 )
2741 })
2742 .await
2743 }
2744
2745 /// Waits until an at least partially synced room is received, and returns
2746 /// it.
2747 ///
2748 /// **Note: this function will loop endlessly until either it finds the room
2749 /// or an externally set timeout happens.**
2750 pub async fn await_room_remote_echo(&self, room_id: &RoomId) -> Room {
2751 loop {
2752 if let Some(room) = self.get_room(room_id) {
2753 if room.is_state_partially_or_fully_synced() {
2754 debug!("Found just created room!");
2755 return room;
2756 }
2757 debug!("Room wasn't partially synced, waiting for sync beat to try again");
2758 } else {
2759 debug!("Room wasn't found, waiting for sync beat to try again");
2760 }
2761 self.inner.sync_beat.listen().await;
2762 }
2763 }
2764
2765 /// Knock on a room given its `room_id_or_alias` to ask for permission to
2766 /// join it.
2767 pub async fn knock(
2768 &self,
2769 room_id_or_alias: OwnedRoomOrAliasId,
2770 reason: Option<String>,
2771 server_names: Vec<OwnedServerName>,
2772 ) -> Result<Room> {
2773 let request =
2774 assign!(knock_room::v3::Request::new(room_id_or_alias), { reason, via: server_names });
2775 let response = self.send(request).await?;
2776 let base_room = self.inner.base_client.room_knocked(&response.room_id).await?;
2777 Ok(Room::new(self.clone(), base_room))
2778 }
2779
2780 /// Checks whether the provided `user_id` belongs to an ignored user.
2781 pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
2782 self.base_client().is_user_ignored(user_id).await
2783 }
2784
2785 /// Gets the `max_upload_size` value from the homeserver, getting either a
2786 /// cached value or with a `/_matrix/client/v1/media/config` request if it's
2787 /// missing.
2788 ///
2789 /// Check the spec for more info:
2790 /// <https://spec.matrix.org/v1.14/client-server-api/#get_matrixclientv1mediaconfig>
2791 pub async fn load_or_fetch_max_upload_size(&self) -> Result<UInt> {
2792 let max_upload_size_lock = self.inner.server_max_upload_size.lock().await;
2793 if let Some(data) = max_upload_size_lock.get() {
2794 return Ok(data.to_owned());
2795 }
2796
2797 // Use the authenticated endpoint when the server supports it.
2798 let supported_versions = self.supported_versions().await?;
2799 let use_auth =
2800 authenticated_media::get_media_config::v1::Request::is_supported(&supported_versions);
2801
2802 let upload_size = if use_auth {
2803 self.send(authenticated_media::get_media_config::v1::Request::default())
2804 .await?
2805 .upload_size
2806 } else {
2807 #[allow(deprecated)]
2808 self.send(media::get_media_config::v3::Request::default()).await?.upload_size
2809 };
2810
2811 match max_upload_size_lock.set(upload_size) {
2812 Ok(_) => Ok(upload_size),
2813 Err(error) => {
2814 Err(Error::Media(MediaError::FetchMaxUploadSizeFailed(error.to_string())))
2815 }
2816 }
2817 }
2818
2819 /// The settings to use for decrypting events.
2820 #[cfg(feature = "e2e-encryption")]
2821 pub fn decryption_settings(&self) -> &DecryptionSettings {
2822 &self.base_client().decryption_settings
2823 }
2824
2825 /// Whether the client is configured to take thread subscriptions (MSC4306
2826 /// and MSC4308) into account.
2827 ///
2828 /// This may cause filtering out of thread subscriptions, and loading the
2829 /// thread subscriptions via the sliding sync extension, when the room
2830 /// list service is being used.
2831 pub fn enabled_thread_subscriptions(&self) -> bool {
2832 match self.base_client().threading_support {
2833 ThreadingSupport::Enabled { with_subscriptions } => with_subscriptions,
2834 ThreadingSupport::Disabled => false,
2835 }
2836 }
2837}
2838
2839#[cfg(any(feature = "testing", test))]
2840impl Client {
2841 /// Test helper to mark users as tracked by the crypto layer.
2842 #[cfg(feature = "e2e-encryption")]
2843 pub async fn update_tracked_users_for_testing(
2844 &self,
2845 user_ids: impl IntoIterator<Item = &UserId>,
2846 ) {
2847 let olm = self.olm_machine().await;
2848 let olm = olm.as_ref().unwrap();
2849 olm.update_tracked_users(user_ids).await.unwrap();
2850 }
2851}
2852
2853/// A weak reference to the inner client, useful when trying to get a handle
2854/// on the owning client.
2855#[derive(Clone, Debug)]
2856pub(crate) struct WeakClient {
2857 client: Weak<ClientInner>,
2858}
2859
2860impl WeakClient {
2861 /// Construct a [`WeakClient`] from a `Arc<ClientInner>`.
2862 pub fn from_inner(client: &Arc<ClientInner>) -> Self {
2863 Self { client: Arc::downgrade(client) }
2864 }
2865
2866 /// Construct a [`WeakClient`] from a [`Client`].
2867 pub fn from_client(client: &Client) -> Self {
2868 Self::from_inner(&client.inner)
2869 }
2870
2871 /// Attempts to get a [`Client`] from this [`WeakClient`].
2872 pub fn get(&self) -> Option<Client> {
2873 self.client.upgrade().map(|inner| Client { inner })
2874 }
2875
2876 /// Gets the number of strong (`Arc`) pointers still pointing to this
2877 /// client.
2878 #[allow(dead_code)]
2879 pub fn strong_count(&self) -> usize {
2880 self.client.strong_count()
2881 }
2882}
2883
2884#[derive(Clone)]
2885struct ClientServerInfo {
2886 /// The Matrix versions and unstable features the server supports (known
2887 /// ones only).
2888 supported_versions: CachedValue<SupportedVersions>,
2889
2890 /// The server's well-known file, if any.
2891 well_known: CachedValue<Option<WellKnownResponse>>,
2892}
2893
2894/// A cached value that can either be set or not set, used to avoid confusion
2895/// between a value that is set to `None` (because it doesn't exist) and a value
2896/// that has not been cached yet.
2897#[derive(Clone)]
2898enum CachedValue<Value> {
2899 /// A value has been cached.
2900 Cached(Value),
2901 /// Nothing has been cached yet.
2902 NotSet,
2903}
2904
2905impl<Value> CachedValue<Value> {
2906 /// Unwraps the cached value, returning it if it exists.
2907 ///
2908 /// # Panics
2909 ///
2910 /// If the cached value is not set, this will panic.
2911 fn unwrap_cached_value(self) -> Value {
2912 match self {
2913 CachedValue::Cached(value) => value,
2914 CachedValue::NotSet => panic!("Tried to unwrap a cached value that wasn't set"),
2915 }
2916 }
2917
2918 /// Converts from `&CachedValue<Value>` to `CachedValue<&Value>`.
2919 fn as_ref(&self) -> CachedValue<&Value> {
2920 match self {
2921 Self::Cached(value) => CachedValue::Cached(value),
2922 Self::NotSet => CachedValue::NotSet,
2923 }
2924 }
2925
2926 /// Maps a `CachedValue<Value>` to `CachedValue<Other>` by applying a
2927 /// function to a contained value (if `Cached`) or returns `NotSet` (if
2928 /// `NotSet`).
2929 fn map<Other, F>(self, f: F) -> CachedValue<Other>
2930 where
2931 F: FnOnce(Value) -> Other,
2932 {
2933 match self {
2934 Self::Cached(value) => CachedValue::Cached(f(value)),
2935 Self::NotSet => CachedValue::NotSet,
2936 }
2937 }
2938}
2939
2940/// Information about the state of a room before we joined it.
2941#[derive(Debug, Clone, Default)]
2942struct PreJoinRoomInfo {
2943 /// The user who invited us to the room, if any.
2944 pub inviter: Option<RoomMember>,
2945}
2946
2947// The http mocking library is not supported for wasm32
2948#[cfg(all(test, not(target_family = "wasm")))]
2949pub(crate) mod tests {
2950 use std::{sync::Arc, time::Duration};
2951
2952 use assert_matches::assert_matches;
2953 use assert_matches2::assert_let;
2954 use eyeball::SharedObservable;
2955 use futures_util::{pin_mut, FutureExt, StreamExt};
2956 use js_int::{uint, UInt};
2957 use matrix_sdk_base::{
2958 store::{MemoryStore, StoreConfig},
2959 RoomState,
2960 };
2961 use matrix_sdk_test::{
2962 async_test, GlobalAccountDataTestEvent, JoinedRoomBuilder, StateTestEvent,
2963 SyncResponseBuilder, DEFAULT_TEST_ROOM_ID,
2964 };
2965 #[cfg(target_family = "wasm")]
2966 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
2967
2968 use ruma::{
2969 api::{
2970 client::{
2971 discovery::discover_homeserver::RtcFocusInfo,
2972 room::create_room::v3::Request as CreateRoomRequest,
2973 },
2974 FeatureFlag, MatrixVersion,
2975 },
2976 assign,
2977 events::{
2978 ignored_user_list::IgnoredUserListEventContent,
2979 media_preview_config::{InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews},
2980 },
2981 owned_room_id, room_alias_id, room_id, RoomId, ServerName, UserId,
2982 };
2983 use serde_json::json;
2984 use stream_assert::{assert_next_matches, assert_pending};
2985 use tokio::{
2986 spawn,
2987 time::{sleep, timeout},
2988 };
2989 use url::Url;
2990
2991 use super::Client;
2992 use crate::{
2993 assert_let_timeout,
2994 client::{futures::SendMediaUploadRequest, WeakClient},
2995 config::{RequestConfig, SyncSettings},
2996 futures::SendRequest,
2997 media::MediaError,
2998 test_utils::{client::MockClientBuilder, mocks::MatrixMockServer},
2999 Error, TransmissionProgress,
3000 };
3001
3002 #[async_test]
3003 async fn test_account_data() {
3004 let server = MatrixMockServer::new().await;
3005 let client = server.client_builder().build().await;
3006
3007 server
3008 .mock_sync()
3009 .ok_and_run(&client, |builder| {
3010 builder.add_global_account_data_event(GlobalAccountDataTestEvent::IgnoredUserList);
3011 })
3012 .await;
3013
3014 let content = client
3015 .account()
3016 .account_data::<IgnoredUserListEventContent>()
3017 .await
3018 .unwrap()
3019 .unwrap()
3020 .deserialize()
3021 .unwrap();
3022
3023 assert_eq!(content.ignored_users.len(), 1);
3024 }
3025
3026 #[async_test]
3027 async fn test_successful_discovery() {
3028 // Imagine this is `matrix.org`.
3029 let server = MatrixMockServer::new().await;
3030 let server_url = server.uri();
3031
3032 // Imagine this is `matrix-client.matrix.org`.
3033 let homeserver = MatrixMockServer::new().await;
3034 let homeserver_url = homeserver.uri();
3035
3036 // Imagine Alice has the user ID `@alice:matrix.org`.
3037 let domain = server_url.strip_prefix("http://").unwrap();
3038 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
3039
3040 // The `.well-known` is on the server (e.g. `matrix.org`).
3041 server
3042 .mock_well_known()
3043 .ok_with_homeserver_url(&homeserver_url)
3044 .mock_once()
3045 .named("well-known")
3046 .mount()
3047 .await;
3048
3049 // The `/versions` is on the homeserver (e.g. `matrix-client.matrix.org`).
3050 homeserver.mock_versions().ok().mock_once().named("versions").mount().await;
3051
3052 let client = Client::builder()
3053 .insecure_server_name_no_tls(alice.server_name())
3054 .build()
3055 .await
3056 .unwrap();
3057
3058 assert_eq!(client.server().unwrap(), &Url::parse(&server_url).unwrap());
3059 assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
3060 client.server_versions().await.unwrap();
3061 }
3062
3063 #[async_test]
3064 async fn test_discovery_broken_server() {
3065 let server = MatrixMockServer::new().await;
3066 let server_url = server.uri();
3067 let domain = server_url.strip_prefix("http://").unwrap();
3068 let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
3069
3070 server.mock_well_known().error404().mock_once().named("well-known").mount().await;
3071
3072 assert!(
3073 Client::builder()
3074 .insecure_server_name_no_tls(alice.server_name())
3075 .build()
3076 .await
3077 .is_err(),
3078 "Creating a client from a user ID should fail when the .well-known request fails."
3079 );
3080 }
3081
3082 #[async_test]
3083 async fn test_room_creation() {
3084 let server = MatrixMockServer::new().await;
3085 let client = server.client_builder().build().await;
3086
3087 server
3088 .mock_sync()
3089 .ok_and_run(&client, |builder| {
3090 builder.add_joined_room(
3091 JoinedRoomBuilder::default()
3092 .add_state_event(StateTestEvent::Member)
3093 .add_state_event(StateTestEvent::PowerLevels),
3094 );
3095 })
3096 .await;
3097
3098 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
3099 assert_eq!(room.state(), RoomState::Joined);
3100 }
3101
3102 #[async_test]
3103 async fn test_retry_limit_http_requests() {
3104 let server = MatrixMockServer::new().await;
3105 let client = server
3106 .client_builder()
3107 .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(3)))
3108 .build()
3109 .await;
3110
3111 assert!(client.request_config().retry_limit.unwrap() == 3);
3112
3113 server.mock_login().error500().expect(3).mount().await;
3114
3115 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
3116 }
3117
3118 #[async_test]
3119 async fn test_retry_timeout_http_requests() {
3120 // Keep this timeout small so that the test doesn't take long
3121 let retry_timeout = Duration::from_secs(5);
3122 let server = MatrixMockServer::new().await;
3123 let client = server
3124 .client_builder()
3125 .on_builder(|builder| {
3126 builder.request_config(RequestConfig::new().max_retry_time(retry_timeout))
3127 })
3128 .build()
3129 .await;
3130
3131 assert!(client.request_config().max_retry_time.unwrap() == retry_timeout);
3132
3133 server.mock_login().error500().expect(2..).mount().await;
3134
3135 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
3136 }
3137
3138 #[async_test]
3139 async fn test_short_retry_initial_http_requests() {
3140 let server = MatrixMockServer::new().await;
3141 let client = server
3142 .client_builder()
3143 .on_builder(|builder| builder.request_config(RequestConfig::short_retry()))
3144 .build()
3145 .await;
3146
3147 server.mock_login().error500().expect(3..).mount().await;
3148
3149 client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
3150 }
3151
3152 #[async_test]
3153 async fn test_no_retry_http_requests() {
3154 let server = MatrixMockServer::new().await;
3155 let client = server.client_builder().build().await;
3156
3157 server.mock_devices().error500().mock_once().mount().await;
3158
3159 client.devices().await.unwrap_err();
3160 }
3161
3162 #[async_test]
3163 async fn test_set_homeserver() {
3164 let client = MockClientBuilder::new(None).build().await;
3165 assert_eq!(client.homeserver().as_ref(), "http://localhost/");
3166
3167 let homeserver = Url::parse("http://example.com/").unwrap();
3168 client.set_homeserver(homeserver.clone());
3169 assert_eq!(client.homeserver(), homeserver);
3170 }
3171
3172 #[async_test]
3173 async fn test_search_user_request() {
3174 let server = MatrixMockServer::new().await;
3175 let client = server.client_builder().build().await;
3176
3177 server.mock_user_directory().ok().mock_once().mount().await;
3178
3179 let response = client.search_users("test", 50).await.unwrap();
3180 assert_eq!(response.results.len(), 1);
3181 let result = &response.results[0];
3182 assert_eq!(result.user_id.to_string(), "@test:example.me");
3183 assert_eq!(result.display_name.clone().unwrap(), "Test");
3184 assert_eq!(result.avatar_url.clone().unwrap().to_string(), "mxc://example.me/someid");
3185 assert!(!response.limited);
3186 }
3187
3188 #[async_test]
3189 async fn test_request_unstable_features() {
3190 let server = MatrixMockServer::new().await;
3191 let client = server.client_builder().no_server_versions().build().await;
3192
3193 server.mock_versions().ok_with_unstable_features().mock_once().mount().await;
3194
3195 let unstable_features = client.unstable_features().await.unwrap();
3196 assert!(unstable_features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
3197 assert!(!unstable_features.contains(&FeatureFlag::from("you.shall.pass")));
3198 }
3199
3200 #[async_test]
3201 async fn test_can_homeserver_push_encrypted_event_to_device() {
3202 let server = MatrixMockServer::new().await;
3203 let client = server.client_builder().no_server_versions().build().await;
3204
3205 server.mock_versions().ok_with_unstable_features().mock_once().mount().await;
3206
3207 let msc4028_enabled = client.can_homeserver_push_encrypted_event_to_device().await.unwrap();
3208 assert!(msc4028_enabled);
3209 }
3210
3211 #[async_test]
3212 async fn test_recently_visited_rooms() {
3213 // Tracking recently visited rooms requires authentication
3214 let client = MockClientBuilder::new(None).unlogged().build().await;
3215 assert_matches!(
3216 client.account().track_recently_visited_room(owned_room_id!("!alpha:localhost")).await,
3217 Err(Error::AuthenticationRequired)
3218 );
3219
3220 let client = MockClientBuilder::new(None).build().await;
3221 let account = client.account();
3222
3223 // We should start off with an empty list
3224 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 0);
3225
3226 // Tracking a valid room id should add it to the list
3227 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
3228 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
3229 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
3230
3231 // And the existing list shouldn't be changed
3232 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
3233 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
3234
3235 // Tracking the same room again shouldn't change the list
3236 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
3237 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
3238 assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
3239
3240 // Tracking a second room should add it to the front of the list
3241 account.track_recently_visited_room(owned_room_id!("!beta:localhost")).await.unwrap();
3242 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
3243 assert_eq!(
3244 account.get_recently_visited_rooms().await.unwrap(),
3245 [room_id!("!beta:localhost"), room_id!("!alpha:localhost")]
3246 );
3247
3248 // Tracking the first room yet again should move it to the front of the list
3249 account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
3250 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
3251 assert_eq!(
3252 account.get_recently_visited_rooms().await.unwrap(),
3253 [room_id!("!alpha:localhost"), room_id!("!beta:localhost")]
3254 );
3255
3256 // Tracking should be capped at 20
3257 for n in 0..20 {
3258 account
3259 .track_recently_visited_room(RoomId::parse(format!("!{n}:localhost")).unwrap())
3260 .await
3261 .unwrap();
3262 }
3263
3264 assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 20);
3265
3266 // And the initial rooms should've been pushed out
3267 let rooms = account.get_recently_visited_rooms().await.unwrap();
3268 assert!(!rooms.contains(&owned_room_id!("!alpha:localhost")));
3269 assert!(!rooms.contains(&owned_room_id!("!beta:localhost")));
3270
3271 // And the last tracked room should be the first
3272 assert_eq!(rooms.first().unwrap(), room_id!("!19:localhost"));
3273 }
3274
3275 #[async_test]
3276 async fn test_client_no_cycle_with_event_cache() {
3277 let client = MockClientBuilder::new(None).build().await;
3278
3279 // Wait for the init tasks to die.
3280 sleep(Duration::from_secs(1)).await;
3281
3282 let weak_client = WeakClient::from_client(&client);
3283 assert_eq!(weak_client.strong_count(), 1);
3284
3285 {
3286 let room_id = room_id!("!room:example.org");
3287
3288 // Have the client know the room.
3289 let response = SyncResponseBuilder::default()
3290 .add_joined_room(JoinedRoomBuilder::new(room_id))
3291 .build_sync_response();
3292 client.inner.base_client.receive_sync_response(response).await.unwrap();
3293
3294 client.event_cache().subscribe().unwrap();
3295
3296 let (_room_event_cache, _drop_handles) =
3297 client.get_room(room_id).unwrap().event_cache().await.unwrap();
3298 }
3299
3300 drop(client);
3301
3302 // Give a bit of time for background tasks to die.
3303 sleep(Duration::from_secs(1)).await;
3304
3305 // The weak client must be the last reference to the client now.
3306 assert_eq!(weak_client.strong_count(), 0);
3307 let client = weak_client.get();
3308 assert!(
3309 client.is_none(),
3310 "too many strong references to the client: {}",
3311 Arc::strong_count(&client.unwrap().inner)
3312 );
3313 }
3314
3315 #[async_test]
3316 async fn test_server_info_caching() {
3317 let server = MatrixMockServer::new().await;
3318 let server_url = server.uri();
3319 let domain = server_url.strip_prefix("http://").unwrap();
3320 let server_name = <&ServerName>::try_from(domain).unwrap();
3321 let rtc_foci = vec![RtcFocusInfo::livekit("https://livekit.example.com".to_owned())];
3322
3323 let well_known_mock = server
3324 .mock_well_known()
3325 .ok()
3326 .named("well known mock")
3327 .expect(2) // One for ClientBuilder discovery, one for the ServerInfo cache.
3328 .mount_as_scoped()
3329 .await;
3330
3331 let versions_mock = server
3332 .mock_versions()
3333 .ok_with_unstable_features()
3334 .named("first versions mock")
3335 .expect(1)
3336 .mount_as_scoped()
3337 .await;
3338
3339 let memory_store = Arc::new(MemoryStore::new());
3340 let client = Client::builder()
3341 .insecure_server_name_no_tls(server_name)
3342 .store_config(
3343 StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
3344 .state_store(memory_store.clone()),
3345 )
3346 .build()
3347 .await
3348 .unwrap();
3349
3350 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
3351
3352 // These subsequent calls hit the in-memory cache.
3353 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
3354 assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
3355
3356 drop(client);
3357
3358 let client = server
3359 .client_builder()
3360 .no_server_versions()
3361 .on_builder(|builder| {
3362 builder.store_config(
3363 StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
3364 .state_store(memory_store.clone()),
3365 )
3366 })
3367 .build()
3368 .await;
3369
3370 // These calls to the new client hit the on-disk cache.
3371 assert!(client
3372 .unstable_features()
3373 .await
3374 .unwrap()
3375 .contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
3376 let supported = client.supported_versions().await.unwrap();
3377 assert!(supported.versions.contains(&MatrixVersion::V1_0));
3378 assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
3379
3380 // Then this call hits the in-memory cache.
3381 assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
3382
3383 drop(versions_mock);
3384 drop(well_known_mock);
3385
3386 // Now, reset the cache, and observe the endpoints being called again once.
3387 client.reset_server_info().await.unwrap();
3388
3389 server.mock_well_known().ok().named("second well known mock").expect(1).mount().await;
3390
3391 server.mock_versions().ok().expect(1).named("second versions mock").mount().await;
3392
3393 // Hits network again.
3394 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
3395 // Hits in-memory cache again.
3396 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
3397 assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
3398 }
3399
3400 #[async_test]
3401 async fn test_server_info_without_a_well_known() {
3402 let server = MatrixMockServer::new().await;
3403 let rtc_foci: Vec<RtcFocusInfo> = vec![];
3404
3405 let versions_mock = server
3406 .mock_versions()
3407 .ok_with_unstable_features()
3408 .named("first versions mock")
3409 .expect(1)
3410 .mount_as_scoped()
3411 .await;
3412
3413 let memory_store = Arc::new(MemoryStore::new());
3414 let client = server
3415 .client_builder()
3416 .no_server_versions()
3417 .on_builder(|builder| {
3418 builder.store_config(
3419 StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
3420 .state_store(memory_store.clone()),
3421 )
3422 })
3423 .build()
3424 .await;
3425
3426 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
3427
3428 // These subsequent calls hit the in-memory cache.
3429 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
3430 assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
3431
3432 drop(client);
3433
3434 let client = server
3435 .client_builder()
3436 .no_server_versions()
3437 .on_builder(|builder| {
3438 builder.store_config(
3439 StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
3440 .state_store(memory_store.clone()),
3441 )
3442 })
3443 .build()
3444 .await;
3445
3446 // This call to the new client hits the on-disk cache.
3447 assert!(client
3448 .unstable_features()
3449 .await
3450 .unwrap()
3451 .contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
3452
3453 // Then this call hits the in-memory cache.
3454 assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
3455
3456 drop(versions_mock);
3457
3458 // Now, reset the cache, and observe the endpoints being called again once.
3459 client.reset_server_info().await.unwrap();
3460
3461 server.mock_versions().ok().expect(1).named("second versions mock").mount().await;
3462
3463 // Hits network again.
3464 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
3465 // Hits in-memory cache again.
3466 assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
3467 assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
3468 }
3469
3470 #[async_test]
3471 async fn test_no_network_doesnt_cause_infinite_retries() {
3472 // We want infinite retries for transient errors.
3473 let client = MockClientBuilder::new(None)
3474 .on_builder(|builder| builder.request_config(RequestConfig::new()))
3475 .build()
3476 .await;
3477
3478 // We don't define a mock server on purpose here, so that the error is really a
3479 // network error.
3480 client.whoami().await.unwrap_err();
3481 }
3482
3483 #[async_test]
3484 async fn test_await_room_remote_echo_returns_the_room_if_it_was_already_synced() {
3485 let server = MatrixMockServer::new().await;
3486 let client = server.client_builder().build().await;
3487
3488 let room_id = room_id!("!room:example.org");
3489
3490 server
3491 .mock_sync()
3492 .ok_and_run(&client, |builder| {
3493 builder.add_joined_room(JoinedRoomBuilder::new(room_id));
3494 })
3495 .await;
3496
3497 let room = client.await_room_remote_echo(room_id).now_or_never().unwrap();
3498 assert_eq!(room.room_id(), room_id);
3499 }
3500
3501 #[async_test]
3502 async fn test_await_room_remote_echo_returns_the_room_when_it_is_ready() {
3503 let server = MatrixMockServer::new().await;
3504 let client = server.client_builder().build().await;
3505
3506 let room_id = room_id!("!room:example.org");
3507
3508 let client = Arc::new(client);
3509
3510 // Perform the /sync request with a delay so it starts after the
3511 // `await_room_remote_echo` call has happened
3512 spawn({
3513 let client = client.clone();
3514 async move {
3515 sleep(Duration::from_millis(100)).await;
3516
3517 server
3518 .mock_sync()
3519 .ok_and_run(&client, |builder| {
3520 builder.add_joined_room(JoinedRoomBuilder::new(room_id));
3521 })
3522 .await;
3523 }
3524 });
3525
3526 let room =
3527 timeout(Duration::from_secs(10), client.await_room_remote_echo(room_id)).await.unwrap();
3528 assert_eq!(room.room_id(), room_id);
3529 }
3530
3531 #[async_test]
3532 async fn test_await_room_remote_echo_will_timeout_if_no_room_is_found() {
3533 let client = MockClientBuilder::new(None).build().await;
3534
3535 let room_id = room_id!("!room:example.org");
3536 // Room is not present so the client won't be able to find it. The call will
3537 // timeout.
3538 timeout(Duration::from_secs(1), client.await_room_remote_echo(room_id)).await.unwrap_err();
3539 }
3540
3541 #[async_test]
3542 async fn test_await_room_remote_echo_will_timeout_if_room_is_found_but_not_synced() {
3543 let server = MatrixMockServer::new().await;
3544 let client = server.client_builder().build().await;
3545
3546 server.mock_create_room().ok().mount().await;
3547
3548 // Create a room in the internal store
3549 let room = client
3550 .create_room(assign!(CreateRoomRequest::new(), {
3551 invite: vec![],
3552 is_direct: false,
3553 }))
3554 .await
3555 .unwrap();
3556
3557 // Room is locally present, but not synced, the call will timeout
3558 timeout(Duration::from_secs(1), client.await_room_remote_echo(room.room_id()))
3559 .await
3560 .unwrap_err();
3561 }
3562
3563 #[async_test]
3564 async fn test_is_room_alias_available_if_alias_is_not_resolved() {
3565 let server = MatrixMockServer::new().await;
3566 let client = server.client_builder().build().await;
3567
3568 server.mock_room_directory_resolve_alias().not_found().expect(1).mount().await;
3569
3570 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
3571 assert_matches!(ret, Ok(true));
3572 }
3573
3574 #[async_test]
3575 async fn test_is_room_alias_available_if_alias_is_resolved() {
3576 let server = MatrixMockServer::new().await;
3577 let client = server.client_builder().build().await;
3578
3579 server
3580 .mock_room_directory_resolve_alias()
3581 .ok("!some_room_id:matrix.org", Vec::new())
3582 .expect(1)
3583 .mount()
3584 .await;
3585
3586 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
3587 assert_matches!(ret, Ok(false));
3588 }
3589
3590 #[async_test]
3591 async fn test_is_room_alias_available_if_error_found() {
3592 let server = MatrixMockServer::new().await;
3593 let client = server.client_builder().build().await;
3594
3595 server.mock_room_directory_resolve_alias().error500().expect(1).mount().await;
3596
3597 let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
3598 assert_matches!(ret, Err(_));
3599 }
3600
3601 #[async_test]
3602 async fn test_create_room_alias() {
3603 let server = MatrixMockServer::new().await;
3604 let client = server.client_builder().build().await;
3605
3606 server.mock_room_directory_create_room_alias().ok().expect(1).mount().await;
3607
3608 let ret = client
3609 .create_room_alias(
3610 room_alias_id!("#some_alias:matrix.org"),
3611 room_id!("!some_room:matrix.org"),
3612 )
3613 .await;
3614 assert_matches!(ret, Ok(()));
3615 }
3616
3617 #[async_test]
3618 async fn test_room_preview_for_invited_room_hits_summary_endpoint() {
3619 let server = MatrixMockServer::new().await;
3620 let client = server.client_builder().build().await;
3621
3622 let room_id = room_id!("!a-room:matrix.org");
3623
3624 // Make sure the summary endpoint is called once
3625 server.mock_room_summary().ok(room_id).mock_once().mount().await;
3626
3627 // We create a locally cached invited room
3628 let invited_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Invited);
3629
3630 // And we get a preview, the server endpoint was reached
3631 let preview = client
3632 .get_room_preview(room_id.into(), Vec::new())
3633 .await
3634 .expect("Room preview should be retrieved");
3635
3636 assert_eq!(invited_room.room_id().to_owned(), preview.room_id);
3637 }
3638
3639 #[async_test]
3640 async fn test_room_preview_for_left_room_hits_summary_endpoint() {
3641 let server = MatrixMockServer::new().await;
3642 let client = server.client_builder().build().await;
3643
3644 let room_id = room_id!("!a-room:matrix.org");
3645
3646 // Make sure the summary endpoint is called once
3647 server.mock_room_summary().ok(room_id).mock_once().mount().await;
3648
3649 // We create a locally cached left room
3650 let left_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Left);
3651
3652 // And we get a preview, the server endpoint was reached
3653 let preview = client
3654 .get_room_preview(room_id.into(), Vec::new())
3655 .await
3656 .expect("Room preview should be retrieved");
3657
3658 assert_eq!(left_room.room_id().to_owned(), preview.room_id);
3659 }
3660
3661 #[async_test]
3662 async fn test_room_preview_for_knocked_room_hits_summary_endpoint() {
3663 let server = MatrixMockServer::new().await;
3664 let client = server.client_builder().build().await;
3665
3666 let room_id = room_id!("!a-room:matrix.org");
3667
3668 // Make sure the summary endpoint is called once
3669 server.mock_room_summary().ok(room_id).mock_once().mount().await;
3670
3671 // We create a locally cached knocked room
3672 let knocked_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Knocked);
3673
3674 // And we get a preview, the server endpoint was reached
3675 let preview = client
3676 .get_room_preview(room_id.into(), Vec::new())
3677 .await
3678 .expect("Room preview should be retrieved");
3679
3680 assert_eq!(knocked_room.room_id().to_owned(), preview.room_id);
3681 }
3682
3683 #[async_test]
3684 async fn test_room_preview_for_joined_room_retrieves_local_room_info() {
3685 let server = MatrixMockServer::new().await;
3686 let client = server.client_builder().build().await;
3687
3688 let room_id = room_id!("!a-room:matrix.org");
3689
3690 // Make sure the summary endpoint is not called
3691 server.mock_room_summary().ok(room_id).never().mount().await;
3692
3693 // We create a locally cached joined room
3694 let joined_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Joined);
3695
3696 // And we get a preview, no server endpoint was reached
3697 let preview = client
3698 .get_room_preview(room_id.into(), Vec::new())
3699 .await
3700 .expect("Room preview should be retrieved");
3701
3702 assert_eq!(joined_room.room_id().to_owned(), preview.room_id);
3703 }
3704
3705 #[async_test]
3706 async fn test_media_preview_config() {
3707 let server = MatrixMockServer::new().await;
3708 let client = server.client_builder().build().await;
3709
3710 server
3711 .mock_sync()
3712 .ok_and_run(&client, |builder| {
3713 builder.add_global_account_data_event(GlobalAccountDataTestEvent::Custom(json!({
3714 "content": {
3715 "media_previews": "private",
3716 "invite_avatars": "off"
3717 },
3718 "type": "m.media_preview_config"
3719 })));
3720 })
3721 .await;
3722
3723 let (initial_value, stream) =
3724 client.account().observe_media_preview_config().await.unwrap();
3725
3726 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
3727 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
3728 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
3729 pin_mut!(stream);
3730 assert_pending!(stream);
3731
3732 server
3733 .mock_sync()
3734 .ok_and_run(&client, |builder| {
3735 builder.add_global_account_data_event(GlobalAccountDataTestEvent::Custom(json!({
3736 "content": {
3737 "media_previews": "off",
3738 "invite_avatars": "on"
3739 },
3740 "type": "m.media_preview_config"
3741 })));
3742 })
3743 .await;
3744
3745 assert_next_matches!(
3746 stream,
3747 MediaPreviewConfigEventContent {
3748 media_previews: Some(MediaPreviews::Off),
3749 invite_avatars: Some(InviteAvatars::On),
3750 ..
3751 }
3752 );
3753 assert_pending!(stream);
3754 }
3755
3756 #[async_test]
3757 async fn test_unstable_media_preview_config() {
3758 let server = MatrixMockServer::new().await;
3759 let client = server.client_builder().build().await;
3760
3761 server
3762 .mock_sync()
3763 .ok_and_run(&client, |builder| {
3764 builder.add_global_account_data_event(GlobalAccountDataTestEvent::Custom(json!({
3765 "content": {
3766 "media_previews": "private",
3767 "invite_avatars": "off"
3768 },
3769 "type": "io.element.msc4278.media_preview_config"
3770 })));
3771 })
3772 .await;
3773
3774 let (initial_value, stream) =
3775 client.account().observe_media_preview_config().await.unwrap();
3776
3777 let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
3778 assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
3779 assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
3780 pin_mut!(stream);
3781 assert_pending!(stream);
3782
3783 server
3784 .mock_sync()
3785 .ok_and_run(&client, |builder| {
3786 builder.add_global_account_data_event(GlobalAccountDataTestEvent::Custom(json!({
3787 "content": {
3788 "media_previews": "off",
3789 "invite_avatars": "on"
3790 },
3791 "type": "io.element.msc4278.media_preview_config"
3792 })));
3793 })
3794 .await;
3795
3796 assert_next_matches!(
3797 stream,
3798 MediaPreviewConfigEventContent {
3799 media_previews: Some(MediaPreviews::Off),
3800 invite_avatars: Some(InviteAvatars::On),
3801 ..
3802 }
3803 );
3804 assert_pending!(stream);
3805 }
3806
3807 #[async_test]
3808 async fn test_media_preview_config_not_found() {
3809 let server = MatrixMockServer::new().await;
3810 let client = server.client_builder().build().await;
3811
3812 let (initial_value, _) = client.account().observe_media_preview_config().await.unwrap();
3813
3814 assert!(initial_value.is_none());
3815 }
3816
3817 #[async_test]
3818 async fn test_load_or_fetch_max_upload_size_with_auth_matrix_version() {
3819 // The default Matrix version we use is 1.11 or higher, so authenticated media
3820 // is supported.
3821 let server = MatrixMockServer::new().await;
3822 let client = server.client_builder().build().await;
3823
3824 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
3825
3826 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
3827 client.load_or_fetch_max_upload_size().await.unwrap();
3828
3829 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
3830 }
3831
3832 #[async_test]
3833 async fn test_load_or_fetch_max_upload_size_with_auth_stable_feature() {
3834 // The server must advertise support for the stable feature for authenticated
3835 // media support, so we mock the `GET /versions` response.
3836 let server = MatrixMockServer::new().await;
3837 let client = server.client_builder().no_server_versions().build().await;
3838
3839 server
3840 .mock_versions()
3841 .ok_custom(
3842 &["v1.7", "v1.8", "v1.9", "v1.10"],
3843 &[("org.matrix.msc3916.stable", true)].into(),
3844 )
3845 .named("versions")
3846 .expect(1)
3847 .mount()
3848 .await;
3849
3850 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
3851
3852 server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
3853 client.load_or_fetch_max_upload_size().await.unwrap();
3854
3855 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
3856 }
3857
3858 #[async_test]
3859 async fn test_load_or_fetch_max_upload_size_no_auth() {
3860 // The server must not support Matrix 1.11 or higher for unauthenticated
3861 // media requests, so we mock the `GET /versions` response.
3862 let server = MatrixMockServer::new().await;
3863 let client = server.client_builder().no_server_versions().build().await;
3864
3865 server
3866 .mock_versions()
3867 .ok_custom(&["v1.1"], &Default::default())
3868 .named("versions")
3869 .expect(1)
3870 .mount()
3871 .await;
3872
3873 assert!(!client.inner.server_max_upload_size.lock().await.initialized());
3874
3875 server.mock_media_config().ok(uint!(2)).mock_once().mount().await;
3876 client.load_or_fetch_max_upload_size().await.unwrap();
3877
3878 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
3879 }
3880
3881 #[async_test]
3882 async fn test_uploading_a_too_large_media_file() {
3883 let server = MatrixMockServer::new().await;
3884 let client = server.client_builder().build().await;
3885
3886 server.mock_authenticated_media_config().ok(uint!(1)).mock_once().mount().await;
3887 client.load_or_fetch_max_upload_size().await.unwrap();
3888 assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(1));
3889
3890 let data = vec![1, 2];
3891 let upload_request =
3892 ruma::api::client::media::create_content::v3::Request::new(data.clone());
3893 let request = SendRequest {
3894 client: client.clone(),
3895 request: upload_request,
3896 config: None,
3897 send_progress: SharedObservable::new(TransmissionProgress::default()),
3898 };
3899 let media_request = SendMediaUploadRequest::new(request);
3900
3901 let error = media_request.await.err();
3902 assert_let!(Some(Error::Media(MediaError::MediaTooLargeToUpload { max, current })) = error);
3903 assert_eq!(max, uint!(1));
3904 assert_eq!(current, UInt::new_wrapping(data.len() as u64));
3905 }
3906
3907 #[async_test]
3908 async fn test_dont_ignore_timeout_on_first_sync() {
3909 let server = MatrixMockServer::new().await;
3910 let client = server.client_builder().build().await;
3911
3912 server
3913 .mock_sync()
3914 .timeout(Some(Duration::from_secs(30)))
3915 .ok(|_| {})
3916 .mock_once()
3917 .named("sync_with_timeout")
3918 .mount()
3919 .await;
3920
3921 // Call the endpoint once to check the timeout.
3922 let mut stream = Box::pin(client.sync_stream(SyncSettings::new()).await);
3923 assert_let_timeout!(Some(Ok(_)) = stream.next());
3924 }
3925
3926 #[async_test]
3927 async fn test_ignore_timeout_on_first_sync() {
3928 let server = MatrixMockServer::new().await;
3929 let client = server.client_builder().build().await;
3930
3931 server
3932 .mock_sync()
3933 .timeout(None)
3934 .ok(|_| {})
3935 .mock_once()
3936 .named("sync_no_timeout")
3937 .mount()
3938 .await;
3939 server
3940 .mock_sync()
3941 .timeout(Some(Duration::from_secs(30)))
3942 .ok(|_| {})
3943 .mock_once()
3944 .named("sync_with_timeout")
3945 .mount()
3946 .await;
3947
3948 // Call each version of the endpoint once to check the timeouts.
3949 let mut stream = Box::pin(
3950 client.sync_stream(SyncSettings::new().ignore_timeout_on_first_sync(true)).await,
3951 );
3952 assert_let_timeout!(Some(Ok(_)) = stream.next());
3953 assert_let_timeout!(Some(Ok(_)) = stream.next());
3954 }
3955}