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