Skip to main content

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::{BTreeMap, BTreeSet, btree_map},
19    fmt::{self, Debug},
20    future::{Future, ready},
21    pin::Pin,
22    sync::{Arc, Mutex as StdMutex, RwLock as StdRwLock, Weak},
23    time::Duration,
24};
25
26use eyeball::{SharedObservable, Subscriber};
27use eyeball_im::{Vector, VectorDiff};
28use futures_core::Stream;
29use futures_util::{StreamExt, join};
30#[cfg(feature = "e2e-encryption")]
31use matrix_sdk_base::crypto::{
32    DecryptionSettings, store::LockableCryptoStore, store::types::RoomPendingKeyBundleDetails,
33};
34use matrix_sdk_base::{
35    BaseClient, DmRoomDefinition, RoomInfoNotableUpdate, RoomState, RoomStateFilter,
36    SendOutsideWasm, SessionMeta, StateStoreDataKey, StateStoreDataValue, StoreError,
37    SyncOutsideWasm, ThreadingSupport,
38    event_cache::store::EventCacheStoreLock,
39    media::store::MediaStoreLock,
40    store::{DynStateStore, RoomLoadSettings, SupportedVersionsResponse, WellKnownResponse},
41    sync::{Notification, RoomUpdates},
42    task_monitor::TaskMonitor,
43};
44use matrix_sdk_common::{cross_process_lock::CrossProcessLockConfig, ttl::TtlValue};
45#[cfg(feature = "e2e-encryption")]
46use ruma::events::{InitialStateEvent, room::encryption::RoomEncryptionEventContent};
47use ruma::{
48    DeviceId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
49    RoomAliasId, RoomId, RoomOrAliasId, ServerName, UInt, UserId,
50    api::{
51        FeatureFlag, MatrixVersion, Metadata, OutgoingRequest, SupportedVersions,
52        client::{
53            account::whoami,
54            alias::{create_alias, delete_alias, get_alias},
55            authenticated_media,
56            device::{self, delete_devices, get_devices, update_device},
57            directory::{get_public_rooms, get_public_rooms_filtered},
58            discovery::{discover_homeserver, get_supported_versions},
59            filter::{FilterDefinition, create_filter::v3::Request as FilterUploadRequest},
60            knock::knock_room,
61            media,
62            membership::{join_room_by_id, join_room_by_id_or_alias},
63            presence::set_presence as set_presence_status,
64            room::create_room,
65            rtc::RtcTransport,
66            session::login::v3::DiscoveryInfo,
67            sync::sync_events,
68            threads::get_thread_subscriptions_changes,
69            uiaa,
70            user_directory::search_users,
71        },
72        error::{ErrorKind, FromHttpResponseError, UnknownTokenErrorData},
73        path_builder::PathBuilder,
74    },
75    assign,
76    events::{beacon_info::OriginalSyncBeaconInfoEvent, direct::DirectUserIdentifier},
77    presence::PresenceState,
78    push::Ruleset,
79    time::Instant,
80};
81use serde::de::DeserializeOwned;
82use tokio::sync::{Mutex, OnceCell, RwLock, RwLockReadGuard, broadcast};
83use tracing::{Instrument, Span, debug, error, info, instrument, trace, warn};
84use url::Url;
85
86use self::{
87    caches::{Cache, CachedValue, ClientCaches},
88    futures::SendRequest,
89};
90use crate::{
91    Account, AuthApi, AuthSession, Error, HttpError, Media, Pusher, RefreshTokenError, Result,
92    Room, SessionTokens, TransmissionProgress,
93    authentication::{
94        AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback, matrix::MatrixAuth,
95        oauth::OAuth,
96    },
97    client::{
98        homeserver_capabilities::HomeserverCapabilities,
99        thread_subscriptions::ThreadSubscriptionCatchup,
100    },
101    config::{RequestConfig, SyncToken},
102    deduplicating_handler::DeduplicatingHandler,
103    error::HttpResult,
104    event_cache::EventCache,
105    event_handler::{
106        EventHandler, EventHandlerContext, EventHandlerDropGuard, EventHandlerHandle,
107        EventHandlerStore, ObservableEventHandler, SyncEvent,
108    },
109    http_client::{HttpClient, SupportedAuthScheme, SupportedPathBuilder},
110    latest_events::LatestEvents,
111    live_locations_observer::BeaconInfoUpdate,
112    media::{MediaError, MediaFetcher},
113    notification_settings::NotificationSettings,
114    room::RoomMember,
115    room_preview::RoomPreview,
116    send_queue::{SendQueue, SendQueueData},
117    sliding_sync::Version as SlidingSyncVersion,
118    sync::{RoomUpdate, SyncResponse},
119};
120#[cfg(feature = "e2e-encryption")]
121use crate::{
122    cross_process_lock::CrossProcessLock,
123    encryption::{
124        DuplicateOneTimeKeyErrorMessage, Encryption, EncryptionData, EncryptionSettings,
125        VerificationState,
126    },
127};
128
129mod builder;
130pub(crate) mod caches;
131pub(crate) mod futures;
132pub(crate) mod homeserver_capabilities;
133pub(crate) mod thread_subscriptions;
134
135pub use self::builder::{ClientBuildError, ClientBuilder, sanitize_server_name};
136#[cfg(feature = "experimental-search")]
137use crate::search_index::SearchIndex;
138
139#[cfg(not(target_family = "wasm"))]
140type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()> + Send>>;
141#[cfg(target_family = "wasm")]
142type NotificationHandlerFut = Pin<Box<dyn Future<Output = ()>>>;
143
144#[cfg(not(target_family = "wasm"))]
145type NotificationHandlerFn =
146    Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut + Send + Sync>;
147#[cfg(target_family = "wasm")]
148type NotificationHandlerFn = Box<dyn Fn(Notification, Room, Client) -> NotificationHandlerFut>;
149
150/// Enum controlling if a loop running callbacks should continue or abort.
151///
152/// This is mainly used in the [`sync_with_callback`] method, the return value
153/// of the provided callback controls if the sync loop should be exited.
154///
155/// [`sync_with_callback`]: #method.sync_with_callback
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum LoopCtrl {
158    /// Continue running the loop.
159    Continue,
160    /// Break out of the loop.
161    Break,
162}
163
164/// Represents changes that can occur to a `Client`s `Session`.
165#[derive(Debug, Clone, PartialEq)]
166pub enum SessionChange {
167    /// The session's token is no longer valid.
168    UnknownToken(UnknownTokenErrorData),
169    /// The session's tokens have been refreshed.
170    TokensRefreshed,
171}
172
173/// Information about the server vendor obtained from the federation API.
174#[derive(Debug, Clone, PartialEq, Eq)]
175#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
176pub struct ServerVendorInfo {
177    /// The server name.
178    pub server_name: String,
179    /// The server version.
180    pub version: String,
181}
182
183/// Information about a map tile server advertised by the homeserver through the
184/// `tile_server` field of the matrix client well-known (MSC3488).
185#[derive(Debug, Clone, PartialEq, Eq, Hash)]
186#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
187pub struct TileServerInfo {
188    /// The URL of a map tile server's `style.json` file. See the
189    /// [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/)
190    /// for more details.
191    pub map_style_url: String,
192}
193
194impl From<discover_homeserver::TileServerInfo> for TileServerInfo {
195    fn from(value: discover_homeserver::TileServerInfo) -> Self {
196        Self { map_style_url: value.map_style_url }
197    }
198}
199
200/// An async/await enabled Matrix client.
201///
202/// All of the state is held in an `Arc` so the `Client` can be cloned freely.
203#[derive(Clone)]
204pub struct Client {
205    pub(crate) inner: Arc<ClientInner>,
206}
207
208#[derive(Default)]
209pub(crate) struct ClientLocks {
210    /// Lock ensuring that only a single room may be marked as a DM at once.
211    /// Look at the [`Account::mark_as_dm()`] method for a more detailed
212    /// explanation.
213    pub(crate) mark_as_dm_lock: Mutex<()>,
214
215    /// Lock ensuring that only a single secret store is getting opened at the
216    /// same time.
217    ///
218    /// This is important so we don't accidentally create multiple different new
219    /// default secret storage keys.
220    #[cfg(feature = "e2e-encryption")]
221    pub(crate) open_secret_store_lock: Mutex<()>,
222
223    /// Lock ensuring that we're only storing a single secret at a time.
224    ///
225    /// Take a look at the [`SecretStore::put_secret`] method for a more
226    /// detailed explanation.
227    ///
228    /// [`SecretStore::put_secret`]: crate::encryption::secret_storage::SecretStore::put_secret
229    #[cfg(feature = "e2e-encryption")]
230    pub(crate) store_secret_lock: Mutex<()>,
231
232    /// Lock ensuring that only one method at a time might modify our backup.
233    #[cfg(feature = "e2e-encryption")]
234    pub(crate) backup_modify_lock: Mutex<()>,
235
236    /// Lock ensuring that we're going to attempt to upload backups for a single
237    /// requester.
238    #[cfg(feature = "e2e-encryption")]
239    pub(crate) backup_upload_lock: Mutex<()>,
240
241    /// Handler making sure we only have one group session sharing request in
242    /// flight per room.
243    #[cfg(feature = "e2e-encryption")]
244    pub(crate) group_session_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
245
246    /// Lock making sure we're only doing one key claim request at a time.
247    #[cfg(feature = "e2e-encryption")]
248    pub(crate) key_claim_lock: Mutex<()>,
249
250    /// Handler to ensure that only one members request is running at a time,
251    /// given a room.
252    pub(crate) members_request_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
253
254    /// Handler to ensure that only one encryption state request is running at a
255    /// time, given a room.
256    pub(crate) encryption_state_deduplicated_handler: DeduplicatingHandler<OwnedRoomId>,
257
258    /// Deduplicating handler for sending read receipts. The string is an
259    /// internal implementation detail, see [`Self::send_single_receipt`].
260    pub(crate) read_receipt_deduplicated_handler: DeduplicatingHandler<(String, OwnedEventId)>,
261
262    #[cfg(feature = "e2e-encryption")]
263    pub(crate) cross_process_crypto_store_lock: OnceCell<CrossProcessLock<LockableCryptoStore>>,
264
265    /// Latest "generation" of data known by the crypto store.
266    ///
267    /// This is a counter that only increments, set in the database (and can
268    /// wrap). It's incremented whenever some process acquires a lock for the
269    /// first time. *This assumes the crypto store lock is being held, to
270    /// avoid data races on writing to this value in the store*.
271    ///
272    /// The current process will maintain this value in local memory and in the
273    /// DB over time. Observing a different value than the one read in
274    /// memory, when reading from the store indicates that somebody else has
275    /// written into the database under our feet.
276    ///
277    /// TODO: this should live in the `OlmMachine`, since it's information
278    /// related to the lock. As of today (2023-07-28), we blow up the entire
279    /// olm machine when there's a generation mismatch. So storing the
280    /// generation in the olm machine would make the client think there's
281    /// *always* a mismatch, and that's why we need to store the generation
282    /// outside the `OlmMachine`.
283    #[cfg(feature = "e2e-encryption")]
284    pub(crate) crypto_store_generation: Arc<Mutex<Option<u64>>>,
285}
286
287pub(crate) struct ClientInner {
288    /// All the data related to authentication and authorization.
289    pub(crate) auth_ctx: Arc<AuthCtx>,
290
291    /// The URL of the server.
292    ///
293    /// Not to be confused with the `Self::homeserver`. `server` is usually
294    /// the server part in a user ID, e.g. with `@mnt_io:matrix.org`, here
295    /// `matrix.org` is the server, whilst `matrix-client.matrix.org` is the
296    /// homeserver (at the time of writing — 2024-08-28).
297    ///
298    /// This value is optional depending on how the `Client` has been built.
299    /// If it's been built from a homeserver URL directly, we don't know the
300    /// server. However, if the `Client` has been built from a server URL or
301    /// name, then the homeserver has been discovered, and we know both.
302    server: Option<Url>,
303
304    /// The URL of the homeserver to connect to.
305    ///
306    /// This is the URL for the client-server Matrix API.
307    homeserver: StdRwLock<Url>,
308
309    /// The sliding sync version.
310    sliding_sync_version: StdRwLock<SlidingSyncVersion>,
311
312    /// Default presence state to send with generated sync requests.
313    ///
314    /// This is process-local. Consumers that create clients in multiple
315    /// processes must configure it in each process.
316    sync_presence: Arc<StdRwLock<PresenceState>>,
317
318    /// The underlying HTTP client.
319    pub(crate) http_client: HttpClient,
320
321    /// User session data.
322    pub(super) base_client: BaseClient,
323
324    /// Collection of in-memory caches for the [`Client`].
325    pub(crate) caches: ClientCaches,
326
327    /// Collection of locks individual client methods might want to use, either
328    /// to ensure that only a single call to a method happens at once or to
329    /// deduplicate multiple calls to a method.
330    pub(crate) locks: ClientLocks,
331
332    /// The cross-process lock configuration.
333    ///
334    /// The SDK provides cross-process store locks (see
335    /// [`matrix_sdk_common::cross_process_lock::CrossProcessLock`]) when
336    /// [`CrossProcessLockConfig::MultiProcess`] is used.
337    ///
338    /// If multiple `Client`s are running in different processes, this
339    /// value MUST be different for each `Client`.
340    cross_process_lock_config: CrossProcessLockConfig,
341
342    /// A mapping of the times at which the current user sent typing notices,
343    /// keyed by room.
344    pub(crate) typing_notice_times: StdRwLock<BTreeMap<OwnedRoomId, Instant>>,
345
346    /// Event handlers. See `add_event_handler`.
347    pub(crate) event_handlers: EventHandlerStore,
348
349    /// Notification handlers. See `register_notification_handler`.
350    notification_handlers: RwLock<Vec<NotificationHandlerFn>>,
351
352    /// The sender-side of channels used to receive room updates.
353    pub(crate) room_update_channels: StdMutex<BTreeMap<OwnedRoomId, broadcast::Sender<RoomUpdate>>>,
354
355    /// The sender-side of a channel used to observe all the room updates of a
356    /// sync response.
357    pub(crate) room_updates_sender: broadcast::Sender<RoomUpdates>,
358
359    /// Whether the client should update its homeserver URL with the discovery
360    /// information present in the login response.
361    respect_login_well_known: bool,
362
363    /// An event that can be listened on to wait for a successful sync. The
364    /// event will only be fired if a sync loop is running. Can be used for
365    /// synchronization, e.g. if we send out a request to create a room, we can
366    /// wait for the sync to get the data to fetch a room object from the state
367    /// store.
368    pub(crate) sync_beat: event_listener::Event,
369
370    /// A central cache for events, inactive first.
371    ///
372    /// It becomes active when [`EventCache::subscribe`] is called.
373    pub(crate) event_cache: OnceCell<EventCache>,
374
375    /// End-to-end encryption related state.
376    #[cfg(feature = "e2e-encryption")]
377    pub(crate) e2ee: EncryptionData,
378
379    /// The verification state of our own device.
380    #[cfg(feature = "e2e-encryption")]
381    pub(crate) verification_state: SharedObservable<VerificationState>,
382
383    /// Whether to enable the experimental support for sending and receiving
384    /// encrypted room history on invite, per [MSC4268].
385    ///
386    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
387    #[cfg(feature = "e2e-encryption")]
388    pub(crate) enable_share_history_on_invite: bool,
389
390    /// Data related to the [`SendQueue`].
391    ///
392    /// [`SendQueue`]: crate::send_queue::SendQueue
393    pub(crate) send_queue_data: Arc<SendQueueData>,
394
395    /// The `max_upload_size` value of the homeserver, it contains the max
396    /// request size you can send.
397    pub(crate) server_max_upload_size: Mutex<OnceCell<UInt>>,
398
399    /// The entry point to get the [`LatestEvent`] of rooms and threads.
400    ///
401    /// [`LatestEvent`]: crate::latest_event::LatestEvent
402    latest_events: OnceCell<LatestEvents>,
403
404    /// Service handling the catching up of thread subscriptions in the
405    /// background.
406    thread_subscription_catchup: OnceCell<Arc<ThreadSubscriptionCatchup>>,
407
408    #[cfg(feature = "experimental-search")]
409    /// Handler for [`RoomIndex`]'s of each room
410    search_index: SearchIndex,
411
412    /// A monitor for background tasks spawned by the client.
413    pub(crate) task_monitor: TaskMonitor,
414
415    /// A sender to notify subscribers about duplicate key upload errors
416    /// triggered by requests to /keys/upload.
417    #[cfg(feature = "e2e-encryption")]
418    pub(crate) duplicate_key_upload_error_sender:
419        broadcast::Sender<Option<DuplicateOneTimeKeyErrorMessage>>,
420
421    pub(crate) media_fetcher: RwLock<Arc<dyn MediaFetcher>>,
422}
423
424impl ClientInner {
425    /// Create a new `ClientInner`.
426    ///
427    /// All the fields passed as parameters here are those that must be cloned
428    /// upon instantiation of a sub-client, e.g. a client specialized for
429    /// notifications.
430    #[allow(clippy::too_many_arguments)]
431    async fn new(
432        auth_ctx: Arc<AuthCtx>,
433        server: Option<Url>,
434        homeserver: Url,
435        sliding_sync_version: SlidingSyncVersion,
436        sync_presence: Arc<StdRwLock<PresenceState>>,
437        http_client: HttpClient,
438        base_client: BaseClient,
439        supported_versions: CachedValue<TtlValue<SupportedVersions>>,
440        well_known: CachedValue<TtlValue<Option<WellKnownResponse>>>,
441        respect_login_well_known: bool,
442        event_cache: OnceCell<EventCache>,
443        send_queue: Arc<SendQueueData>,
444        latest_events: OnceCell<LatestEvents>,
445        #[cfg(feature = "e2e-encryption")] encryption_settings: EncryptionSettings,
446        #[cfg(feature = "e2e-encryption")] enable_share_history_on_invite: bool,
447        cross_process_lock_config: CrossProcessLockConfig,
448        #[cfg(feature = "experimental-search")] search_index_handler: SearchIndex,
449        thread_subscription_catchup: OnceCell<Arc<ThreadSubscriptionCatchup>>,
450        media_fetcher: Arc<dyn MediaFetcher>,
451    ) -> Arc<Self> {
452        let caches = ClientCaches {
453            supported_versions: Cache::with_value(supported_versions),
454            well_known: Cache::with_value(well_known),
455            server_metadata: Cache::new(),
456            homeserver_capabilities: Cache::new(),
457        };
458
459        let client = Self {
460            server,
461            homeserver: StdRwLock::new(homeserver),
462            auth_ctx,
463            sliding_sync_version: StdRwLock::new(sliding_sync_version),
464            sync_presence,
465            http_client,
466            base_client,
467            caches,
468            locks: Default::default(),
469            cross_process_lock_config,
470            typing_notice_times: Default::default(),
471            event_handlers: Default::default(),
472            notification_handlers: Default::default(),
473            room_update_channels: Default::default(),
474            // A single `RoomUpdates` is sent once per sync, so we assume that 32 is sufficient
475            // ballast for all observers to catch up.
476            room_updates_sender: broadcast::Sender::new(32),
477            respect_login_well_known,
478            sync_beat: event_listener::Event::new(),
479            event_cache,
480            send_queue_data: send_queue,
481            latest_events,
482            #[cfg(feature = "e2e-encryption")]
483            e2ee: EncryptionData::new(encryption_settings),
484            #[cfg(feature = "e2e-encryption")]
485            verification_state: SharedObservable::new(VerificationState::Unknown),
486            #[cfg(feature = "e2e-encryption")]
487            enable_share_history_on_invite,
488            server_max_upload_size: Mutex::new(OnceCell::new()),
489            #[cfg(feature = "experimental-search")]
490            search_index: search_index_handler,
491            thread_subscription_catchup,
492            task_monitor: TaskMonitor::new(),
493            #[cfg(feature = "e2e-encryption")]
494            duplicate_key_upload_error_sender: broadcast::channel(1).0,
495            media_fetcher: RwLock::new(media_fetcher),
496        };
497
498        #[allow(clippy::let_and_return)]
499        let client = Arc::new(client);
500
501        #[cfg(feature = "e2e-encryption")]
502        client.e2ee.initialize_tasks(&client);
503
504        let init_event_cache = client.event_cache.get_or_init(|| async {
505            EventCache::new(&client, client.base_client.event_cache_store().clone())
506        });
507
508        let init_thread_subscription_catchup = client
509            .thread_subscription_catchup
510            .get_or_init(|| ThreadSubscriptionCatchup::new(Client { inner: client.clone() }));
511
512        let _ = join!(init_event_cache, init_thread_subscription_catchup);
513
514        client
515    }
516}
517
518#[cfg(not(tarpaulin_include))]
519impl Debug for Client {
520    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
521        write!(fmt, "Client")
522    }
523}
524
525impl Client {
526    /// Create a new [`Client`] that will use the given homeserver.
527    ///
528    /// # Arguments
529    ///
530    /// * `homeserver_url` - The homeserver that the client should connect to.
531    pub async fn new(homeserver_url: Url) -> Result<Self, ClientBuildError> {
532        Self::builder().homeserver_url(homeserver_url).build().await
533    }
534
535    /// Returns a subscriber that publishes an event every time the ignore user
536    /// list changes.
537    pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<Vec<String>> {
538        self.inner.base_client.subscribe_to_ignore_user_list_changes()
539    }
540
541    /// Create a new [`ClientBuilder`].
542    pub fn builder() -> ClientBuilder {
543        ClientBuilder::new()
544    }
545
546    pub(crate) fn base_client(&self) -> &BaseClient {
547        &self.inner.base_client
548    }
549
550    /// The underlying HTTP client.
551    pub fn http_client(&self) -> &reqwest::Client {
552        &self.inner.http_client.inner
553    }
554
555    pub(crate) fn locks(&self) -> &ClientLocks {
556        &self.inner.locks
557    }
558
559    pub(crate) fn auth_ctx(&self) -> &AuthCtx {
560        &self.inner.auth_ctx
561    }
562
563    /// The cross-process store lock configuration used by this [`Client`].
564    ///
565    /// The SDK provides cross-process store locks (see
566    /// [`matrix_sdk_common::cross_process_lock::CrossProcessLock`]) when this
567    /// value is [`CrossProcessLockConfig::MultiProcess`]. Its holder name is
568    /// the value used for all cross-process store locks used by this
569    /// `Client`.
570    pub fn cross_process_lock_config(&self) -> &CrossProcessLockConfig {
571        &self.inner.cross_process_lock_config
572    }
573
574    /// Change the homeserver URL used by this client.
575    ///
576    /// # Arguments
577    ///
578    /// * `homeserver_url` - The new URL to use.
579    fn set_homeserver(&self, homeserver_url: Url) {
580        *self.inner.homeserver.write().unwrap() = homeserver_url;
581    }
582
583    /// Change to a different homeserver and re-resolve well-known.
584    #[cfg(feature = "e2e-encryption")]
585    pub(crate) async fn switch_homeserver_and_re_resolve_well_known(
586        &self,
587        homeserver_url: Url,
588    ) -> Result<()> {
589        self.set_homeserver(homeserver_url);
590        self.reset_well_known().await?;
591        if let Some(well_known) = self.well_known().await {
592            self.set_homeserver(Url::parse(&well_known.homeserver.base_url)?);
593        }
594        Ok(())
595    }
596
597    /// Retrieves a helper component to access the [`HomeserverCapabilities`]
598    /// supported or disabled by the homeserver.
599    pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
600        HomeserverCapabilities::new(self.clone())
601    }
602
603    /// Get the server vendor information from the federation API.
604    ///
605    /// This method calls the `/_matrix/federation/v1/version` endpoint to get
606    /// both the server's software name and version.
607    ///
608    /// # Examples
609    ///
610    /// ```no_run
611    /// # use matrix_sdk::Client;
612    /// # use url::Url;
613    /// # async {
614    /// # let homeserver = Url::parse("http://example.com")?;
615    /// let client = Client::new(homeserver).await?;
616    ///
617    /// let server_info = client.server_vendor_info(None).await?;
618    /// println!(
619    ///     "Server: {}, Version: {}",
620    ///     server_info.server_name, server_info.version
621    /// );
622    /// # anyhow::Ok(()) };
623    /// ```
624    #[cfg(feature = "federation-api")]
625    pub async fn server_vendor_info(
626        &self,
627        request_config: Option<RequestConfig>,
628    ) -> HttpResult<ServerVendorInfo> {
629        use ruma::api::federation::discovery::get_server_version;
630
631        let res = self
632            .send_inner(get_server_version::v1::Request::new(), request_config, Default::default())
633            .await?;
634
635        // Extract server info, using defaults if fields are missing.
636        let server = res.server.unwrap_or_default();
637        let server_name_str = server.name.unwrap_or_else(|| "unknown".to_owned());
638        let version = server.version.unwrap_or_else(|| "unknown".to_owned());
639
640        Ok(ServerVendorInfo { server_name: server_name_str, version })
641    }
642
643    /// Get a copy of the default request config.
644    ///
645    /// The default request config is what's used when sending requests if no
646    /// `RequestConfig` is explicitly passed to [`send`][Self::send] or another
647    /// function with such a parameter.
648    ///
649    /// If the default request config was not customized through
650    /// [`ClientBuilder`] when creating this `Client`, the returned value will
651    /// be equivalent to [`RequestConfig::default()`].
652    pub fn request_config(&self) -> RequestConfig {
653        self.inner.http_client.request_config
654    }
655
656    /// Check whether the client has been activated.
657    ///
658    /// A client is considered active when:
659    ///
660    /// 1. It has a `SessionMeta` (user ID, device ID and access token), i.e. it
661    ///    is logged in,
662    /// 2. Has loaded cached data from storage,
663    /// 3. If encryption is enabled, it also initialized or restored its
664    ///    `OlmMachine`.
665    pub fn is_active(&self) -> bool {
666        self.inner.base_client.is_active()
667    }
668
669    /// The server used by the client.
670    ///
671    /// See `Self::server` to learn more.
672    pub fn server(&self) -> Option<&Url> {
673        self.inner.server.as_ref()
674    }
675
676    /// The homeserver of the client.
677    pub fn homeserver(&self) -> Url {
678        self.inner.homeserver.read().unwrap().clone()
679    }
680
681    /// Get the sliding sync version.
682    pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
683        self.inner.sliding_sync_version.read().unwrap().clone()
684    }
685
686    /// Override the sliding sync version.
687    pub fn set_sliding_sync_version(&self, version: SlidingSyncVersion) {
688        let mut lock = self.inner.sliding_sync_version.write().unwrap();
689        *lock = version;
690    }
691
692    /// Get the default presence state used by generated sync requests.
693    pub(crate) fn sync_presence(&self) -> PresenceState {
694        self.inner.sync_presence.read().unwrap().clone()
695    }
696
697    /// Get the Matrix user session meta information.
698    ///
699    /// If the client is currently logged in, this will return a
700    /// [`SessionMeta`] object which contains the user ID and device ID.
701    /// Otherwise it returns `None`.
702    pub fn session_meta(&self) -> Option<&SessionMeta> {
703        self.base_client().session_meta()
704    }
705
706    /// Returns a receiver that gets events for each room info update. To watch
707    /// for new events, use `receiver.resubscribe()`.
708    pub fn room_info_notable_update_receiver(&self) -> broadcast::Receiver<RoomInfoNotableUpdate> {
709        self.base_client().room_info_notable_update_receiver()
710    }
711
712    /// Returns a receiver of the user IDs whose global profile changed during a
713    /// sync. Consumers can use this as a trigger to e.g. merge any global
714    /// fields into a user's room profile.
715    ///
716    /// Requires the Profiles sliding sync extension to be enabled.
717    pub fn subscribe_to_global_profile_updates(
718        &self,
719    ) -> broadcast::Receiver<BTreeSet<ruma::OwnedUserId>> {
720        self.base_client().subscribe_to_global_profile_updates()
721    }
722
723    /// Observe updates to the current user's global profile.
724    ///
725    /// Emits the current value immediately, then again whenever the user's
726    /// global profile changes during sync. When no profile is stored (nothing
727    /// received yet) an empty [`UserProfile`] is emitted.
728    ///
729    /// **Note:** Without the Profiles sliding sync extension enabled only an
730    /// empty profile will be emitted and no updates will be published.
731    ///
732    /// [`UserProfile`]: ruma::profile::UserProfile
733    pub fn subscribe_to_own_profile(
734        &self,
735    ) -> Result<impl Stream<Item = ruma::profile::UserProfile> + use<>> {
736        let own_user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
737        let mut updates = self.subscribe_to_global_profile_updates();
738        let client = self.clone();
739
740        Ok(async_stream::stream! {
741            // Emit the initial value.
742            match client.state_store().get_global_profile(&own_user_id).await {
743                Ok(profile) => yield profile.unwrap_or_default(),
744                Err(error) => error!(?error, "Failed to load the stored global profile"),
745            }
746
747            while let Ok(updated_user_ids) = updates.recv().await {
748                if !updated_user_ids.contains(&own_user_id) {
749                    continue;
750                }
751
752                match client.state_store().get_global_profile(&own_user_id).await {
753                    Ok(profile) => yield profile.unwrap_or_default(),
754                    Err(error) => error!(?error, "Failed to load the updated global profile"),
755                }
756            }
757        })
758    }
759
760    /// Performs a search for users.
761    /// The search is performed case-insensitively on user IDs and display names
762    ///
763    /// # Arguments
764    ///
765    /// * `search_term` - The search term for the search
766    /// * `limit` - The maximum number of results to return. Defaults to 10.
767    ///
768    /// [user directory]: https://spec.matrix.org/v1.6/client-server-api/#user-directory
769    pub async fn search_users(
770        &self,
771        search_term: &str,
772        limit: u64,
773    ) -> HttpResult<search_users::v3::Response> {
774        let mut request = search_users::v3::Request::new(search_term.to_owned());
775
776        if let Some(limit) = UInt::new(limit) {
777            request.limit = limit;
778        }
779
780        self.send(request).await
781    }
782
783    /// Get the user id of the current owner of the client.
784    pub fn user_id(&self) -> Option<&UserId> {
785        self.session_meta().map(|s| s.user_id.as_ref())
786    }
787
788    /// Get the device ID that identifies the current session.
789    pub fn device_id(&self) -> Option<&DeviceId> {
790        self.session_meta().map(|s| s.device_id.as_ref())
791    }
792
793    /// Get the current access token for this session.
794    ///
795    /// Will be `None` if the client has not been logged in.
796    pub fn access_token(&self) -> Option<String> {
797        self.auth_ctx().access_token()
798    }
799
800    /// Set the presence state for the current user.
801    ///
802    /// The presence state is stored as the default used by future generated
803    /// sync requests, regardless of `immediate`. The initial default is
804    /// [`PresenceState::Online`]. If `immediate` is `true`, this also
805    /// calls the Matrix presence endpoint directly. `status_msg` is only sent
806    /// when `immediate` is `true`.
807    pub async fn set_presence(
808        &self,
809        presence: PresenceState,
810        status_msg: Option<String>,
811        immediate: bool,
812    ) -> Result<()> {
813        *self.inner.sync_presence.write().unwrap() = presence.clone();
814
815        if !immediate {
816            return Ok(());
817        }
818
819        let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
820        let mut request = set_presence_status::v3::Request::new(user_id, presence);
821        request.status_msg = status_msg;
822
823        self.send(request).await?;
824
825        Ok(())
826    }
827
828    /// Get the current tokens for this session.
829    ///
830    /// To be notified of changes in the session tokens, use
831    /// [`Client::subscribe_to_session_changes()`] or
832    /// [`Client::set_session_callbacks()`].
833    ///
834    /// Returns `None` if the client has not been logged in.
835    pub fn session_tokens(&self) -> Option<SessionTokens> {
836        self.auth_ctx().session_tokens()
837    }
838
839    /// Access the authentication API used to log in this client.
840    ///
841    /// Will be `None` if the client has not been logged in.
842    pub fn auth_api(&self) -> Option<AuthApi> {
843        match self.auth_ctx().auth_data.get()? {
844            AuthData::Matrix => Some(AuthApi::Matrix(self.matrix_auth())),
845            AuthData::OAuth(_) => Some(AuthApi::OAuth(self.oauth())),
846        }
847    }
848
849    /// Get the whole session info of this client.
850    ///
851    /// Will be `None` if the client has not been logged in.
852    ///
853    /// Can be used with [`Client::restore_session`] to restore a previously
854    /// logged-in session.
855    pub fn session(&self) -> Option<AuthSession> {
856        match self.auth_api()? {
857            AuthApi::Matrix(api) => api.session().map(Into::into),
858            AuthApi::OAuth(api) => api.full_session().map(Into::into),
859        }
860    }
861
862    /// Get a reference to the state store.
863    pub fn state_store(&self) -> &DynStateStore {
864        self.base_client().state_store()
865    }
866
867    /// Get a reference to the event cache store.
868    pub fn event_cache_store(&self) -> &EventCacheStoreLock {
869        self.base_client().event_cache_store()
870    }
871
872    /// Get a reference to the media store.
873    pub fn media_store(&self) -> &MediaStoreLock {
874        self.base_client().media_store()
875    }
876
877    /// Access the native Matrix authentication API with this client.
878    pub fn matrix_auth(&self) -> MatrixAuth {
879        MatrixAuth::new(self.clone())
880    }
881
882    /// Get the account of the current owner of the client.
883    pub fn account(&self) -> Account {
884        Account::new(self.clone())
885    }
886
887    /// Get the encryption manager of the client.
888    #[cfg(feature = "e2e-encryption")]
889    pub fn encryption(&self) -> Encryption {
890        Encryption::new(self.clone())
891    }
892
893    /// Get the media manager of the client.
894    pub fn media(&self) -> Media {
895        Media::new(self.clone())
896    }
897
898    /// Get the pusher manager of the client.
899    pub fn pusher(&self) -> Pusher {
900        Pusher::new(self.clone())
901    }
902
903    /// Access the OAuth 2.0 API of the client.
904    pub fn oauth(&self) -> OAuth {
905        OAuth::new(self.clone())
906    }
907
908    /// Register a handler for a specific event type.
909    ///
910    /// The handler is a function or closure with one or more arguments. The
911    /// first argument is the event itself. All additional arguments are
912    /// "context" arguments: They have to implement [`EventHandlerContext`].
913    /// This trait is named that way because most of the types implementing it
914    /// give additional context about an event: The room it was in, its raw form
915    /// and other similar things. As two exceptions to this,
916    /// [`Client`] and [`EventHandlerHandle`] also implement the
917    /// `EventHandlerContext` trait so you don't have to clone your client
918    /// into the event handler manually and a handler can decide to remove
919    /// itself.
920    ///
921    /// Some context arguments are not universally applicable. A context
922    /// argument that isn't available for the given event type will result in
923    /// the event handler being skipped and an error being logged. The following
924    /// context argument types are only available for a subset of event types:
925    ///
926    /// * [`Room`] is only available for room-specific events, i.e. not for
927    ///   events like global account data events or presence events.
928    ///
929    /// You can provide custom context via
930    /// [`add_event_handler_context`](Client::add_event_handler_context) and
931    /// then use [`Ctx<T>`](crate::event_handler::Ctx) to extract the context
932    /// into the event handler.
933    ///
934    /// [`EventHandlerContext`]: crate::event_handler::EventHandlerContext
935    ///
936    /// # Examples
937    ///
938    /// ```no_run
939    /// use matrix_sdk::{
940    ///     deserialized_responses::EncryptionInfo,
941    ///     event_handler::Ctx,
942    ///     ruma::{
943    ///         events::{
944    ///             macros::EventContent,
945    ///             push_rules::PushRulesEvent,
946    ///             room::{
947    ///                 message::SyncRoomMessageEvent,
948    ///                 topic::SyncRoomTopicEvent,
949    ///                 member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
950    ///             },
951    ///         },
952    ///         push::Action,
953    ///         Int, MilliSecondsSinceUnixEpoch,
954    ///     },
955    ///     Client, Room,
956    /// };
957    /// use serde::{Deserialize, Serialize};
958    ///
959    /// # async fn example(client: Client) {
960    /// client.add_event_handler(
961    ///     |ev: SyncRoomMessageEvent, room: Room, client: Client| async move {
962    ///         // Common usage: Room event plus room and client.
963    ///     },
964    /// );
965    /// client.add_event_handler(
966    ///     |ev: SyncRoomMessageEvent, room: Room, encryption_info: Option<EncryptionInfo>| {
967    ///         async move {
968    ///             // An `Option<EncryptionInfo>` parameter lets you distinguish between
969    ///             // unencrypted events and events that were decrypted by the SDK.
970    ///         }
971    ///     },
972    /// );
973    /// client.add_event_handler(
974    ///     |ev: SyncRoomMessageEvent, room: Room, push_actions: Vec<Action>| {
975    ///         async move {
976    ///             // A `Vec<Action>` parameter allows you to know which push actions
977    ///             // are applicable for an event. For example, an event with
978    ///             // `Action::SetTweak(Tweak::Highlight(HighlightTweakValue::Yes))`
979    ///             // should be highlighted in the timeline.
980    ///         }
981    ///     },
982    /// );
983    /// client.add_event_handler(|ev: SyncRoomTopicEvent| async move {
984    ///     // You can omit any or all arguments after the first.
985    /// });
986    ///
987    /// // Registering a temporary event handler:
988    /// let handle = client.add_event_handler(|ev: SyncRoomMessageEvent| async move {
989    ///     /* Event handler */
990    /// });
991    /// client.remove_event_handler(handle);
992    ///
993    /// // Registering custom event handler context:
994    /// #[derive(Debug, Clone)] // The context will be cloned for event handler.
995    /// struct MyContext {
996    ///     number: usize,
997    /// }
998    /// client.add_event_handler_context(MyContext { number: 5 });
999    /// client.add_event_handler(|ev: SyncRoomMessageEvent, context: Ctx<MyContext>| async move {
1000    ///     // Use the context
1001    /// });
1002    ///
1003    /// // This will handle membership events in joined rooms. Invites are special, see below.
1004    /// client.add_event_handler(
1005    ///     |ev: SyncRoomMemberEvent| async move {},
1006    /// );
1007    ///
1008    /// // To handle state events in invited rooms (including invite membership events),
1009    /// // `StrippedRoomMemberEvent` should be used.
1010    /// // https://spec.matrix.org/v1.16/client-server-api/#stripped-state
1011    /// client.add_event_handler(
1012    ///     |ev: StrippedRoomMemberEvent| async move {},
1013    /// );
1014    ///
1015    /// // Custom events work exactly the same way, you just need to declare
1016    /// // the content struct and use the EventContent derive macro on it.
1017    /// #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
1018    /// #[ruma_event(type = "org.shiny_new_2fa.token", kind = MessageLike)]
1019    /// struct TokenEventContent {
1020    ///     token: String,
1021    ///     #[serde(rename = "exp")]
1022    ///     expires_at: MilliSecondsSinceUnixEpoch,
1023    /// }
1024    ///
1025    /// client.add_event_handler(async |ev: SyncTokenEvent, room: Room| -> () {
1026    ///     todo!("Display the token");
1027    /// });
1028    ///
1029    /// // Event handler closures can also capture local variables.
1030    /// // Make sure they are cheap to clone though, because they will be cloned
1031    /// // every time the closure is called.
1032    /// let data: std::sync::Arc<str> = "MyCustomIdentifier".into();
1033    ///
1034    /// client.add_event_handler(move |ev: SyncRoomMessageEvent | async move {
1035    ///     println!("Calling the handler with identifier {data}");
1036    /// });
1037    /// # }
1038    /// ```
1039    pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
1040    where
1041        Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
1042        H: EventHandler<Ev, Ctx>,
1043    {
1044        self.add_event_handler_impl(handler, None)
1045    }
1046
1047    /// Register a handler for a specific room, and event type.
1048    ///
1049    /// This method works the same way as
1050    /// [`add_event_handler`][Self::add_event_handler], except that the handler
1051    /// will only be called for events in the room with the specified ID. See
1052    /// that method for more details on event handler functions.
1053    ///
1054    /// `client.add_room_event_handler(room_id, hdl)` is equivalent to
1055    /// `room.add_event_handler(hdl)`. Use whichever one is more convenient in
1056    /// your use case.
1057    pub fn add_room_event_handler<Ev, Ctx, H>(
1058        &self,
1059        room_id: &RoomId,
1060        handler: H,
1061    ) -> EventHandlerHandle
1062    where
1063        Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + 'static,
1064        H: EventHandler<Ev, Ctx>,
1065    {
1066        self.add_event_handler_impl(handler, Some(room_id.to_owned()))
1067    }
1068
1069    /// Observe a specific event type.
1070    ///
1071    /// `Ev` represents the kind of event that will be observed. `Ctx`
1072    /// represents the context that will come with the event. It relies on the
1073    /// same mechanism as [`Client::add_event_handler`]. The main difference is
1074    /// that it returns an [`ObservableEventHandler`] and doesn't require a
1075    /// user-defined closure. It is possible to subscribe to the
1076    /// [`ObservableEventHandler`] to get an [`EventHandlerSubscriber`], which
1077    /// implements a [`Stream`]. The `Stream::Item` will be of type `(Ev,
1078    /// Ctx)`.
1079    ///
1080    /// Be careful that only the most recent value can be observed. Subscribers
1081    /// are notified when a new value is sent, but there is no guarantee
1082    /// that they will see all values.
1083    ///
1084    /// # Example
1085    ///
1086    /// Let's see a classical usage:
1087    ///
1088    /// ```
1089    /// use futures_util::StreamExt as _;
1090    /// use matrix_sdk::{
1091    ///     Client, Room,
1092    ///     ruma::{events::room::message::SyncRoomMessageEvent, push::Action},
1093    /// };
1094    ///
1095    /// # async fn example(client: Client) -> Option<()> {
1096    /// let observer =
1097    ///     client.observe_events::<SyncRoomMessageEvent, (Room, Vec<Action>)>();
1098    ///
1099    /// let mut subscriber = observer.subscribe();
1100    ///
1101    /// let (event, (room, push_actions)) = subscriber.next().await?;
1102    /// # Some(())
1103    /// # }
1104    /// ```
1105    ///
1106    /// Now let's see how to get several contexts that can be useful for you:
1107    ///
1108    /// ```
1109    /// use matrix_sdk::{
1110    ///     Client, Room,
1111    ///     deserialized_responses::EncryptionInfo,
1112    ///     ruma::{
1113    ///         events::room::{
1114    ///             message::SyncRoomMessageEvent, topic::SyncRoomTopicEvent,
1115    ///         },
1116    ///         push::Action,
1117    ///     },
1118    /// };
1119    ///
1120    /// # async fn example(client: Client) {
1121    /// // Observe `SyncRoomMessageEvent` and fetch `Room` + `Client`.
1122    /// let _ = client.observe_events::<SyncRoomMessageEvent, (Room, Client)>();
1123    ///
1124    /// // Observe `SyncRoomMessageEvent` and fetch `Room` + `EncryptionInfo`
1125    /// // to distinguish between unencrypted events and events that were decrypted
1126    /// // by the SDK.
1127    /// let _ = client
1128    ///     .observe_events::<SyncRoomMessageEvent, (Room, Option<EncryptionInfo>)>(
1129    ///     );
1130    ///
1131    /// // Observe `SyncRoomMessageEvent` and fetch `Room` + push actions.
1132    /// // For example, an event with `Action::SetTweak(Tweak::Highlight(HighlightTweakValue::Yes))`
1133    /// // should be highlighted in the timeline.
1134    /// let _ =
1135    ///     client.observe_events::<SyncRoomMessageEvent, (Room, Vec<Action>)>();
1136    ///
1137    /// // Observe `SyncRoomTopicEvent` and fetch nothing else.
1138    /// let _ = client.observe_events::<SyncRoomTopicEvent, ()>();
1139    /// # }
1140    /// ```
1141    ///
1142    /// [`EventHandlerSubscriber`]: crate::event_handler::EventHandlerSubscriber
1143    pub fn observe_events<Ev, Ctx>(&self) -> ObservableEventHandler<(Ev, Ctx)>
1144    where
1145        Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1146        Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1147    {
1148        self.observe_room_events_impl(None)
1149    }
1150
1151    /// Observe a specific room, and event type.
1152    ///
1153    /// This method works the same way as [`Client::observe_events`], except
1154    /// that the observability will only be applied for events in the room with
1155    /// the specified ID. See that method for more details.
1156    ///
1157    /// Be careful that only the most recent value can be observed. Subscribers
1158    /// are notified when a new value is sent, but there is no guarantee
1159    /// that they will see all values.
1160    pub fn observe_room_events<Ev, Ctx>(
1161        &self,
1162        room_id: &RoomId,
1163    ) -> ObservableEventHandler<(Ev, Ctx)>
1164    where
1165        Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1166        Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1167    {
1168        self.observe_room_events_impl(Some(room_id.to_owned()))
1169    }
1170
1171    /// Shared implementation for `Client::observe_events` and
1172    /// `Client::observe_room_events`.
1173    fn observe_room_events_impl<Ev, Ctx>(
1174        &self,
1175        room_id: Option<OwnedRoomId>,
1176    ) -> ObservableEventHandler<(Ev, Ctx)>
1177    where
1178        Ev: SyncEvent + DeserializeOwned + SendOutsideWasm + SyncOutsideWasm + 'static,
1179        Ctx: EventHandlerContext + SendOutsideWasm + SyncOutsideWasm + 'static,
1180    {
1181        // The default value is `None`. It becomes `Some((Ev, Ctx))` once it has a
1182        // new value.
1183        let shared_observable = SharedObservable::new(None);
1184
1185        ObservableEventHandler::new(
1186            shared_observable.clone(),
1187            self.event_handler_drop_guard(self.add_event_handler_impl(
1188                move |event: Ev, context: Ctx| {
1189                    shared_observable.set(Some((event, context)));
1190
1191                    ready(())
1192                },
1193                room_id,
1194            )),
1195        )
1196    }
1197
1198    /// Subscribe to future `beacon_info` updates for the current user across
1199    /// all rooms.
1200    ///
1201    /// This stream is push-only: it emits only future updates observed during
1202    /// sync processing and does not replay existing state.
1203    pub fn observe_own_beacon_info_updates(
1204        &self,
1205    ) -> Result<impl Stream<Item = BeaconInfoUpdate> + use<>> {
1206        let observer = self.observe_events::<OriginalSyncBeaconInfoEvent, Room>();
1207        let mut stream = observer.subscribe();
1208        let own_user_id = self.user_id().ok_or(Error::AuthenticationRequired)?.to_owned();
1209        Ok(async_stream::stream! {
1210            let _observer = observer;
1211
1212            while let Some((event, room)) = stream.next().await {
1213                if event.state_key != own_user_id {
1214                    continue;
1215                }
1216                yield BeaconInfoUpdate {
1217                    room_id: room.room_id().to_owned(),
1218                    event_id: event.event_id,
1219                    content: event.content,
1220                };
1221            }
1222        })
1223    }
1224
1225    /// Remove the event handler associated with the handle.
1226    ///
1227    /// Note that you **must not** call `remove_event_handler` from the
1228    /// non-async part of an event handler, that is:
1229    ///
1230    /// ```ignore
1231    /// client.add_event_handler(|ev: SomeEvent, client: Client, handle: EventHandlerHandle| {
1232    ///     // ⚠ this will cause a deadlock ⚠
1233    ///     client.remove_event_handler(handle);
1234    ///
1235    ///     async move {
1236    ///         // removing the event handler here is fine
1237    ///         client.remove_event_handler(handle);
1238    ///     }
1239    /// })
1240    /// ```
1241    ///
1242    /// Note also that handlers that remove themselves will still execute with
1243    /// events received in the same sync cycle.
1244    ///
1245    /// # Arguments
1246    ///
1247    /// `handle` - The [`EventHandlerHandle`] that is returned when
1248    /// registering the event handler with [`Client::add_event_handler`].
1249    ///
1250    /// # Examples
1251    ///
1252    /// ```no_run
1253    /// # use url::Url;
1254    /// # use tokio::sync::mpsc;
1255    /// #
1256    /// # let homeserver = Url::parse("http://localhost:8080").unwrap();
1257    /// #
1258    /// use matrix_sdk::{
1259    ///     Client, event_handler::EventHandlerHandle,
1260    ///     ruma::events::room::member::SyncRoomMemberEvent,
1261    /// };
1262    /// #
1263    /// # futures_executor::block_on(async {
1264    /// # let client = matrix_sdk::Client::builder()
1265    /// #     .homeserver_url(homeserver)
1266    /// #     .server_versions([ruma::api::MatrixVersion::V1_0])
1267    /// #     .build()
1268    /// #     .await
1269    /// #     .unwrap();
1270    ///
1271    /// client.add_event_handler(
1272    ///     |ev: SyncRoomMemberEvent,
1273    ///      client: Client,
1274    ///      handle: EventHandlerHandle| async move {
1275    ///         // Common usage: Check arriving Event is the expected one
1276    ///         println!("Expected RoomMemberEvent received!");
1277    ///         client.remove_event_handler(handle);
1278    ///     },
1279    /// );
1280    /// # });
1281    /// ```
1282    pub fn remove_event_handler(&self, handle: EventHandlerHandle) {
1283        self.inner.event_handlers.remove(handle);
1284    }
1285
1286    /// Create an [`EventHandlerDropGuard`] for the event handler identified by
1287    /// the given handle.
1288    ///
1289    /// When the returned value is dropped, the event handler will be removed.
1290    pub fn event_handler_drop_guard(&self, handle: EventHandlerHandle) -> EventHandlerDropGuard {
1291        EventHandlerDropGuard::new(handle, self.clone())
1292    }
1293
1294    /// Add an arbitrary value for use as event handler context.
1295    ///
1296    /// The value can be obtained in an event handler by adding an argument of
1297    /// the type [`Ctx<T>`][crate::event_handler::Ctx].
1298    ///
1299    /// If a value of the same type has been added before, it will be
1300    /// overwritten.
1301    ///
1302    /// # Examples
1303    ///
1304    /// ```no_run
1305    /// use matrix_sdk::{
1306    ///     Room, event_handler::Ctx,
1307    ///     ruma::events::room::message::SyncRoomMessageEvent,
1308    /// };
1309    /// # #[derive(Clone)]
1310    /// # struct SomeType;
1311    /// # fn obtain_gui_handle() -> SomeType { SomeType }
1312    /// # let homeserver = url::Url::parse("http://localhost:8080").unwrap();
1313    /// # futures_executor::block_on(async {
1314    /// # let client = matrix_sdk::Client::builder()
1315    /// #     .homeserver_url(homeserver)
1316    /// #     .server_versions([ruma::api::MatrixVersion::V1_0])
1317    /// #     .build()
1318    /// #     .await
1319    /// #     .unwrap();
1320    ///
1321    /// // Handle used to send messages to the UI part of the app
1322    /// let my_gui_handle: SomeType = obtain_gui_handle();
1323    ///
1324    /// client.add_event_handler_context(my_gui_handle.clone());
1325    /// client.add_event_handler(
1326    ///     |ev: SyncRoomMessageEvent, room: Room, gui_handle: Ctx<SomeType>| {
1327    ///         async move {
1328    ///             // gui_handle.send(DisplayMessage { message: ev });
1329    ///         }
1330    ///     },
1331    /// );
1332    /// # });
1333    /// ```
1334    pub fn add_event_handler_context<T>(&self, ctx: T)
1335    where
1336        T: Clone + Send + Sync + 'static,
1337    {
1338        self.inner.event_handlers.add_context(ctx);
1339    }
1340
1341    /// Register a handler for a notification.
1342    ///
1343    /// Similar to [`Client::add_event_handler`], but only allows functions
1344    /// or closures with exactly the three arguments [`Notification`], [`Room`],
1345    /// [`Client`] for now.
1346    pub async fn register_notification_handler<H, Fut>(&self, handler: H) -> &Self
1347    where
1348        H: Fn(Notification, Room, Client) -> Fut + SendOutsideWasm + SyncOutsideWasm + 'static,
1349        Fut: Future<Output = ()> + SendOutsideWasm + 'static,
1350    {
1351        self.inner.notification_handlers.write().await.push(Box::new(
1352            move |notification, room, client| Box::pin((handler)(notification, room, client)),
1353        ));
1354
1355        self
1356    }
1357
1358    /// Subscribe to all updates for the room with the given ID.
1359    ///
1360    /// The returned receiver will receive a new message for each sync response
1361    /// that contains updates for that room.
1362    pub fn subscribe_to_room_updates(&self, room_id: &RoomId) -> broadcast::Receiver<RoomUpdate> {
1363        match self.inner.room_update_channels.lock().unwrap().entry(room_id.to_owned()) {
1364            btree_map::Entry::Vacant(entry) => {
1365                let (tx, rx) = broadcast::channel(8);
1366                entry.insert(tx);
1367                rx
1368            }
1369            btree_map::Entry::Occupied(entry) => entry.get().subscribe(),
1370        }
1371    }
1372
1373    /// Subscribe to all updates to all rooms, whenever any has been received in
1374    /// a sync response.
1375    pub fn subscribe_to_all_room_updates(&self) -> broadcast::Receiver<RoomUpdates> {
1376        self.inner.room_updates_sender.subscribe()
1377    }
1378
1379    pub(crate) async fn notification_handlers(
1380        &self,
1381    ) -> RwLockReadGuard<'_, Vec<NotificationHandlerFn>> {
1382        self.inner.notification_handlers.read().await
1383    }
1384
1385    /// Get all the rooms the client knows about.
1386    ///
1387    /// This will return the list of joined, invited, and left rooms.
1388    pub fn rooms(&self) -> Vec<Room> {
1389        self.base_client().rooms().into_iter().map(|room| Room::new(self.clone(), room)).collect()
1390    }
1391
1392    /// Get all the rooms the client knows about, filtered by room state.
1393    pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
1394        self.base_client()
1395            .rooms_filtered(filter)
1396            .into_iter()
1397            .map(|room| Room::new(self.clone(), room))
1398            .collect()
1399    }
1400
1401    /// Get a stream of all the rooms, in addition to the existing rooms.
1402    pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + '_) {
1403        let (rooms, stream) = self.base_client().rooms_stream();
1404
1405        let map_room = |room| Room::new(self.clone(), room);
1406
1407        (
1408            rooms.into_iter().map(map_room).collect(),
1409            stream.map(move |diffs| diffs.into_iter().map(|diff| diff.map(map_room)).collect()),
1410        )
1411    }
1412
1413    /// Returns the joined rooms this client knows about.
1414    pub fn joined_rooms(&self) -> Vec<Room> {
1415        self.rooms_filtered(RoomStateFilter::JOINED)
1416    }
1417
1418    /// Returns the invited rooms this client knows about.
1419    pub fn invited_rooms(&self) -> Vec<Room> {
1420        self.rooms_filtered(RoomStateFilter::INVITED)
1421    }
1422
1423    /// Returns the left rooms this client knows about.
1424    pub fn left_rooms(&self) -> Vec<Room> {
1425        self.rooms_filtered(RoomStateFilter::LEFT)
1426    }
1427
1428    /// Returns the joined space rooms this client knows about.
1429    pub fn joined_space_rooms(&self) -> Vec<Room> {
1430        self.base_client()
1431            .rooms_filtered(RoomStateFilter::JOINED)
1432            .into_iter()
1433            .flat_map(|room| room.is_space().then_some(Room::new(self.clone(), room)))
1434            .collect()
1435    }
1436
1437    /// Get a room with the given room id.
1438    ///
1439    /// # Arguments
1440    ///
1441    /// `room_id` - The unique id of the room that should be fetched.
1442    pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1443        self.base_client().get_room(room_id).map(|room| Room::new(self.clone(), room))
1444    }
1445
1446    /// Gets the preview of a room, whether the current user has joined it or
1447    /// not.
1448    pub async fn get_room_preview(
1449        &self,
1450        room_or_alias_id: &RoomOrAliasId,
1451        via: Vec<OwnedServerName>,
1452    ) -> Result<RoomPreview> {
1453        let room_id = match <&RoomId>::try_from(room_or_alias_id) {
1454            Ok(room_id) => room_id.to_owned(),
1455            Err(alias) => self.resolve_room_alias(alias).await?.room_id,
1456        };
1457
1458        if let Some(room) = self.get_room(&room_id) {
1459            // The cached data can only be trusted if the room state is joined or
1460            // banned: for invite and knock rooms, no updates will be received
1461            // for the rooms after the invite/knock action took place so we may
1462            // have very out to date data for important fields such as
1463            // `join_rule`. For left rooms, the homeserver should return the latest info.
1464            match room.state() {
1465                RoomState::Joined | RoomState::Banned => {
1466                    return Ok(RoomPreview::from_known_room(&room).await);
1467                }
1468                RoomState::Left | RoomState::Invited | RoomState::Knocked => {}
1469            }
1470        }
1471
1472        RoomPreview::from_remote_room(self, room_id, room_or_alias_id, via).await
1473    }
1474
1475    /// Resolve a room alias to a room id and a list of servers which know
1476    /// about it.
1477    ///
1478    /// # Arguments
1479    ///
1480    /// `room_alias` - The room alias to be resolved.
1481    pub async fn resolve_room_alias(
1482        &self,
1483        room_alias: &RoomAliasId,
1484    ) -> HttpResult<get_alias::v3::Response> {
1485        let request = get_alias::v3::Request::new(room_alias.to_owned());
1486        self.send(request).await
1487    }
1488
1489    /// Checks if a room alias is not in use yet.
1490    ///
1491    /// Returns:
1492    /// - `Ok(true)` if the room alias is available.
1493    /// - `Ok(false)` if it's not (the resolve alias request returned a `404`
1494    ///   status code).
1495    /// - An `Err` otherwise.
1496    pub async fn is_room_alias_available(&self, alias: &RoomAliasId) -> HttpResult<bool> {
1497        match self.resolve_room_alias(alias).await {
1498            // The room alias was resolved, so it's already in use.
1499            Ok(_) => Ok(false),
1500            Err(error) => {
1501                match error.client_api_error_kind() {
1502                    // The room alias wasn't found, so it's available.
1503                    Some(ErrorKind::NotFound) => Ok(true),
1504                    _ => Err(error),
1505                }
1506            }
1507        }
1508    }
1509
1510    /// Adds a new room alias associated with a room to the room directory.
1511    pub async fn create_room_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> HttpResult<()> {
1512        let request = create_alias::v3::Request::new(alias.to_owned(), room_id.to_owned());
1513        self.send(request).await?;
1514        Ok(())
1515    }
1516
1517    /// Removes a room alias from the room directory.
1518    pub async fn remove_room_alias(&self, alias: &RoomAliasId) -> HttpResult<()> {
1519        let request = delete_alias::v3::Request::new(alias.to_owned());
1520        self.send(request).await?;
1521        Ok(())
1522    }
1523
1524    /// Update the homeserver from the login response well-known if needed.
1525    ///
1526    /// # Arguments
1527    ///
1528    /// * `login_well_known` - The `well_known` field from a successful login
1529    ///   response.
1530    pub(crate) fn maybe_update_login_well_known(&self, login_well_known: Option<&DiscoveryInfo>) {
1531        if self.inner.respect_login_well_known
1532            && let Some(well_known) = login_well_known
1533            && let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url)
1534        {
1535            self.set_homeserver(homeserver);
1536        }
1537    }
1538
1539    /// Similar to [`Client::restore_session_with`], with
1540    /// [`RoomLoadSettings::default()`].
1541    ///
1542    /// # Panics
1543    ///
1544    /// Panics if a session was already restored or logged in.
1545    #[instrument(skip_all)]
1546    pub async fn restore_session(&self, session: impl Into<AuthSession>) -> Result<()> {
1547        self.restore_session_with(session, RoomLoadSettings::default()).await
1548    }
1549
1550    /// Restore a session previously logged-in using one of the available
1551    /// authentication APIs. The number of rooms to restore is controlled by
1552    /// [`RoomLoadSettings`].
1553    ///
1554    /// See the documentation of the corresponding authentication API's
1555    /// `restore_session` method for more information.
1556    ///
1557    /// # Panics
1558    ///
1559    /// Panics if a session was already restored or logged in.
1560    #[instrument(skip_all)]
1561    pub async fn restore_session_with(
1562        &self,
1563        session: impl Into<AuthSession>,
1564        room_load_settings: RoomLoadSettings,
1565    ) -> Result<()> {
1566        let session = session.into();
1567        match session {
1568            AuthSession::Matrix(session) => {
1569                Box::pin(self.matrix_auth().restore_session(session, room_load_settings)).await
1570            }
1571            AuthSession::OAuth(session) => {
1572                Box::pin(self.oauth().restore_session(*session, room_load_settings)).await
1573            }
1574        }
1575    }
1576
1577    /// Refresh the access token using the authentication API used to log into
1578    /// this session.
1579    ///
1580    /// See the documentation of the authentication API's `refresh_access_token`
1581    /// method for more information.
1582    pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1583        let Some(auth_api) = self.auth_api() else {
1584            return Err(RefreshTokenError::RefreshTokenRequired);
1585        };
1586
1587        match auth_api {
1588            AuthApi::Matrix(api) => {
1589                trace!("Token refresh: Using the homeserver.");
1590                Box::pin(api.refresh_access_token()).await?;
1591            }
1592            AuthApi::OAuth(api) => {
1593                trace!("Token refresh: Using OAuth 2.0.");
1594                Box::pin(api.refresh_access_token()).await?;
1595            }
1596        }
1597
1598        Ok(())
1599    }
1600
1601    /// Log out the current session using the proper authentication API.
1602    ///
1603    /// # Errors
1604    ///
1605    /// Returns an error if the session is not authenticated or if an error
1606    /// occurred while making the request to the server.
1607    pub async fn logout(&self) -> Result<(), Error> {
1608        let auth_api = self.auth_api().ok_or(Error::AuthenticationRequired)?;
1609        match auth_api {
1610            AuthApi::Matrix(matrix_auth) => {
1611                matrix_auth.logout().await?;
1612                Ok(())
1613            }
1614            AuthApi::OAuth(oauth) => Ok(oauth.logout().await?),
1615        }
1616    }
1617
1618    /// Get or upload a sync filter.
1619    ///
1620    /// This method will either get a filter ID from the store or upload the
1621    /// filter definition to the homeserver and return the new filter ID.
1622    ///
1623    /// # Arguments
1624    ///
1625    /// * `filter_name` - The unique name of the filter, this name will be used
1626    /// locally to store and identify the filter ID returned by the server.
1627    ///
1628    /// * `definition` - The filter definition that should be uploaded to the
1629    /// server if no filter ID can be found in the store.
1630    ///
1631    /// # Examples
1632    ///
1633    /// ```no_run
1634    /// # use matrix_sdk::{
1635    /// #    Client, config::SyncSettings,
1636    /// #    ruma::api::client::{
1637    /// #        filter::{
1638    /// #           FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter,
1639    /// #        },
1640    /// #        sync::sync_events::v3::Filter,
1641    /// #    }
1642    /// # };
1643    /// # use url::Url;
1644    /// # async {
1645    /// # let homeserver = Url::parse("http://example.com").unwrap();
1646    /// # let client = Client::new(homeserver).await.unwrap();
1647    /// let mut filter = FilterDefinition::default();
1648    ///
1649    /// // Let's enable member lazy loading.
1650    /// filter.room.state.lazy_load_options =
1651    ///     LazyLoadOptions::Enabled { include_redundant_members: false };
1652    ///
1653    /// let filter_id = client
1654    ///     .get_or_upload_filter("sync", filter)
1655    ///     .await
1656    ///     .unwrap();
1657    ///
1658    /// let sync_settings = SyncSettings::new()
1659    ///     .filter(Filter::FilterId(filter_id));
1660    ///
1661    /// let response = client.sync_once(sync_settings).await.unwrap();
1662    /// # };
1663    #[instrument(skip(self, definition))]
1664    pub async fn get_or_upload_filter(
1665        &self,
1666        filter_name: &str,
1667        definition: FilterDefinition,
1668    ) -> Result<String> {
1669        if let Some(filter) = self.inner.base_client.get_filter(filter_name).await? {
1670            debug!("Found filter locally");
1671            Ok(filter)
1672        } else {
1673            debug!("Didn't find filter locally");
1674            let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?;
1675            let request = FilterUploadRequest::new(user_id.to_owned(), definition);
1676            let response = self.send(request).await?;
1677
1678            self.inner.base_client.receive_filter_upload(filter_name, &response).await?;
1679
1680            Ok(response.filter_id)
1681        }
1682    }
1683
1684    /// Prepare to join a room by ID, by getting the current details about it
1685    async fn prepare_join_room_by_id(&self, room_id: &RoomId) -> Option<PreJoinRoomInfo> {
1686        let room = self.get_room(room_id)?;
1687
1688        let inviter = match room.invite_details().await {
1689            Ok(details) => details.inviter,
1690            Err(Error::WrongRoomState(_)) => None,
1691            Err(e) => {
1692                warn!("Error fetching invite details for room: {e:?}");
1693                None
1694            }
1695        };
1696
1697        Some(PreJoinRoomInfo { inviter })
1698    }
1699
1700    /// Finish joining a room.
1701    ///
1702    /// If the room was an invite that should be marked as a DM, will include it
1703    /// in the DM event after creating the joined room.
1704    ///
1705    /// If encrypted history sharing is enabled, will check to see if we have a
1706    /// key bundle, and import it if so.
1707    ///
1708    /// # Arguments
1709    ///
1710    /// * `room_id` - The `RoomId` of the room that was joined.
1711    /// * `pre_join_room_info` - Information about the room before we joined.
1712    async fn finish_join_room(
1713        &self,
1714        room_id: &RoomId,
1715        pre_join_room_info: Option<PreJoinRoomInfo>,
1716    ) -> Result<Room> {
1717        info!(?room_id, ?pre_join_room_info, "Completing room join");
1718        let mark_as_dm = if let Some(room) = self.get_room(room_id) {
1719            room.state() == RoomState::Invited
1720                && room.is_direct().await.unwrap_or_else(|e| {
1721                    warn!(%room_id, "is_direct() failed: {e}");
1722                    false
1723                })
1724        } else {
1725            false
1726        };
1727
1728        let base_room = self
1729            .base_client()
1730            .room_joined(
1731                room_id,
1732                pre_join_room_info
1733                    .as_ref()
1734                    .and_then(|info| info.inviter.as_ref())
1735                    .map(|i| i.user_id().to_owned()),
1736            )
1737            .await?;
1738        let room = Room::new(self.clone(), base_room);
1739
1740        if mark_as_dm {
1741            room.set_is_direct(true).await?;
1742        }
1743
1744        // If we joined following an invite, check if we had previously received a key
1745        // bundle from the inviter, and import it if so.
1746        //
1747        // It's important that we only do this once `BaseClient::room_joined` has
1748        // completed: see the notes on `BundleReceiverTask::handle_bundle` on avoiding a
1749        // race.
1750        #[cfg(feature = "e2e-encryption")]
1751        if self.inner.enable_share_history_on_invite
1752            && let Some(inviter) =
1753                pre_join_room_info.as_ref().and_then(|info| info.inviter.as_ref())
1754        {
1755            crate::room::shared_room_history::maybe_accept_key_bundle(&room, inviter.user_id())
1756                .await?;
1757        }
1758
1759        // Suppress "unused variable" and "unused field" lints
1760        #[cfg(not(feature = "e2e-encryption"))]
1761        let _ = pre_join_room_info.map(|i| i.inviter);
1762
1763        Ok(room)
1764    }
1765
1766    /// Join a room by `RoomId`.
1767    ///
1768    /// Returns the `Room` in the joined state.
1769    ///
1770    /// # Arguments
1771    ///
1772    /// * `room_id` - The `RoomId` of the room to be joined.
1773    #[instrument(skip(self))]
1774    pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result<Room> {
1775        // See who invited us to this room, if anyone. Note we have to do this before
1776        // making the `/join` request, otherwise we could race against the sync.
1777        let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1778
1779        let request = join_room_by_id::v3::Request::new(room_id.to_owned());
1780        let response = self.send(request).await?;
1781        self.finish_join_room(&response.room_id, pre_join_info).await
1782    }
1783
1784    /// Join a room by `RoomOrAliasId`.
1785    ///
1786    /// Returns the `Room` in the joined state.
1787    ///
1788    /// # Arguments
1789    ///
1790    /// * `alias` - The `RoomId` or `RoomAliasId` of the room to be joined. An
1791    ///   alias looks like `#name:example.com`.
1792    /// * `server_names` - The server names to be used for resolving the alias,
1793    ///   if needs be.
1794    #[instrument(skip(self))]
1795    pub async fn join_room_by_id_or_alias(
1796        &self,
1797        alias: &RoomOrAliasId,
1798        server_names: &[OwnedServerName],
1799    ) -> Result<Room> {
1800        let room_id = match <&RoomId>::try_from(alias) {
1801            Ok(room_id) => room_id,
1802            Err(room_alias) => &self.resolve_room_alias(room_alias).await?.room_id,
1803        };
1804        let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1805        let request = assign!(join_room_by_id_or_alias::v3::Request::new(alias.to_owned()), {
1806            via: server_names.to_owned(),
1807        });
1808        let response = self.send(request).await?;
1809        self.finish_join_room(&response.room_id, pre_join_info).await
1810    }
1811
1812    /// Search the homeserver's directory of public rooms.
1813    ///
1814    /// Sends a request to "_matrix/client/r0/publicRooms", returns
1815    /// a `get_public_rooms::Response`.
1816    ///
1817    /// # Arguments
1818    ///
1819    /// * `limit` - The number of `PublicRoomsChunk`s in each response.
1820    ///
1821    /// * `since` - Pagination token from a previous request.
1822    ///
1823    /// * `server` - The name of the server, if `None` the requested server is
1824    ///   used.
1825    ///
1826    /// # Examples
1827    /// ```no_run
1828    /// use matrix_sdk::Client;
1829    /// # use url::Url;
1830    /// # let homeserver = Url::parse("http://example.com").unwrap();
1831    /// # let limit = Some(10);
1832    /// # let since = Some("since token");
1833    /// # let server = Some("servername.com".try_into().unwrap());
1834    /// # async {
1835    /// let mut client = Client::new(homeserver).await.unwrap();
1836    ///
1837    /// client.public_rooms(limit, since, server).await;
1838    /// # };
1839    /// ```
1840    #[cfg_attr(not(target_family = "wasm"), deny(clippy::future_not_send))]
1841    pub async fn public_rooms(
1842        &self,
1843        limit: Option<u32>,
1844        since: Option<&str>,
1845        server: Option<&ServerName>,
1846    ) -> HttpResult<get_public_rooms::v3::Response> {
1847        let limit = limit.map(UInt::from);
1848
1849        let request = assign!(get_public_rooms::v3::Request::new(), {
1850            limit,
1851            since: since.map(ToOwned::to_owned),
1852            server: server.map(ToOwned::to_owned),
1853        });
1854        self.send(request).await
1855    }
1856
1857    /// Create a room with the given parameters.
1858    ///
1859    /// Sends a request to `/_matrix/client/r0/createRoom` and returns the
1860    /// created room.
1861    ///
1862    /// If you want to create a direct message with one specific user, you can
1863    /// use [`create_dm`][Self::create_dm], which is more convenient than
1864    /// assembling the [`create_room::v3::Request`] yourself.
1865    ///
1866    /// If the `is_direct` field of the request is set to `true` and at least
1867    /// one user is invited, the room will be automatically added to the direct
1868    /// rooms in the account data.
1869    ///
1870    /// # Examples
1871    ///
1872    /// ```no_run
1873    /// use matrix_sdk::{
1874    ///     Client,
1875    ///     ruma::api::client::room::create_room::v3::Request as CreateRoomRequest,
1876    /// };
1877    /// # use url::Url;
1878    /// #
1879    /// # async {
1880    /// # let homeserver = Url::parse("http://example.com").unwrap();
1881    /// let request = CreateRoomRequest::new();
1882    /// let client = Client::new(homeserver).await.unwrap();
1883    /// assert!(client.create_room(request).await.is_ok());
1884    /// # };
1885    /// ```
1886    pub async fn create_room(&self, request: create_room::v3::Request) -> Result<Room> {
1887        let invite = request.invite.clone();
1888        let is_direct_room = request.is_direct;
1889        let response = self.send(request).await?;
1890
1891        let base_room = self.base_client().get_or_create_room(&response.room_id, RoomState::Joined);
1892
1893        let joined_room = Room::new(self.clone(), base_room);
1894
1895        if is_direct_room
1896            && !invite.is_empty()
1897            && let Err(error) =
1898                self.account().mark_as_dm(joined_room.room_id(), invite.as_slice()).await
1899        {
1900            // FIXME: Retry in the background
1901            error!("Failed to mark room as DM: {error}");
1902        }
1903
1904        Ok(joined_room)
1905    }
1906
1907    /// Create a DM room.
1908    ///
1909    /// Convenience shorthand for [`create_room`][Self::create_room] with the
1910    /// given user being invited, the room marked `is_direct` and both the
1911    /// creator and invitee getting the default maximum power level.
1912    ///
1913    /// If the `e2e-encryption` feature is enabled, the room will also be
1914    /// encrypted.
1915    ///
1916    /// # Arguments
1917    ///
1918    /// * `user_id` - The ID of the user to create a DM for.
1919    pub async fn create_dm(&self, user_id: &UserId) -> Result<Room> {
1920        #[cfg(feature = "e2e-encryption")]
1921        let initial_state = vec![
1922            InitialStateEvent::with_empty_state_key(
1923                RoomEncryptionEventContent::with_recommended_defaults(),
1924            )
1925            .to_raw_any(),
1926        ];
1927
1928        #[cfg(not(feature = "e2e-encryption"))]
1929        let initial_state = vec![];
1930
1931        let request = assign!(create_room::v3::Request::new(), {
1932            invite: vec![user_id.to_owned()],
1933            is_direct: true,
1934            preset: Some(create_room::v3::RoomPreset::TrustedPrivateChat),
1935            initial_state,
1936        });
1937
1938        self.create_room(request).await
1939    }
1940
1941    /// Get the first existing DM room with the given user, if any.
1942    pub fn get_dm_room(&self, user_id: &UserId) -> Option<Room> {
1943        self.get_dm_rooms(user_id).next()
1944    }
1945
1946    /// Get an iterator with the existing DM rooms for the given user.
1947    pub fn get_dm_rooms(&self, user_id: &UserId) -> impl Iterator<Item = Room> {
1948        let rooms = self.joined_rooms();
1949
1950        let dm_definition = &self.base_client().dm_room_definition;
1951
1952        // Find the room we share with the `user_id` and only with `user_id`
1953        let rooms = rooms.into_iter().filter(move |r| {
1954            let targets = r.direct_targets();
1955            let targets_match =
1956                targets.len() == 1 && targets.contains(<&DirectUserIdentifier>::from(user_id));
1957            match dm_definition {
1958                DmRoomDefinition::MatrixSpec => targets_match,
1959                DmRoomDefinition::TwoMembers => {
1960                    let service_members_count =
1961                        r.service_members().map(|s| s.len()).unwrap_or_default() as u64;
1962                    let active_non_service_members =
1963                        r.active_members_count().saturating_sub(service_members_count);
1964                    targets_match && active_non_service_members <= 2
1965                }
1966            }
1967        });
1968
1969        trace!(?user_id, ?rooms, "Found DM rooms with user");
1970        rooms
1971    }
1972
1973    /// Search the homeserver's directory for public rooms with a filter.
1974    ///
1975    /// # Arguments
1976    ///
1977    /// * `room_search` - The easiest way to create this request is using the
1978    ///   `get_public_rooms_filtered::Request` itself.
1979    ///
1980    /// # Examples
1981    ///
1982    /// ```no_run
1983    /// # use url::Url;
1984    /// # use matrix_sdk::Client;
1985    /// # async {
1986    /// # let homeserver = Url::parse("http://example.com")?;
1987    /// use matrix_sdk::ruma::{
1988    ///     api::client::directory::get_public_rooms_filtered, directory::Filter,
1989    /// };
1990    /// # let mut client = Client::new(homeserver).await?;
1991    ///
1992    /// let mut filter = Filter::new();
1993    /// filter.generic_search_term = Some("rust".to_owned());
1994    /// let mut request = get_public_rooms_filtered::v3::Request::new();
1995    /// request.filter = filter;
1996    ///
1997    /// let response = client.public_rooms_filtered(request).await?;
1998    ///
1999    /// for room in response.chunk {
2000    ///     println!("Found room {room:?}");
2001    /// }
2002    /// # anyhow::Ok(()) };
2003    /// ```
2004    pub async fn public_rooms_filtered(
2005        &self,
2006        request: get_public_rooms_filtered::v3::Request,
2007    ) -> HttpResult<get_public_rooms_filtered::v3::Response> {
2008        self.send(request).await
2009    }
2010
2011    /// Send an arbitrary request to the server, without updating client state.
2012    ///
2013    /// **Warning:** Because this method *does not* update the client state, it
2014    /// is important to make sure that you account for this yourself, and
2015    /// use wrapper methods where available.  This method should *only* be
2016    /// used if a wrapper method for the endpoint you'd like to use is not
2017    /// available.
2018    ///
2019    /// # Arguments
2020    ///
2021    /// * `request` - A filled out and valid request for the endpoint to be hit
2022    ///
2023    /// * `timeout` - An optional request timeout setting, this overrides the
2024    ///   default request setting if one was set.
2025    ///
2026    /// # Examples
2027    ///
2028    /// ```no_run
2029    /// # use matrix_sdk::{Client, config::SyncSettings};
2030    /// # use url::Url;
2031    /// # async {
2032    /// # let homeserver = Url::parse("http://localhost:8080")?;
2033    /// # let mut client = Client::new(homeserver).await?;
2034    /// use matrix_sdk::ruma::{api::client::profile, owned_user_id};
2035    ///
2036    /// // First construct the request you want to make
2037    /// // See https://docs.rs/ruma-client-api/latest/ruma_client_api/index.html
2038    /// // for all available Endpoints
2039    /// let user_id = owned_user_id!("@example:localhost");
2040    /// let request = profile::get_profile::v3::Request::new(user_id);
2041    ///
2042    /// // Start the request using Client::send()
2043    /// let response = client.send(request).await?;
2044    ///
2045    /// // Check the corresponding Response struct to find out what types are
2046    /// // returned
2047    /// # anyhow::Ok(()) };
2048    /// ```
2049    pub fn send<Request>(&self, request: Request) -> SendRequest<Request>
2050    where
2051        Request: OutgoingRequest + Clone + Debug,
2052        Request::Authentication: SupportedAuthScheme,
2053        Request::PathBuilder: SupportedPathBuilder,
2054        for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2055        HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2056    {
2057        SendRequest {
2058            client: self.clone(),
2059            request,
2060            config: None,
2061            send_progress: Default::default(),
2062        }
2063    }
2064
2065    pub(crate) async fn send_inner<Request>(
2066        &self,
2067        request: Request,
2068        config: Option<RequestConfig>,
2069        send_progress: SharedObservable<TransmissionProgress>,
2070    ) -> HttpResult<Request::IncomingResponse>
2071    where
2072        Request: OutgoingRequest + Debug,
2073        Request::Authentication: SupportedAuthScheme,
2074        Request::PathBuilder: SupportedPathBuilder,
2075        for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2076        HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2077    {
2078        let homeserver = self.homeserver().to_string();
2079        let access_token = self.access_token();
2080        let skip_auth = config.map(|c| c.skip_auth).unwrap_or(self.request_config().skip_auth);
2081
2082        let path_builder_input =
2083            Request::PathBuilder::get_path_builder_input(self, skip_auth).await?;
2084
2085        let result = self
2086            .inner
2087            .http_client
2088            .send(
2089                request,
2090                config,
2091                homeserver,
2092                access_token.as_deref(),
2093                path_builder_input,
2094                send_progress,
2095            )
2096            .await;
2097
2098        if let Err(Some(ErrorKind::UnknownToken { .. })) =
2099            result.as_ref().map_err(HttpError::client_api_error_kind)
2100            && let Some(access_token) = &access_token
2101        {
2102            // Mark the access token as expired.
2103            self.auth_ctx().set_access_token_expired(access_token);
2104        }
2105
2106        result
2107    }
2108
2109    fn broadcast_unknown_token(&self, unknown_token_data: &UnknownTokenErrorData) {
2110        _ = self
2111            .inner
2112            .auth_ctx
2113            .session_change_sender
2114            .send(SessionChange::UnknownToken(unknown_token_data.clone()));
2115    }
2116
2117    /// Fetches server versions from network; no caching.
2118    pub async fn fetch_server_versions(
2119        &self,
2120        request_config: Option<RequestConfig>,
2121    ) -> HttpResult<get_supported_versions::Response> {
2122        // Since this was called by the user, try to refresh the access token if
2123        // necessary.
2124        self.fetch_server_versions_inner(false, request_config).await
2125    }
2126
2127    /// Fetches server versions from network; no caching.
2128    ///
2129    /// If the access token is expired and `failsafe` is `false`, this will
2130    /// attempt to refresh the access token, otherwise this will try to make an
2131    /// unauthenticated request instead.
2132    pub(crate) async fn fetch_server_versions_inner(
2133        &self,
2134        failsafe: bool,
2135        request_config: Option<RequestConfig>,
2136    ) -> HttpResult<get_supported_versions::Response> {
2137        if !failsafe {
2138            // `Client::send()` handles refreshing access tokens.
2139            return self
2140                .send(get_supported_versions::Request::new())
2141                .with_request_config(request_config)
2142                .await;
2143        }
2144
2145        let homeserver = self.homeserver().to_string();
2146
2147        // If we have a fresh access token, try with it first.
2148        if !request_config.as_ref().is_some_and(|config| config.skip_auth && !config.force_auth)
2149            && self.auth_ctx().has_valid_access_token()
2150            && let Some(access_token) = self.access_token()
2151        {
2152            let result = self
2153                .inner
2154                .http_client
2155                .send(
2156                    get_supported_versions::Request::new(),
2157                    request_config,
2158                    homeserver.clone(),
2159                    Some(&access_token),
2160                    (),
2161                    Default::default(),
2162                )
2163                .await;
2164
2165            if let Err(Some(ErrorKind::UnknownToken { .. })) =
2166                result.as_ref().map_err(HttpError::client_api_error_kind)
2167            {
2168                // If the access token is actually expired, mark it as expired and fallback to
2169                // the unauthenticated request below.
2170                self.auth_ctx().set_access_token_expired(&access_token);
2171            } else {
2172                // If the request succeeded or it's an other error, just stop now.
2173                return result;
2174            }
2175        }
2176
2177        // Try without authentication.
2178        self.inner
2179            .http_client
2180            .send(
2181                get_supported_versions::Request::new(),
2182                request_config,
2183                homeserver.clone(),
2184                None,
2185                (),
2186                Default::default(),
2187            )
2188            .await
2189    }
2190
2191    /// Fetches client well_known from network; no caching.
2192    ///
2193    /// 1. If the [`Client::server`] value is available, we use it to fetch the
2194    ///    well-known contents.
2195    /// 2. If it's not, we try extracting the server name from the
2196    ///    [`Client::user_id`] and building the server URL from it.
2197    /// 3. If we couldn't get the well-known contents with either the explicit
2198    ///    server name or the implicit extracted one, we try the homeserver URL
2199    ///    as a last resort.
2200    pub async fn fetch_client_well_known(&self) -> Option<discover_homeserver::Response> {
2201        let homeserver = self.homeserver();
2202        let scheme = homeserver.scheme();
2203
2204        // Use the server name, either an explicit one or an implicit one taken from
2205        // the user id: sometimes we'll have only the homeserver url available and no
2206        // server name, but the server name can be extracted from the current user id.
2207        let server_url = self
2208            .server()
2209            .map(|server| server.to_string())
2210            // If the server name wasn't available, extract it from the user id and build a URL:
2211            // Reuse the same scheme as the homeserver url does, assuming if it's `http` there it
2212            // will be the same for the public server url, lacking a better candidate.
2213            .or_else(|| self.user_id().map(|id| format!("{}://{}", scheme, id.server_name())));
2214
2215        // If the server name is available, first try using it
2216        let response = if let Some(server_url) = server_url {
2217            // First try using the server name
2218            self.fetch_client_well_known_with_url(server_url).await
2219        } else {
2220            None
2221        };
2222
2223        // If we didn't get a well-known value yet, try with the homeserver url instead:
2224        if response.is_none() {
2225            // Sometimes people configure their well-known directly on the homeserver so use
2226            // this as a fallback when the server name is unknown.
2227            warn!(
2228                "Fetching the well-known from the server name didn't work, using the homeserver url instead"
2229            );
2230            self.fetch_client_well_known_with_url(homeserver.to_string()).await
2231        } else {
2232            response
2233        }
2234    }
2235
2236    async fn fetch_client_well_known_with_url(
2237        &self,
2238        url: String,
2239    ) -> Option<discover_homeserver::Response> {
2240        let well_known = self
2241            .inner
2242            .http_client
2243            .send(
2244                discover_homeserver::Request::new(),
2245                Some(RequestConfig::short_retry()),
2246                url,
2247                None,
2248                (),
2249                Default::default(),
2250            )
2251            .await;
2252
2253        match well_known {
2254            Ok(well_known) => Some(well_known),
2255            Err(http_error) => {
2256                // It is perfectly valid to not have a well-known file.
2257                // Maybe we should check for a specific error code to be sure?
2258                warn!("Failed to fetch client well-known: {http_error}");
2259                None
2260            }
2261        }
2262    }
2263
2264    /// Load supported versions from storage, or fetch them from network and
2265    /// cache them.
2266    ///
2267    /// If `failsafe` is true, this will try to minimize side effects to avoid
2268    /// possible deadlocks.
2269    async fn fetch_supported_versions(
2270        &self,
2271        failsafe: bool,
2272    ) -> HttpResult<SupportedVersionsResponse> {
2273        let server_versions = self.fetch_server_versions_inner(failsafe, None).await?;
2274        let supported_versions = SupportedVersionsResponse {
2275            versions: server_versions.versions,
2276            unstable_features: server_versions.unstable_features,
2277        };
2278
2279        Ok(supported_versions)
2280    }
2281
2282    /// Get the Matrix versions and features supported by the homeserver by
2283    /// fetching them from the server or the cache.
2284    ///
2285    /// This is equivalent to calling both [`Client::server_versions()`] and
2286    /// [`Client::unstable_features()`]. To always fetch the result from the
2287    /// homeserver, you can call [`Client::fetch_server_versions()`] instead,
2288    /// and then `.as_supported_versions()` on the response.
2289    ///
2290    /// # Examples
2291    ///
2292    /// ```no_run
2293    /// use ruma::api::{FeatureFlag, MatrixVersion};
2294    /// # use matrix_sdk::{Client, config::SyncSettings};
2295    /// # use url::Url;
2296    /// # async {
2297    /// # let homeserver = Url::parse("http://localhost:8080")?;
2298    /// # let mut client = Client::new(homeserver).await?;
2299    ///
2300    /// let supported = client.supported_versions().await?;
2301    /// let supports_1_1 = supported.versions.contains(&MatrixVersion::V1_1);
2302    /// println!("The homeserver supports Matrix 1.1: {supports_1_1:?}");
2303    ///
2304    /// let msc_x_feature = FeatureFlag::from("msc_x");
2305    /// let supports_msc_x = supported.features.contains(&msc_x_feature);
2306    /// println!("The homeserver supports msc X: {supports_msc_x:?}");
2307    /// # anyhow::Ok(()) };
2308    /// ```
2309    pub async fn supported_versions(&self) -> HttpResult<SupportedVersions> {
2310        self.supported_versions_inner(false).await
2311    }
2312
2313    /// Get the Matrix versions and features supported by the homeserver by
2314    /// fetching them from the server or the cache.
2315    ///
2316    /// If `failsafe` is true, this will try to minimize side effects to avoid
2317    /// possible deadlocks.
2318    pub(crate) async fn supported_versions_inner(
2319        &self,
2320        failsafe: bool,
2321    ) -> HttpResult<SupportedVersions> {
2322        match self.supported_versions_cached_inner(failsafe).await {
2323            Ok(Some(value)) => {
2324                return Ok(value);
2325            }
2326            Ok(None) => {
2327                // The cache is empty, make a request.
2328            }
2329            Err(error) => {
2330                warn!("error when loading cached supported versions: {error}");
2331                // Fallthrough to make a request.
2332            }
2333        }
2334
2335        self.refresh_supported_versions_cache(failsafe).await
2336    }
2337
2338    /// Refresh the Matrix versions and features supported by the homeserver in
2339    /// the cache.
2340    ///
2341    /// If `failsafe` is true, this will try to minimize side effects to avoid
2342    /// possible deadlocks.
2343    async fn refresh_supported_versions_cache(
2344        &self,
2345        failsafe: bool,
2346    ) -> HttpResult<SupportedVersions> {
2347        let cached_supported_versions = &self.inner.caches.supported_versions;
2348
2349        let mut supported_versions_guard = match cached_supported_versions.refresh_lock.try_lock() {
2350            Ok(guard) => guard,
2351            Err(_) => {
2352                // There is already a refresh in progress, wait for it to finish.
2353                let guard = cached_supported_versions.refresh_lock.lock().await;
2354
2355                if let Err(error) = guard.as_ref() {
2356                    // There was an error in the previous refresh, return it.
2357                    return Err(HttpError::Cached(error.clone()));
2358                }
2359
2360                // Reuse the data if it was cached and it hasn't expired.
2361                if let CachedValue::Cached(value) = cached_supported_versions.value()
2362                    && !value.has_expired()
2363                {
2364                    return Ok(value.into_data());
2365                }
2366
2367                // The data wasn't cached or has expired, we need to make another request.
2368                guard
2369            }
2370        };
2371
2372        let response = match self.fetch_supported_versions(failsafe).await {
2373            Ok(response) => {
2374                *supported_versions_guard = Ok(());
2375                TtlValue::new(response)
2376            }
2377            Err(error) => {
2378                let error = Arc::new(error);
2379                *supported_versions_guard = Err(error.clone());
2380                return Err(HttpError::Cached(error));
2381            }
2382        };
2383
2384        let supported_versions = response.as_ref().map(|response| response.supported_versions());
2385
2386        // Only cache the result if the request was authenticated.
2387        if self.auth_ctx().has_valid_access_token() {
2388            if let Err(err) = self
2389                .state_store()
2390                .set_kv_data(
2391                    StateStoreDataKey::SupportedVersions,
2392                    StateStoreDataValue::SupportedVersions(response),
2393                )
2394                .await
2395            {
2396                warn!("error when caching supported versions: {err}");
2397            }
2398
2399            cached_supported_versions.set_value(supported_versions.clone());
2400        }
2401
2402        Ok(supported_versions.into_data())
2403    }
2404
2405    /// Get the Matrix versions and features supported by the homeserver by
2406    /// fetching them from the cache.
2407    ///
2408    /// For a version of this function that fetches the supported versions and
2409    /// features from the homeserver if the [`SupportedVersions`] aren't
2410    /// found in the cache, take a look at the [`Client::supported_versions()`]
2411    /// method.
2412    ///
2413    /// If the data in the cache has expired, this will trigger a background
2414    /// task to refresh it.
2415    ///
2416    /// # Examples
2417    ///
2418    /// ```no_run
2419    /// use ruma::api::{FeatureFlag, MatrixVersion};
2420    /// # use matrix_sdk::{Client, config::SyncSettings};
2421    /// # use url::Url;
2422    /// # async {
2423    /// # let homeserver = Url::parse("http://localhost:8080")?;
2424    /// # let mut client = Client::new(homeserver).await?;
2425    ///
2426    /// let supported =
2427    ///     if let Some(supported) = client.supported_versions_cached().await? {
2428    ///         supported
2429    ///     } else {
2430    ///         client.fetch_server_versions(None).await?.as_supported_versions()
2431    ///     };
2432    ///
2433    /// let supports_1_1 = supported.versions.contains(&MatrixVersion::V1_1);
2434    /// println!("The homeserver supports Matrix 1.1: {supports_1_1:?}");
2435    ///
2436    /// let msc_x_feature = FeatureFlag::from("msc_x");
2437    /// let supports_msc_x = supported.features.contains(&msc_x_feature);
2438    /// println!("The homeserver supports msc X: {supports_msc_x:?}");
2439    /// # anyhow::Ok(()) };
2440    /// ```
2441    pub async fn supported_versions_cached(&self) -> Result<Option<SupportedVersions>, StoreError> {
2442        self.supported_versions_cached_inner(false).await
2443    }
2444
2445    async fn supported_versions_cached_inner(
2446        &self,
2447        failsafe: bool,
2448    ) -> Result<Option<SupportedVersions>, StoreError> {
2449        let supported_versions_cache = &self.inner.caches.supported_versions;
2450
2451        let value = if let CachedValue::Cached(cached) = supported_versions_cache.value() {
2452            cached
2453        } else if let Some(stored) = self
2454            .state_store()
2455            .get_kv_data(StateStoreDataKey::SupportedVersions)
2456            .await?
2457            .and_then(|value| value.into_supported_versions())
2458        {
2459            let stored = stored.map(|response| response.supported_versions());
2460
2461            // Copy the data from the store in the in-memory cache.
2462            supported_versions_cache.set_value(stored.clone());
2463
2464            stored
2465        } else {
2466            return Ok(None);
2467        };
2468
2469        // Spawn a task to refresh the cache if it has expired and we have a valid
2470        // access token.
2471        if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2472            debug!("spawning task to refresh supported versions cache");
2473
2474            let client = self.clone();
2475            self.task_monitor().spawn_finite_task("refresh supported versions cache", async move {
2476                if let Err(error) = client.refresh_supported_versions_cache(failsafe).await {
2477                    warn!("failed to refresh supported versions cache: {error}");
2478                }
2479            });
2480        }
2481
2482        Ok(Some(value.into_data()))
2483    }
2484
2485    /// Get the Matrix versions supported by the homeserver by fetching them
2486    /// from the server or the cache.
2487    ///
2488    /// # Examples
2489    ///
2490    /// ```no_run
2491    /// use ruma::api::MatrixVersion;
2492    /// # use matrix_sdk::{Client, config::SyncSettings};
2493    /// # use url::Url;
2494    /// # async {
2495    /// # let homeserver = Url::parse("http://localhost:8080")?;
2496    /// # let mut client = Client::new(homeserver).await?;
2497    ///
2498    /// let server_versions = client.server_versions().await?;
2499    /// let supports_1_1 = server_versions.contains(&MatrixVersion::V1_1);
2500    /// println!("The homeserver supports Matrix 1.1: {supports_1_1:?}");
2501    /// # anyhow::Ok(()) };
2502    /// ```
2503    pub async fn server_versions(&self) -> HttpResult<BTreeSet<MatrixVersion>> {
2504        Ok(self.supported_versions().await?.versions)
2505    }
2506
2507    /// Get the unstable features supported by the homeserver by fetching them
2508    /// from the server or the cache.
2509    ///
2510    /// # Examples
2511    ///
2512    /// ```no_run
2513    /// use matrix_sdk::ruma::api::FeatureFlag;
2514    /// # use matrix_sdk::{Client, config::SyncSettings};
2515    /// # use url::Url;
2516    /// # async {
2517    /// # let homeserver = Url::parse("http://localhost:8080")?;
2518    /// # let mut client = Client::new(homeserver).await?;
2519    ///
2520    /// let msc_x_feature = FeatureFlag::from("msc_x");
2521    /// let unstable_features = client.unstable_features().await?;
2522    /// let supports_msc_x = unstable_features.contains(&msc_x_feature);
2523    /// println!("The homeserver supports msc X: {supports_msc_x:?}");
2524    /// # anyhow::Ok(()) };
2525    /// ```
2526    pub async fn unstable_features(&self) -> HttpResult<BTreeSet<FeatureFlag>> {
2527        Ok(self.supported_versions().await?.features)
2528    }
2529
2530    /// Empty the supported versions and unstable features cache.
2531    ///
2532    /// Since the SDK caches the supported versions, it's possible to have a
2533    /// stale entry in the cache. This functions makes it possible to force
2534    /// reset it.
2535    pub async fn reset_supported_versions(&self) -> Result<()> {
2536        // Empty the in-memory cache.
2537        self.inner.caches.supported_versions.reset();
2538
2539        // Empty the store cache.
2540        Ok(self.state_store().remove_kv_data(StateStoreDataKey::SupportedVersions).await?)
2541    }
2542
2543    /// Get the well-known file of the homeserver from the cache.
2544    ///
2545    /// If the data in the cache has expired, this will trigger a background
2546    /// task to refresh it.
2547    async fn well_known_cached(
2548        &self,
2549    ) -> Result<CachedValue<Option<WellKnownResponse>>, StoreError> {
2550        let well_known_cache = &self.inner.caches.well_known;
2551
2552        let value = if let CachedValue::Cached(cached) = well_known_cache.value() {
2553            cached
2554        } else if let Some(stored) = self
2555            .state_store()
2556            .get_kv_data(StateStoreDataKey::WellKnown)
2557            .await?
2558            .and_then(|value| value.into_well_known())
2559        {
2560            // Copy the data from the store into the in-memory cache.
2561            well_known_cache.set_value(stored.clone());
2562
2563            stored
2564        } else {
2565            return Ok(CachedValue::NotSet);
2566        };
2567
2568        // Spawn a task to refresh the cache if it has expired.
2569        if value.has_expired() {
2570            debug!("spawning task to refresh well-known cache");
2571
2572            let client = self.clone();
2573            self.task_monitor().spawn_finite_task("refresh well-known cache", async move {
2574                client.refresh_well_known_cache().await;
2575            });
2576        }
2577
2578        Ok(CachedValue::Cached(value.into_data()))
2579    }
2580
2581    /// Refresh the well-known file of the homeserver in the cache.
2582    async fn refresh_well_known_cache(&self) -> Option<WellKnownResponse> {
2583        let well_known_cache = &self.inner.caches.well_known;
2584
2585        let _well_known_guard = match well_known_cache.refresh_lock.try_lock() {
2586            Ok(guard) => guard,
2587            Err(_) => {
2588                // There is already a refresh in progress, wait for it to finish.
2589                let guard = well_known_cache.refresh_lock.lock().await;
2590
2591                // A refresh can't fail because we ignore failures, so there shouldn't be an
2592                // error in the refresh lock.
2593
2594                // Reuse the data if it was cached and it hasn't expired.
2595                if let CachedValue::Cached(value) = well_known_cache.value()
2596                    && !value.has_expired()
2597                {
2598                    return value.into_data();
2599                }
2600
2601                // The data wasn't cached or has expired, we need to make another request.
2602                guard
2603            }
2604        };
2605
2606        let well_known = TtlValue::new(self.fetch_client_well_known().await.map(Into::into));
2607
2608        if let Err(err) = self
2609            .state_store()
2610            .set_kv_data(
2611                StateStoreDataKey::WellKnown,
2612                StateStoreDataValue::WellKnown(well_known.clone()),
2613            )
2614            .await
2615        {
2616            warn!("error when caching well-known: {err}");
2617        }
2618
2619        well_known_cache.set_value(well_known.clone());
2620
2621        well_known.into_data()
2622    }
2623
2624    /// Get the well-known file of the homeserver by fetching it from the server
2625    /// or the cache.
2626    async fn well_known(&self) -> Option<WellKnownResponse> {
2627        match self.well_known_cached().await {
2628            Ok(CachedValue::Cached(value)) => {
2629                return value;
2630            }
2631            Ok(CachedValue::NotSet) => {
2632                // The cache is empty, make a request.
2633            }
2634            Err(error) => {
2635                warn!("error when loading cached well-known: {error}");
2636                // Fallthrough to make a request.
2637            }
2638        }
2639
2640        self.refresh_well_known_cache().await
2641    }
2642
2643    /// Get information about the homeserver's advertised RTC foci by fetching
2644    /// the well-known file from the server or the cache.
2645    ///
2646    /// # Examples
2647    /// ```no_run
2648    /// # use matrix_sdk::{Client, config::SyncSettings, ruma::api::client::rtc::RtcTransport};
2649    /// # use url::Url;
2650    /// # async {
2651    /// # let homeserver = Url::parse("http://localhost:8080")?;
2652    /// # let mut client = Client::new(homeserver).await?;
2653    /// let rtc_foci = client.rtc_foci().await?;
2654    /// let default_livekit_focus_info = rtc_foci.iter().find_map(|focus| match focus {
2655    ///     RtcTransport::LiveKit(info) => Some(info),
2656    ///     _ => None,
2657    /// });
2658    /// if let Some(info) = default_livekit_focus_info {
2659    ///     println!("Default LiveKit service URL: {}", info.service_url);
2660    /// }
2661    /// # anyhow::Ok(()) };
2662    /// ```
2663    pub async fn rtc_foci(&self) -> HttpResult<Vec<RtcTransport>> {
2664        let well_known = self.well_known().await;
2665
2666        Ok(well_known.map(|well_known| well_known.rtc_foci).unwrap_or_default())
2667    }
2668
2669    /// Get information about the homeserver's advertised map tile server, if
2670    /// any, by fetching the well-known file from the server or the cache.
2671    ///
2672    /// Returns `None` if the homeserver has not advertised a tile server in its
2673    /// well-known, or if the well-known is otherwise unavailable.
2674    pub async fn tile_server(&self) -> Option<TileServerInfo> {
2675        self.well_known().await.and_then(|well_known| well_known.tile_server).map(Into::into)
2676    }
2677
2678    /// Empty the well-known cache.
2679    ///
2680    /// Since the SDK caches the well-known, it's possible to have a stale entry
2681    /// in the cache. This functions makes it possible to force reset it.
2682    pub async fn reset_well_known(&self) -> Result<()> {
2683        // Empty the in-memory caches.
2684        self.inner.caches.well_known.reset();
2685
2686        // Empty the store cache.
2687        Ok(self.state_store().remove_kv_data(StateStoreDataKey::WellKnown).await?)
2688    }
2689
2690    /// Check whether MSC 4028 is enabled on the homeserver.
2691    ///
2692    /// # Examples
2693    ///
2694    /// ```no_run
2695    /// # use matrix_sdk::{Client, config::SyncSettings};
2696    /// # use url::Url;
2697    /// # async {
2698    /// # let homeserver = Url::parse("http://localhost:8080")?;
2699    /// # let mut client = Client::new(homeserver).await?;
2700    /// let msc4028_enabled =
2701    ///     client.can_homeserver_push_encrypted_event_to_device().await?;
2702    /// # anyhow::Ok(()) };
2703    /// ```
2704    pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> HttpResult<bool> {
2705        Ok(self.unstable_features().await?.contains(&FeatureFlag::from("org.matrix.msc4028")))
2706    }
2707
2708    /// Get information of all our own devices.
2709    ///
2710    /// # Examples
2711    ///
2712    /// ```no_run
2713    /// # use matrix_sdk::{Client, config::SyncSettings};
2714    /// # use url::Url;
2715    /// # async {
2716    /// # let homeserver = Url::parse("http://localhost:8080")?;
2717    /// # let mut client = Client::new(homeserver).await?;
2718    /// let response = client.devices().await?;
2719    ///
2720    /// for device in response.devices {
2721    ///     println!(
2722    ///         "Device: {} {}",
2723    ///         device.device_id,
2724    ///         device.display_name.as_deref().unwrap_or("")
2725    ///     );
2726    /// }
2727    /// # anyhow::Ok(()) };
2728    /// ```
2729    pub async fn devices(&self) -> HttpResult<get_devices::v3::Response> {
2730        let request = get_devices::v3::Request::new();
2731
2732        self.send(request).await
2733    }
2734
2735    /// Delete the given devices from the server.
2736    ///
2737    /// # Arguments
2738    ///
2739    /// * `devices` - The list of devices that should be deleted from the
2740    ///   server.
2741    ///
2742    /// * `auth_data` - This request requires user interactive auth, the first
2743    ///   request needs to set this to `None` and will always fail with an
2744    ///   `UiaaResponse`. The response will contain information for the
2745    ///   interactive auth and the same request needs to be made but this time
2746    ///   with some `auth_data` provided.
2747    ///
2748    /// ```no_run
2749    /// # use matrix_sdk::{
2750    /// #    ruma::{api::client::uiaa, owned_device_id},
2751    /// #    Client, Error, config::SyncSettings,
2752    /// # };
2753    /// # use serde_json::json;
2754    /// # use url::Url;
2755    /// # use std::collections::BTreeMap;
2756    /// # async {
2757    /// # let homeserver = Url::parse("http://localhost:8080")?;
2758    /// # let mut client = Client::new(homeserver).await?;
2759    /// let devices = &[owned_device_id!("DEVICEID")];
2760    ///
2761    /// if let Err(e) = client.delete_devices(devices, None).await {
2762    ///     if let Some(info) = e.as_uiaa_response() {
2763    ///         let mut password = uiaa::Password::new(
2764    ///             uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier::new("example".to_owned())),
2765    ///             "wordpass".to_owned(),
2766    ///         );
2767    ///         password.session = info.session.clone();
2768    ///
2769    ///         client
2770    ///             .delete_devices(devices, Some(uiaa::AuthData::Password(password)))
2771    ///             .await?;
2772    ///     }
2773    /// }
2774    /// # anyhow::Ok(()) };
2775    pub async fn delete_devices(
2776        &self,
2777        devices: &[OwnedDeviceId],
2778        auth_data: Option<uiaa::AuthData>,
2779    ) -> HttpResult<delete_devices::v3::Response> {
2780        let mut request = delete_devices::v3::Request::new(devices.to_owned());
2781        request.auth = auth_data;
2782
2783        self.send(request).await
2784    }
2785
2786    /// Change the display name of a device owned by the current user.
2787    ///
2788    /// Returns a `update_device::Response` which specifies the result
2789    /// of the operation.
2790    ///
2791    /// # Arguments
2792    ///
2793    /// * `device_id` - The ID of the device to change the display name of.
2794    /// * `display_name` - The new display name to set.
2795    pub async fn rename_device(
2796        &self,
2797        device_id: &DeviceId,
2798        display_name: &str,
2799    ) -> HttpResult<update_device::v3::Response> {
2800        let mut request = update_device::v3::Request::new(device_id.to_owned());
2801        request.display_name = Some(display_name.to_owned());
2802
2803        self.send(request).await
2804    }
2805
2806    /// Check whether a device with a specific ID exists on the server.
2807    ///
2808    /// Returns Ok(true) if the device exists, Ok(false) if the server responded
2809    /// with 404 and the underlying error otherwise.
2810    ///
2811    /// # Arguments
2812    ///
2813    /// * `device_id` - The ID of the device to query.
2814    pub async fn device_exists(&self, device_id: OwnedDeviceId) -> Result<bool> {
2815        let request = device::get_device::v3::Request::new(device_id);
2816        match self.send(request).await {
2817            Ok(_) => Ok(true),
2818            Err(err) => {
2819                if let Some(error) = err.as_client_api_error()
2820                    && error.status_code == 404
2821                {
2822                    Ok(false)
2823                } else {
2824                    Err(err.into())
2825                }
2826            }
2827        }
2828    }
2829
2830    /// Synchronize the client's state with the latest state on the server.
2831    ///
2832    /// ## Syncing Events
2833    ///
2834    /// Messages or any other type of event need to be periodically fetched from
2835    /// the server, this is achieved by sending a `/sync` request to the server.
2836    ///
2837    /// The first sync is sent out without a [`token`]. The response of the
2838    /// first sync will contain a [`next_batch`] field which should then be
2839    /// used in the subsequent sync calls as the [`token`]. This ensures that we
2840    /// don't receive the same events multiple times.
2841    ///
2842    /// ## Long Polling
2843    ///
2844    /// A sync should in the usual case always be in flight. The
2845    /// [`SyncSettings`] have a  [`timeout`] option, which controls how
2846    /// long the server will wait for new events before it will respond.
2847    /// The server will respond immediately if some new events arrive before the
2848    /// timeout has expired. If no changes arrive and the timeout expires an
2849    /// empty sync response will be sent to the client.
2850    ///
2851    /// This method of sending a request that may not receive a response
2852    /// immediately is called long polling.
2853    ///
2854    /// ## Filtering Events
2855    ///
2856    /// The number or type of messages and events that the client should receive
2857    /// from the server can be altered using a [`Filter`].
2858    ///
2859    /// Filters can be non-trivial and, since they will be sent with every sync
2860    /// request, they may take up a bunch of unnecessary bandwidth.
2861    ///
2862    /// Luckily filters can be uploaded to the server and reused using an unique
2863    /// identifier, this can be achieved using the [`get_or_upload_filter()`]
2864    /// method.
2865    ///
2866    /// # Arguments
2867    ///
2868    /// * `sync_settings` - Settings for the sync call, this allows us to set
2869    /// various options to configure the sync:
2870    ///     * [`filter`] - To configure which events we receive and which get
2871    ///       [filtered] by the server
2872    ///     * [`timeout`] - To configure our [long polling] setup.
2873    ///     * [`token`] - To tell the server which events we already received
2874    ///       and where we wish to continue syncing.
2875    ///     * [`full_state`] - To tell the server that we wish to receive all
2876    ///       state events, regardless of our configured [`token`].
2877    ///     * [`set_presence`] - To override the presence state sent with this
2878    ///       classic `/sync` request. If this is not set, the request uses the
2879    ///       client-owned sync presence configured with
2880    ///       [`Client::set_presence`], which defaults to
2881    ///       [`PresenceState::Online`].
2882    ///
2883    /// # Examples
2884    ///
2885    /// ```no_run
2886    /// # use url::Url;
2887    /// # async {
2888    /// # let homeserver = Url::parse("http://localhost:8080")?;
2889    /// # let username = "";
2890    /// # let password = "";
2891    /// use matrix_sdk::{
2892    ///     Client, config::SyncSettings,
2893    ///     ruma::events::room::message::OriginalSyncRoomMessageEvent,
2894    /// };
2895    ///
2896    /// let client = Client::new(homeserver).await?;
2897    /// client.matrix_auth().login_username(username, password).send().await?;
2898    ///
2899    /// // Sync once so we receive the client state and old messages.
2900    /// client.sync_once(SyncSettings::default()).await?;
2901    ///
2902    /// // Register our handler so we start responding once we receive a new
2903    /// // event.
2904    /// client.add_event_handler(|ev: OriginalSyncRoomMessageEvent| async move {
2905    ///     println!("Received event {}: {:?}", ev.sender, ev.content);
2906    /// });
2907    ///
2908    /// // Now keep on syncing forever. `sync()` will use the stored sync token
2909    /// // from our `sync_once()` call automatically.
2910    /// client.sync(SyncSettings::default()).await;
2911    /// # anyhow::Ok(()) };
2912    /// ```
2913    ///
2914    /// [`sync`]: #method.sync
2915    /// [`SyncSettings`]: crate::config::SyncSettings
2916    /// [`token`]: crate::config::SyncSettings#method.token
2917    /// [`timeout`]: crate::config::SyncSettings#method.timeout
2918    /// [`full_state`]: crate::config::SyncSettings#method.full_state
2919    /// [`set_presence`]: crate::config::SyncSettings::set_presence
2920    /// [`filter`]: crate::config::SyncSettings#method.filter
2921    /// [`Filter`]: ruma::api::client::sync::sync_events::v3::Filter
2922    /// [`next_batch`]: SyncResponse#structfield.next_batch
2923    /// [`get_or_upload_filter()`]: #method.get_or_upload_filter
2924    /// [long polling]: #long-polling
2925    /// [filtered]: #filtering-events
2926    #[instrument(skip(self))]
2927    pub async fn sync_once(
2928        &self,
2929        sync_settings: crate::config::SyncSettings,
2930    ) -> Result<SyncResponse> {
2931        // The sync might not return for quite a while due to the timeout.
2932        // We'll see if there's anything crypto related to send out before we
2933        // sync, i.e. if we closed our client after a sync but before the
2934        // crypto requests were sent out.
2935        //
2936        // This will mostly be a no-op.
2937        #[cfg(feature = "e2e-encryption")]
2938        if let Err(e) = self.send_outgoing_requests().await {
2939            error!(error = ?e, "Error while sending outgoing E2EE requests");
2940        }
2941
2942        let token = match sync_settings.token {
2943            SyncToken::Specific(token) => Some(token),
2944            SyncToken::NoToken => None,
2945            SyncToken::ReusePrevious => self.sync_token().await,
2946        };
2947
2948        let request = assign!(sync_events::v3::Request::new(), {
2949            filter: sync_settings.filter.map(|f| *f),
2950            since: token,
2951            full_state: sync_settings.full_state,
2952            set_presence: sync_settings.set_presence.unwrap_or_else(|| self.sync_presence()),
2953            timeout: sync_settings.timeout,
2954            use_state_after: true,
2955        });
2956        let mut request_config = self.request_config();
2957        if let Some(timeout) = sync_settings.timeout {
2958            let base_timeout = request_config.timeout.unwrap_or(Duration::from_secs(30));
2959            request_config.timeout = Some(base_timeout + timeout);
2960        }
2961
2962        let response = self.send(request).with_request_config(request_config).await?;
2963        let next_batch = response.next_batch.clone();
2964        let response = self.process_sync(response).await?;
2965
2966        #[cfg(feature = "e2e-encryption")]
2967        if let Err(e) = self.send_outgoing_requests().await {
2968            error!(error = ?e, "Error while sending outgoing E2EE requests");
2969        }
2970
2971        self.inner.sync_beat.notify(usize::MAX);
2972
2973        Ok(SyncResponse::new(next_batch, response))
2974    }
2975
2976    /// Repeatedly synchronize the client state with the server.
2977    ///
2978    /// This method will only return on error, if cancellation is needed
2979    /// the method should be wrapped in a cancelable task or the
2980    /// [`Client::sync_with_callback`] method can be used or
2981    /// [`Client::sync_with_result_callback`] if you want to handle error
2982    /// cases in the loop, too.
2983    ///
2984    /// This method will internally call [`Client::sync_once`] in a loop.
2985    ///
2986    /// This method can be used with the [`Client::add_event_handler`]
2987    /// method to react to individual events. If you instead wish to handle
2988    /// events in a bulk manner the [`Client::sync_with_callback`],
2989    /// [`Client::sync_with_result_callback`] and
2990    /// [`Client::sync_stream`] methods can be used instead. Those methods
2991    /// repeatedly return the whole sync response.
2992    ///
2993    /// # Arguments
2994    ///
2995    /// * `sync_settings` - Settings for the sync call. *Note* that those
2996    ///   settings will be only used for the first sync call. See the argument
2997    ///   docs for [`Client::sync_once`] for more info.
2998    ///
2999    /// # Return
3000    /// The sync runs until an error occurs, returning with `Err(Error)`. It is
3001    /// up to the user of the API to check the error and decide whether the sync
3002    /// should continue or not.
3003    ///
3004    /// # Examples
3005    ///
3006    /// ```no_run
3007    /// # use url::Url;
3008    /// # async {
3009    /// # let homeserver = Url::parse("http://localhost:8080")?;
3010    /// # let username = "";
3011    /// # let password = "";
3012    /// use matrix_sdk::{
3013    ///     Client, config::SyncSettings,
3014    ///     ruma::events::room::message::OriginalSyncRoomMessageEvent,
3015    /// };
3016    ///
3017    /// let client = Client::new(homeserver).await?;
3018    /// client.matrix_auth().login_username(&username, &password).send().await?;
3019    ///
3020    /// // Register our handler so we start responding once we receive a new
3021    /// // event.
3022    /// client.add_event_handler(|ev: OriginalSyncRoomMessageEvent| async move {
3023    ///     println!("Received event {}: {:?}", ev.sender, ev.content);
3024    /// });
3025    ///
3026    /// // Now keep on syncing forever. `sync()` will use the latest sync token
3027    /// // automatically.
3028    /// client.sync(SyncSettings::default()).await?;
3029    /// # anyhow::Ok(()) };
3030    /// ```
3031    ///
3032    /// [argument docs]: #method.sync_once
3033    /// [`sync_with_callback`]: #method.sync_with_callback
3034    pub async fn sync(&self, sync_settings: crate::config::SyncSettings) -> Result<(), Error> {
3035        self.sync_with_callback(sync_settings, |_| async { LoopCtrl::Continue }).await
3036    }
3037
3038    /// Repeatedly call sync to synchronize the client state with the server.
3039    ///
3040    /// # Arguments
3041    ///
3042    /// * `sync_settings` - Settings for the sync call. *Note* that those
3043    ///   settings will be only used for the first sync call. See the argument
3044    ///   docs for [`Client::sync_once`] for more info.
3045    ///
3046    /// * `callback` - A callback that will be called every time a successful
3047    ///   response has been fetched from the server. The callback must return a
3048    ///   boolean which signalizes if the method should stop syncing. If the
3049    ///   callback returns `LoopCtrl::Continue` the sync will continue, if the
3050    ///   callback returns `LoopCtrl::Break` the sync will be stopped.
3051    ///
3052    /// # Return
3053    /// The sync runs until an error occurs or the
3054    /// callback indicates that the Loop should stop. If the callback asked for
3055    /// a regular stop, the result will be `Ok(())` otherwise the
3056    /// `Err(Error)` is returned.
3057    ///
3058    /// # Examples
3059    ///
3060    /// The following example demonstrates how to sync forever while sending all
3061    /// the interesting events through a mpsc channel to another thread e.g. a
3062    /// UI thread.
3063    ///
3064    /// ```no_run
3065    /// # use std::time::Duration;
3066    /// # use matrix_sdk::{Client, config::SyncSettings, LoopCtrl};
3067    /// # use url::Url;
3068    /// # async {
3069    /// # let homeserver = Url::parse("http://localhost:8080").unwrap();
3070    /// # let mut client = Client::new(homeserver).await.unwrap();
3071    ///
3072    /// use tokio::sync::mpsc::channel;
3073    ///
3074    /// let (tx, rx) = channel(100);
3075    ///
3076    /// let sync_channel = &tx;
3077    /// let sync_settings = SyncSettings::new()
3078    ///     .timeout(Duration::from_secs(30));
3079    ///
3080    /// client
3081    ///     .sync_with_callback(sync_settings, |response| async move {
3082    ///         let channel = sync_channel;
3083    ///         for (room_id, room) in response.rooms.joined {
3084    ///             for event in room.timeline.events {
3085    ///                 channel.send(event).await.unwrap();
3086    ///             }
3087    ///         }
3088    ///
3089    ///         LoopCtrl::Continue
3090    ///     })
3091    ///     .await;
3092    /// };
3093    /// ```
3094    #[instrument(skip_all)]
3095    pub async fn sync_with_callback<C>(
3096        &self,
3097        sync_settings: crate::config::SyncSettings,
3098        callback: impl Fn(SyncResponse) -> C,
3099    ) -> Result<(), Error>
3100    where
3101        C: Future<Output = LoopCtrl>,
3102    {
3103        self.sync_with_result_callback(sync_settings, |result| async {
3104            Ok(callback(result?).await)
3105        })
3106        .await
3107    }
3108
3109    /// Repeatedly call sync to synchronize the client state with the server.
3110    ///
3111    /// # Arguments
3112    ///
3113    /// * `sync_settings` - Settings for the sync call. *Note* that those
3114    ///   settings will be only used for the first sync call. See the argument
3115    ///   docs for [`Client::sync_once`] for more info.
3116    ///
3117    /// * `callback` - A callback that will be called every time after a
3118    ///   response has been received, failure or not. The callback returns a
3119    ///   `Result<LoopCtrl, Error>`, too. When returning
3120    ///   `Ok(LoopCtrl::Continue)` the sync will continue, if the callback
3121    ///   returns `Ok(LoopCtrl::Break)` the sync will be stopped and the
3122    ///   function returns `Ok(())`. In case the callback can't handle the
3123    ///   `Error` or has a different malfunction, it can return an `Err(Error)`,
3124    ///   which results in the sync ending and the `Err(Error)` being returned.
3125    ///
3126    /// # Return
3127    /// The sync runs until an error occurs that the callback can't handle or
3128    /// the callback indicates that the Loop should stop. If the callback
3129    /// asked for a regular stop, the result will be `Ok(())` otherwise the
3130    /// `Err(Error)` is returned.
3131    ///
3132    /// _Note_: Lower-level configuration (e.g. for retries) are not changed by
3133    /// this, and are handled first without sending the result to the
3134    /// callback. Only after they have exceeded is the `Result` handed to
3135    /// the callback.
3136    ///
3137    /// # Examples
3138    ///
3139    /// The following example demonstrates how to sync forever while sending all
3140    /// the interesting events through a mpsc channel to another thread e.g. a
3141    /// UI thread.
3142    ///
3143    /// ```no_run
3144    /// # use std::time::Duration;
3145    /// # use matrix_sdk::{Client, config::SyncSettings, LoopCtrl};
3146    /// # use url::Url;
3147    /// # async {
3148    /// # let homeserver = Url::parse("http://localhost:8080").unwrap();
3149    /// # let mut client = Client::new(homeserver).await.unwrap();
3150    /// #
3151    /// use tokio::sync::mpsc::channel;
3152    ///
3153    /// let (tx, rx) = channel(100);
3154    ///
3155    /// let sync_channel = &tx;
3156    /// let sync_settings = SyncSettings::new()
3157    ///     .timeout(Duration::from_secs(30));
3158    ///
3159    /// client
3160    ///     .sync_with_result_callback(sync_settings, |response| async move {
3161    ///         let channel = sync_channel;
3162    ///         let sync_response = response?;
3163    ///         for (room_id, room) in sync_response.rooms.joined {
3164    ///              for event in room.timeline.events {
3165    ///                  channel.send(event).await.unwrap();
3166    ///               }
3167    ///         }
3168    ///
3169    ///         Ok(LoopCtrl::Continue)
3170    ///     })
3171    ///     .await;
3172    /// };
3173    /// ```
3174    #[instrument(skip(self, callback))]
3175    pub async fn sync_with_result_callback<C>(
3176        &self,
3177        sync_settings: crate::config::SyncSettings,
3178        callback: impl Fn(Result<SyncResponse, Error>) -> C,
3179    ) -> Result<(), Error>
3180    where
3181        C: Future<Output = Result<LoopCtrl, Error>>,
3182    {
3183        let mut sync_stream = Box::pin(self.sync_stream(sync_settings).await);
3184
3185        while let Some(result) = sync_stream.next().await {
3186            trace!("Running callback");
3187            if callback(result).await? == LoopCtrl::Break {
3188                trace!("Callback told us to stop");
3189                break;
3190            }
3191            trace!("Done running callback");
3192        }
3193
3194        Ok(())
3195    }
3196
3197    //// Repeatedly synchronize the client state with the server.
3198    ///
3199    /// This method will internally call [`Client::sync_once`] in a loop and is
3200    /// equivalent to the [`Client::sync`] method but the responses are provided
3201    /// as an async stream.
3202    ///
3203    /// # Arguments
3204    ///
3205    /// * `sync_settings` - Settings for the sync call. *Note* that those
3206    ///   settings will be only used for the first sync call. See the argument
3207    ///   docs for [`Client::sync_once`] for more info.
3208    ///
3209    /// # Examples
3210    ///
3211    /// ```no_run
3212    /// # use url::Url;
3213    /// # async {
3214    /// # let homeserver = Url::parse("http://localhost:8080")?;
3215    /// # let username = "";
3216    /// # let password = "";
3217    /// use futures_util::StreamExt;
3218    /// use matrix_sdk::{Client, config::SyncSettings};
3219    ///
3220    /// let client = Client::new(homeserver).await?;
3221    /// client.matrix_auth().login_username(&username, &password).send().await?;
3222    ///
3223    /// let mut sync_stream =
3224    ///     Box::pin(client.sync_stream(SyncSettings::default()).await);
3225    ///
3226    /// while let Some(Ok(response)) = sync_stream.next().await {
3227    ///     for room in response.rooms.joined.values() {
3228    ///         for e in &room.timeline.events {
3229    ///             if let Ok(event) = e.raw().deserialize() {
3230    ///                 println!("Received event {:?}", event);
3231    ///             }
3232    ///         }
3233    ///     }
3234    /// }
3235    ///
3236    /// # anyhow::Ok(()) };
3237    /// ```
3238    #[allow(unknown_lints, clippy::let_with_type_underscore)] // triggered by instrument macro
3239    #[instrument(skip(self))]
3240    pub async fn sync_stream(
3241        &self,
3242        mut sync_settings: crate::config::SyncSettings,
3243    ) -> impl Stream<Item = Result<SyncResponse>> + '_ {
3244        let mut is_first_sync = true;
3245        let mut timeout = None;
3246        let mut last_sync_time: Option<Instant> = None;
3247
3248        let parent_span = Span::current();
3249
3250        async_stream::stream!({
3251            loop {
3252                trace!("Syncing");
3253
3254                if sync_settings.ignore_timeout_on_first_sync {
3255                    if is_first_sync {
3256                        timeout = sync_settings.timeout.take();
3257                    } else if sync_settings.timeout.is_none() && timeout.is_some() {
3258                        sync_settings.timeout = timeout.take();
3259                    }
3260
3261                    is_first_sync = false;
3262                }
3263
3264                yield self
3265                    .sync_loop_helper(&mut sync_settings)
3266                    .instrument(parent_span.clone())
3267                    .await;
3268
3269                Client::delay_sync(&mut last_sync_time).await
3270            }
3271        })
3272    }
3273
3274    /// Get the current, if any, sync token of the client.
3275    /// This will be None if the client didn't sync at least once.
3276    pub(crate) async fn sync_token(&self) -> Option<String> {
3277        self.inner.base_client.sync_token().await
3278    }
3279
3280    /// Gets information about the owner of a given access token.
3281    pub async fn whoami(&self) -> HttpResult<whoami::v3::Response> {
3282        let request = whoami::v3::Request::new();
3283        self.send(request).await
3284    }
3285
3286    /// Subscribes a new receiver to client SessionChange broadcasts.
3287    pub fn subscribe_to_session_changes(&self) -> broadcast::Receiver<SessionChange> {
3288        let broadcast = &self.auth_ctx().session_change_sender;
3289        broadcast.subscribe()
3290    }
3291
3292    /// Sets the save/restore session callbacks.
3293    ///
3294    /// This is another mechanism to get synchronous updates to session tokens,
3295    /// while [`Self::subscribe_to_session_changes`] provides an async update.
3296    pub fn set_session_callbacks(
3297        &self,
3298        reload_session_callback: Box<ReloadSessionCallback>,
3299        save_session_callback: Box<SaveSessionCallback>,
3300    ) -> Result<()> {
3301        self.inner
3302            .auth_ctx
3303            .reload_session_callback
3304            .set(reload_session_callback)
3305            .map_err(|_| Error::MultipleSessionCallbacks)?;
3306
3307        self.inner
3308            .auth_ctx
3309            .save_session_callback
3310            .set(save_session_callback)
3311            .map_err(|_| Error::MultipleSessionCallbacks)?;
3312
3313        Ok(())
3314    }
3315
3316    /// Get the notification settings of the current owner of the client.
3317    pub async fn notification_settings(&self) -> NotificationSettings {
3318        let ruleset = self.account().push_rules().await.unwrap_or_else(|_| Ruleset::new());
3319        NotificationSettings::new(self.clone(), ruleset)
3320    }
3321
3322    /// Create a new specialized `Client` that can process notifications.
3323    ///
3324    /// See [`CrossProcessLock::new`] to learn more about
3325    /// `cross_process_lock_config`.
3326    ///
3327    /// [`CrossProcessLock::new`]: matrix_sdk_common::cross_process_lock::CrossProcessLock::new
3328    pub async fn notification_client(
3329        &self,
3330        cross_process_lock_config: CrossProcessLockConfig,
3331    ) -> Result<Client> {
3332        let client = Client {
3333            inner: ClientInner::new(
3334                self.inner.auth_ctx.clone(),
3335                self.server().cloned(),
3336                self.homeserver(),
3337                self.sliding_sync_version(),
3338                self.inner.sync_presence.clone(),
3339                self.inner.http_client.clone(),
3340                self.inner
3341                    .base_client
3342                    .clone_with_in_memory_state_store(cross_process_lock_config.clone(), false)
3343                    .await?,
3344                self.inner.caches.supported_versions.value(),
3345                self.inner.caches.well_known.value(),
3346                self.inner.respect_login_well_known,
3347                self.inner.event_cache.clone(),
3348                self.inner.send_queue_data.clone(),
3349                self.inner.latest_events.clone(),
3350                #[cfg(feature = "e2e-encryption")]
3351                self.inner.e2ee.encryption_settings,
3352                #[cfg(feature = "e2e-encryption")]
3353                self.inner.enable_share_history_on_invite,
3354                cross_process_lock_config,
3355                #[cfg(feature = "experimental-search")]
3356                self.inner.search_index.clone(),
3357                self.inner.thread_subscription_catchup.clone(),
3358                (*self.inner.media_fetcher.read().await).clone(),
3359            )
3360            .await,
3361        };
3362
3363        Ok(client)
3364    }
3365
3366    /// The [`EventCache`] instance for this [`Client`].
3367    pub fn event_cache(&self) -> &EventCache {
3368        // SAFETY: always initialized in the `Client` ctor.
3369        self.inner.event_cache.get().unwrap()
3370    }
3371
3372    /// The [`LatestEvents`] instance for this [`Client`].
3373    pub async fn latest_events(&self) -> &LatestEvents {
3374        self.inner
3375            .latest_events
3376            .get_or_init(|| async {
3377                LatestEvents::new(
3378                    WeakClient::from_client(self),
3379                    self.event_cache().clone(),
3380                    SendQueue::new(self.clone()),
3381                    self.room_info_notable_update_receiver(),
3382                )
3383            })
3384            .await
3385    }
3386
3387    /// Waits until an at least partially synced room is received, and returns
3388    /// it.
3389    ///
3390    /// **Note: this function will loop endlessly until either it finds the room
3391    /// or an externally set timeout happens.**
3392    pub async fn await_room_remote_echo(&self, room_id: &RoomId) -> Room {
3393        loop {
3394            if let Some(room) = self.get_room(room_id) {
3395                if room.is_state_partially_or_fully_synced() {
3396                    debug!("Found just created room!");
3397                    return room;
3398                }
3399                debug!("Room wasn't partially synced, waiting for sync beat to try again");
3400            } else {
3401                debug!("Room wasn't found, waiting for sync beat to try again");
3402            }
3403            self.inner.sync_beat.listen().await;
3404        }
3405    }
3406
3407    /// Knock on a room given its `room_id_or_alias` to ask for permission to
3408    /// join it.
3409    pub async fn knock(
3410        &self,
3411        room_id_or_alias: OwnedRoomOrAliasId,
3412        reason: Option<String>,
3413        server_names: Vec<OwnedServerName>,
3414    ) -> Result<Room> {
3415        let request =
3416            assign!(knock_room::v3::Request::new(room_id_or_alias), { reason, via: server_names });
3417        let response = self.send(request).await?;
3418        let base_room = self.inner.base_client.room_knocked(&response.room_id).await?;
3419        Ok(Room::new(self.clone(), base_room))
3420    }
3421
3422    /// Checks whether the provided `user_id` belongs to an ignored user.
3423    pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
3424        self.base_client().is_user_ignored(user_id).await
3425    }
3426
3427    /// Gets the `max_upload_size` value from the homeserver, getting either a
3428    /// cached value or with a `/_matrix/client/v1/media/config` request if it's
3429    /// missing.
3430    ///
3431    /// Check the spec for more info:
3432    /// <https://spec.matrix.org/v1.14/client-server-api/#get_matrixclientv1mediaconfig>
3433    pub async fn load_or_fetch_max_upload_size(&self) -> Result<UInt> {
3434        let max_upload_size_lock = self.inner.server_max_upload_size.lock().await;
3435        if let Some(data) = max_upload_size_lock.get() {
3436            return Ok(data.to_owned());
3437        }
3438
3439        // Use the authenticated endpoint when the server supports it.
3440        let supported_versions = self.supported_versions().await?;
3441        let use_auth = authenticated_media::get_media_config::v1::Request::PATH_BUILDER
3442            .is_supported(&supported_versions);
3443
3444        let upload_size = if use_auth {
3445            self.send(authenticated_media::get_media_config::v1::Request::default())
3446                .await?
3447                .upload_size
3448        } else {
3449            #[allow(deprecated)]
3450            self.send(media::get_media_config::v3::Request::default()).await?.upload_size
3451        };
3452
3453        match max_upload_size_lock.set(upload_size) {
3454            Ok(_) => Ok(upload_size),
3455            Err(error) => {
3456                Err(Error::Media(MediaError::FetchMaxUploadSizeFailed(error.to_string())))
3457            }
3458        }
3459    }
3460
3461    /// The settings to use for decrypting events.
3462    #[cfg(feature = "e2e-encryption")]
3463    pub fn decryption_settings(&self) -> &DecryptionSettings {
3464        &self.base_client().decryption_settings
3465    }
3466
3467    /// Returns the [`SearchIndex`] for this [`Client`].
3468    #[cfg(feature = "experimental-search")]
3469    pub fn search_index(&self) -> &SearchIndex {
3470        &self.inner.search_index
3471    }
3472
3473    /// Whether the client is configured to take thread subscriptions (MSC4306
3474    /// and MSC4308) into account, and the server enabled the experimental
3475    /// feature flag for it.
3476    ///
3477    /// This may cause filtering out of thread subscriptions, and loading the
3478    /// thread subscriptions via the sliding sync extension, when the room
3479    /// list service is being used.
3480    ///
3481    /// This is async and fallible as it may use the network to retrieve the
3482    /// server supported features, if they aren't cached already.
3483    pub async fn enabled_thread_subscriptions(&self) -> Result<bool> {
3484        // Check if the client is configured to support thread subscriptions first.
3485        match self.base_client().threading_support {
3486            ThreadingSupport::Enabled { with_subscriptions: false }
3487            | ThreadingSupport::Disabled => return Ok(false),
3488            ThreadingSupport::Enabled { with_subscriptions: true } => {}
3489        }
3490
3491        // Now, let's check that the server supports it.
3492        let server_enabled = self
3493            .supported_versions()
3494            .await?
3495            .features
3496            .contains(&FeatureFlag::from("org.matrix.msc4306"));
3497
3498        Ok(server_enabled)
3499    }
3500
3501    /// Fetch thread subscriptions changes between `from` and up to `to`.
3502    ///
3503    /// The `limit` optional parameter can be used to limit the number of
3504    /// entries in a response. It can also be overridden by the server, if
3505    /// it's deemed too large.
3506    pub async fn fetch_thread_subscriptions(
3507        &self,
3508        from: Option<String>,
3509        to: Option<String>,
3510        limit: Option<UInt>,
3511    ) -> Result<get_thread_subscriptions_changes::unstable::Response> {
3512        let request = assign!(get_thread_subscriptions_changes::unstable::Request::new(), {
3513            from,
3514            to,
3515            limit,
3516        });
3517        Ok(self.send(request).await?)
3518    }
3519
3520    pub(crate) fn thread_subscription_catchup(&self) -> &ThreadSubscriptionCatchup {
3521        self.inner.thread_subscription_catchup.get().unwrap()
3522    }
3523
3524    /// Pause the client for background suspension.
3525    ///
3526    /// This method:
3527    /// 1. Disables all send queues (prevents new message sends).
3528    /// 2. Pauses all database stores, waiting for in-flight operations and
3529    ///    releasing all connections and file locks.
3530    ///
3531    /// Call [`Client::resume()`] when the app returns to the foreground.
3532    ///
3533    /// # iOS
3534    ///
3535    /// Call this before the app is suspended to avoid `0xdead10cc` kills.
3536    /// Typically called from
3537    /// [`applicationDidEnterBackground`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/applicationdidenterbackground(_:))
3538    /// or an equivalent SwiftUI lifecycle event, *after* stopping the
3539    /// `matrix_sdk_ui::sync_service::SyncService`.
3540    pub async fn pause(&self) -> Result<()> {
3541        info!("Client::pause — releasing database resources");
3542
3543        // Disable send queues so no new sends hit the stores.
3544        self.send_queue().set_enabled(false).await;
3545
3546        // Close all stores (waits for in-flight ops, closes connections).
3547        self.base_client().close_stores().await?;
3548
3549        info!("Client::pause — complete, all database connections released");
3550        Ok(())
3551    }
3552
3553    /// Resume the client after a [`Client::pause()`].
3554    ///
3555    /// Re-acquires store resources and re-enables send queues.
3556    ///
3557    /// If your app stopped the `matrix_sdk_ui::sync_service::SyncService`
3558    /// before pausing, restart it separately as appropriate for your app
3559    /// lifecycle.
3560    pub async fn resume(&self) -> Result<()> {
3561        info!("Client::resume — re-acquiring database resources");
3562
3563        // Reopen stores (creates new connection pools).
3564        self.base_client().reopen_stores().await?;
3565
3566        // Re-enable send queues.
3567        self.send_queue().set_enabled(true).await;
3568
3569        info!("Client::resume — complete");
3570        Ok(())
3571    }
3572
3573    /// Perform database optimizations if any are available, i.e. vacuuming in
3574    /// SQLite.
3575    ///
3576    /// **Warning:** this was added to check if SQLite fragmentation was the
3577    /// source of performance issues, **DO NOT use in production**.
3578    #[doc(hidden)]
3579    pub async fn optimize_stores(&self) -> Result<()> {
3580        trace!("Optimizing state store...");
3581        self.state_store().optimize().await?;
3582
3583        trace!("Optimizing event cache store...");
3584        if let Some(clean_lock) = self.event_cache_store().lock().await?.as_clean() {
3585            clean_lock.optimize().await?;
3586        }
3587
3588        trace!("Optimizing media store...");
3589        self.media_store().lock().await?.optimize().await?;
3590
3591        Ok(())
3592    }
3593
3594    /// Returns the sizes of the existing stores, if known.
3595    pub async fn get_store_sizes(&self) -> Result<StoreSizes> {
3596        #[cfg(feature = "e2e-encryption")]
3597        let crypto_store_size = if let Some(olm_machine) = self.olm_machine().await.as_ref()
3598            && let Ok(Some(store_size)) = olm_machine.store().get_size().await
3599        {
3600            Some(store_size)
3601        } else {
3602            None
3603        };
3604        #[cfg(not(feature = "e2e-encryption"))]
3605        let crypto_store_size = None;
3606
3607        let state_store_size = self.state_store().get_size().await.ok().flatten();
3608
3609        let event_cache_store_size = if let Some(clean_lock) =
3610            self.event_cache_store().lock().await?.as_clean()
3611            && let Ok(Some(store_size)) = clean_lock.get_size().await
3612        {
3613            Some(store_size)
3614        } else {
3615            None
3616        };
3617
3618        let media_store_size = self.media_store().lock().await?.get_size().await.ok().flatten();
3619
3620        Ok(StoreSizes {
3621            crypto_store: crypto_store_size,
3622            state_store: state_store_size,
3623            event_cache_store: event_cache_store_size,
3624            media_store: media_store_size,
3625        })
3626    }
3627
3628    /// Get a reference to the client's task monitor, for spawning background
3629    /// tasks.
3630    pub fn task_monitor(&self) -> &TaskMonitor {
3631        &self.inner.task_monitor
3632    }
3633
3634    /// Add a subscriber for duplicate key upload error notifications triggered
3635    /// by requests to /keys/upload.
3636    #[cfg(feature = "e2e-encryption")]
3637    pub fn subscribe_to_duplicate_key_upload_errors(
3638        &self,
3639    ) -> broadcast::Receiver<Option<DuplicateOneTimeKeyErrorMessage>> {
3640        self.inner.duplicate_key_upload_error_sender.subscribe()
3641    }
3642
3643    /// Check the record of whether we are waiting for an [MSC4268] key bundle
3644    /// for the given room.
3645    ///
3646    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
3647    #[cfg(feature = "e2e-encryption")]
3648    pub async fn get_pending_key_bundle_details_for_room(
3649        &self,
3650        room_id: &RoomId,
3651    ) -> Result<Option<RoomPendingKeyBundleDetails>> {
3652        Ok(self.base_client().get_pending_key_bundle_details_for_room(room_id).await?)
3653    }
3654
3655    /// Returns the [`DmRoomDefinition`] this client uses to check if a room is
3656    /// a DM.
3657    pub fn dm_room_definition(&self) -> &DmRoomDefinition {
3658        &self.inner.base_client.dm_room_definition
3659    }
3660
3661    /// Replaces the [`MediaFetcher`] used to download media from the media
3662    /// server with the provided one.
3663    pub async fn set_media_fetcher(&self, media_fetcher: Arc<dyn MediaFetcher>) {
3664        *self.inner.media_fetcher.write().await = media_fetcher;
3665    }
3666
3667    /// Returns the currently used [`MediaFetcher`] used to download media from
3668    /// the media server.
3669    pub async fn get_media_fetcher(&self) -> Arc<dyn MediaFetcher> {
3670        self.inner.media_fetcher.read().await.clone()
3671    }
3672}
3673
3674/// Contains the disk size of the different stores, if known. It won't be
3675/// available for in-memory stores.
3676#[derive(Debug, Clone)]
3677pub struct StoreSizes {
3678    /// The size of the CryptoStore.
3679    pub crypto_store: Option<usize>,
3680    /// The size of the StateStore.
3681    pub state_store: Option<usize>,
3682    /// The size of the EventCacheStore.
3683    pub event_cache_store: Option<usize>,
3684    /// The size of the MediaStore.
3685    pub media_store: Option<usize>,
3686}
3687
3688#[cfg(any(feature = "testing", test))]
3689impl Client {
3690    /// Test helper to mark users as tracked by the crypto layer.
3691    #[cfg(feature = "e2e-encryption")]
3692    pub async fn update_tracked_users_for_testing(
3693        &self,
3694        user_ids: impl IntoIterator<Item = &UserId>,
3695    ) {
3696        let olm = self.olm_machine().await;
3697        let olm = olm.as_ref().unwrap();
3698        olm.update_tracked_users(user_ids).await.unwrap();
3699    }
3700}
3701
3702/// A weak reference to the inner client, useful when trying to get a handle
3703/// on the owning client.
3704#[derive(Clone, Debug)]
3705pub(crate) struct WeakClient {
3706    client: Weak<ClientInner>,
3707}
3708
3709impl WeakClient {
3710    /// Construct a [`WeakClient`] from a `Arc<ClientInner>`.
3711    pub(crate) fn from_inner(client: &Arc<ClientInner>) -> Self {
3712        Self { client: Arc::downgrade(client) }
3713    }
3714
3715    /// Construct a [`WeakClient`] from a [`Client`].
3716    pub fn from_client(client: &Client) -> Self {
3717        Self::from_inner(&client.inner)
3718    }
3719
3720    /// Attempts to get a [`Client`] from this [`WeakClient`].
3721    pub fn get(&self) -> Option<Client> {
3722        self.client.upgrade().map(|inner| Client { inner })
3723    }
3724
3725    /// Gets the number of strong (`Arc`) pointers still pointing to this
3726    /// client.
3727    #[allow(dead_code)]
3728    pub fn strong_count(&self) -> usize {
3729        self.client.strong_count()
3730    }
3731}
3732
3733/// Information about the state of a room before we joined it.
3734#[derive(Debug, Clone, Default)]
3735struct PreJoinRoomInfo {
3736    /// The user who invited us to the room, if any.
3737    pub inviter: Option<RoomMember>,
3738}
3739
3740// The http mocking library is not supported for wasm32
3741#[cfg(all(test, not(target_family = "wasm")))]
3742pub(crate) mod tests {
3743    use std::{sync::Arc, time::Duration};
3744
3745    use assert_matches::assert_matches;
3746    use assert_matches2::assert_let;
3747    use eyeball::SharedObservable;
3748    use futures_util::{FutureExt, StreamExt, pin_mut};
3749    use js_int::{UInt, uint};
3750    use matrix_sdk_base::{
3751        RoomState,
3752        store::{MemoryStore, StoreConfig},
3753        ttl::TtlValue,
3754    };
3755    use matrix_sdk_test::{
3756        DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
3757        event_factory::EventFactory,
3758    };
3759    #[cfg(target_family = "wasm")]
3760    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
3761
3762    use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
3763    use ruma::{
3764        RoomId, ServerName, UserId,
3765        api::{
3766            FeatureFlag, MatrixVersion,
3767            client::{room::create_room::v3::Request as CreateRoomRequest, rtc::RtcTransport},
3768        },
3769        assign,
3770        events::{
3771            ignored_user_list::IgnoredUserListEventContent,
3772            media_preview_config::{InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews},
3773        },
3774        owned_device_id, owned_room_id, owned_user_id,
3775        presence::PresenceState,
3776        room_alias_id, room_id, user_id,
3777    };
3778    use serde_json::json;
3779    use stream_assert::{assert_next_matches, assert_pending};
3780    use tokio::{
3781        spawn,
3782        time::{sleep, timeout},
3783    };
3784    use url::Url;
3785
3786    use super::Client;
3787    use crate::{
3788        Error, TransmissionProgress,
3789        client::{WeakClient, caches::CachedValue, futures::SendMediaUploadRequest},
3790        config::{RequestConfig, SyncSettings},
3791        futures::SendRequest,
3792        media::MediaError,
3793        test_utils::{client::MockClientBuilder, mocks::MatrixMockServer},
3794    };
3795
3796    #[async_test]
3797    async fn test_sync_presence_is_shared_by_client_clones_and_notification_child() {
3798        let client = MockClientBuilder::new(None).build().await;
3799        let clone = client.clone();
3800        let notification_client =
3801            client.notification_client(CrossProcessLockConfig::SingleProcess).await.unwrap();
3802
3803        assert_eq!(client.sync_presence(), PresenceState::Online);
3804        assert_eq!(clone.sync_presence(), PresenceState::Online);
3805        assert_eq!(notification_client.sync_presence(), PresenceState::Online);
3806
3807        client
3808            .set_presence(PresenceState::Unavailable, None, false)
3809            .await
3810            .expect("presence should update");
3811
3812        assert_eq!(client.sync_presence(), PresenceState::Unavailable);
3813        assert_eq!(clone.sync_presence(), PresenceState::Unavailable);
3814        assert_eq!(notification_client.sync_presence(), PresenceState::Unavailable);
3815
3816        notification_client
3817            .set_presence(PresenceState::Offline, None, false)
3818            .await
3819            .expect("presence should update");
3820
3821        assert_eq!(client.sync_presence(), PresenceState::Offline);
3822        assert_eq!(clone.sync_presence(), PresenceState::Offline);
3823        assert_eq!(notification_client.sync_presence(), PresenceState::Offline);
3824    }
3825
3826    #[async_test]
3827    async fn test_sync_once_uses_client_sync_presence_unless_overridden() {
3828        let server = MatrixMockServer::new().await;
3829        let client = server.client_builder().build().await;
3830
3831        {
3832            let _sync_guard = server
3833                .mock_sync()
3834                .set_presence_missing()
3835                .ok(|_| {})
3836                .expect(1)
3837                .mount_as_scoped()
3838                .await;
3839
3840            client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
3841        }
3842
3843        client
3844            .set_presence(PresenceState::Offline, None, false)
3845            .await
3846            .expect("presence should update");
3847
3848        {
3849            let _sync_guard = server
3850                .mock_sync()
3851                .set_presence("offline")
3852                .ok(|_| {})
3853                .expect(1)
3854                .mount_as_scoped()
3855                .await;
3856
3857            client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
3858        }
3859
3860        {
3861            let _sync_guard = server
3862                .mock_sync()
3863                .set_presence("unavailable")
3864                .ok(|_| {})
3865                .expect(1)
3866                .mount_as_scoped()
3867                .await;
3868
3869            client
3870                .sync_once(SyncSettings::new().set_presence(PresenceState::Unavailable))
3871                .await
3872                .expect("sync should succeed");
3873        }
3874    }
3875
3876    #[async_test]
3877    async fn test_set_presence_sends_presence_status_update() {
3878        use wiremock::{
3879            Mock, ResponseTemplate,
3880            matchers::{body_partial_json, method, path_regex},
3881        };
3882
3883        let server = MatrixMockServer::new().await;
3884        let client = server.client_builder().build().await;
3885
3886        Mock::given(method("PUT"))
3887            .and(path_regex(r"^/_matrix/client/(r0|v3)/presence/.*/status$"))
3888            .and(body_partial_json(json!({
3889                "presence": "online",
3890                "status_msg": "Here"
3891            })))
3892            .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3893            .expect(1)
3894            .mount(server.server())
3895            .await;
3896
3897        client
3898            .set_presence(PresenceState::Online, Some("Here".to_owned()), true)
3899            .await
3900            .expect("presence update should succeed");
3901
3902        assert_eq!(client.sync_presence(), PresenceState::Online);
3903    }
3904
3905    #[async_test]
3906    async fn test_set_presence_requires_authentication() {
3907        let client = MockClientBuilder::new(None).unlogged().build().await;
3908
3909        assert_matches!(
3910            client.set_presence(PresenceState::Unavailable, None, true).await,
3911            Err(Error::AuthenticationRequired)
3912        );
3913    }
3914
3915    #[async_test]
3916    async fn test_set_presence_without_immediate_does_not_require_authentication() {
3917        let client = MockClientBuilder::new(None).unlogged().build().await;
3918
3919        client
3920            .set_presence(PresenceState::Offline, None, false)
3921            .await
3922            .expect("presence should update");
3923
3924        assert_eq!(client.sync_presence(), PresenceState::Offline);
3925    }
3926
3927    #[async_test]
3928    async fn test_account_data() {
3929        let server = MatrixMockServer::new().await;
3930        let client = server.client_builder().build().await;
3931
3932        let f = EventFactory::new();
3933        server
3934            .mock_sync()
3935            .ok_and_run(&client, |builder| {
3936                builder.add_global_account_data(
3937                    f.ignored_user_list([owned_user_id!("@someone:example.org")]),
3938                );
3939            })
3940            .await;
3941
3942        let content = client
3943            .account()
3944            .account_data::<IgnoredUserListEventContent>()
3945            .await
3946            .unwrap()
3947            .unwrap()
3948            .deserialize()
3949            .unwrap();
3950
3951        assert_eq!(content.ignored_users.len(), 1);
3952    }
3953
3954    #[async_test]
3955    async fn test_successful_discovery() {
3956        // Imagine this is `matrix.org`.
3957        let server = MatrixMockServer::new().await;
3958        let server_url = server.uri();
3959
3960        // Imagine this is `matrix-client.matrix.org`.
3961        let homeserver = MatrixMockServer::new().await;
3962        let homeserver_url = homeserver.uri();
3963
3964        // Imagine Alice has the user ID `@alice:matrix.org`.
3965        let domain = server_url.strip_prefix("http://").unwrap();
3966        let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
3967
3968        // The `.well-known` is on the server (e.g. `matrix.org`).
3969        server
3970            .mock_well_known()
3971            .ok_with_homeserver_url(&homeserver_url)
3972            .mock_once()
3973            .named("well-known")
3974            .mount()
3975            .await;
3976
3977        // The `/versions` is on the homeserver (e.g. `matrix-client.matrix.org`).
3978        homeserver.mock_versions().ok().mock_once().named("versions").mount().await;
3979
3980        let client = Client::builder()
3981            .insecure_server_name_no_tls(alice.server_name())
3982            .build()
3983            .await
3984            .unwrap();
3985
3986        assert_eq!(client.server().unwrap(), &Url::parse(&server_url).unwrap());
3987        assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
3988        client.server_versions().await.unwrap();
3989    }
3990
3991    #[async_test]
3992    async fn test_discovery_broken_server() {
3993        let server = MatrixMockServer::new().await;
3994        let server_url = server.uri();
3995        let domain = server_url.strip_prefix("http://").unwrap();
3996        let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
3997
3998        server.mock_well_known().error404().mock_once().named("well-known").mount().await;
3999
4000        assert!(
4001            Client::builder()
4002                .insecure_server_name_no_tls(alice.server_name())
4003                .build()
4004                .await
4005                .is_err(),
4006            "Creating a client from a user ID should fail when the .well-known request fails."
4007        );
4008    }
4009
4010    #[async_test]
4011    async fn test_room_creation() {
4012        let server = MatrixMockServer::new().await;
4013        let client = server.client_builder().build().await;
4014
4015        let f = EventFactory::new().sender(user_id!("@example:localhost"));
4016        server
4017            .mock_sync()
4018            .ok_and_run(&client, |builder| {
4019                builder.add_joined_room(
4020                    JoinedRoomBuilder::default()
4021                        .add_state_event(
4022                            f.member(user_id!("@example:localhost")).display_name("example"),
4023                        )
4024                        .add_state_event(f.default_power_levels()),
4025                );
4026            })
4027            .await;
4028
4029        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
4030        assert_eq!(room.state(), RoomState::Joined);
4031    }
4032
4033    #[async_test]
4034    async fn test_retry_limit_http_requests() {
4035        let server = MatrixMockServer::new().await;
4036        let client = server
4037            .client_builder()
4038            .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(4)))
4039            .build()
4040            .await;
4041
4042        assert!(client.request_config().retry_limit.unwrap() == 4);
4043
4044        server.mock_who_am_i().error500().expect(4).mount().await;
4045
4046        client.whoami().await.unwrap_err();
4047    }
4048
4049    #[async_test]
4050    async fn test_retry_timeout_http_requests() {
4051        // Keep this timeout small so that the test doesn't take long
4052        let retry_timeout = Duration::from_secs(5);
4053        let server = MatrixMockServer::new().await;
4054        let client = server
4055            .client_builder()
4056            .on_builder(|builder| {
4057                builder.request_config(RequestConfig::new().max_retry_time(retry_timeout))
4058            })
4059            .build()
4060            .await;
4061
4062        assert!(client.request_config().max_retry_time.unwrap() == retry_timeout);
4063
4064        server.mock_login().error500().expect(2..).mount().await;
4065
4066        client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4067    }
4068
4069    #[async_test]
4070    async fn test_short_retry_initial_http_requests() {
4071        let server = MatrixMockServer::new().await;
4072        let client = server
4073            .client_builder()
4074            .on_builder(|builder| builder.request_config(RequestConfig::short_retry()))
4075            .build()
4076            .await;
4077
4078        server.mock_login().error500().expect(3..).mount().await;
4079
4080        client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4081    }
4082
4083    #[async_test]
4084    async fn test_no_retry_http_requests() {
4085        let server = MatrixMockServer::new().await;
4086        let client = server.client_builder().build().await;
4087
4088        server.mock_devices().error500().mock_once().mount().await;
4089
4090        client.devices().await.unwrap_err();
4091    }
4092
4093    #[async_test]
4094    async fn test_set_homeserver() {
4095        let client = MockClientBuilder::new(None).build().await;
4096        assert_eq!(client.homeserver().as_ref(), "http://localhost/");
4097
4098        let homeserver = Url::parse("http://example.com/").unwrap();
4099        client.set_homeserver(homeserver.clone());
4100        assert_eq!(client.homeserver(), homeserver);
4101    }
4102
4103    #[async_test]
4104    async fn test_search_user_request() {
4105        let server = MatrixMockServer::new().await;
4106        let client = server.client_builder().build().await;
4107
4108        server.mock_user_directory().ok().mock_once().mount().await;
4109
4110        let response = client.search_users("test", 50).await.unwrap();
4111        assert_eq!(response.results.len(), 1);
4112        let result = &response.results[0];
4113        assert_eq!(result.user_id.to_string(), "@test:example.me");
4114        assert_eq!(result.display_name.clone().unwrap(), "Test");
4115        assert_eq!(result.avatar_url.clone().unwrap().to_string(), "mxc://example.me/someid");
4116        assert!(!response.limited);
4117    }
4118
4119    #[async_test]
4120    async fn test_request_unstable_features() {
4121        let server = MatrixMockServer::new().await;
4122        let client = server.client_builder().no_server_versions().build().await;
4123
4124        server
4125            .mock_versions()
4126            .with_feature("org.matrix.e2e_cross_signing", true)
4127            .ok()
4128            .mock_once()
4129            .mount()
4130            .await;
4131
4132        let unstable_features = client.unstable_features().await.unwrap();
4133        assert!(unstable_features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4134        assert!(!unstable_features.contains(&FeatureFlag::from("you.shall.pass")));
4135    }
4136
4137    #[async_test]
4138    async fn test_can_homeserver_push_encrypted_event_to_device() {
4139        let server = MatrixMockServer::new().await;
4140        let client = server.client_builder().no_server_versions().build().await;
4141
4142        server.mock_versions().with_push_encrypted_events().ok().mock_once().mount().await;
4143
4144        let msc4028_enabled = client.can_homeserver_push_encrypted_event_to_device().await.unwrap();
4145        assert!(msc4028_enabled);
4146    }
4147
4148    #[async_test]
4149    async fn test_recently_visited_rooms() {
4150        // Tracking recently visited rooms requires authentication
4151        let client = MockClientBuilder::new(None).unlogged().build().await;
4152        assert_matches!(
4153            client.account().track_recently_visited_room(owned_room_id!("!alpha:localhost")).await,
4154            Err(Error::AuthenticationRequired)
4155        );
4156
4157        let client = MockClientBuilder::new(None).build().await;
4158        let account = client.account();
4159
4160        // We should start off with an empty list
4161        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 0);
4162
4163        // Tracking a valid room id should add it to the list
4164        account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4165        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4166        assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4167
4168        // And the existing list shouldn't be changed
4169        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4170        assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4171
4172        // Tracking the same room again shouldn't change the list
4173        account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4174        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4175        assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4176
4177        // Tracking a second room should add it to the front of the list
4178        account.track_recently_visited_room(owned_room_id!("!beta:localhost")).await.unwrap();
4179        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4180        assert_eq!(
4181            account.get_recently_visited_rooms().await.unwrap(),
4182            [room_id!("!beta:localhost"), room_id!("!alpha:localhost")]
4183        );
4184
4185        // Tracking the first room yet again should move it to the front of the list
4186        account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4187        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4188        assert_eq!(
4189            account.get_recently_visited_rooms().await.unwrap(),
4190            [room_id!("!alpha:localhost"), room_id!("!beta:localhost")]
4191        );
4192
4193        // Tracking should be capped at 20
4194        for n in 0..20 {
4195            account
4196                .track_recently_visited_room(RoomId::parse(format!("!{n}:localhost")).unwrap())
4197                .await
4198                .unwrap();
4199        }
4200
4201        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 20);
4202
4203        // And the initial rooms should've been pushed out
4204        let rooms = account.get_recently_visited_rooms().await.unwrap();
4205        assert!(!rooms.contains(&owned_room_id!("!alpha:localhost")));
4206        assert!(!rooms.contains(&owned_room_id!("!beta:localhost")));
4207
4208        // And the last tracked room should be the first
4209        assert_eq!(rooms.first().unwrap(), room_id!("!19:localhost"));
4210    }
4211
4212    #[async_test]
4213    async fn test_client_no_cycle_with_event_cache() {
4214        let client = MockClientBuilder::new(None).build().await;
4215
4216        // Wait for the init tasks to die.
4217        sleep(Duration::from_secs(1)).await;
4218
4219        let weak_client = WeakClient::from_client(&client);
4220        assert_eq!(weak_client.strong_count(), 1);
4221
4222        {
4223            let room_id = room_id!("!room:example.org");
4224
4225            // Have the client know the room.
4226            let response = SyncResponseBuilder::default()
4227                .add_joined_room(JoinedRoomBuilder::new(room_id))
4228                .build_sync_response();
4229            client.inner.base_client.receive_sync_response(response).await.unwrap();
4230
4231            client.event_cache().subscribe().unwrap();
4232
4233            let (_room_event_cache, _drop_handles) =
4234                client.get_room(room_id).unwrap().event_cache().await.unwrap();
4235        }
4236
4237        drop(client);
4238
4239        // Give a bit of time for background tasks to die.
4240        sleep(Duration::from_secs(1)).await;
4241
4242        // The weak client must be the last reference to the client now.
4243        assert_eq!(weak_client.strong_count(), 0);
4244        let client = weak_client.get();
4245        assert!(
4246            client.is_none(),
4247            "too many strong references to the client: {}",
4248            Arc::strong_count(&client.unwrap().inner)
4249        );
4250    }
4251
4252    #[async_test]
4253    async fn test_supported_versions_caching() {
4254        let server = MatrixMockServer::new().await;
4255
4256        let versions_mock = server
4257            .mock_versions()
4258            .expect_default_access_token()
4259            .with_feature("org.matrix.e2e_cross_signing", true)
4260            .ok()
4261            .named("first versions mock")
4262            .expect(1)
4263            .mount_as_scoped()
4264            .await;
4265
4266        let memory_store = Arc::new(MemoryStore::new());
4267        let client = server
4268            .client_builder()
4269            .no_server_versions()
4270            .on_builder(|builder| {
4271                builder.store_config(
4272                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4273                        .state_store(memory_store.clone()),
4274                )
4275            })
4276            .build()
4277            .await;
4278
4279        assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4280
4281        // The result was cached.
4282        assert_matches!(client.supported_versions_cached().await, Ok(Some(_)));
4283        // This subsequent call hits the in-memory cache.
4284        assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4285
4286        drop(client);
4287
4288        let client = server
4289            .client_builder()
4290            .no_server_versions()
4291            .on_builder(|builder| {
4292                builder.store_config(
4293                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4294                        .state_store(memory_store.clone()),
4295                )
4296            })
4297            .build()
4298            .await;
4299
4300        // These calls to the new client hit the on-disk cache.
4301        assert!(
4302            client
4303                .unstable_features()
4304                .await
4305                .unwrap()
4306                .contains(&FeatureFlag::from("org.matrix.e2e_cross_signing"))
4307        );
4308
4309        let supported = client.supported_versions().await.unwrap();
4310        assert!(supported.versions.contains(&MatrixVersion::V1_0));
4311        assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4312
4313        // Then this call hits the in-memory cache.
4314        let supported = client.supported_versions().await.unwrap();
4315        assert!(supported.versions.contains(&MatrixVersion::V1_0));
4316        assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4317
4318        drop(versions_mock);
4319
4320        // Now, reset the cache, and observe the endpoint being called again once.
4321        client.reset_supported_versions().await.unwrap();
4322
4323        server.mock_versions().ok().expect(2).named("second versions mock").mount().await;
4324
4325        // Hits network again.
4326        assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4327        // Hits in-memory cache again.
4328        assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4329        assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4330
4331        // Force an expiry of the data.
4332        let supported_versions = client.supported_versions_cached().await.unwrap().unwrap();
4333        let mut ttl_value = TtlValue::new(supported_versions);
4334        ttl_value.expire();
4335        client.inner.caches.supported_versions.set_value(ttl_value);
4336
4337        // Call the method to trigger a cache refresh background task.
4338        client.supported_versions_cached().await.unwrap().unwrap();
4339
4340        // We wait for the task to finish, the endpoint should have been called again.
4341        sleep(Duration::from_secs(1)).await;
4342        assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4343    }
4344
4345    #[async_test]
4346    async fn test_well_known_caching() {
4347        let server = MatrixMockServer::new().await;
4348        let server_url = server.uri();
4349        let domain = server_url.strip_prefix("http://").unwrap();
4350        let server_name = <&ServerName>::try_from(domain).unwrap();
4351        let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4352
4353        let well_known_mock = server
4354            .mock_well_known()
4355            .ok()
4356            .named("well known mock")
4357            .expect(2) // One for ClientBuilder discovery, one for the ServerInfo cache.
4358            .mount_as_scoped()
4359            .await;
4360
4361        let memory_store = Arc::new(MemoryStore::new());
4362        let client = Client::builder()
4363            .insecure_server_name_no_tls(server_name)
4364            .store_config(
4365                StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4366                    .state_store(memory_store.clone()),
4367            )
4368            .build()
4369            .await
4370            .unwrap();
4371
4372        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4373
4374        // This subsequent call hits the in-memory cache.
4375        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4376
4377        drop(client);
4378
4379        let client = server
4380            .client_builder()
4381            .no_server_versions()
4382            .on_builder(|builder| {
4383                builder.store_config(
4384                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4385                        .state_store(memory_store.clone()),
4386                )
4387            })
4388            .build()
4389            .await;
4390
4391        // This call to the new client hits the on-disk cache.
4392        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4393
4394        // Then this call hits the in-memory cache.
4395        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4396
4397        drop(well_known_mock);
4398
4399        // Now, reset the cache, and observe the endpoints being called again once.
4400        client.reset_well_known().await.unwrap();
4401
4402        server.mock_well_known().ok().named("second well known mock").expect(2).mount().await;
4403
4404        // Hits network again.
4405        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4406        // Hits in-memory cache again.
4407        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4408
4409        // Force an expiry of the data.
4410        let well_known = client.well_known().await;
4411        let mut ttl_value = TtlValue::new(well_known);
4412        ttl_value.expire();
4413        client.inner.caches.well_known.set_value(ttl_value);
4414
4415        // Call the method again to trigger a cache refresh background task.
4416        client.well_known().await;
4417
4418        // We wait for the task to finish, the endpoint should have been called again.
4419        // We need to wait a bit because the first requests using the server name of the
4420        // user will fail, only the requests using the homeserver URL will succeed.
4421        sleep(Duration::from_secs(5)).await;
4422        assert_matches!(client.inner.caches.well_known.value(), CachedValue::Cached(value) if !value.has_expired());
4423    }
4424
4425    #[async_test]
4426    async fn test_missing_well_known_caching() {
4427        let server = MatrixMockServer::new().await;
4428        let rtc_foci: Vec<RtcTransport> = vec![];
4429
4430        let well_known_mock = server
4431            .mock_well_known()
4432            .error_unrecognized()
4433            .named("first well-known mock")
4434            .expect(1)
4435            .mount_as_scoped()
4436            .await;
4437
4438        let memory_store = Arc::new(MemoryStore::new());
4439        let client = server
4440            .client_builder()
4441            .on_builder(|builder| {
4442                builder.store_config(
4443                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4444                        .state_store(memory_store.clone()),
4445                )
4446            })
4447            .build()
4448            .await;
4449
4450        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4451
4452        // This subsequent call hits the in-memory cache.
4453        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4454
4455        drop(client);
4456
4457        let client = server
4458            .client_builder()
4459            .on_builder(|builder| {
4460                builder.store_config(
4461                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4462                        .state_store(memory_store.clone()),
4463                )
4464            })
4465            .build()
4466            .await;
4467
4468        // This call to the new client hits the on-disk cache.
4469        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4470
4471        // Then this call hits the in-memory cache.
4472        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4473
4474        drop(well_known_mock);
4475
4476        // Now, reset the cache, and observe the endpoints being called again once.
4477        client.reset_well_known().await.unwrap();
4478
4479        server
4480            .mock_well_known()
4481            .error_unrecognized()
4482            .expect(1)
4483            .named("second well-known mock")
4484            .mount()
4485            .await;
4486
4487        // Hits network again.
4488        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4489        // Hits in-memory cache again.
4490        assert_eq!(client.rtc_foci().await.unwrap(), rtc_foci);
4491    }
4492
4493    #[async_test]
4494    async fn test_no_network_doesnt_cause_infinite_retries() {
4495        // We want infinite retries for transient errors.
4496        let client = MockClientBuilder::new(None)
4497            .on_builder(|builder| builder.request_config(RequestConfig::new()))
4498            .build()
4499            .await;
4500
4501        // We don't define a mock server on purpose here, so that the error is really a
4502        // network error.
4503        client.whoami().await.unwrap_err();
4504    }
4505
4506    #[async_test]
4507    async fn test_await_room_remote_echo_returns_the_room_if_it_was_already_synced() {
4508        let server = MatrixMockServer::new().await;
4509        let client = server.client_builder().build().await;
4510
4511        let room_id = room_id!("!room:example.org");
4512
4513        server
4514            .mock_sync()
4515            .ok_and_run(&client, |builder| {
4516                builder.add_joined_room(JoinedRoomBuilder::new(room_id));
4517            })
4518            .await;
4519
4520        let room = client.await_room_remote_echo(room_id).now_or_never().unwrap();
4521        assert_eq!(room.room_id(), room_id);
4522    }
4523
4524    #[async_test]
4525    async fn test_await_room_remote_echo_returns_the_room_when_it_is_ready() {
4526        let server = MatrixMockServer::new().await;
4527        let client = server.client_builder().build().await;
4528
4529        let room_id = room_id!("!room:example.org");
4530
4531        let client = Arc::new(client);
4532
4533        // Perform the /sync request with a delay so it starts after the
4534        // `await_room_remote_echo` call has happened
4535        spawn({
4536            let client = client.clone();
4537            async move {
4538                sleep(Duration::from_millis(100)).await;
4539
4540                server
4541                    .mock_sync()
4542                    .ok_and_run(&client, |builder| {
4543                        builder.add_joined_room(JoinedRoomBuilder::new(room_id));
4544                    })
4545                    .await;
4546            }
4547        });
4548
4549        let room =
4550            timeout(Duration::from_secs(10), client.await_room_remote_echo(room_id)).await.unwrap();
4551        assert_eq!(room.room_id(), room_id);
4552    }
4553
4554    #[async_test]
4555    async fn test_await_room_remote_echo_will_timeout_if_no_room_is_found() {
4556        let client = MockClientBuilder::new(None).build().await;
4557
4558        let room_id = room_id!("!room:example.org");
4559        // Room is not present so the client won't be able to find it. The call will
4560        // timeout.
4561        timeout(Duration::from_secs(1), client.await_room_remote_echo(room_id)).await.unwrap_err();
4562    }
4563
4564    #[async_test]
4565    async fn test_await_room_remote_echo_will_timeout_if_room_is_found_but_not_synced() {
4566        let server = MatrixMockServer::new().await;
4567        let client = server.client_builder().build().await;
4568
4569        server.mock_create_room().ok().mount().await;
4570
4571        // Create a room in the internal store
4572        let room = client
4573            .create_room(assign!(CreateRoomRequest::new(), {
4574                invite: vec![],
4575                is_direct: false,
4576            }))
4577            .await
4578            .unwrap();
4579
4580        // Room is locally present, but not synced, the call will timeout
4581        timeout(Duration::from_secs(1), client.await_room_remote_echo(room.room_id()))
4582            .await
4583            .unwrap_err();
4584    }
4585
4586    #[async_test]
4587    async fn test_is_room_alias_available_if_alias_is_not_resolved() {
4588        let server = MatrixMockServer::new().await;
4589        let client = server.client_builder().build().await;
4590
4591        server.mock_room_directory_resolve_alias().not_found().expect(1).mount().await;
4592
4593        let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
4594        assert_matches!(ret, Ok(true));
4595    }
4596
4597    #[async_test]
4598    async fn test_is_room_alias_available_if_alias_is_resolved() {
4599        let server = MatrixMockServer::new().await;
4600        let client = server.client_builder().build().await;
4601
4602        server
4603            .mock_room_directory_resolve_alias()
4604            .ok("!some_room_id:matrix.org", Vec::new())
4605            .expect(1)
4606            .mount()
4607            .await;
4608
4609        let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
4610        assert_matches!(ret, Ok(false));
4611    }
4612
4613    #[async_test]
4614    async fn test_is_room_alias_available_if_error_found() {
4615        let server = MatrixMockServer::new().await;
4616        let client = server.client_builder().build().await;
4617
4618        server.mock_room_directory_resolve_alias().error500().expect(1).mount().await;
4619
4620        let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
4621        assert_matches!(ret, Err(_));
4622    }
4623
4624    #[async_test]
4625    async fn test_create_room_alias() {
4626        let server = MatrixMockServer::new().await;
4627        let client = server.client_builder().build().await;
4628
4629        server.mock_room_directory_create_room_alias().ok().expect(1).mount().await;
4630
4631        let ret = client
4632            .create_room_alias(
4633                room_alias_id!("#some_alias:matrix.org"),
4634                room_id!("!some_room:matrix.org"),
4635            )
4636            .await;
4637        assert_matches!(ret, Ok(()));
4638    }
4639
4640    #[async_test]
4641    async fn test_join_room_by_id_or_alias() {
4642        use wiremock::{
4643            Mock, ResponseTemplate,
4644            matchers::{method, path_regex},
4645        };
4646        let server = MatrixMockServer::new().await;
4647        let client = server.client_builder().build().await;
4648
4649        let target_room_id = room_id!("!some_id:matrix.org");
4650        let target_alias = room_alias_id!("#some_alias:matrix.org");
4651
4652        Mock::given(method("POST"))
4653            .and(path_regex("^/_matrix/client/v3/join/.*$"))
4654            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4655                "room_id": target_room_id
4656            })))
4657            .mount(server.server())
4658            .await;
4659
4660        server
4661            .mock_room_directory_resolve_alias()
4662            .ok(target_room_id.as_str(), Vec::new())
4663            .mount()
4664            .await;
4665
4666        server.mock_room_join(target_room_id).ok().mount().await;
4667
4668        let ret = client.join_room_by_id_or_alias(target_alias.into(), &[]).await;
4669        assert!(ret.is_ok());
4670
4671        let ret = client.join_room_by_id_or_alias(target_room_id.into(), &[]).await;
4672        assert!(ret.is_ok());
4673    }
4674
4675    #[async_test]
4676    async fn test_room_preview_for_invited_room_hits_summary_endpoint() {
4677        let server = MatrixMockServer::new().await;
4678        let client = server.client_builder().build().await;
4679
4680        let room_id = room_id!("!a-room:matrix.org");
4681
4682        // Make sure the summary endpoint is called once
4683        server.mock_room_summary().ok(room_id).mock_once().mount().await;
4684
4685        // We create a locally cached invited room
4686        let invited_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Invited);
4687
4688        // And we get a preview, the server endpoint was reached
4689        let preview = client
4690            .get_room_preview(room_id.into(), Vec::new())
4691            .await
4692            .expect("Room preview should be retrieved");
4693
4694        assert_eq!(invited_room.room_id(), preview.room_id);
4695    }
4696
4697    #[async_test]
4698    async fn test_room_preview_for_left_room_hits_summary_endpoint() {
4699        let server = MatrixMockServer::new().await;
4700        let client = server.client_builder().build().await;
4701
4702        let room_id = room_id!("!a-room:matrix.org");
4703
4704        // Make sure the summary endpoint is called once
4705        server.mock_room_summary().ok(room_id).mock_once().mount().await;
4706
4707        // We create a locally cached left room
4708        let left_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Left);
4709
4710        // And we get a preview, the server endpoint was reached
4711        let preview = client
4712            .get_room_preview(room_id.into(), Vec::new())
4713            .await
4714            .expect("Room preview should be retrieved");
4715
4716        assert_eq!(left_room.room_id(), preview.room_id);
4717    }
4718
4719    #[async_test]
4720    async fn test_room_preview_for_knocked_room_hits_summary_endpoint() {
4721        let server = MatrixMockServer::new().await;
4722        let client = server.client_builder().build().await;
4723
4724        let room_id = room_id!("!a-room:matrix.org");
4725
4726        // Make sure the summary endpoint is called once
4727        server.mock_room_summary().ok(room_id).mock_once().mount().await;
4728
4729        // We create a locally cached knocked room
4730        let knocked_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Knocked);
4731
4732        // And we get a preview, the server endpoint was reached
4733        let preview = client
4734            .get_room_preview(room_id.into(), Vec::new())
4735            .await
4736            .expect("Room preview should be retrieved");
4737
4738        assert_eq!(knocked_room.room_id(), preview.room_id);
4739    }
4740
4741    #[async_test]
4742    async fn test_room_preview_for_joined_room_retrieves_local_room_info() {
4743        let server = MatrixMockServer::new().await;
4744        let client = server.client_builder().build().await;
4745
4746        let room_id = room_id!("!a-room:matrix.org");
4747
4748        // Make sure the summary endpoint is not called
4749        server.mock_room_summary().ok(room_id).never().mount().await;
4750
4751        // We create a locally cached joined room
4752        let joined_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Joined);
4753
4754        // And we get a preview, no server endpoint was reached
4755        let preview = client
4756            .get_room_preview(room_id.into(), Vec::new())
4757            .await
4758            .expect("Room preview should be retrieved");
4759
4760        assert_eq!(joined_room.room_id(), preview.room_id);
4761    }
4762
4763    #[async_test]
4764    async fn test_media_preview_config() {
4765        let server = MatrixMockServer::new().await;
4766        let client = server.client_builder().build().await;
4767
4768        server
4769            .mock_sync()
4770            .ok_and_run(&client, |builder| {
4771                builder.add_custom_global_account_data(json!({
4772                    "content": {
4773                        "media_previews": "private",
4774                        "invite_avatars": "off"
4775                    },
4776                    "type": "m.media_preview_config"
4777                }));
4778            })
4779            .await;
4780
4781        let (initial_value, stream) =
4782            client.account().observe_media_preview_config().await.unwrap();
4783
4784        let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
4785        assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
4786        assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
4787        pin_mut!(stream);
4788        assert_pending!(stream);
4789
4790        server
4791            .mock_sync()
4792            .ok_and_run(&client, |builder| {
4793                builder.add_custom_global_account_data(json!({
4794                    "content": {
4795                        "media_previews": "off",
4796                        "invite_avatars": "on"
4797                    },
4798                    "type": "m.media_preview_config"
4799                }));
4800            })
4801            .await;
4802
4803        assert_next_matches!(
4804            stream,
4805            MediaPreviewConfigEventContent {
4806                media_previews: Some(MediaPreviews::Off),
4807                invite_avatars: Some(InviteAvatars::On),
4808                ..
4809            }
4810        );
4811        assert_pending!(stream);
4812    }
4813
4814    #[async_test]
4815    async fn test_unstable_media_preview_config() {
4816        let server = MatrixMockServer::new().await;
4817        let client = server.client_builder().build().await;
4818
4819        server
4820            .mock_sync()
4821            .ok_and_run(&client, |builder| {
4822                builder.add_custom_global_account_data(json!({
4823                    "content": {
4824                        "media_previews": "private",
4825                        "invite_avatars": "off"
4826                    },
4827                    "type": "io.element.msc4278.media_preview_config"
4828                }));
4829            })
4830            .await;
4831
4832        let (initial_value, stream) =
4833            client.account().observe_media_preview_config().await.unwrap();
4834
4835        let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
4836        assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
4837        assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
4838        pin_mut!(stream);
4839        assert_pending!(stream);
4840
4841        server
4842            .mock_sync()
4843            .ok_and_run(&client, |builder| {
4844                builder.add_custom_global_account_data(json!({
4845                    "content": {
4846                        "media_previews": "off",
4847                        "invite_avatars": "on"
4848                    },
4849                    "type": "io.element.msc4278.media_preview_config"
4850                }));
4851            })
4852            .await;
4853
4854        assert_next_matches!(
4855            stream,
4856            MediaPreviewConfigEventContent {
4857                media_previews: Some(MediaPreviews::Off),
4858                invite_avatars: Some(InviteAvatars::On),
4859                ..
4860            }
4861        );
4862        assert_pending!(stream);
4863    }
4864
4865    #[async_test]
4866    async fn test_media_preview_config_not_found() {
4867        let server = MatrixMockServer::new().await;
4868        let client = server.client_builder().build().await;
4869
4870        let (initial_value, _) = client.account().observe_media_preview_config().await.unwrap();
4871
4872        assert!(initial_value.is_none());
4873    }
4874
4875    #[async_test]
4876    async fn test_load_or_fetch_max_upload_size_with_auth_matrix_version() {
4877        // The default Matrix version we use is 1.11 or higher, so authenticated media
4878        // is supported.
4879        let server = MatrixMockServer::new().await;
4880        let client = server.client_builder().build().await;
4881
4882        assert!(!client.inner.server_max_upload_size.lock().await.initialized());
4883
4884        server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
4885        client.load_or_fetch_max_upload_size().await.unwrap();
4886
4887        assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
4888    }
4889
4890    #[async_test]
4891    async fn test_load_or_fetch_max_upload_size_with_auth_stable_feature() {
4892        // The server must advertise support for the stable feature for authenticated
4893        // media support, so we mock the `GET /versions` response.
4894        let server = MatrixMockServer::new().await;
4895        let client = server.client_builder().no_server_versions().build().await;
4896
4897        server
4898            .mock_versions()
4899            .with_versions(vec!["v1.7", "v1.8", "v1.9", "v1.10"])
4900            .with_feature("org.matrix.msc3916.stable", true)
4901            .ok()
4902            .named("versions")
4903            .expect(1)
4904            .mount()
4905            .await;
4906
4907        assert!(!client.inner.server_max_upload_size.lock().await.initialized());
4908
4909        server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
4910        client.load_or_fetch_max_upload_size().await.unwrap();
4911
4912        assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
4913    }
4914
4915    #[async_test]
4916    async fn test_load_or_fetch_max_upload_size_no_auth() {
4917        // The server must not support Matrix 1.11 or higher for unauthenticated
4918        // media requests, so we mock the `GET /versions` response.
4919        let server = MatrixMockServer::new().await;
4920        let client = server.client_builder().no_server_versions().build().await;
4921
4922        server
4923            .mock_versions()
4924            .with_versions(vec!["v1.1"])
4925            .ok()
4926            .named("versions")
4927            .expect(1)
4928            .mount()
4929            .await;
4930
4931        assert!(!client.inner.server_max_upload_size.lock().await.initialized());
4932
4933        server.mock_media_config().ok(uint!(2)).mock_once().mount().await;
4934        client.load_or_fetch_max_upload_size().await.unwrap();
4935
4936        assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
4937    }
4938
4939    #[async_test]
4940    async fn test_uploading_a_too_large_media_file() {
4941        let server = MatrixMockServer::new().await;
4942        let client = server.client_builder().build().await;
4943
4944        server.mock_authenticated_media_config().ok(uint!(1)).mock_once().mount().await;
4945        client.load_or_fetch_max_upload_size().await.unwrap();
4946        assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(1));
4947
4948        let data = vec![1, 2];
4949        let upload_request =
4950            ruma::api::client::media::create_content::v3::Request::new(data.clone());
4951        let request = SendRequest {
4952            client: client.clone(),
4953            request: upload_request,
4954            config: None,
4955            send_progress: SharedObservable::new(TransmissionProgress::default()),
4956        };
4957        let media_request = SendMediaUploadRequest::new(request);
4958
4959        let error = media_request.await.err();
4960        assert_let!(Some(Error::Media(MediaError::MediaTooLargeToUpload { max, current })) = error);
4961        assert_eq!(max, uint!(1));
4962        assert_eq!(current, UInt::new_wrapping(data.len() as u64));
4963    }
4964
4965    #[async_test]
4966    async fn test_dont_ignore_timeout_on_first_sync() {
4967        let server = MatrixMockServer::new().await;
4968        let client = server.client_builder().build().await;
4969
4970        server
4971            .mock_sync()
4972            .timeout(Some(Duration::from_secs(30)))
4973            .ok(|_| {})
4974            .mock_once()
4975            .named("sync_with_timeout")
4976            .mount()
4977            .await;
4978
4979        // Call the endpoint once to check the timeout.
4980        let mut stream = Box::pin(client.sync_stream(SyncSettings::new()).await);
4981
4982        timeout(Duration::from_secs(1), async {
4983            stream.next().await.unwrap().unwrap();
4984        })
4985        .await
4986        .unwrap();
4987    }
4988
4989    #[async_test]
4990    async fn test_ignore_timeout_on_first_sync() {
4991        let server = MatrixMockServer::new().await;
4992        let client = server.client_builder().build().await;
4993
4994        server
4995            .mock_sync()
4996            .timeout(None)
4997            .ok(|_| {})
4998            .mock_once()
4999            .named("sync_no_timeout")
5000            .mount()
5001            .await;
5002        server
5003            .mock_sync()
5004            .timeout(Some(Duration::from_secs(30)))
5005            .ok(|_| {})
5006            .mock_once()
5007            .named("sync_with_timeout")
5008            .mount()
5009            .await;
5010
5011        // Call each version of the endpoint once to check the timeouts.
5012        let mut stream = Box::pin(
5013            client.sync_stream(SyncSettings::new().ignore_timeout_on_first_sync(true)).await,
5014        );
5015
5016        timeout(Duration::from_secs(1), async {
5017            stream.next().await.unwrap().unwrap();
5018            stream.next().await.unwrap().unwrap();
5019        })
5020        .await
5021        .unwrap();
5022    }
5023
5024    #[async_test]
5025    async fn test_get_dm_room_returns_the_room_we_have_with_this_user() {
5026        let server = MatrixMockServer::new().await;
5027        let client = server.client_builder().build().await;
5028        // This is the user ID that is inside MemberAdditional.
5029        // Note the confusing username, so we can share
5030        // GlobalAccountDataTestEvent::Direct with the invited test.
5031        let user_id = user_id!("@invited:localhost");
5032
5033        // When we receive a sync response saying "invited" is invited to a DM
5034        let f = EventFactory::new().sender(user_id!("@example:localhost"));
5035        let response = SyncResponseBuilder::default()
5036            .add_joined_room(JoinedRoomBuilder::default().add_state_event(f.member(user_id)))
5037            .add_global_account_data(
5038                f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5039            )
5040            .build_sync_response();
5041        client.base_client().receive_sync_response(response).await.unwrap();
5042
5043        // Then get_dm_room finds this room
5044        let found_room = client.get_dm_room(user_id).expect("DM not found!");
5045        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5046    }
5047
5048    #[async_test]
5049    async fn test_get_dm_room_still_finds_room_where_participant_is_only_invited() {
5050        let server = MatrixMockServer::new().await;
5051        let client = server.client_builder().build().await;
5052        // This is the user ID that is inside MemberInvite
5053        let user_id = user_id!("@invited:localhost");
5054
5055        // When we receive a sync response saying "invited" is invited to a DM
5056        let f = EventFactory::new().sender(user_id!("@example:localhost"));
5057        let response = SyncResponseBuilder::default()
5058            .add_joined_room(
5059                JoinedRoomBuilder::default()
5060                    .add_state_event(f.member(user_id).invited(user_id).display_name("example")),
5061            )
5062            .add_global_account_data(
5063                f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5064            )
5065            .build_sync_response();
5066        client.base_client().receive_sync_response(response).await.unwrap();
5067
5068        // Then get_dm_room finds this room
5069        let found_room = client.get_dm_room(user_id).expect("DM not found!");
5070        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5071    }
5072
5073    #[async_test]
5074    async fn test_get_dm_room_still_finds_left_room() {
5075        // See the discussion in https://github.com/matrix-org/matrix-rust-sdk/issues/2017
5076        // and the high-level issue at https://github.com/vector-im/element-x-ios/issues/1077
5077
5078        let server = MatrixMockServer::new().await;
5079        let client = server.client_builder().build().await;
5080        // This is the user ID that is inside MemberAdditional.
5081        // Note the confusing username, so we can share
5082        // GlobalAccountDataTestEvent::Direct with the invited test.
5083        let user_id = user_id!("@invited:localhost");
5084
5085        // When we receive a sync response saying "invited" has left a DM
5086        let f = EventFactory::new().sender(user_id);
5087        let response = SyncResponseBuilder::default()
5088            .add_joined_room(
5089                JoinedRoomBuilder::default().add_state_event(f.member(user_id).leave()),
5090            )
5091            .add_global_account_data(
5092                f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5093            )
5094            .build_sync_response();
5095        client.base_client().receive_sync_response(response).await.unwrap();
5096
5097        // Then get_dm_room finds this room
5098        let found_room = client.get_dm_room(user_id).expect("DM not found!");
5099        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5100    }
5101
5102    #[async_test]
5103    async fn test_device_exists() {
5104        let server = MatrixMockServer::new().await;
5105        let client = server.client_builder().build().await;
5106
5107        server.mock_get_device().ok().expect(1).mount().await;
5108
5109        assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(true));
5110    }
5111
5112    #[async_test]
5113    async fn test_device_exists_404() {
5114        let server = MatrixMockServer::new().await;
5115        let client = server.client_builder().build().await;
5116
5117        assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(false));
5118    }
5119
5120    #[async_test]
5121    async fn test_device_exists_500() {
5122        let server = MatrixMockServer::new().await;
5123        let client = server.client_builder().build().await;
5124
5125        server.mock_get_device().error500().expect(1).mount().await;
5126
5127        assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Err(_));
5128    }
5129
5130    #[async_test]
5131    async fn test_fetching_well_known_with_homeserver_url() {
5132        let server = MatrixMockServer::new().await;
5133        let client = server.client_builder().build().await;
5134        server.mock_well_known().ok().mount().await;
5135
5136        assert_matches!(client.fetch_client_well_known().await, Some(_));
5137    }
5138
5139    #[async_test]
5140    async fn test_fetching_well_known_with_server_name() {
5141        let server = MatrixMockServer::new().await;
5142        let server_name = ServerName::parse(server.server().address().to_string()).unwrap();
5143
5144        server.mock_well_known().ok().mount().await;
5145
5146        let client = MockClientBuilder::new(None)
5147            .on_builder(|builder| builder.insecure_server_name_no_tls(&server_name))
5148            .build()
5149            .await;
5150
5151        assert_matches!(client.fetch_client_well_known().await, Some(_));
5152    }
5153
5154    #[async_test]
5155    async fn test_fetching_well_known_with_domain_part_of_user_id() {
5156        let server = MatrixMockServer::new().await;
5157        server.mock_well_known().ok().mount().await;
5158
5159        let user_id =
5160            UserId::parse(format!("@user:{}", server.server().address())).expect("Invalid user id");
5161        let client = MockClientBuilder::new(None)
5162            .logged_in_with_token("A_TOKEN".to_owned(), user_id, owned_device_id!("ABCDEF"))
5163            .build()
5164            .await;
5165
5166        assert_matches!(client.fetch_client_well_known().await, Some(_));
5167    }
5168}