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