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}