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    /// Get a room with the given room id.
1472    ///
1473    /// # Arguments
1474    ///
1475    /// `room_id` - The unique id of the room that should be fetched.
1476    pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1477        self.base_client().get_room(room_id).map(|room| Room::new(self.clone(), room))
1478    }
1479
1480    /// Gets the preview of a room, whether the current user has joined it or
1481    /// not.
1482    pub async fn get_room_preview(
1483        &self,
1484        room_or_alias_id: &RoomOrAliasId,
1485        via: Vec<OwnedServerName>,
1486    ) -> Result<RoomPreview> {
1487        let room_id = match <&RoomId>::try_from(room_or_alias_id) {
1488            Ok(room_id) => room_id.to_owned(),
1489            Err(alias) => self.resolve_room_alias(alias).await?.room_id,
1490        };
1491
1492        if let Some(room) = self.get_room(&room_id) {
1493            // The cached data can only be trusted if the room state is joined or
1494            // banned: for invite and knock rooms, no updates will be received
1495            // for the rooms after the invite/knock action took place so we may
1496            // have very out to date data for important fields such as
1497            // `join_rule`. For left rooms, the homeserver should return the latest info.
1498            match room.state() {
1499                RoomState::Joined | RoomState::Banned => {
1500                    return Ok(RoomPreview::from_known_room(&room).await);
1501                }
1502                RoomState::Left | RoomState::Invited | RoomState::Knocked => {}
1503            }
1504        }
1505
1506        RoomPreview::from_remote_room(self, room_id, room_or_alias_id, via).await
1507    }
1508
1509    /// Resolve a room alias to a room id and a list of servers which know
1510    /// about it.
1511    ///
1512    /// # Arguments
1513    ///
1514    /// `room_alias` - The room alias to be resolved.
1515    pub async fn resolve_room_alias(
1516        &self,
1517        room_alias: &RoomAliasId,
1518    ) -> HttpResult<get_alias::v3::Response> {
1519        let request = get_alias::v3::Request::new(room_alias.to_owned());
1520        self.send(request).await
1521    }
1522
1523    /// Checks if a room alias is not in use yet.
1524    ///
1525    /// Returns:
1526    /// - `Ok(true)` if the room alias is available.
1527    /// - `Ok(false)` if it's not (the resolve alias request returned a `404`
1528    ///   status code).
1529    /// - An `Err` otherwise.
1530    pub async fn is_room_alias_available(&self, alias: &RoomAliasId) -> HttpResult<bool> {
1531        match self.resolve_room_alias(alias).await {
1532            // The room alias was resolved, so it's already in use.
1533            Ok(_) => Ok(false),
1534            Err(error) => {
1535                match error.client_api_error_kind() {
1536                    // The room alias wasn't found, so it's available.
1537                    Some(ErrorKind::NotFound) => Ok(true),
1538                    _ => Err(error),
1539                }
1540            }
1541        }
1542    }
1543
1544    /// Adds a new room alias associated with a room to the room directory.
1545    pub async fn create_room_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> HttpResult<()> {
1546        let request = create_alias::v3::Request::new(alias.to_owned(), room_id.to_owned());
1547        self.send(request).await?;
1548        Ok(())
1549    }
1550
1551    /// Removes a room alias from the room directory.
1552    pub async fn remove_room_alias(&self, alias: &RoomAliasId) -> HttpResult<()> {
1553        let request = delete_alias::v3::Request::new(alias.to_owned());
1554        self.send(request).await?;
1555        Ok(())
1556    }
1557
1558    /// Update the homeserver from the login response well-known if needed.
1559    ///
1560    /// # Arguments
1561    ///
1562    /// * `login_well_known` - The `well_known` field from a successful login
1563    ///   response.
1564    pub(crate) fn maybe_update_login_well_known(&self, login_well_known: Option<&DiscoveryInfo>) {
1565        if self.inner.respect_login_well_known
1566            && let Some(well_known) = login_well_known
1567            && let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url)
1568        {
1569            self.set_homeserver(homeserver);
1570        }
1571    }
1572
1573    /// Similar to [`Client::restore_session_with`], with
1574    /// [`RoomLoadSettings::default()`].
1575    ///
1576    /// # Panics
1577    ///
1578    /// Panics if a session was already restored or logged in.
1579    #[instrument(skip_all)]
1580    pub async fn restore_session(&self, session: impl Into<AuthSession>) -> Result<()> {
1581        self.restore_session_with(session, RoomLoadSettings::default()).await
1582    }
1583
1584    /// Restore a session previously logged-in using one of the available
1585    /// authentication APIs. The number of rooms to restore is controlled by
1586    /// [`RoomLoadSettings`].
1587    ///
1588    /// See the documentation of the corresponding authentication API's
1589    /// `restore_session` method for more information.
1590    ///
1591    /// # Panics
1592    ///
1593    /// Panics if a session was already restored or logged in.
1594    #[instrument(skip_all)]
1595    pub async fn restore_session_with(
1596        &self,
1597        session: impl Into<AuthSession>,
1598        room_load_settings: RoomLoadSettings,
1599    ) -> Result<()> {
1600        let session = session.into();
1601        match session {
1602            AuthSession::Matrix(session) => {
1603                Box::pin(self.matrix_auth().restore_session(session, room_load_settings)).await
1604            }
1605            AuthSession::OAuth(session) => {
1606                Box::pin(self.oauth().restore_session(*session, room_load_settings)).await
1607            }
1608        }
1609    }
1610
1611    /// Refresh the access token using the authentication API used to log into
1612    /// this session.
1613    ///
1614    /// See the documentation of the authentication API's `refresh_access_token`
1615    /// method for more information.
1616    pub async fn refresh_access_token(&self) -> Result<(), RefreshTokenError> {
1617        let Some(auth_api) = self.auth_api() else {
1618            return Err(RefreshTokenError::RefreshTokenRequired);
1619        };
1620
1621        match auth_api {
1622            AuthApi::Matrix(api) => {
1623                trace!("Token refresh: Using the homeserver.");
1624                Box::pin(api.refresh_access_token()).await?;
1625            }
1626            AuthApi::OAuth(api) => {
1627                trace!("Token refresh: Using OAuth 2.0.");
1628                Box::pin(api.refresh_access_token()).await?;
1629            }
1630        }
1631
1632        Ok(())
1633    }
1634
1635    /// Log out the current session using the proper authentication API.
1636    ///
1637    /// # Errors
1638    ///
1639    /// Returns an error if the session is not authenticated or if an error
1640    /// occurred while making the request to the server.
1641    pub async fn logout(&self) -> Result<(), Error> {
1642        let auth_api = self.auth_api().ok_or(Error::AuthenticationRequired)?;
1643        match auth_api {
1644            AuthApi::Matrix(matrix_auth) => {
1645                matrix_auth.logout().await?;
1646                Ok(())
1647            }
1648            AuthApi::OAuth(oauth) => Ok(oauth.logout().await?),
1649        }
1650    }
1651
1652    /// Get or upload a sync filter.
1653    ///
1654    /// This method will either get a filter ID from the store or upload the
1655    /// filter definition to the homeserver and return the new filter ID.
1656    ///
1657    /// # Arguments
1658    ///
1659    /// * `filter_name` - The unique name of the filter, this name will be used
1660    /// locally to store and identify the filter ID returned by the server.
1661    ///
1662    /// * `definition` - The filter definition that should be uploaded to the
1663    /// server if no filter ID can be found in the store.
1664    ///
1665    /// # Examples
1666    ///
1667    /// ```no_run
1668    /// # use matrix_sdk::{
1669    /// #    Client, config::SyncSettings,
1670    /// #    ruma::api::client::{
1671    /// #        filter::{
1672    /// #           FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter,
1673    /// #        },
1674    /// #        sync::sync_events::v3::Filter,
1675    /// #    }
1676    /// # };
1677    /// # use url::Url;
1678    /// # async {
1679    /// # let homeserver = Url::parse("http://example.com").unwrap();
1680    /// # let client = Client::new(homeserver).await.unwrap();
1681    /// let mut filter = FilterDefinition::default();
1682    ///
1683    /// // Let's enable member lazy loading.
1684    /// filter.room.state.lazy_load_options =
1685    ///     LazyLoadOptions::Enabled { include_redundant_members: false };
1686    ///
1687    /// let filter_id = client
1688    ///     .get_or_upload_filter("sync", filter)
1689    ///     .await
1690    ///     .unwrap();
1691    ///
1692    /// let sync_settings = SyncSettings::new()
1693    ///     .filter(Filter::FilterId(filter_id));
1694    ///
1695    /// let response = client.sync_once(sync_settings).await.unwrap();
1696    /// # };
1697    #[instrument(skip(self, definition))]
1698    pub async fn get_or_upload_filter(
1699        &self,
1700        filter_name: &str,
1701        definition: FilterDefinition,
1702    ) -> Result<String> {
1703        if let Some(filter) = self.inner.base_client.get_filter(filter_name).await? {
1704            debug!("Found filter locally");
1705            Ok(filter)
1706        } else {
1707            debug!("Didn't find filter locally");
1708            let user_id = self.user_id().ok_or(Error::AuthenticationRequired)?;
1709            let request = FilterUploadRequest::new(user_id.to_owned(), definition);
1710            let response = self.send(request).await?;
1711
1712            self.inner.base_client.receive_filter_upload(filter_name, &response).await?;
1713
1714            Ok(response.filter_id)
1715        }
1716    }
1717
1718    /// Prepare to join a room by ID, by getting the current details about it
1719    async fn prepare_join_room_by_id(&self, room_id: &RoomId) -> Option<PreJoinRoomInfo> {
1720        let room = self.get_room(room_id)?;
1721
1722        let inviter = match room.invite_details().await {
1723            Ok(details) => details.inviter,
1724            Err(Error::WrongRoomState(_)) => None,
1725            Err(e) => {
1726                warn!("Error fetching invite details for room: {e:?}");
1727                None
1728            }
1729        };
1730
1731        Some(PreJoinRoomInfo { inviter })
1732    }
1733
1734    /// Finish joining a room.
1735    ///
1736    /// If the room was an invite that should be marked as a DM, will include it
1737    /// in the DM event after creating the joined room.
1738    ///
1739    /// If encrypted history sharing is enabled, will check to see if we have a
1740    /// key bundle, and import it if so.
1741    ///
1742    /// # Arguments
1743    ///
1744    /// * `room_id` - The `RoomId` of the room that was joined.
1745    /// * `pre_join_room_info` - Information about the room before we joined.
1746    async fn finish_join_room(
1747        &self,
1748        room_id: &RoomId,
1749        pre_join_room_info: Option<PreJoinRoomInfo>,
1750    ) -> Result<Room> {
1751        info!(?room_id, ?pre_join_room_info, "Completing room join");
1752        let mark_as_dm = if let Some(room) = self.get_room(room_id) {
1753            room.state() == RoomState::Invited
1754                && room.is_direct().await.unwrap_or_else(|e| {
1755                    warn!(%room_id, "is_direct() failed: {e}");
1756                    false
1757                })
1758        } else {
1759            false
1760        };
1761
1762        let base_room = self
1763            .base_client()
1764            .room_joined(
1765                room_id,
1766                pre_join_room_info
1767                    .as_ref()
1768                    .and_then(|info| info.inviter.as_ref())
1769                    .map(|i| i.user_id().to_owned()),
1770            )
1771            .await?;
1772        let room = Room::new(self.clone(), base_room);
1773
1774        if mark_as_dm {
1775            room.set_is_direct(true).await?;
1776        }
1777
1778        // If we joined following an invite, check if we had previously received a key
1779        // bundle from the inviter, and import it if so.
1780        //
1781        // It's important that we only do this once `BaseClient::room_joined` has
1782        // completed: see the notes on `BundleReceiverTask::handle_bundle` on avoiding a
1783        // race.
1784        #[cfg(feature = "e2e-encryption")]
1785        if self.inner.enable_share_history_on_invite
1786            && let Some(inviter) =
1787                pre_join_room_info.as_ref().and_then(|info| info.inviter.as_ref())
1788        {
1789            crate::room::shared_room_history::maybe_accept_key_bundle(&room, inviter.user_id())
1790                .await?;
1791        }
1792
1793        // Suppress "unused variable" and "unused field" lints
1794        #[cfg(not(feature = "e2e-encryption"))]
1795        let _ = pre_join_room_info.map(|i| i.inviter);
1796
1797        Ok(room)
1798    }
1799
1800    /// Join a room by `RoomId`.
1801    ///
1802    /// Returns the `Room` in the joined state.
1803    ///
1804    /// # Arguments
1805    ///
1806    /// * `room_id` - The `RoomId` of the room to be joined.
1807    #[instrument(skip(self))]
1808    pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result<Room> {
1809        // See who invited us to this room, if anyone. Note we have to do this before
1810        // making the `/join` request, otherwise we could race against the sync.
1811        let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1812
1813        let request = join_room_by_id::v3::Request::new(room_id.to_owned());
1814        let response = self.send(request).await?;
1815        self.finish_join_room(&response.room_id, pre_join_info).await
1816    }
1817
1818    /// Join a room by `RoomOrAliasId`.
1819    ///
1820    /// Returns the `Room` in the joined state.
1821    ///
1822    /// # Arguments
1823    ///
1824    /// * `alias` - The `RoomId` or `RoomAliasId` of the room to be joined. An
1825    ///   alias looks like `#name:example.com`.
1826    /// * `server_names` - The server names to be used for resolving the alias,
1827    ///   if needs be.
1828    #[instrument(skip(self))]
1829    pub async fn join_room_by_id_or_alias(
1830        &self,
1831        alias: &RoomOrAliasId,
1832        server_names: &[OwnedServerName],
1833    ) -> Result<Room> {
1834        let room_id = match <&RoomId>::try_from(alias) {
1835            Ok(room_id) => room_id,
1836            Err(room_alias) => &self.resolve_room_alias(room_alias).await?.room_id,
1837        };
1838        let pre_join_info = self.prepare_join_room_by_id(room_id).await;
1839        let request = assign!(join_room_by_id_or_alias::v3::Request::new(alias.to_owned()), {
1840            via: server_names.to_owned(),
1841        });
1842        let response = self.send(request).await?;
1843        self.finish_join_room(&response.room_id, pre_join_info).await
1844    }
1845
1846    /// Search the homeserver's directory of public rooms.
1847    ///
1848    /// Sends a request to "_matrix/client/r0/publicRooms", returns
1849    /// a `get_public_rooms::Response`.
1850    ///
1851    /// # Arguments
1852    ///
1853    /// * `limit` - The number of `PublicRoomsChunk`s in each response.
1854    ///
1855    /// * `since` - Pagination token from a previous request.
1856    ///
1857    /// * `server` - The name of the server, if `None` the requested server is
1858    ///   used.
1859    ///
1860    /// # Examples
1861    /// ```no_run
1862    /// use matrix_sdk::Client;
1863    /// # use url::Url;
1864    /// # let homeserver = Url::parse("http://example.com").unwrap();
1865    /// # let limit = Some(10);
1866    /// # let since = Some("since token");
1867    /// # let server = Some("servername.com".try_into().unwrap());
1868    /// # async {
1869    /// let mut client = Client::new(homeserver).await.unwrap();
1870    ///
1871    /// client.public_rooms(limit, since, server).await;
1872    /// # };
1873    /// ```
1874    #[cfg_attr(not(target_family = "wasm"), deny(clippy::future_not_send))]
1875    pub async fn public_rooms(
1876        &self,
1877        limit: Option<u32>,
1878        since: Option<&str>,
1879        server: Option<&ServerName>,
1880    ) -> HttpResult<get_public_rooms::v3::Response> {
1881        let limit = limit.map(UInt::from);
1882
1883        let request = assign!(get_public_rooms::v3::Request::new(), {
1884            limit,
1885            since: since.map(ToOwned::to_owned),
1886            server: server.map(ToOwned::to_owned),
1887        });
1888        self.send(request).await
1889    }
1890
1891    /// Create a room with the given parameters.
1892    ///
1893    /// Sends a request to `/_matrix/client/r0/createRoom` and returns the
1894    /// created room.
1895    ///
1896    /// If you want to create a direct message with one specific user, you can
1897    /// use [`create_dm`][Self::create_dm], which is more convenient than
1898    /// assembling the [`create_room::v3::Request`] yourself.
1899    ///
1900    /// If the `is_direct` field of the request is set to `true` and at least
1901    /// one user is invited, the room will be automatically added to the direct
1902    /// rooms in the account data.
1903    ///
1904    /// # Examples
1905    ///
1906    /// ```no_run
1907    /// use matrix_sdk::{
1908    ///     Client,
1909    ///     ruma::api::client::room::create_room::v3::Request as CreateRoomRequest,
1910    /// };
1911    /// # use url::Url;
1912    /// #
1913    /// # async {
1914    /// # let homeserver = Url::parse("http://example.com").unwrap();
1915    /// let request = CreateRoomRequest::new();
1916    /// let client = Client::new(homeserver).await.unwrap();
1917    /// assert!(client.create_room(request).await.is_ok());
1918    /// # };
1919    /// ```
1920    pub async fn create_room(&self, request: create_room::v3::Request) -> Result<Room> {
1921        let invite = request.invite.clone();
1922        let is_direct_room = request.is_direct;
1923        let response = self.send(request).await?;
1924
1925        let base_room = self.base_client().get_or_create_room(&response.room_id, RoomState::Joined);
1926
1927        let joined_room = Room::new(self.clone(), base_room);
1928
1929        if is_direct_room
1930            && !invite.is_empty()
1931            && let Err(error) =
1932                self.account().mark_as_dm(joined_room.room_id(), invite.as_slice()).await
1933        {
1934            // FIXME: Retry in the background
1935            error!("Failed to mark room as DM: {error}");
1936        }
1937
1938        Ok(joined_room)
1939    }
1940
1941    /// Create a DM room.
1942    ///
1943    /// Convenience shorthand for [`create_room`][Self::create_room] with the
1944    /// given user being invited, the room marked `is_direct` and both the
1945    /// creator and invitee getting the default maximum power level.
1946    ///
1947    /// If the `e2e-encryption` feature is enabled, the room will also be
1948    /// encrypted.
1949    ///
1950    /// # Arguments
1951    ///
1952    /// * `user_id` - The ID of the user to create a DM for.
1953    pub async fn create_dm(&self, user_id: &UserId) -> Result<Room> {
1954        #[cfg(feature = "e2e-encryption")]
1955        let initial_state = vec![
1956            InitialStateEvent::with_empty_state_key(
1957                RoomEncryptionEventContent::with_recommended_defaults(),
1958            )
1959            .to_raw_any(),
1960        ];
1961
1962        #[cfg(not(feature = "e2e-encryption"))]
1963        let initial_state = vec![];
1964
1965        let request = assign!(create_room::v3::Request::new(), {
1966            invite: vec![user_id.to_owned()],
1967            is_direct: true,
1968            preset: Some(create_room::v3::RoomPreset::TrustedPrivateChat),
1969            initial_state,
1970        });
1971
1972        self.create_room(request).await
1973    }
1974
1975    /// Get the first existing DM room with the given user, if any.
1976    pub fn get_dm_room(&self, user_id: &UserId) -> Option<Room> {
1977        self.get_dm_rooms(user_id).next()
1978    }
1979
1980    /// Get an iterator with the existing DM rooms for the given user.
1981    pub fn get_dm_rooms(&self, user_id: &UserId) -> impl Iterator<Item = Room> {
1982        let rooms = self.joined_rooms();
1983
1984        let dm_definition = &self.base_client().dm_room_definition;
1985
1986        // Find the room we share with the `user_id` and only with `user_id`
1987        let rooms = rooms.into_iter().filter(move |r| {
1988            let targets = r.direct_targets();
1989            let targets_match =
1990                targets.len() == 1 && targets.contains(<&DirectUserIdentifier>::from(user_id));
1991            match dm_definition {
1992                DmRoomDefinition::MatrixSpec => targets_match,
1993                DmRoomDefinition::TwoMembers => {
1994                    let service_members_count =
1995                        r.service_members().map(|s| s.len()).unwrap_or_default() as u64;
1996                    let active_non_service_members =
1997                        r.active_members_count().saturating_sub(service_members_count);
1998                    targets_match && active_non_service_members <= 2
1999                }
2000            }
2001        });
2002
2003        trace!(?user_id, ?rooms, "Found DM rooms with user");
2004        rooms
2005    }
2006
2007    /// Search the homeserver's directory for public rooms with a filter.
2008    ///
2009    /// # Arguments
2010    ///
2011    /// * `room_search` - The easiest way to create this request is using the
2012    ///   `get_public_rooms_filtered::Request` itself.
2013    ///
2014    /// # Examples
2015    ///
2016    /// ```no_run
2017    /// # use url::Url;
2018    /// # use matrix_sdk::Client;
2019    /// # async {
2020    /// # let homeserver = Url::parse("http://example.com")?;
2021    /// use matrix_sdk::ruma::{
2022    ///     api::client::directory::get_public_rooms_filtered, directory::Filter,
2023    /// };
2024    /// # let mut client = Client::new(homeserver).await?;
2025    ///
2026    /// let mut filter = Filter::new();
2027    /// filter.generic_search_term = Some("rust".to_owned());
2028    /// let mut request = get_public_rooms_filtered::v3::Request::new();
2029    /// request.filter = filter;
2030    ///
2031    /// let response = client.public_rooms_filtered(request).await?;
2032    ///
2033    /// for room in response.chunk {
2034    ///     println!("Found room {room:?}");
2035    /// }
2036    /// # anyhow::Ok(()) };
2037    /// ```
2038    pub async fn public_rooms_filtered(
2039        &self,
2040        request: get_public_rooms_filtered::v3::Request,
2041    ) -> HttpResult<get_public_rooms_filtered::v3::Response> {
2042        self.send(request).await
2043    }
2044
2045    /// Send an arbitrary request to the server, without updating client state.
2046    ///
2047    /// **Warning:** Because this method *does not* update the client state, it
2048    /// is important to make sure that you account for this yourself, and
2049    /// use wrapper methods where available.  This method should *only* be
2050    /// used if a wrapper method for the endpoint you'd like to use is not
2051    /// available.
2052    ///
2053    /// # Arguments
2054    ///
2055    /// * `request` - A filled out and valid request for the endpoint to be hit
2056    ///
2057    /// * `timeout` - An optional request timeout setting, this overrides the
2058    ///   default request setting if one was set.
2059    ///
2060    /// # Examples
2061    ///
2062    /// ```no_run
2063    /// # use matrix_sdk::{Client, config::SyncSettings};
2064    /// # use url::Url;
2065    /// # async {
2066    /// # let homeserver = Url::parse("http://localhost:8080")?;
2067    /// # let mut client = Client::new(homeserver).await?;
2068    /// use matrix_sdk::ruma::{api::client::profile, owned_user_id};
2069    ///
2070    /// // First construct the request you want to make
2071    /// // See https://docs.rs/ruma-client-api/latest/ruma_client_api/index.html
2072    /// // for all available Endpoints
2073    /// let user_id = owned_user_id!("@example:localhost");
2074    /// let request = profile::get_profile::v3::Request::new(user_id);
2075    ///
2076    /// // Start the request using Client::send()
2077    /// let response = client.send(request).await?;
2078    ///
2079    /// // Check the corresponding Response struct to find out what types are
2080    /// // returned
2081    /// # anyhow::Ok(()) };
2082    /// ```
2083    pub fn send<Request>(&self, request: Request) -> SendRequest<Request>
2084    where
2085        Request: OutgoingRequest + Clone + Debug,
2086        Request::Authentication: SupportedAuthScheme,
2087        Request::PathBuilder: SupportedPathBuilder,
2088        for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2089        HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2090    {
2091        SendRequest {
2092            client: self.clone(),
2093            request,
2094            config: None,
2095            send_progress: Default::default(),
2096        }
2097    }
2098
2099    pub(crate) async fn send_inner<Request>(
2100        &self,
2101        request: Request,
2102        config: Option<RequestConfig>,
2103        send_progress: SharedObservable<TransmissionProgress>,
2104    ) -> HttpResult<Request::IncomingResponse>
2105    where
2106        Request: OutgoingRequest + Debug,
2107        Request::Authentication: SupportedAuthScheme,
2108        Request::PathBuilder: SupportedPathBuilder,
2109        for<'a> <Request::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
2110        HttpError: From<FromHttpResponseError<Request::EndpointError>>,
2111    {
2112        let homeserver = self.homeserver().to_string();
2113        let access_token = self.access_token();
2114        let skip_auth = config.map(|c| c.skip_auth).unwrap_or(self.request_config().skip_auth);
2115
2116        let path_builder_input =
2117            Request::PathBuilder::get_path_builder_input(self, skip_auth).await?;
2118
2119        let result = self
2120            .inner
2121            .http_client
2122            .send(
2123                request,
2124                config,
2125                homeserver,
2126                access_token.as_deref(),
2127                path_builder_input,
2128                send_progress,
2129            )
2130            .await;
2131
2132        if let Err(Some(ErrorKind::UnknownToken { .. })) =
2133            result.as_ref().map_err(HttpError::client_api_error_kind)
2134            && let Some(access_token) = &access_token
2135        {
2136            // Mark the access token as expired.
2137            self.auth_ctx().set_access_token_expired(access_token);
2138        }
2139
2140        result
2141    }
2142
2143    fn broadcast_unknown_token(&self, unknown_token_data: &UnknownTokenErrorData) {
2144        _ = self
2145            .inner
2146            .auth_ctx
2147            .session_change_sender
2148            .send(SessionChange::UnknownToken(unknown_token_data.clone()));
2149    }
2150
2151    /// Fetches server versions from network; no caching.
2152    pub async fn fetch_server_versions(
2153        &self,
2154        request_config: Option<RequestConfig>,
2155    ) -> HttpResult<get_supported_versions::Response> {
2156        // Since this was called by the user, try to refresh the access token if
2157        // necessary.
2158        self.fetch_server_versions_inner(false, request_config).await
2159    }
2160
2161    /// Fetches server versions from network; no caching.
2162    ///
2163    /// If the access token is expired and `failsafe` is `false`, this will
2164    /// attempt to refresh the access token, otherwise this will try to make an
2165    /// unauthenticated request instead.
2166    pub(crate) async fn fetch_server_versions_inner(
2167        &self,
2168        failsafe: bool,
2169        request_config: Option<RequestConfig>,
2170    ) -> HttpResult<get_supported_versions::Response> {
2171        if !failsafe {
2172            // `Client::send()` handles refreshing access tokens.
2173            return self
2174                .send(get_supported_versions::Request::new())
2175                .with_request_config(request_config)
2176                .await;
2177        }
2178
2179        let homeserver = self.homeserver().to_string();
2180
2181        // If we have a fresh access token, try with it first.
2182        if !request_config.as_ref().is_some_and(|config| config.skip_auth && !config.force_auth)
2183            && self.auth_ctx().has_valid_access_token()
2184            && let Some(access_token) = self.access_token()
2185        {
2186            let result = self
2187                .inner
2188                .http_client
2189                .send(
2190                    get_supported_versions::Request::new(),
2191                    request_config,
2192                    homeserver.clone(),
2193                    Some(&access_token),
2194                    (),
2195                    Default::default(),
2196                )
2197                .await;
2198
2199            if let Err(Some(ErrorKind::UnknownToken { .. })) =
2200                result.as_ref().map_err(HttpError::client_api_error_kind)
2201            {
2202                // If the access token is actually expired, mark it as expired and fallback to
2203                // the unauthenticated request below.
2204                self.auth_ctx().set_access_token_expired(&access_token);
2205            } else {
2206                // If the request succeeded or it's an other error, just stop now.
2207                return result;
2208            }
2209        }
2210
2211        // Try without authentication.
2212        self.inner
2213            .http_client
2214            .send(
2215                get_supported_versions::Request::new(),
2216                request_config,
2217                homeserver.clone(),
2218                None,
2219                (),
2220                Default::default(),
2221            )
2222            .await
2223    }
2224
2225    /// Fetches client well_known from network; no caching.
2226    ///
2227    /// 1. If the [`Client::server`] value is available, we use it to fetch the
2228    ///    well-known contents.
2229    /// 2. If it's not, we try extracting the server name from the
2230    ///    [`Client::user_id`] and building the server URL from it.
2231    /// 3. If we couldn't get the well-known contents with either the explicit
2232    ///    server name or the implicit extracted one, we try the homeserver URL
2233    ///    as a last resort.
2234    ///
2235    /// Always returns `None` if well-known lookups were disabled with
2236    /// [`ClientBuilder::disable_well_known_lookup`].
2237    pub async fn fetch_client_well_known(&self) -> Option<discover_homeserver::Response> {
2238        if self.well_known_lookup_disabled() {
2239            return None;
2240        }
2241
2242        let homeserver = self.homeserver();
2243        let scheme = homeserver.scheme();
2244
2245        // Use the server name, either an explicit one or an implicit one taken from
2246        // the user id: sometimes we'll have only the homeserver url available and no
2247        // server name, but the server name can be extracted from the current user id.
2248        let server_url = self
2249            .server()
2250            .map(|server| server.to_string())
2251            // If the server name wasn't available, extract it from the user id and build a URL:
2252            // Reuse the same scheme as the homeserver url does, assuming if it's `http` there it
2253            // will be the same for the public server url, lacking a better candidate.
2254            .or_else(|| self.user_id().map(|id| format!("{}://{}", scheme, id.server_name())));
2255
2256        // If the server name is available, first try using it
2257        let response = if let Some(server_url) = server_url {
2258            // First try using the server name
2259            self.fetch_client_well_known_with_url(server_url).await
2260        } else {
2261            None
2262        };
2263
2264        // If we didn't get a well-known value yet, try with the homeserver url instead:
2265        if response.is_none() {
2266            // Sometimes people configure their well-known directly on the homeserver so use
2267            // this as a fallback when the server name is unknown.
2268            warn!(
2269                "Fetching the well-known from the server name didn't work, using the homeserver url instead"
2270            );
2271            self.fetch_client_well_known_with_url(homeserver.to_string()).await
2272        } else {
2273            response
2274        }
2275    }
2276
2277    async fn fetch_client_well_known_with_url(
2278        &self,
2279        url: String,
2280    ) -> Option<discover_homeserver::Response> {
2281        let well_known = self
2282            .inner
2283            .http_client
2284            .send(
2285                discover_homeserver::Request::new(),
2286                Some(RequestConfig::short_retry()),
2287                url,
2288                None,
2289                (),
2290                Default::default(),
2291            )
2292            .await;
2293
2294        match well_known {
2295            Ok(well_known) => Some(well_known),
2296            Err(http_error) => {
2297                // It is perfectly valid to not have a well-known file.
2298                // Maybe we should check for a specific error code to be sure?
2299                warn!("Failed to fetch client well-known: {http_error}");
2300                None
2301            }
2302        }
2303    }
2304
2305    /// Load supported versions from storage, or fetch them from network and
2306    /// cache them.
2307    ///
2308    /// If `failsafe` is true, this will try to minimize side effects to avoid
2309    /// possible deadlocks.
2310    async fn fetch_supported_versions(
2311        &self,
2312        failsafe: bool,
2313    ) -> HttpResult<SupportedVersionsResponse> {
2314        let server_versions = self.fetch_server_versions_inner(failsafe, None).await?;
2315        let supported_versions = SupportedVersionsResponse {
2316            versions: server_versions.versions,
2317            unstable_features: server_versions.unstable_features,
2318        };
2319
2320        Ok(supported_versions)
2321    }
2322
2323    /// Get the Matrix versions and features supported by the homeserver by
2324    /// fetching them from the server or the cache.
2325    ///
2326    /// This is equivalent to calling both [`Client::server_versions()`] and
2327    /// [`Client::unstable_features()`]. To always fetch the result from the
2328    /// homeserver, you can call [`Client::fetch_server_versions()`] instead,
2329    /// and then `.as_supported_versions()` on the response.
2330    ///
2331    /// # Examples
2332    ///
2333    /// ```no_run
2334    /// use ruma::api::{FeatureFlag, MatrixVersion};
2335    /// # use matrix_sdk::{Client, config::SyncSettings};
2336    /// # use url::Url;
2337    /// # async {
2338    /// # let homeserver = Url::parse("http://localhost:8080")?;
2339    /// # let mut client = Client::new(homeserver).await?;
2340    ///
2341    /// let supported = client.supported_versions().await?;
2342    /// let supports_1_1 = supported.versions.contains(&MatrixVersion::V1_1);
2343    /// println!("The homeserver supports Matrix 1.1: {supports_1_1:?}");
2344    ///
2345    /// let msc_x_feature = FeatureFlag::from("msc_x");
2346    /// let supports_msc_x = supported.features.contains(&msc_x_feature);
2347    /// println!("The homeserver supports msc X: {supports_msc_x:?}");
2348    /// # anyhow::Ok(()) };
2349    /// ```
2350    pub async fn supported_versions(&self) -> HttpResult<SupportedVersions> {
2351        self.supported_versions_inner(false).await
2352    }
2353
2354    /// Get the Matrix versions and features supported by the homeserver by
2355    /// fetching them from the server or the cache.
2356    ///
2357    /// If `failsafe` is true, this will try to minimize side effects to avoid
2358    /// possible deadlocks.
2359    pub(crate) async fn supported_versions_inner(
2360        &self,
2361        failsafe: bool,
2362    ) -> HttpResult<SupportedVersions> {
2363        match self.supported_versions_cached_inner(failsafe).await {
2364            Ok(Some(value)) => {
2365                return Ok(value);
2366            }
2367            Ok(None) => {
2368                // The cache is empty, make a request.
2369            }
2370            Err(error) => {
2371                warn!("error when loading cached supported versions: {error}");
2372                // Fallthrough to make a request.
2373            }
2374        }
2375
2376        self.refresh_supported_versions_cache(failsafe).await
2377    }
2378
2379    /// Refresh the Matrix versions and features supported by the homeserver in
2380    /// the cache.
2381    ///
2382    /// If `failsafe` is true, this will try to minimize side effects to avoid
2383    /// possible deadlocks.
2384    async fn refresh_supported_versions_cache(
2385        &self,
2386        failsafe: bool,
2387    ) -> HttpResult<SupportedVersions> {
2388        let cached_supported_versions = &self.inner.caches.supported_versions;
2389
2390        let mut supported_versions_guard = match cached_supported_versions.refresh_lock.try_lock() {
2391            Ok(guard) => guard,
2392            Err(_) => {
2393                // There is already a refresh in progress, wait for it to finish.
2394                let guard = cached_supported_versions.refresh_lock.lock().await;
2395
2396                if let Err(error) = guard.as_ref() {
2397                    // There was an error in the previous refresh, return it.
2398                    return Err(HttpError::Cached(error.clone()));
2399                }
2400
2401                // Reuse the data if it was cached and it hasn't expired.
2402                if let CachedValue::Cached(value) = cached_supported_versions.value()
2403                    && !value.has_expired()
2404                {
2405                    return Ok(value.into_data());
2406                }
2407
2408                // The data wasn't cached or has expired, we need to make another request.
2409                guard
2410            }
2411        };
2412
2413        let response = match self.fetch_supported_versions(failsafe).await {
2414            Ok(response) => {
2415                *supported_versions_guard = Ok(());
2416                TtlValue::new(response)
2417            }
2418            Err(error) => {
2419                let error = Arc::new(error);
2420                *supported_versions_guard = Err(error.clone());
2421                return Err(HttpError::Cached(error));
2422            }
2423        };
2424
2425        let supported_versions = response.as_ref().map(|response| response.supported_versions());
2426
2427        // Only cache the result if the request was authenticated.
2428        if self.auth_ctx().has_valid_access_token() {
2429            if let Err(err) = self
2430                .state_store()
2431                .set_kv_data(
2432                    StateStoreDataKey::SupportedVersions,
2433                    StateStoreDataValue::SupportedVersions(response),
2434                )
2435                .await
2436            {
2437                warn!("error when caching supported versions: {err}");
2438            }
2439
2440            cached_supported_versions.set_value(supported_versions.clone());
2441        }
2442
2443        Ok(supported_versions.into_data())
2444    }
2445
2446    /// Get the Matrix versions and features supported by the homeserver by
2447    /// fetching them from the cache.
2448    ///
2449    /// For a version of this function that fetches the supported versions and
2450    /// features from the homeserver if the [`SupportedVersions`] aren't
2451    /// found in the cache, take a look at the [`Client::supported_versions()`]
2452    /// method.
2453    ///
2454    /// If the data in the cache has expired, this will trigger a background
2455    /// task to refresh it.
2456    ///
2457    /// # Examples
2458    ///
2459    /// ```no_run
2460    /// use ruma::api::{FeatureFlag, MatrixVersion};
2461    /// # use matrix_sdk::{Client, config::SyncSettings};
2462    /// # use url::Url;
2463    /// # async {
2464    /// # let homeserver = Url::parse("http://localhost:8080")?;
2465    /// # let mut client = Client::new(homeserver).await?;
2466    ///
2467    /// let supported =
2468    ///     if let Some(supported) = client.supported_versions_cached().await? {
2469    ///         supported
2470    ///     } else {
2471    ///         client.fetch_server_versions(None).await?.as_supported_versions()
2472    ///     };
2473    ///
2474    /// let supports_1_1 = supported.versions.contains(&MatrixVersion::V1_1);
2475    /// println!("The homeserver supports Matrix 1.1: {supports_1_1:?}");
2476    ///
2477    /// let msc_x_feature = FeatureFlag::from("msc_x");
2478    /// let supports_msc_x = supported.features.contains(&msc_x_feature);
2479    /// println!("The homeserver supports msc X: {supports_msc_x:?}");
2480    /// # anyhow::Ok(()) };
2481    /// ```
2482    pub async fn supported_versions_cached(&self) -> Result<Option<SupportedVersions>, StoreError> {
2483        self.supported_versions_cached_inner(false).await
2484    }
2485
2486    async fn supported_versions_cached_inner(
2487        &self,
2488        failsafe: bool,
2489    ) -> Result<Option<SupportedVersions>, StoreError> {
2490        let supported_versions_cache = &self.inner.caches.supported_versions;
2491
2492        let value = if let CachedValue::Cached(cached) = supported_versions_cache.value() {
2493            cached
2494        } else if let Some(stored) = self
2495            .state_store()
2496            .get_kv_data(StateStoreDataKey::SupportedVersions)
2497            .await?
2498            .and_then(|value| value.into_supported_versions())
2499        {
2500            let stored = stored.map(|response| response.supported_versions());
2501
2502            // Copy the data from the store in the in-memory cache.
2503            supported_versions_cache.set_value(stored.clone());
2504
2505            stored
2506        } else {
2507            return Ok(None);
2508        };
2509
2510        // Spawn a task to refresh the cache if it has expired and we have a valid
2511        // access token.
2512        if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2513            debug!("spawning task to refresh supported versions cache");
2514
2515            let client = self.clone();
2516            self.task_monitor().spawn_finite_task("refresh supported versions cache", async move {
2517                if let Err(error) = client.refresh_supported_versions_cache(failsafe).await {
2518                    warn!("failed to refresh supported versions cache: {error}");
2519                }
2520            });
2521        }
2522
2523        Ok(Some(value.into_data()))
2524    }
2525
2526    /// Get the Matrix versions supported by the homeserver by fetching them
2527    /// from the server or the cache.
2528    ///
2529    /// # Examples
2530    ///
2531    /// ```no_run
2532    /// use ruma::api::MatrixVersion;
2533    /// # use matrix_sdk::{Client, config::SyncSettings};
2534    /// # use url::Url;
2535    /// # async {
2536    /// # let homeserver = Url::parse("http://localhost:8080")?;
2537    /// # let mut client = Client::new(homeserver).await?;
2538    ///
2539    /// let server_versions = client.server_versions().await?;
2540    /// let supports_1_1 = server_versions.contains(&MatrixVersion::V1_1);
2541    /// println!("The homeserver supports Matrix 1.1: {supports_1_1:?}");
2542    /// # anyhow::Ok(()) };
2543    /// ```
2544    pub async fn server_versions(&self) -> HttpResult<BTreeSet<MatrixVersion>> {
2545        Ok(self.supported_versions().await?.versions)
2546    }
2547
2548    /// Get the unstable features supported by the homeserver by fetching them
2549    /// from the server or the cache.
2550    ///
2551    /// # Examples
2552    ///
2553    /// ```no_run
2554    /// use matrix_sdk::ruma::api::FeatureFlag;
2555    /// # use matrix_sdk::{Client, config::SyncSettings};
2556    /// # use url::Url;
2557    /// # async {
2558    /// # let homeserver = Url::parse("http://localhost:8080")?;
2559    /// # let mut client = Client::new(homeserver).await?;
2560    ///
2561    /// let msc_x_feature = FeatureFlag::from("msc_x");
2562    /// let unstable_features = client.unstable_features().await?;
2563    /// let supports_msc_x = unstable_features.contains(&msc_x_feature);
2564    /// println!("The homeserver supports msc X: {supports_msc_x:?}");
2565    /// # anyhow::Ok(()) };
2566    /// ```
2567    pub async fn unstable_features(&self) -> HttpResult<BTreeSet<FeatureFlag>> {
2568        Ok(self.supported_versions().await?.features)
2569    }
2570
2571    /// Empty the supported versions and unstable features cache.
2572    ///
2573    /// Since the SDK caches the supported versions, it's possible to have a
2574    /// stale entry in the cache. This functions makes it possible to force
2575    /// reset it.
2576    pub async fn reset_supported_versions(&self) -> Result<()> {
2577        // Empty the in-memory cache.
2578        self.inner.caches.supported_versions.reset();
2579
2580        // Empty the store cache.
2581        Ok(self.state_store().remove_kv_data(StateStoreDataKey::SupportedVersions).await?)
2582    }
2583
2584    /// Get the well-known file of the homeserver from the cache.
2585    ///
2586    /// If the data in the cache has expired, this will trigger a background
2587    /// task to refresh it.
2588    async fn well_known_cached(
2589        &self,
2590    ) -> Result<CachedValue<Option<WellKnownResponse>>, StoreError> {
2591        let well_known_cache = &self.inner.caches.well_known;
2592
2593        let value = if let CachedValue::Cached(cached) = well_known_cache.value() {
2594            cached
2595        } else if let Some(stored) = self
2596            .state_store()
2597            .get_kv_data(StateStoreDataKey::WellKnown)
2598            .await?
2599            .and_then(|value| value.into_well_known())
2600        {
2601            // Copy the data from the store into the in-memory cache.
2602            well_known_cache.set_value(stored.clone());
2603
2604            stored
2605        } else {
2606            return Ok(CachedValue::NotSet);
2607        };
2608
2609        // Spawn a task to refresh the cache if it has expired.
2610        if value.has_expired() {
2611            debug!("spawning task to refresh well-known cache");
2612
2613            let client = self.clone();
2614            self.task_monitor().spawn_finite_task("refresh well-known cache", async move {
2615                client.refresh_well_known_cache().await;
2616            });
2617        }
2618
2619        Ok(CachedValue::Cached(value.into_data()))
2620    }
2621
2622    /// Refresh the well-known file of the homeserver in the cache.
2623    async fn refresh_well_known_cache(&self) -> Option<WellKnownResponse> {
2624        let well_known_cache = &self.inner.caches.well_known;
2625
2626        let _well_known_guard = match well_known_cache.refresh_lock.try_lock() {
2627            Ok(guard) => guard,
2628            Err(_) => {
2629                // There is already a refresh in progress, wait for it to finish.
2630                let guard = well_known_cache.refresh_lock.lock().await;
2631
2632                // A refresh can't fail because we ignore failures, so there shouldn't be an
2633                // error in the refresh lock.
2634
2635                // Reuse the data if it was cached and it hasn't expired.
2636                if let CachedValue::Cached(value) = well_known_cache.value()
2637                    && !value.has_expired()
2638                {
2639                    return value.into_data();
2640                }
2641
2642                // The data wasn't cached or has expired, we need to make another request.
2643                guard
2644            }
2645        };
2646
2647        let well_known = TtlValue::new(self.fetch_client_well_known().await.map(Into::into));
2648
2649        if let Err(err) = self
2650            .state_store()
2651            .set_kv_data(
2652                StateStoreDataKey::WellKnown,
2653                StateStoreDataValue::WellKnown(well_known.clone()),
2654            )
2655            .await
2656        {
2657            warn!("error when caching well-known: {err}");
2658        }
2659
2660        well_known_cache.set_value(well_known.clone());
2661
2662        well_known.into_data()
2663    }
2664
2665    /// Whether this client is allowed to look up the homeserver's
2666    /// /.well-known/matrix/client file.
2667    fn well_known_lookup_disabled(&self) -> bool {
2668        *self.inner.well_known_lookup_disabled.read().unwrap()
2669    }
2670
2671    /// Change whether this client is allowed to look up the homeserver's
2672    /// /.well-known/matrix/client file.
2673    pub fn disable_well_known_lookup(&self, disable: bool) {
2674        *self.inner.well_known_lookup_disabled.write().unwrap() = disable;
2675    }
2676
2677    /// Get the well-known file of the homeserver by fetching it from the server
2678    /// or the cache.
2679    ///
2680    /// Always returns `None` if well-known discovery was disabled with
2681    /// [`ClientBuilder::disable_well_known_lookup`].
2682    async fn well_known(&self) -> Option<WellKnownResponse> {
2683        if self.well_known_lookup_disabled() {
2684            return None;
2685        }
2686
2687        match self.well_known_cached().await {
2688            Ok(CachedValue::Cached(value)) => {
2689                return value;
2690            }
2691            Ok(CachedValue::NotSet) => {
2692                // The cache is empty, make a request.
2693            }
2694            Err(error) => {
2695                warn!("error when loading cached well-known: {error}");
2696                // Fallthrough to make a request.
2697            }
2698        }
2699
2700        self.refresh_well_known_cache().await
2701    }
2702
2703    /// Get information about the homeserver's advertised RTC transports by
2704    /// fetching the well-known file from the server or the cache.
2705    ///
2706    /// Returns an empty list if well-known discovery was disabled with
2707    /// [`ClientBuilder::disable_well_known_lookup`].
2708    #[deprecated = "Use `Client::discover_rtc_transports` instead"]
2709    pub async fn rtc_foci(&self) -> HttpResult<Vec<RtcTransport>> {
2710        self.well_known_rtc_transports().await
2711    }
2712
2713    /// Get information about the homeserver's advertised RTC foci by fetching
2714    /// the well-known file from the server or the cache.
2715    ///
2716    /// This will be soon deprecated in favor of
2717    /// [`Client::discover_rtc_transports`], which fetches the RTC
2718    /// transports advertised by the homeserver through the authenticated
2719    /// `GET /_matrix/client/v1/rtc/transports` endpoint.
2720    ///
2721    /// Returns an empty list if well-known discovery was disabled with
2722    /// [`ClientBuilder::disable_well_known_lookup`].
2723    ///
2724    /// # Examples
2725    /// ```no_run
2726    /// # use matrix_sdk::{Client, config::SyncSettings, ruma::api::client::rtc::RtcTransport};
2727    /// # use url::Url;
2728    /// # async {
2729    /// # let homeserver = Url::parse("http://localhost:8080")?;
2730    /// # let mut client = Client::new(homeserver).await?;
2731    /// let rtc_foci = client.well_known_rtc_transports().await?;
2732    /// let default_livekit_focus_info = rtc_foci.iter().find_map(|focus| match focus {
2733    ///     RtcTransport::LiveKit(info) => Some(info),
2734    ///     _ => None,
2735    /// });
2736    /// if let Some(info) = default_livekit_focus_info {
2737    ///     println!("Default LiveKit service URL: {}", info.service_url);
2738    /// }
2739    /// # anyhow::Ok(()) };
2740    /// ```
2741    pub async fn well_known_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
2742        let well_known = self.well_known().await;
2743
2744        Ok(well_known.map(|well_known| well_known.rtc_foci).unwrap_or_default())
2745    }
2746
2747    /// Get the RTC transports advertised by the homeserver by fetching them
2748    /// from the server or the cache.
2749    ///
2750    /// The transports are discovered through the authenticated
2751    /// `GET /_matrix/client/v1/rtc/transports` endpoint
2752    /// ([MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)).
2753    async fn rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2754        match self.rtc_transports_cached() {
2755            CachedValue::Cached(value) => Ok(value),
2756            // The cache is empty, make a request.
2757            CachedValue::NotSet => self.refresh_rtc_transports_cache().await,
2758        }
2759    }
2760
2761    /// Get the RTC transports advertised by the homeserver from the cache.
2762    ///
2763    /// Returns [`CachedValue::NotSet`] if nothing has been cached yet. If the
2764    /// cached data has expired, this triggers a background task to refresh it
2765    /// and returns the stale value.
2766    fn rtc_transports_cached(&self) -> CachedValue<Option<Vec<RtcTransport>>> {
2767        let cache = &self.inner.caches.rtc_transports;
2768
2769        let CachedValue::Cached(value) = cache.value() else {
2770            return CachedValue::NotSet;
2771        };
2772
2773        // Spawn a task to refresh the cache if it has expired and we have a valid
2774        // access token.
2775        if value.has_expired() && self.auth_ctx().has_valid_access_token() {
2776            debug!("spawning task to refresh RTC transports cache");
2777
2778            let client = self.clone();
2779            self.task_monitor().spawn_finite_task("refresh RTC transports cache", async move {
2780                if let Err(error) = client.refresh_rtc_transports_cache().await {
2781                    warn!("failed to refresh RTC transports cache: {error}");
2782                }
2783            });
2784        }
2785
2786        CachedValue::Cached(value.into_data())
2787    }
2788
2789    /// Refresh the RTC transports advertised by the homeserver in the cache.
2790    async fn refresh_rtc_transports_cache(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2791        let cache = &self.inner.caches.rtc_transports;
2792
2793        let mut refresh_guard = match cache.refresh_lock.try_lock() {
2794            Ok(guard) => guard,
2795            Err(_) => {
2796                // There is already a refresh in progress, wait for it to finish.
2797                let guard = cache.refresh_lock.lock().await;
2798
2799                if let Err(error) = guard.as_ref() {
2800                    // There was an error in the previous refresh, return it.
2801                    return Err(HttpError::Cached(error.clone()));
2802                }
2803
2804                // Reuse the data if it was cached and it hasn't expired.
2805                if let CachedValue::Cached(value) = cache.value()
2806                    && !value.has_expired()
2807                {
2808                    return Ok(value.into_data());
2809                }
2810
2811                // The data wasn't cached or has expired, we need to make another request.
2812                guard
2813            }
2814        };
2815
2816        match self.fetch_rtc_transports().await {
2817            Ok(transports) => {
2818                *refresh_guard = Ok(());
2819                cache.set_value(TtlValue::new(Some(transports.clone())));
2820                Ok(Some(transports))
2821            }
2822            Err(error) if error.is_endpoint_not_implemented() => {
2823                // The homeserver doesn't implement the RTC transports endpoint. Cache
2824                // `None` (with the normal TTL) so we don't hit the endpoint on every
2825                // call; this self-heals after the TTL in case the homeserver is
2826                // upgraded. `None` is kept distinct from `Some(vec![])` (a homeserver
2827                // that advertises no transports) so callers can decide whether to fall
2828                // back to the well-known foci (see `Client::rtc_foci`).
2829                debug!("homeserver does not implement the RTC transports endpoint");
2830                *refresh_guard = Ok(());
2831                cache.set_value(TtlValue::new(None));
2832                Ok(None)
2833            }
2834            Err(error) => {
2835                let error = Arc::new(error);
2836                *refresh_guard = Err(error.clone());
2837                Err(HttpError::Cached(error))
2838            }
2839        }
2840    }
2841
2842    /// Fetch the RTC transports advertised by the homeserver from the network,
2843    /// bypassing the cache.
2844    pub async fn fetch_rtc_transports(&self) -> HttpResult<Vec<RtcTransport>> {
2845        let response = self
2846            .send(transports::v1::Request::new())
2847            .with_request_config(RequestConfig::short_retry())
2848            .await?;
2849        Ok(response.rtc_transports)
2850    }
2851
2852    /// Empty the RTC transports cache.
2853    ///
2854    /// Since the SDK caches the RTC transports, it's possible to have a stale
2855    /// entry in the cache. This function makes it possible to force reset it.
2856    pub fn reset_rtc_transports(&self) {
2857        self.inner.caches.rtc_transports.reset();
2858    }
2859
2860    /// Discover the RTC transports advertised by the homeserver.
2861    ///
2862    /// The transports are first looked up through the authenticated
2863    /// `GET /_matrix/client/v1/rtc/transports` endpoint
2864    /// ([MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)).
2865    /// If the homeserver doesn't implement that endpoint, this falls back to
2866    /// the `m.rtc_foci` field of the well-known, see
2867    /// [`Client::well_known_rtc_transports`] — unless well-known discovery
2868    /// was disabled with [`ClientBuilder::disable_well_known_lookup`].
2869    ///
2870    /// Returns `None` if neither source could provide transports, which is
2871    /// kept distinct from `Some(vec![])`, i.e. a homeserver that advertises no
2872    /// transports at all.
2873    ///
2874    /// # Examples
2875    /// ```no_run
2876    /// # use matrix_sdk::Client;
2877    /// # use url::Url;
2878    /// # async {
2879    /// # let homeserver = Url::parse("http://localhost:8080")?;
2880    /// # let client = Client::new(homeserver).await?;
2881    /// for transport in client.discover_rtc_transports().await?.unwrap_or_default()
2882    /// {
2883    ///     println!("transport type: {}", transport.transport_type());
2884    /// }
2885    /// # anyhow::Ok(()) };
2886    /// ```
2887    pub async fn discover_rtc_transports(&self) -> HttpResult<Option<Vec<RtcTransport>>> {
2888        if let Some(transports) = self.rtc_transports().await? {
2889            return Ok(Some(transports));
2890        }
2891
2892        // The homeserver doesn't implement the discovery endpoint or does not expose
2893        // any transports, fall back to the well-known foci.
2894        // `well_known` returns `None` when well-known discovery is
2895        // disabled, which correctly collapses into "nothing was discovered".
2896        Ok(self.well_known().await.map(|well_known| well_known.rtc_foci))
2897    }
2898
2899    /// Get information about the homeserver's advertised map tile server, if
2900    /// any, by fetching the well-known file from the server or the cache.
2901    ///
2902    /// Returns `None` if the homeserver has not advertised a tile server in its
2903    /// well-known, or if the well-known is otherwise unavailable — including
2904    /// when well-known discovery was disabled with
2905    /// [`ClientBuilder::disable_well_known_lookup`].
2906    pub async fn tile_server(&self) -> Option<TileServerInfo> {
2907        self.well_known().await.and_then(|well_known| well_known.tile_server).map(Into::into)
2908    }
2909
2910    /// Empty the well-known cache.
2911    ///
2912    /// Since the SDK caches the well-known, it's possible to have a stale entry
2913    /// in the cache. This functions makes it possible to force reset it.
2914    pub async fn reset_well_known(&self) -> Result<()> {
2915        // Empty the in-memory caches.
2916        self.inner.caches.well_known.reset();
2917
2918        // Empty the store cache.
2919        Ok(self.state_store().remove_kv_data(StateStoreDataKey::WellKnown).await?)
2920    }
2921
2922    /// Check whether MSC 4028 is enabled on the homeserver.
2923    ///
2924    /// # Examples
2925    ///
2926    /// ```no_run
2927    /// # use matrix_sdk::{Client, config::SyncSettings};
2928    /// # use url::Url;
2929    /// # async {
2930    /// # let homeserver = Url::parse("http://localhost:8080")?;
2931    /// # let mut client = Client::new(homeserver).await?;
2932    /// let msc4028_enabled =
2933    ///     client.can_homeserver_push_encrypted_event_to_device().await?;
2934    /// # anyhow::Ok(()) };
2935    /// ```
2936    pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> HttpResult<bool> {
2937        Ok(self.unstable_features().await?.contains(&FeatureFlag::from("org.matrix.msc4028")))
2938    }
2939
2940    /// Get information of all our own devices.
2941    ///
2942    /// # Examples
2943    ///
2944    /// ```no_run
2945    /// # use matrix_sdk::{Client, config::SyncSettings};
2946    /// # use url::Url;
2947    /// # async {
2948    /// # let homeserver = Url::parse("http://localhost:8080")?;
2949    /// # let mut client = Client::new(homeserver).await?;
2950    /// let response = client.devices().await?;
2951    ///
2952    /// for device in response.devices {
2953    ///     println!(
2954    ///         "Device: {} {}",
2955    ///         device.device_id,
2956    ///         device.display_name.as_deref().unwrap_or("")
2957    ///     );
2958    /// }
2959    /// # anyhow::Ok(()) };
2960    /// ```
2961    pub async fn devices(&self) -> HttpResult<get_devices::v3::Response> {
2962        let request = get_devices::v3::Request::new();
2963
2964        self.send(request).await
2965    }
2966
2967    /// Get the server's message retention policy configuration.
2968    ///
2969    /// Returns the server-level retention policy limits and any per-room
2970    /// overrides defined by the server.
2971    ///
2972    /// See [MSC1763](https://github.com/matrix-org/matrix-spec-proposals/pull/1763) for more info.
2973    pub async fn get_retention_configuration(
2974        &self,
2975    ) -> HttpResult<get_retention_configuration::unstable::Response> {
2976        self.send(get_retention_configuration::unstable::Request::default()).await
2977    }
2978
2979    /// Delete the given devices from the server.
2980    ///
2981    /// # Arguments
2982    ///
2983    /// * `devices` - The list of devices that should be deleted from the
2984    ///   server.
2985    ///
2986    /// * `auth_data` - This request requires user interactive auth, the first
2987    ///   request needs to set this to `None` and will always fail with an
2988    ///   `UiaaResponse`. The response will contain information for the
2989    ///   interactive auth and the same request needs to be made but this time
2990    ///   with some `auth_data` provided.
2991    ///
2992    /// ```no_run
2993    /// # use matrix_sdk::{
2994    /// #    ruma::{api::client::uiaa, owned_device_id},
2995    /// #    Client, Error, config::SyncSettings,
2996    /// # };
2997    /// # use serde_json::json;
2998    /// # use url::Url;
2999    /// # use std::collections::BTreeMap;
3000    /// # async {
3001    /// # let homeserver = Url::parse("http://localhost:8080")?;
3002    /// # let mut client = Client::new(homeserver).await?;
3003    /// let devices = &[owned_device_id!("DEVICEID")];
3004    ///
3005    /// if let Err(e) = client.delete_devices(devices, None).await {
3006    ///     if let Some(info) = e.as_uiaa_response() {
3007    ///         let mut password = uiaa::Password::new(
3008    ///             uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier::new("example".to_owned())),
3009    ///             "wordpass".to_owned(),
3010    ///         );
3011    ///         password.session = info.session.clone();
3012    ///
3013    ///         client
3014    ///             .delete_devices(devices, Some(uiaa::AuthData::Password(password)))
3015    ///             .await?;
3016    ///     }
3017    /// }
3018    /// # anyhow::Ok(()) };
3019    pub async fn delete_devices(
3020        &self,
3021        devices: &[OwnedDeviceId],
3022        auth_data: Option<uiaa::AuthData>,
3023    ) -> HttpResult<delete_devices::v3::Response> {
3024        let mut request = delete_devices::v3::Request::new(devices.to_owned());
3025        request.auth = auth_data;
3026
3027        self.send(request).await
3028    }
3029
3030    /// Change the display name of a device owned by the current user.
3031    ///
3032    /// Returns a `update_device::Response` which specifies the result
3033    /// of the operation.
3034    ///
3035    /// # Arguments
3036    ///
3037    /// * `device_id` - The ID of the device to change the display name of.
3038    /// * `display_name` - The new display name to set.
3039    pub async fn rename_device(
3040        &self,
3041        device_id: &DeviceId,
3042        display_name: &str,
3043    ) -> HttpResult<update_device::v3::Response> {
3044        let mut request = update_device::v3::Request::new(device_id.to_owned());
3045        request.display_name = Some(display_name.to_owned());
3046
3047        self.send(request).await
3048    }
3049
3050    /// Check whether a device with a specific ID exists on the server.
3051    ///
3052    /// Returns Ok(true) if the device exists, Ok(false) if the server responded
3053    /// with 404 and the underlying error otherwise.
3054    ///
3055    /// # Arguments
3056    ///
3057    /// * `device_id` - The ID of the device to query.
3058    pub async fn device_exists(&self, device_id: OwnedDeviceId) -> Result<bool> {
3059        let request = device::get_device::v3::Request::new(device_id);
3060        match self.send(request).await {
3061            Ok(_) => Ok(true),
3062            Err(err) => {
3063                if let Some(error) = err.as_client_api_error()
3064                    && error.status_code == 404
3065                {
3066                    Ok(false)
3067                } else {
3068                    Err(err.into())
3069                }
3070            }
3071        }
3072    }
3073
3074    /// Synchronize the client's state with the latest state on the server.
3075    ///
3076    /// ## Syncing Events
3077    ///
3078    /// Messages or any other type of event need to be periodically fetched from
3079    /// the server, this is achieved by sending a `/sync` request to the server.
3080    ///
3081    /// The first sync is sent out without a [`token`]. The response of the
3082    /// first sync will contain a [`next_batch`] field which should then be
3083    /// used in the subsequent sync calls as the [`token`]. This ensures that we
3084    /// don't receive the same events multiple times.
3085    ///
3086    /// ## Long Polling
3087    ///
3088    /// A sync should in the usual case always be in flight. The
3089    /// [`SyncSettings`] have a  [`timeout`] option, which controls how
3090    /// long the server will wait for new events before it will respond.
3091    /// The server will respond immediately if some new events arrive before the
3092    /// timeout has expired. If no changes arrive and the timeout expires an
3093    /// empty sync response will be sent to the client.
3094    ///
3095    /// This method of sending a request that may not receive a response
3096    /// immediately is called long polling.
3097    ///
3098    /// ## Filtering Events
3099    ///
3100    /// The number or type of messages and events that the client should receive
3101    /// from the server can be altered using a [`Filter`].
3102    ///
3103    /// Filters can be non-trivial and, since they will be sent with every sync
3104    /// request, they may take up a bunch of unnecessary bandwidth.
3105    ///
3106    /// Luckily filters can be uploaded to the server and reused using an unique
3107    /// identifier, this can be achieved using the [`get_or_upload_filter()`]
3108    /// method.
3109    ///
3110    /// # Arguments
3111    ///
3112    /// * `sync_settings` - Settings for the sync call, this allows us to set
3113    /// various options to configure the sync:
3114    ///     * [`filter`] - To configure which events we receive and which get
3115    ///       [filtered] by the server
3116    ///     * [`timeout`] - To configure our [long polling] setup.
3117    ///     * [`token`] - To tell the server which events we already received
3118    ///       and where we wish to continue syncing.
3119    ///     * [`full_state`] - To tell the server that we wish to receive all
3120    ///       state events, regardless of our configured [`token`].
3121    ///     * [`set_presence`] - To override the presence state sent with this
3122    ///       classic `/sync` request. If this is not set, the request uses the
3123    ///       client-owned sync presence configured with
3124    ///       [`Client::set_presence`], which defaults to
3125    ///       [`PresenceState::Online`].
3126    ///
3127    /// # Examples
3128    ///
3129    /// ```no_run
3130    /// # use url::Url;
3131    /// # async {
3132    /// # let homeserver = Url::parse("http://localhost:8080")?;
3133    /// # let username = "";
3134    /// # let password = "";
3135    /// use matrix_sdk::{
3136    ///     Client, config::SyncSettings,
3137    ///     ruma::events::room::message::OriginalSyncRoomMessageEvent,
3138    /// };
3139    ///
3140    /// let client = Client::new(homeserver).await?;
3141    /// client.matrix_auth().login_username(username, password).send().await?;
3142    ///
3143    /// // Sync once so we receive the client state and old messages.
3144    /// client.sync_once(SyncSettings::default()).await?;
3145    ///
3146    /// // Register our handler so we start responding once we receive a new
3147    /// // event.
3148    /// client.add_event_handler(|ev: OriginalSyncRoomMessageEvent| async move {
3149    ///     println!("Received event {}: {:?}", ev.sender, ev.content);
3150    /// });
3151    ///
3152    /// // Now keep on syncing forever. `sync()` will use the stored sync token
3153    /// // from our `sync_once()` call automatically.
3154    /// client.sync(SyncSettings::default()).await;
3155    /// # anyhow::Ok(()) };
3156    /// ```
3157    ///
3158    /// [`sync`]: #method.sync
3159    /// [`SyncSettings`]: crate::config::SyncSettings
3160    /// [`token`]: crate::config::SyncSettings#method.token
3161    /// [`timeout`]: crate::config::SyncSettings#method.timeout
3162    /// [`full_state`]: crate::config::SyncSettings#method.full_state
3163    /// [`set_presence`]: crate::config::SyncSettings::set_presence
3164    /// [`filter`]: crate::config::SyncSettings#method.filter
3165    /// [`Filter`]: ruma::api::client::sync::sync_events::v3::Filter
3166    /// [`next_batch`]: SyncResponse#structfield.next_batch
3167    /// [`get_or_upload_filter()`]: #method.get_or_upload_filter
3168    /// [long polling]: #long-polling
3169    /// [filtered]: #filtering-events
3170    #[instrument(skip(self))]
3171    pub async fn sync_once(
3172        &self,
3173        sync_settings: crate::config::SyncSettings,
3174    ) -> Result<SyncResponse> {
3175        // The sync might not return for quite a while due to the timeout.
3176        // We'll see if there's anything crypto related to send out before we
3177        // sync, i.e. if we closed our client after a sync but before the
3178        // crypto requests were sent out.
3179        //
3180        // This will mostly be a no-op.
3181        #[cfg(feature = "e2e-encryption")]
3182        if let Err(e) = self.send_outgoing_requests().await {
3183            error!(error = ?e, "Error while sending outgoing E2EE requests");
3184        }
3185
3186        let token = match sync_settings.token {
3187            SyncToken::Specific(token) => Some(token),
3188            SyncToken::NoToken => None,
3189            SyncToken::ReusePrevious => self.sync_token().await,
3190        };
3191
3192        let request = assign!(sync_events::v3::Request::new(), {
3193            filter: sync_settings.filter.map(|f| *f),
3194            since: token,
3195            full_state: sync_settings.full_state,
3196            set_presence: sync_settings.set_presence.unwrap_or_else(|| self.sync_presence()),
3197            timeout: sync_settings.timeout,
3198            use_state_after: true,
3199        });
3200        let mut request_config = self.request_config();
3201        if let Some(timeout) = sync_settings.timeout {
3202            let base_timeout = request_config.timeout.unwrap_or(Duration::from_secs(30));
3203            request_config.timeout = Some(base_timeout + timeout);
3204        }
3205
3206        let response = self.send(request).with_request_config(request_config).await?;
3207        let next_batch = response.next_batch.clone();
3208        let response = self.process_sync(response).await?;
3209
3210        #[cfg(feature = "e2e-encryption")]
3211        if let Err(e) = self.send_outgoing_requests().await {
3212            error!(error = ?e, "Error while sending outgoing E2EE requests");
3213        }
3214
3215        self.inner.sync_beat.notify(usize::MAX);
3216
3217        Ok(SyncResponse::new(next_batch, response))
3218    }
3219
3220    /// Repeatedly synchronize the client state with the server.
3221    ///
3222    /// This method will only return on error, if cancellation is needed
3223    /// the method should be wrapped in a cancelable task or the
3224    /// [`Client::sync_with_callback`] method can be used or
3225    /// [`Client::sync_with_result_callback`] if you want to handle error
3226    /// cases in the loop, too.
3227    ///
3228    /// This method will internally call [`Client::sync_once`] in a loop.
3229    ///
3230    /// This method can be used with the [`Client::add_event_handler`]
3231    /// method to react to individual events. If you instead wish to handle
3232    /// events in a bulk manner the [`Client::sync_with_callback`],
3233    /// [`Client::sync_with_result_callback`] and
3234    /// [`Client::sync_stream`] methods can be used instead. Those methods
3235    /// repeatedly return the whole sync response.
3236    ///
3237    /// # Arguments
3238    ///
3239    /// * `sync_settings` - Settings for the sync call. *Note* that those
3240    ///   settings will be only used for the first sync call. See the argument
3241    ///   docs for [`Client::sync_once`] for more info.
3242    ///
3243    /// # Return
3244    /// The sync runs until an error occurs, returning with `Err(Error)`. It is
3245    /// up to the user of the API to check the error and decide whether the sync
3246    /// should continue or not.
3247    ///
3248    /// # Examples
3249    ///
3250    /// ```no_run
3251    /// # use url::Url;
3252    /// # async {
3253    /// # let homeserver = Url::parse("http://localhost:8080")?;
3254    /// # let username = "";
3255    /// # let password = "";
3256    /// use matrix_sdk::{
3257    ///     Client, config::SyncSettings,
3258    ///     ruma::events::room::message::OriginalSyncRoomMessageEvent,
3259    /// };
3260    ///
3261    /// let client = Client::new(homeserver).await?;
3262    /// client.matrix_auth().login_username(&username, &password).send().await?;
3263    ///
3264    /// // Register our handler so we start responding once we receive a new
3265    /// // event.
3266    /// client.add_event_handler(|ev: OriginalSyncRoomMessageEvent| async move {
3267    ///     println!("Received event {}: {:?}", ev.sender, ev.content);
3268    /// });
3269    ///
3270    /// // Now keep on syncing forever. `sync()` will use the latest sync token
3271    /// // automatically.
3272    /// client.sync(SyncSettings::default()).await?;
3273    /// # anyhow::Ok(()) };
3274    /// ```
3275    ///
3276    /// [argument docs]: #method.sync_once
3277    /// [`sync_with_callback`]: #method.sync_with_callback
3278    pub async fn sync(&self, sync_settings: crate::config::SyncSettings) -> Result<(), Error> {
3279        self.sync_with_callback(sync_settings, |_| async { LoopCtrl::Continue }).await
3280    }
3281
3282    /// Repeatedly call sync to synchronize the client state with the server.
3283    ///
3284    /// # Arguments
3285    ///
3286    /// * `sync_settings` - Settings for the sync call. *Note* that those
3287    ///   settings will be only used for the first sync call. See the argument
3288    ///   docs for [`Client::sync_once`] for more info.
3289    ///
3290    /// * `callback` - A callback that will be called every time a successful
3291    ///   response has been fetched from the server. The callback must return a
3292    ///   boolean which signalizes if the method should stop syncing. If the
3293    ///   callback returns `LoopCtrl::Continue` the sync will continue, if the
3294    ///   callback returns `LoopCtrl::Break` the sync will be stopped.
3295    ///
3296    /// # Return
3297    /// The sync runs until an error occurs or the
3298    /// callback indicates that the Loop should stop. If the callback asked for
3299    /// a regular stop, the result will be `Ok(())` otherwise the
3300    /// `Err(Error)` is returned.
3301    ///
3302    /// # Examples
3303    ///
3304    /// The following example demonstrates how to sync forever while sending all
3305    /// the interesting events through a mpsc channel to another thread e.g. a
3306    /// UI thread.
3307    ///
3308    /// ```no_run
3309    /// # use std::time::Duration;
3310    /// # use matrix_sdk::{Client, config::SyncSettings, LoopCtrl};
3311    /// # use url::Url;
3312    /// # async {
3313    /// # let homeserver = Url::parse("http://localhost:8080").unwrap();
3314    /// # let mut client = Client::new(homeserver).await.unwrap();
3315    ///
3316    /// use tokio::sync::mpsc::channel;
3317    ///
3318    /// let (tx, rx) = channel(100);
3319    ///
3320    /// let sync_channel = &tx;
3321    /// let sync_settings = SyncSettings::new()
3322    ///     .timeout(Duration::from_secs(30));
3323    ///
3324    /// client
3325    ///     .sync_with_callback(sync_settings, |response| async move {
3326    ///         let channel = sync_channel;
3327    ///         for (room_id, room) in response.rooms.joined {
3328    ///             for event in room.timeline.events {
3329    ///                 channel.send(event).await.unwrap();
3330    ///             }
3331    ///         }
3332    ///
3333    ///         LoopCtrl::Continue
3334    ///     })
3335    ///     .await;
3336    /// };
3337    /// ```
3338    #[instrument(skip_all)]
3339    pub async fn sync_with_callback<C>(
3340        &self,
3341        sync_settings: crate::config::SyncSettings,
3342        callback: impl Fn(SyncResponse) -> C,
3343    ) -> Result<(), Error>
3344    where
3345        C: Future<Output = LoopCtrl>,
3346    {
3347        self.sync_with_result_callback(sync_settings, |result| async {
3348            Ok(callback(result?).await)
3349        })
3350        .await
3351    }
3352
3353    /// Repeatedly call sync to synchronize the client state with the server.
3354    ///
3355    /// # Arguments
3356    ///
3357    /// * `sync_settings` - Settings for the sync call. *Note* that those
3358    ///   settings will be only used for the first sync call. See the argument
3359    ///   docs for [`Client::sync_once`] for more info.
3360    ///
3361    /// * `callback` - A callback that will be called every time after a
3362    ///   response has been received, failure or not. The callback returns a
3363    ///   `Result<LoopCtrl, Error>`, too. When returning
3364    ///   `Ok(LoopCtrl::Continue)` the sync will continue, if the callback
3365    ///   returns `Ok(LoopCtrl::Break)` the sync will be stopped and the
3366    ///   function returns `Ok(())`. In case the callback can't handle the
3367    ///   `Error` or has a different malfunction, it can return an `Err(Error)`,
3368    ///   which results in the sync ending and the `Err(Error)` being returned.
3369    ///
3370    /// # Return
3371    /// The sync runs until an error occurs that the callback can't handle or
3372    /// the callback indicates that the Loop should stop. If the callback
3373    /// asked for a regular stop, the result will be `Ok(())` otherwise the
3374    /// `Err(Error)` is returned.
3375    ///
3376    /// _Note_: Lower-level configuration (e.g. for retries) are not changed by
3377    /// this, and are handled first without sending the result to the
3378    /// callback. Only after they have exceeded is the `Result` handed to
3379    /// the callback.
3380    ///
3381    /// # Examples
3382    ///
3383    /// The following example demonstrates how to sync forever while sending all
3384    /// the interesting events through a mpsc channel to another thread e.g. a
3385    /// UI thread.
3386    ///
3387    /// ```no_run
3388    /// # use std::time::Duration;
3389    /// # use matrix_sdk::{Client, config::SyncSettings, LoopCtrl};
3390    /// # use url::Url;
3391    /// # async {
3392    /// # let homeserver = Url::parse("http://localhost:8080").unwrap();
3393    /// # let mut client = Client::new(homeserver).await.unwrap();
3394    /// #
3395    /// use tokio::sync::mpsc::channel;
3396    ///
3397    /// let (tx, rx) = channel(100);
3398    ///
3399    /// let sync_channel = &tx;
3400    /// let sync_settings = SyncSettings::new()
3401    ///     .timeout(Duration::from_secs(30));
3402    ///
3403    /// client
3404    ///     .sync_with_result_callback(sync_settings, |response| async move {
3405    ///         let channel = sync_channel;
3406    ///         let sync_response = response?;
3407    ///         for (room_id, room) in sync_response.rooms.joined {
3408    ///              for event in room.timeline.events {
3409    ///                  channel.send(event).await.unwrap();
3410    ///               }
3411    ///         }
3412    ///
3413    ///         Ok(LoopCtrl::Continue)
3414    ///     })
3415    ///     .await;
3416    /// };
3417    /// ```
3418    #[instrument(skip(self, callback))]
3419    pub async fn sync_with_result_callback<C>(
3420        &self,
3421        sync_settings: crate::config::SyncSettings,
3422        callback: impl Fn(Result<SyncResponse, Error>) -> C,
3423    ) -> Result<(), Error>
3424    where
3425        C: Future<Output = Result<LoopCtrl, Error>>,
3426    {
3427        let mut sync_stream = Box::pin(self.sync_stream(sync_settings).await);
3428
3429        while let Some(result) = sync_stream.next().await {
3430            trace!("Running callback");
3431            if callback(result).await? == LoopCtrl::Break {
3432                trace!("Callback told us to stop");
3433                break;
3434            }
3435            trace!("Done running callback");
3436        }
3437
3438        Ok(())
3439    }
3440
3441    //// Repeatedly synchronize the client state with the server.
3442    ///
3443    /// This method will internally call [`Client::sync_once`] in a loop and is
3444    /// equivalent to the [`Client::sync`] method but the responses are provided
3445    /// as an async stream.
3446    ///
3447    /// # Arguments
3448    ///
3449    /// * `sync_settings` - Settings for the sync call. *Note* that those
3450    ///   settings will be only used for the first sync call. See the argument
3451    ///   docs for [`Client::sync_once`] for more info.
3452    ///
3453    /// # Examples
3454    ///
3455    /// ```no_run
3456    /// # use url::Url;
3457    /// # async {
3458    /// # let homeserver = Url::parse("http://localhost:8080")?;
3459    /// # let username = "";
3460    /// # let password = "";
3461    /// use futures_util::StreamExt;
3462    /// use matrix_sdk::{Client, config::SyncSettings};
3463    ///
3464    /// let client = Client::new(homeserver).await?;
3465    /// client.matrix_auth().login_username(&username, &password).send().await?;
3466    ///
3467    /// let mut sync_stream =
3468    ///     Box::pin(client.sync_stream(SyncSettings::default()).await);
3469    ///
3470    /// while let Some(Ok(response)) = sync_stream.next().await {
3471    ///     for room in response.rooms.joined.values() {
3472    ///         for e in &room.timeline.events {
3473    ///             if let Ok(event) = e.raw().deserialize() {
3474    ///                 println!("Received event {:?}", event);
3475    ///             }
3476    ///         }
3477    ///     }
3478    /// }
3479    ///
3480    /// # anyhow::Ok(()) };
3481    /// ```
3482    #[allow(unknown_lints, clippy::let_with_type_underscore)] // triggered by instrument macro
3483    #[instrument(skip(self))]
3484    pub async fn sync_stream(
3485        &self,
3486        mut sync_settings: crate::config::SyncSettings,
3487    ) -> impl Stream<Item = Result<SyncResponse>> + '_ {
3488        let mut is_first_sync = true;
3489        let mut timeout = None;
3490        let mut last_sync_time: Option<Instant> = None;
3491
3492        let parent_span = Span::current();
3493
3494        async_stream::stream!({
3495            loop {
3496                trace!("Syncing");
3497
3498                if sync_settings.ignore_timeout_on_first_sync {
3499                    if is_first_sync {
3500                        timeout = sync_settings.timeout.take();
3501                    } else if sync_settings.timeout.is_none() && timeout.is_some() {
3502                        sync_settings.timeout = timeout.take();
3503                    }
3504
3505                    is_first_sync = false;
3506                }
3507
3508                yield self
3509                    .sync_loop_helper(&mut sync_settings)
3510                    .instrument(parent_span.clone())
3511                    .await;
3512
3513                Client::delay_sync(&mut last_sync_time).await
3514            }
3515        })
3516    }
3517
3518    /// Get the current, if any, sync token of the client.
3519    /// This will be None if the client didn't sync at least once.
3520    pub(crate) async fn sync_token(&self) -> Option<String> {
3521        self.inner.base_client.sync_token().await
3522    }
3523
3524    /// Gets information about the owner of a given access token.
3525    pub async fn whoami(&self) -> HttpResult<whoami::v3::Response> {
3526        let request = whoami::v3::Request::new();
3527        self.send(request).await
3528    }
3529
3530    /// Subscribes a new receiver to client SessionChange broadcasts.
3531    pub fn subscribe_to_session_changes(&self) -> broadcast::Receiver<SessionChange> {
3532        let broadcast = &self.auth_ctx().session_change_sender;
3533        broadcast.subscribe()
3534    }
3535
3536    /// Sets the save/restore session callbacks.
3537    ///
3538    /// This is another mechanism to get synchronous updates to session tokens,
3539    /// while [`Self::subscribe_to_session_changes`] provides an async update.
3540    pub fn set_session_callbacks(
3541        &self,
3542        reload_session_callback: Box<ReloadSessionCallback>,
3543        save_session_callback: Box<SaveSessionCallback>,
3544    ) -> Result<()> {
3545        self.inner
3546            .auth_ctx
3547            .reload_session_callback
3548            .set(reload_session_callback)
3549            .map_err(|_| Error::MultipleSessionCallbacks)?;
3550
3551        self.inner
3552            .auth_ctx
3553            .save_session_callback
3554            .set(save_session_callback)
3555            .map_err(|_| Error::MultipleSessionCallbacks)?;
3556
3557        Ok(())
3558    }
3559
3560    /// Get the notification settings of the current owner of the client.
3561    pub async fn notification_settings(&self) -> NotificationSettings {
3562        let ruleset = self.account().push_rules().await.unwrap_or_else(|_| Ruleset::new());
3563        NotificationSettings::new(self.clone(), ruleset)
3564    }
3565
3566    /// Create a new specialized `Client` that can process notifications.
3567    ///
3568    /// See [`CrossProcessLock::new`] to learn more about
3569    /// `cross_process_lock_config`.
3570    ///
3571    /// [`CrossProcessLock::new`]: matrix_sdk_common::cross_process_lock::CrossProcessLock::new
3572    pub async fn notification_client(
3573        &self,
3574        cross_process_lock_config: CrossProcessLockConfig,
3575    ) -> Result<Client> {
3576        let client = Client {
3577            inner: ClientInner::new(
3578                self.inner.auth_ctx.clone(),
3579                self.server(),
3580                self.homeserver(),
3581                self.sliding_sync_version(),
3582                self.inner.sync_presence.clone(),
3583                self.inner.http_client.clone(),
3584                self.inner
3585                    .base_client
3586                    .clone_with_in_memory_state_store(cross_process_lock_config.clone(), false)
3587                    .await?,
3588                self.inner.caches.supported_versions.value(),
3589                self.inner.caches.well_known.value(),
3590                self.inner.respect_login_well_known,
3591                self.well_known_lookup_disabled(),
3592                self.inner.event_cache.clone(),
3593                false,
3594                self.inner.send_queue_data.clone(),
3595                self.inner.latest_events.clone(),
3596                #[cfg(feature = "e2e-encryption")]
3597                self.inner.e2ee.encryption_settings,
3598                #[cfg(feature = "e2e-encryption")]
3599                self.inner.enable_share_history_on_invite,
3600                cross_process_lock_config,
3601                #[cfg(feature = "experimental-search")]
3602                self.inner.search_index.clone(),
3603                self.inner.thread_subscription_catchup.clone(),
3604                (*self.inner.media_fetcher.read().await).clone(),
3605            )
3606            .await,
3607        };
3608
3609        Ok(client)
3610    }
3611
3612    /// The [`EventCache`] instance for this [`Client`].
3613    pub fn event_cache(&self) -> &EventCache {
3614        // SAFETY: always initialized in the `Client` ctor.
3615        self.inner.event_cache.get().unwrap()
3616    }
3617
3618    /// The [`LatestEvents`] instance for this [`Client`].
3619    pub async fn latest_events(&self) -> &LatestEvents {
3620        self.inner
3621            .latest_events
3622            .get_or_init(|| async {
3623                LatestEvents::new(
3624                    WeakClient::from_client(self),
3625                    self.event_cache().clone(),
3626                    SendQueue::new(self.clone()),
3627                    self.room_info_notable_update_receiver(),
3628                )
3629            })
3630            .await
3631    }
3632
3633    /// Waits until an at least partially synced room is received, and returns
3634    /// it.
3635    ///
3636    /// **Note: this function will loop endlessly until either it finds the room
3637    /// or an externally set timeout happens.**
3638    pub async fn await_room_remote_echo(&self, room_id: &RoomId) -> Room {
3639        loop {
3640            if let Some(room) = self.get_room(room_id) {
3641                if room.is_state_partially_or_fully_synced() {
3642                    debug!("Found just created room!");
3643                    return room;
3644                }
3645                debug!("Room wasn't partially synced, waiting for sync beat to try again");
3646            } else {
3647                debug!("Room wasn't found, waiting for sync beat to try again");
3648            }
3649            self.inner.sync_beat.listen().await;
3650        }
3651    }
3652
3653    /// Knock on a room given its `room_id_or_alias` to ask for permission to
3654    /// join it.
3655    pub async fn knock(
3656        &self,
3657        room_id_or_alias: OwnedRoomOrAliasId,
3658        reason: Option<String>,
3659        server_names: Vec<OwnedServerName>,
3660    ) -> Result<Room> {
3661        let request =
3662            assign!(knock_room::v3::Request::new(room_id_or_alias), { reason, via: server_names });
3663        let response = self.send(request).await?;
3664        let base_room = self.inner.base_client.room_knocked(&response.room_id).await?;
3665        Ok(Room::new(self.clone(), base_room))
3666    }
3667
3668    /// Checks whether the provided `user_id` belongs to an ignored user.
3669    pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
3670        self.base_client().is_user_ignored(user_id).await
3671    }
3672
3673    /// Gets the `max_upload_size` value from the homeserver, getting either a
3674    /// cached value or with a `/_matrix/client/v1/media/config` request if it's
3675    /// missing.
3676    ///
3677    /// Check the spec for more info:
3678    /// <https://spec.matrix.org/v1.14/client-server-api/#get_matrixclientv1mediaconfig>
3679    pub async fn load_or_fetch_max_upload_size(&self) -> Result<UInt> {
3680        let max_upload_size_lock = self.inner.server_max_upload_size.lock().await;
3681        if let Some(data) = max_upload_size_lock.get() {
3682            return Ok(data.to_owned());
3683        }
3684
3685        // Use the authenticated endpoint when the server supports it.
3686        let supported_versions = self.supported_versions().await?;
3687        let use_auth = authenticated_media::get_media_config::v1::Request::PATH_BUILDER
3688            .is_supported(&supported_versions);
3689
3690        let upload_size = if use_auth {
3691            self.send(authenticated_media::get_media_config::v1::Request::default())
3692                .await?
3693                .upload_size
3694        } else {
3695            #[allow(deprecated)]
3696            self.send(media::get_media_config::v3::Request::default()).await?.upload_size
3697        };
3698
3699        match max_upload_size_lock.set(upload_size) {
3700            Ok(_) => Ok(upload_size),
3701            Err(error) => {
3702                Err(Error::Media(MediaError::FetchMaxUploadSizeFailed(error.to_string())))
3703            }
3704        }
3705    }
3706
3707    /// The settings to use for decrypting events.
3708    #[cfg(feature = "e2e-encryption")]
3709    pub fn decryption_settings(&self) -> &DecryptionSettings {
3710        &self.base_client().decryption_settings
3711    }
3712
3713    /// Returns the [`SearchIndex`] for this [`Client`].
3714    #[cfg(feature = "experimental-search")]
3715    pub fn search_index(&self) -> &SearchIndex {
3716        &self.inner.search_index
3717    }
3718
3719    /// Whether the client is configured to take thread subscriptions (MSC4306
3720    /// and MSC4308) into account, and the server enabled the experimental
3721    /// feature flag for it.
3722    ///
3723    /// This may cause filtering out of thread subscriptions, and loading the
3724    /// thread subscriptions via the sliding sync extension, when the room
3725    /// list service is being used.
3726    ///
3727    /// This is async and fallible as it may use the network to retrieve the
3728    /// server supported features, if they aren't cached already.
3729    pub async fn enabled_thread_subscriptions(&self) -> Result<bool> {
3730        // Check if the client is configured to support thread subscriptions first.
3731        match self.base_client().threading_support {
3732            ThreadingSupport::Enabled { with_subscriptions: false }
3733            | ThreadingSupport::Disabled => return Ok(false),
3734            ThreadingSupport::Enabled { with_subscriptions: true } => {}
3735        }
3736
3737        // Now, let's check that the server supports it.
3738        let server_enabled = self
3739            .supported_versions()
3740            .await?
3741            .features
3742            .contains(&FeatureFlag::from("org.matrix.msc4306"));
3743
3744        Ok(server_enabled)
3745    }
3746
3747    /// Fetch thread subscriptions changes between `from` and up to `to`.
3748    ///
3749    /// The `limit` optional parameter can be used to limit the number of
3750    /// entries in a response. It can also be overridden by the server, if
3751    /// it's deemed too large.
3752    pub async fn fetch_thread_subscriptions(
3753        &self,
3754        from: Option<String>,
3755        to: Option<String>,
3756        limit: Option<UInt>,
3757    ) -> Result<get_thread_subscriptions_changes::unstable::Response> {
3758        let request = assign!(get_thread_subscriptions_changes::unstable::Request::new(), {
3759            from,
3760            to,
3761            limit,
3762        });
3763        Ok(self.send(request).await?)
3764    }
3765
3766    pub(crate) fn thread_subscription_catchup(&self) -> &ThreadSubscriptionCatchup {
3767        self.inner.thread_subscription_catchup.get().unwrap()
3768    }
3769
3770    /// Pause the client for background suspension.
3771    ///
3772    /// This method:
3773    /// 1. Disables all send queues (prevents new message sends).
3774    /// 2. Pauses all database stores, waiting for in-flight operations and
3775    ///    releasing all connections and file locks.
3776    ///
3777    /// Call [`Client::resume()`] when the app returns to the foreground.
3778    ///
3779    /// # iOS
3780    ///
3781    /// Call this before the app is suspended to avoid `0xdead10cc` kills.
3782    /// Typically called from
3783    /// [`applicationDidEnterBackground`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/applicationdidenterbackground(_:))
3784    /// or an equivalent SwiftUI lifecycle event, *after* stopping the
3785    /// `matrix_sdk_ui::sync_service::SyncService`.
3786    pub async fn pause(&self) -> Result<()> {
3787        info!("Client::pause — releasing database resources");
3788
3789        // Disable send queues so no new sends hit the stores.
3790        self.send_queue().set_enabled(false).await;
3791
3792        // Close all stores (waits for in-flight ops, closes connections).
3793        self.base_client().close_stores().await?;
3794
3795        info!("Client::pause — complete, all database connections released");
3796        Ok(())
3797    }
3798
3799    /// Resume the client after a [`Client::pause()`].
3800    ///
3801    /// Re-acquires store resources and re-enables send queues.
3802    ///
3803    /// If your app stopped the `matrix_sdk_ui::sync_service::SyncService`
3804    /// before pausing, restart it separately as appropriate for your app
3805    /// lifecycle.
3806    pub async fn resume(&self) -> Result<()> {
3807        info!("Client::resume — re-acquiring database resources");
3808
3809        // Reopen stores (creates new connection pools).
3810        self.base_client().reopen_stores().await?;
3811
3812        // Re-enable send queues.
3813        self.send_queue().set_enabled(true).await;
3814
3815        info!("Client::resume — complete");
3816        Ok(())
3817    }
3818
3819    /// Perform database optimizations if any are available, i.e. vacuuming in
3820    /// SQLite.
3821    ///
3822    /// **Warning:** this was added to check if SQLite fragmentation was the
3823    /// source of performance issues, **DO NOT use in production**.
3824    #[doc(hidden)]
3825    pub async fn optimize_stores(&self) -> Result<()> {
3826        trace!("Optimizing state store...");
3827        self.state_store().optimize().await?;
3828
3829        trace!("Optimizing event cache store...");
3830        if let Some(clean_lock) = self.event_cache_store().lock().await?.as_clean() {
3831            clean_lock.optimize().await?;
3832        }
3833
3834        trace!("Optimizing media store...");
3835        self.media_store().lock().await?.optimize().await?;
3836
3837        Ok(())
3838    }
3839
3840    /// Returns the sizes of the existing stores, if known.
3841    pub async fn get_store_sizes(&self) -> Result<StoreSizes> {
3842        #[cfg(feature = "e2e-encryption")]
3843        let crypto_store_size = if let Some(olm_machine) = self.olm_machine().await.as_ref()
3844            && let Ok(Some(store_size)) = olm_machine.store().get_size().await
3845        {
3846            Some(store_size)
3847        } else {
3848            None
3849        };
3850        #[cfg(not(feature = "e2e-encryption"))]
3851        let crypto_store_size = None;
3852
3853        let state_store_size = self.state_store().get_size().await.ok().flatten();
3854
3855        let event_cache_store_size = if let Some(clean_lock) =
3856            self.event_cache_store().lock().await?.as_clean()
3857            && let Ok(Some(store_size)) = clean_lock.get_size().await
3858        {
3859            Some(store_size)
3860        } else {
3861            None
3862        };
3863
3864        let media_store_size = self.media_store().lock().await?.get_size().await.ok().flatten();
3865
3866        Ok(StoreSizes {
3867            crypto_store: crypto_store_size,
3868            state_store: state_store_size,
3869            event_cache_store: event_cache_store_size,
3870            media_store: media_store_size,
3871        })
3872    }
3873
3874    /// Get a reference to the client's task monitor, for spawning background
3875    /// tasks.
3876    pub fn task_monitor(&self) -> &TaskMonitor {
3877        &self.inner.task_monitor
3878    }
3879
3880    /// Add a subscriber for duplicate key upload error notifications triggered
3881    /// by requests to /keys/upload.
3882    #[cfg(feature = "e2e-encryption")]
3883    pub fn subscribe_to_duplicate_key_upload_errors(
3884        &self,
3885    ) -> broadcast::Receiver<Option<DuplicateOneTimeKeyErrorMessage>> {
3886        self.inner.duplicate_key_upload_error_sender.subscribe()
3887    }
3888
3889    /// Check the record of whether we are waiting for an [MSC4268] key bundle
3890    /// for the given room.
3891    ///
3892    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
3893    #[cfg(feature = "e2e-encryption")]
3894    pub async fn get_pending_key_bundle_details_for_room(
3895        &self,
3896        room_id: &RoomId,
3897    ) -> Result<Option<RoomPendingKeyBundleDetails>> {
3898        Ok(self.base_client().get_pending_key_bundle_details_for_room(room_id).await?)
3899    }
3900
3901    /// Returns the [`DmRoomDefinition`] this client uses to check if a room is
3902    /// a DM.
3903    pub fn dm_room_definition(&self) -> &DmRoomDefinition {
3904        &self.inner.base_client.dm_room_definition
3905    }
3906
3907    /// Replaces the [`MediaFetcher`] used to download media from the media
3908    /// server with the provided one.
3909    pub async fn set_media_fetcher(&self, media_fetcher: Arc<dyn MediaFetcher>) {
3910        *self.inner.media_fetcher.write().await = media_fetcher;
3911    }
3912
3913    /// Returns the currently used [`MediaFetcher`] used to download media from
3914    /// the media server.
3915    pub async fn get_media_fetcher(&self) -> Arc<dyn MediaFetcher> {
3916        self.inner.media_fetcher.read().await.clone()
3917    }
3918}
3919
3920/// Contains the disk size of the different stores, if known. It won't be
3921/// available for in-memory stores.
3922#[derive(Debug, Clone)]
3923pub struct StoreSizes {
3924    /// The size of the CryptoStore.
3925    pub crypto_store: Option<usize>,
3926    /// The size of the StateStore.
3927    pub state_store: Option<usize>,
3928    /// The size of the EventCacheStore.
3929    pub event_cache_store: Option<usize>,
3930    /// The size of the MediaStore.
3931    pub media_store: Option<usize>,
3932}
3933
3934#[cfg(any(feature = "testing", test))]
3935impl Client {
3936    /// Test helper to mark users as tracked by the crypto layer.
3937    #[cfg(feature = "e2e-encryption")]
3938    pub async fn update_tracked_users_for_testing(
3939        &self,
3940        user_ids: impl IntoIterator<Item = &UserId>,
3941    ) {
3942        let olm = self.olm_machine().await;
3943        let olm = olm.as_ref().unwrap();
3944        olm.update_tracked_users(user_ids).await.unwrap();
3945    }
3946}
3947
3948/// A weak reference to the inner client, useful when trying to get a handle
3949/// on the owning client.
3950#[derive(Clone, Debug)]
3951pub(crate) struct WeakClient {
3952    client: Weak<ClientInner>,
3953}
3954
3955impl WeakClient {
3956    /// Construct a [`WeakClient`] from a `Arc<ClientInner>`.
3957    pub(crate) fn from_inner(client: &Arc<ClientInner>) -> Self {
3958        Self { client: Arc::downgrade(client) }
3959    }
3960
3961    /// Construct a [`WeakClient`] from a [`Client`].
3962    pub fn from_client(client: &Client) -> Self {
3963        Self::from_inner(&client.inner)
3964    }
3965
3966    /// Attempts to get a [`Client`] from this [`WeakClient`].
3967    pub fn get(&self) -> Option<Client> {
3968        self.client.upgrade().map(|inner| Client { inner })
3969    }
3970
3971    /// Gets the number of strong (`Arc`) pointers still pointing to this
3972    /// client.
3973    #[allow(dead_code)]
3974    pub fn strong_count(&self) -> usize {
3975        self.client.strong_count()
3976    }
3977}
3978
3979/// Information about the state of a room before we joined it.
3980#[derive(Debug, Clone, Default)]
3981struct PreJoinRoomInfo {
3982    /// The user who invited us to the room, if any.
3983    pub inviter: Option<RoomMember>,
3984}
3985
3986// The http mocking library is not supported for wasm32
3987#[cfg(all(test, not(target_family = "wasm")))]
3988pub(crate) mod tests {
3989    use std::{sync::Arc, time::Duration};
3990
3991    use assert_matches::assert_matches;
3992    use assert_matches2::assert_let;
3993    use eyeball::SharedObservable;
3994    use futures_util::{FutureExt, StreamExt, pin_mut};
3995    use js_int::{UInt, uint};
3996    use matrix_sdk_base::{
3997        RoomState,
3998        store::{MemoryStore, StoreConfig},
3999        ttl::TtlValue,
4000    };
4001    use matrix_sdk_test::{
4002        DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
4003        event_factory::EventFactory,
4004    };
4005    #[cfg(target_family = "wasm")]
4006    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
4007
4008    use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
4009    use ruma::{
4010        RoomId, ServerName, UserId,
4011        api::{
4012            FeatureFlag, MatrixVersion,
4013            client::{room::create_room::v3::Request as CreateRoomRequest, rtc::RtcTransport},
4014        },
4015        assign,
4016        events::{
4017            ignored_user_list::IgnoredUserListEventContent,
4018            media_preview_config::{InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews},
4019        },
4020        owned_device_id, owned_room_id, owned_user_id,
4021        presence::PresenceState,
4022        room_alias_id, room_id, user_id,
4023    };
4024    use serde_json::json;
4025    use stream_assert::{assert_next_matches, assert_pending};
4026    use tokio::{
4027        spawn,
4028        time::{sleep, timeout},
4029    };
4030    use url::Url;
4031
4032    use super::Client;
4033    use crate::{
4034        Error, Result, TransmissionProgress,
4035        client::{WeakClient, caches::CachedValue, futures::SendMediaUploadRequest},
4036        config::{RequestConfig, SyncSettings},
4037        futures::SendRequest,
4038        media::MediaError,
4039        test_utils::{client::MockClientBuilder, mocks::MatrixMockServer},
4040    };
4041
4042    #[async_test]
4043    async fn test_sync_presence_is_shared_by_client_clones_and_notification_child() {
4044        let client = MockClientBuilder::new(None).build().await;
4045        let clone = client.clone();
4046        let notification_client =
4047            client.notification_client(CrossProcessLockConfig::SingleProcess).await.unwrap();
4048
4049        assert_eq!(client.sync_presence(), PresenceState::Online);
4050        assert_eq!(clone.sync_presence(), PresenceState::Online);
4051        assert_eq!(notification_client.sync_presence(), PresenceState::Online);
4052
4053        client
4054            .set_presence(PresenceState::Unavailable, None, false)
4055            .await
4056            .expect("presence should update");
4057
4058        assert_eq!(client.sync_presence(), PresenceState::Unavailable);
4059        assert_eq!(clone.sync_presence(), PresenceState::Unavailable);
4060        assert_eq!(notification_client.sync_presence(), PresenceState::Unavailable);
4061
4062        notification_client
4063            .set_presence(PresenceState::Offline, None, false)
4064            .await
4065            .expect("presence should update");
4066
4067        assert_eq!(client.sync_presence(), PresenceState::Offline);
4068        assert_eq!(clone.sync_presence(), PresenceState::Offline);
4069        assert_eq!(notification_client.sync_presence(), PresenceState::Offline);
4070    }
4071
4072    #[async_test]
4073    async fn test_sync_once_uses_client_sync_presence_unless_overridden() {
4074        let server = MatrixMockServer::new().await;
4075        let client = server.client_builder().build().await;
4076
4077        {
4078            let _sync_guard = server
4079                .mock_sync()
4080                .set_presence_missing()
4081                .ok(|_| {})
4082                .expect(1)
4083                .mount_as_scoped()
4084                .await;
4085
4086            client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4087        }
4088
4089        client
4090            .set_presence(PresenceState::Offline, None, false)
4091            .await
4092            .expect("presence should update");
4093
4094        {
4095            let _sync_guard = server
4096                .mock_sync()
4097                .set_presence("offline")
4098                .ok(|_| {})
4099                .expect(1)
4100                .mount_as_scoped()
4101                .await;
4102
4103            client.sync_once(SyncSettings::new()).await.expect("sync should succeed");
4104        }
4105
4106        {
4107            let _sync_guard = server
4108                .mock_sync()
4109                .set_presence("unavailable")
4110                .ok(|_| {})
4111                .expect(1)
4112                .mount_as_scoped()
4113                .await;
4114
4115            client
4116                .sync_once(SyncSettings::new().set_presence(PresenceState::Unavailable))
4117                .await
4118                .expect("sync should succeed");
4119        }
4120    }
4121
4122    #[async_test]
4123    async fn test_set_presence_sends_presence_status_update() {
4124        use wiremock::{
4125            Mock, ResponseTemplate,
4126            matchers::{body_partial_json, method, path_regex},
4127        };
4128
4129        let server = MatrixMockServer::new().await;
4130        let client = server.client_builder().build().await;
4131
4132        Mock::given(method("PUT"))
4133            .and(path_regex(r"^/_matrix/client/(r0|v3)/presence/.*/status$"))
4134            .and(body_partial_json(json!({
4135                "presence": "online",
4136                "status_msg": "Here"
4137            })))
4138            .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
4139            .expect(1)
4140            .mount(server.server())
4141            .await;
4142
4143        client
4144            .set_presence(PresenceState::Online, Some("Here".to_owned()), true)
4145            .await
4146            .expect("presence update should succeed");
4147
4148        assert_eq!(client.sync_presence(), PresenceState::Online);
4149    }
4150
4151    #[async_test]
4152    async fn test_set_presence_requires_authentication() {
4153        let client = MockClientBuilder::new(None).unlogged().build().await;
4154
4155        assert_matches!(
4156            client.set_presence(PresenceState::Unavailable, None, true).await,
4157            Err(Error::AuthenticationRequired)
4158        );
4159    }
4160
4161    #[async_test]
4162    async fn test_set_presence_without_immediate_does_not_require_authentication() {
4163        let client = MockClientBuilder::new(None).unlogged().build().await;
4164
4165        client
4166            .set_presence(PresenceState::Offline, None, false)
4167            .await
4168            .expect("presence should update");
4169
4170        assert_eq!(client.sync_presence(), PresenceState::Offline);
4171    }
4172
4173    #[async_test]
4174    async fn test_account_data() {
4175        let server = MatrixMockServer::new().await;
4176        let client = server.client_builder().build().await;
4177
4178        let f = EventFactory::new();
4179        server
4180            .mock_sync()
4181            .ok_and_run(&client, |builder| {
4182                builder.add_global_account_data(
4183                    f.ignored_user_list([owned_user_id!("@someone:example.org")]),
4184                );
4185            })
4186            .await;
4187
4188        let content = client
4189            .account()
4190            .account_data::<IgnoredUserListEventContent>()
4191            .await
4192            .unwrap()
4193            .unwrap()
4194            .deserialize()
4195            .unwrap();
4196
4197        assert_eq!(content.ignored_users.len(), 1);
4198    }
4199
4200    #[async_test]
4201    async fn test_successful_discovery() {
4202        // Imagine this is `matrix.org`.
4203        let server = MatrixMockServer::new().await;
4204        let server_url = server.uri();
4205
4206        // Imagine this is `matrix-client.matrix.org`.
4207        let homeserver = MatrixMockServer::new().await;
4208        let homeserver_url = homeserver.uri();
4209
4210        // Imagine Alice has the user ID `@alice:matrix.org`.
4211        let domain = server_url.strip_prefix("http://").unwrap();
4212        let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4213
4214        // The `.well-known` is on the server (e.g. `matrix.org`).
4215        server
4216            .mock_well_known()
4217            .ok_with_homeserver_url(&homeserver_url)
4218            .mock_once()
4219            .named("well-known")
4220            .mount()
4221            .await;
4222
4223        // The `/versions` is on the homeserver (e.g. `matrix-client.matrix.org`).
4224        homeserver.mock_versions().ok().mock_once().named("versions").mount().await;
4225
4226        let client = Client::builder()
4227            .insecure_server_name_no_tls(alice.server_name())
4228            .build()
4229            .await
4230            .unwrap();
4231
4232        assert_eq!(client.server().unwrap(), Url::parse(&server_url).unwrap());
4233        assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4234        client.server_versions().await.unwrap();
4235    }
4236
4237    #[async_test]
4238    async fn test_homeserver_swap_resets_server_field() {
4239        let homeserver = MatrixMockServer::new().await;
4240        let homeserver_url = homeserver.uri();
4241
4242        let domain = homeserver_url.strip_prefix("http://").unwrap();
4243        let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4244
4245        homeserver.mock_well_known().ok().mock_once().named("well-known").mount().await;
4246
4247        let client = Client::builder()
4248            .insecure_server_name_no_tls(alice.server_name())
4249            .build()
4250            .await
4251            .unwrap();
4252
4253        assert_eq!(client.server().unwrap(), Url::parse(&homeserver_url).unwrap());
4254        assert_eq!(client.homeserver(), Url::parse(&homeserver_url).unwrap());
4255
4256        let new_server = Url::parse("http://example.org").unwrap();
4257        // Since we're explicitly setting the server to something else, like we might do
4258        // during QR code login...
4259        client.set_homeserver(new_server.clone());
4260
4261        // The new URL should be set in the homeserver field.
4262        assert_eq!(client.homeserver(), new_server);
4263        // But the server field should be set to empty, since we didn't do any discovery
4264        // now.
4265        assert!(client.server().is_none())
4266    }
4267
4268    #[async_test]
4269    async fn test_discovery_broken_server() {
4270        let server = MatrixMockServer::new().await;
4271        let server_url = server.uri();
4272        let domain = server_url.strip_prefix("http://").unwrap();
4273        let alice = UserId::parse("@alice:".to_owned() + domain).unwrap();
4274
4275        server.mock_well_known().error404().mock_once().named("well-known").mount().await;
4276
4277        assert!(
4278            Client::builder()
4279                .insecure_server_name_no_tls(alice.server_name())
4280                .build()
4281                .await
4282                .is_err(),
4283            "Creating a client from a user ID should fail when the .well-known request fails."
4284        );
4285    }
4286
4287    #[async_test]
4288    async fn test_room_creation() {
4289        let server = MatrixMockServer::new().await;
4290        let client = server.client_builder().build().await;
4291
4292        let f = EventFactory::new().sender(user_id!("@example:localhost"));
4293        server
4294            .mock_sync()
4295            .ok_and_run(&client, |builder| {
4296                builder.add_joined_room(
4297                    JoinedRoomBuilder::default()
4298                        .add_state_event(
4299                            f.member(user_id!("@example:localhost")).display_name("example"),
4300                        )
4301                        .add_state_event(f.default_power_levels()),
4302                );
4303            })
4304            .await;
4305
4306        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
4307        assert_eq!(room.state(), RoomState::Joined);
4308    }
4309
4310    #[async_test]
4311    async fn test_retry_limit_http_requests() {
4312        let server = MatrixMockServer::new().await;
4313        let client = server
4314            .client_builder()
4315            .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(4)))
4316            .build()
4317            .await;
4318
4319        assert!(client.request_config().retry_limit.unwrap() == 4);
4320
4321        server.mock_who_am_i().error500().expect(4).mount().await;
4322
4323        client.whoami().await.unwrap_err();
4324    }
4325
4326    #[async_test]
4327    async fn test_retry_timeout_http_requests() {
4328        // Keep this timeout small so that the test doesn't take long
4329        let retry_timeout = Duration::from_secs(5);
4330        let server = MatrixMockServer::new().await;
4331        let client = server
4332            .client_builder()
4333            .on_builder(|builder| {
4334                builder.request_config(RequestConfig::new().max_retry_time(retry_timeout))
4335            })
4336            .build()
4337            .await;
4338
4339        assert!(client.request_config().max_retry_time.unwrap() == retry_timeout);
4340
4341        server.mock_login().error500().expect(2..).mount().await;
4342
4343        client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4344    }
4345
4346    #[async_test]
4347    async fn test_short_retry_initial_http_requests() {
4348        let server = MatrixMockServer::new().await;
4349        let client = server
4350            .client_builder()
4351            .on_builder(|builder| builder.request_config(RequestConfig::short_retry()))
4352            .build()
4353            .await;
4354
4355        server.mock_login().error500().expect(3..).mount().await;
4356
4357        client.matrix_auth().login_username("example", "wordpass").send().await.unwrap_err();
4358    }
4359
4360    #[async_test]
4361    async fn test_no_retry_http_requests() {
4362        let server = MatrixMockServer::new().await;
4363        let client = server.client_builder().build().await;
4364
4365        server.mock_devices().error500().mock_once().mount().await;
4366
4367        client.devices().await.unwrap_err();
4368    }
4369
4370    #[async_test]
4371    async fn test_set_homeserver() {
4372        let client = MockClientBuilder::new(None).build().await;
4373        assert_eq!(client.homeserver().as_ref(), "http://localhost/");
4374
4375        let homeserver = Url::parse("http://example.com/").unwrap();
4376        client.set_homeserver(homeserver.clone());
4377        assert_eq!(client.homeserver(), homeserver);
4378    }
4379
4380    #[async_test]
4381    async fn test_search_user_request() {
4382        let server = MatrixMockServer::new().await;
4383        let client = server.client_builder().build().await;
4384
4385        server.mock_user_directory().ok().mock_once().mount().await;
4386
4387        let response = client.search_users("test", 50).await.unwrap();
4388        assert_eq!(response.results.len(), 1);
4389        let result = &response.results[0];
4390        assert_eq!(result.user_id.to_string(), "@test:example.me");
4391        assert_eq!(result.display_name.clone().unwrap(), "Test");
4392        assert_eq!(result.avatar_url.clone().unwrap().to_string(), "mxc://example.me/someid");
4393        assert!(!response.limited);
4394    }
4395
4396    #[async_test]
4397    async fn test_request_unstable_features() {
4398        let server = MatrixMockServer::new().await;
4399        let client = server.client_builder().no_server_versions().build().await;
4400
4401        server
4402            .mock_versions()
4403            .with_feature("org.matrix.e2e_cross_signing", true)
4404            .ok()
4405            .mock_once()
4406            .mount()
4407            .await;
4408
4409        let unstable_features = client.unstable_features().await.unwrap();
4410        assert!(unstable_features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4411        assert!(!unstable_features.contains(&FeatureFlag::from("you.shall.pass")));
4412    }
4413
4414    #[async_test]
4415    async fn test_can_homeserver_push_encrypted_event_to_device() {
4416        let server = MatrixMockServer::new().await;
4417        let client = server.client_builder().no_server_versions().build().await;
4418
4419        server.mock_versions().with_push_encrypted_events().ok().mock_once().mount().await;
4420
4421        let msc4028_enabled = client.can_homeserver_push_encrypted_event_to_device().await.unwrap();
4422        assert!(msc4028_enabled);
4423    }
4424
4425    #[async_test]
4426    async fn test_recently_visited_rooms() {
4427        // Tracking recently visited rooms requires authentication
4428        let client = MockClientBuilder::new(None).unlogged().build().await;
4429        assert_matches!(
4430            client.account().track_recently_visited_room(owned_room_id!("!alpha:localhost")).await,
4431            Err(Error::AuthenticationRequired)
4432        );
4433
4434        let client = MockClientBuilder::new(None).build().await;
4435        let account = client.account();
4436
4437        // We should start off with an empty list
4438        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 0);
4439
4440        // Tracking a valid room id should add it to the list
4441        account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4442        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4443        assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4444
4445        // And the existing list shouldn't be changed
4446        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4447        assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4448
4449        // Tracking the same room again shouldn't change the list
4450        account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4451        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 1);
4452        assert_eq!(account.get_recently_visited_rooms().await.unwrap(), ["!alpha:localhost"]);
4453
4454        // Tracking a second room should add it to the front of the list
4455        account.track_recently_visited_room(owned_room_id!("!beta:localhost")).await.unwrap();
4456        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4457        assert_eq!(
4458            account.get_recently_visited_rooms().await.unwrap(),
4459            [room_id!("!beta:localhost"), room_id!("!alpha:localhost")]
4460        );
4461
4462        // Tracking the first room yet again should move it to the front of the list
4463        account.track_recently_visited_room(owned_room_id!("!alpha:localhost")).await.unwrap();
4464        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 2);
4465        assert_eq!(
4466            account.get_recently_visited_rooms().await.unwrap(),
4467            [room_id!("!alpha:localhost"), room_id!("!beta:localhost")]
4468        );
4469
4470        // Tracking should be capped at 20
4471        for n in 0..20 {
4472            account
4473                .track_recently_visited_room(RoomId::parse(format!("!{n}:localhost")).unwrap())
4474                .await
4475                .unwrap();
4476        }
4477
4478        assert_eq!(account.get_recently_visited_rooms().await.unwrap().len(), 20);
4479
4480        // And the initial rooms should've been pushed out
4481        let rooms = account.get_recently_visited_rooms().await.unwrap();
4482        assert!(!rooms.contains(&owned_room_id!("!alpha:localhost")));
4483        assert!(!rooms.contains(&owned_room_id!("!beta:localhost")));
4484
4485        // And the last tracked room should be the first
4486        assert_eq!(rooms.first().unwrap(), room_id!("!19:localhost"));
4487    }
4488
4489    #[async_test]
4490    async fn test_client_no_cycle_with_event_cache() {
4491        let client = MockClientBuilder::new(None).build().await;
4492
4493        // Wait for the init tasks to die.
4494        sleep(Duration::from_secs(1)).await;
4495
4496        let weak_client = WeakClient::from_client(&client);
4497        assert_eq!(weak_client.strong_count(), 1);
4498
4499        {
4500            let room_id = room_id!("!room:example.org");
4501
4502            // Have the client know the room.
4503            let response = SyncResponseBuilder::default()
4504                .add_joined_room(JoinedRoomBuilder::new(room_id))
4505                .build_sync_response();
4506            client.inner.base_client.receive_sync_response(response).await.unwrap();
4507
4508            client.event_cache().subscribe().unwrap();
4509
4510            let (_room_event_cache, _drop_handles) =
4511                client.get_room(room_id).unwrap().event_cache().await.unwrap();
4512        }
4513
4514        drop(client);
4515
4516        // Give a bit of time for background tasks to die.
4517        sleep(Duration::from_secs(1)).await;
4518
4519        // The weak client must be the last reference to the client now.
4520        assert_eq!(weak_client.strong_count(), 0);
4521        let client = weak_client.get();
4522        assert!(
4523            client.is_none(),
4524            "too many strong references to the client: {}",
4525            Arc::strong_count(&client.unwrap().inner)
4526        );
4527    }
4528
4529    #[async_test]
4530    async fn test_supported_versions_caching() {
4531        let server = MatrixMockServer::new().await;
4532
4533        let versions_mock = server
4534            .mock_versions()
4535            .expect_default_access_token()
4536            .with_feature("org.matrix.e2e_cross_signing", true)
4537            .ok()
4538            .named("first versions mock")
4539            .expect(1)
4540            .mount_as_scoped()
4541            .await;
4542
4543        let memory_store = Arc::new(MemoryStore::new());
4544        let client = server
4545            .client_builder()
4546            .no_server_versions()
4547            .on_builder(|builder| {
4548                builder.store_config(
4549                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4550                        .state_store(memory_store.clone()),
4551                )
4552            })
4553            .build()
4554            .await;
4555
4556        assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4557
4558        // The result was cached.
4559        assert_matches!(client.supported_versions_cached().await, Ok(Some(_)));
4560        // This subsequent call hits the in-memory cache.
4561        assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4562
4563        drop(client);
4564
4565        let client = server
4566            .client_builder()
4567            .no_server_versions()
4568            .on_builder(|builder| {
4569                builder.store_config(
4570                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4571                        .state_store(memory_store.clone()),
4572                )
4573            })
4574            .build()
4575            .await;
4576
4577        // These calls to the new client hit the on-disk cache.
4578        assert!(
4579            client
4580                .unstable_features()
4581                .await
4582                .unwrap()
4583                .contains(&FeatureFlag::from("org.matrix.e2e_cross_signing"))
4584        );
4585
4586        let supported = client.supported_versions().await.unwrap();
4587        assert!(supported.versions.contains(&MatrixVersion::V1_0));
4588        assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4589
4590        // Then this call hits the in-memory cache.
4591        let supported = client.supported_versions().await.unwrap();
4592        assert!(supported.versions.contains(&MatrixVersion::V1_0));
4593        assert!(supported.features.contains(&FeatureFlag::from("org.matrix.e2e_cross_signing")));
4594
4595        drop(versions_mock);
4596
4597        // Now, reset the cache, and observe the endpoint being called again once.
4598        client.reset_supported_versions().await.unwrap();
4599
4600        server.mock_versions().ok().expect(2).named("second versions mock").mount().await;
4601
4602        // Hits network again.
4603        assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4604        // Hits in-memory cache again.
4605        assert!(client.server_versions().await.unwrap().contains(&MatrixVersion::V1_0));
4606        assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4607
4608        // Force an expiry of the data.
4609        let supported_versions = client.supported_versions_cached().await.unwrap().unwrap();
4610        let mut ttl_value = TtlValue::new(supported_versions);
4611        ttl_value.expire();
4612        client.inner.caches.supported_versions.set_value(ttl_value);
4613
4614        // Call the method to trigger a cache refresh background task.
4615        client.supported_versions_cached().await.unwrap().unwrap();
4616
4617        // We wait for the task to finish, the endpoint should have been called again.
4618        sleep(Duration::from_secs(1)).await;
4619        assert_matches!(client.inner.caches.supported_versions.value(), CachedValue::Cached(value) if !value.has_expired());
4620    }
4621
4622    #[async_test]
4623    async fn test_well_known_caching() {
4624        let server = MatrixMockServer::new().await;
4625        let server_url = server.uri();
4626        let domain = server_url.strip_prefix("http://").unwrap();
4627        let server_name = <&ServerName>::try_from(domain).unwrap();
4628        let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4629
4630        let well_known_mock = server
4631            .mock_well_known()
4632            .ok()
4633            .named("well known mock")
4634            .expect(2) // One for ClientBuilder discovery, one for the ServerInfo cache.
4635            .mount_as_scoped()
4636            .await;
4637
4638        let memory_store = Arc::new(MemoryStore::new());
4639        let client = Client::builder()
4640            .insecure_server_name_no_tls(server_name)
4641            .store_config(
4642                StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4643                    .state_store(memory_store.clone()),
4644            )
4645            .build()
4646            .await
4647            .unwrap();
4648
4649        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4650
4651        // This subsequent call hits the in-memory cache.
4652        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4653
4654        drop(client);
4655
4656        let client = server
4657            .client_builder()
4658            .no_server_versions()
4659            .on_builder(|builder| {
4660                builder.store_config(
4661                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4662                        .state_store(memory_store.clone()),
4663                )
4664            })
4665            .build()
4666            .await;
4667
4668        // This call to the new client hits the on-disk cache.
4669        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4670
4671        // Then this call hits the in-memory cache.
4672        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4673
4674        drop(well_known_mock);
4675
4676        // Now, reset the cache, and observe the endpoints being called again once.
4677        client.reset_well_known().await.unwrap();
4678
4679        server.mock_well_known().ok().named("second well known mock").expect(2).mount().await;
4680
4681        // Hits network again.
4682        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4683        // Hits in-memory cache again.
4684        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4685
4686        // Force an expiry of the data.
4687        let well_known = client.well_known().await;
4688        let mut ttl_value = TtlValue::new(well_known);
4689        ttl_value.expire();
4690        client.inner.caches.well_known.set_value(ttl_value);
4691
4692        // Call the method again to trigger a cache refresh background task.
4693        client.well_known().await;
4694
4695        // We wait for the task to finish, the endpoint should have been called again.
4696        // We need to wait a bit because the first requests using the server name of the
4697        // user will fail, only the requests using the homeserver URL will succeed.
4698        sleep(Duration::from_secs(5)).await;
4699        assert_matches!(client.inner.caches.well_known.value(), CachedValue::Cached(value) if !value.has_expired());
4700    }
4701
4702    #[async_test]
4703    async fn test_rtc_transports_caching() {
4704        use wiremock::{
4705            Mock, ResponseTemplate,
4706            matchers::{method, path_regex},
4707        };
4708
4709        let server = MatrixMockServer::new().await;
4710        let transports = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4711
4712        let transports_mock = Mock::given(method("GET"))
4713            .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4714            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4715                "rtc_transports": [
4716                    { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4717                ]
4718            })))
4719            .named("first transports mock")
4720            .expect(1)
4721            .mount_as_scoped(server.server())
4722            .await;
4723
4724        let client = server.client_builder().build().await;
4725
4726        // First call hits the network.
4727        assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4728        // Subsequent call hits the in-memory cache.
4729        assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4730        assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4731
4732        drop(transports_mock);
4733
4734        // Reset the cache, and observe the endpoint being called again once.
4735        client.reset_rtc_transports();
4736
4737        Mock::given(method("GET"))
4738            .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4739            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
4740                "rtc_transports": [
4741                    { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4742                ]
4743            })))
4744            .named("second transports mock")
4745            .expect(2)
4746            .mount(server.server())
4747            .await;
4748
4749        // Hits network again.
4750        assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4751        // Hits in-memory cache again.
4752        assert_eq!(client.rtc_transports().await.unwrap(), Some(transports.clone()));
4753
4754        // Force an expiry of the data.
4755        let mut ttl_value = TtlValue::new(Some(transports.clone()));
4756        ttl_value.expire();
4757        client.inner.caches.rtc_transports.set_value(ttl_value);
4758
4759        // Call the method again to trigger a cache refresh background task.
4760        client.rtc_transports().await.unwrap();
4761
4762        // We wait for the task to finish, the endpoint should have been called again.
4763        sleep(Duration::from_secs(1)).await;
4764        assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4765    }
4766
4767    #[async_test]
4768    async fn test_rtc_transports_unsupported_caching() {
4769        use wiremock::{
4770            Mock, ResponseTemplate,
4771            matchers::{method, path_regex},
4772        };
4773
4774        let server = MatrixMockServer::new().await;
4775
4776        // The homeserver doesn't implement the endpoint: it responds with a 404 and an
4777        // `M_UNRECOGNIZED` error (as a homeserver does for an unrecognized endpoint).
4778        // We expect it to be hit only once, despite several calls, thanks to the
4779        // negative caching.
4780        Mock::given(method("GET"))
4781            .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4782            .respond_with(ResponseTemplate::new(404).set_body_json(json!({
4783                "errcode": "M_UNRECOGNIZED",
4784                "error": "Unrecognized request",
4785            })))
4786            .named("unrecognized transports mock")
4787            .expect(1)
4788            .mount(server.server())
4789            .await;
4790
4791        let client = server.client_builder().build().await;
4792
4793        // First call hits the network and gets a 404, which is cached as `None`
4794        // (unsupported), distinct from `Some(vec![])` (supported but empty).
4795        assert_eq!(client.rtc_transports().await.unwrap(), None);
4796        // Subsequent call hits the in-memory cache, without re-hitting the endpoint.
4797        assert_eq!(client.rtc_transports().await.unwrap(), None);
4798        assert_matches!(client.inner.caches.rtc_transports.value(), CachedValue::Cached(value) if !value.has_expired());
4799    }
4800
4801    /// Mounts a scoped mock for the MSC4143 RTC transports endpoint, either
4802    /// advertising a single LiveKit transport, or responding with the
4803    /// `M_UNRECOGNIZED` error of a homeserver that doesn't implement it.
4804    async fn mock_rtc_transports_endpoint(
4805        server: &MatrixMockServer,
4806        supported: bool,
4807    ) -> wiremock::MockGuard {
4808        use wiremock::{
4809            Mock, ResponseTemplate,
4810            matchers::{method, path_regex},
4811        };
4812
4813        let response = if supported {
4814            ResponseTemplate::new(200).set_body_json(json!({
4815                "rtc_transports": [
4816                    { "type": "livekit", "livekit_service_url": "https://livekit.example.com" }
4817                ]
4818            }))
4819        } else {
4820            ResponseTemplate::new(404).set_body_json(json!({
4821                "errcode": "M_UNRECOGNIZED",
4822                "error": "Unrecognized request",
4823            }))
4824        };
4825
4826        Mock::given(method("GET"))
4827            .and(path_regex(r"^/_matrix/client/unstable/org.matrix.msc4143/rtc/transports"))
4828            .respond_with(response)
4829            .named("transports mock")
4830            .expect(1)
4831            .mount_as_scoped(server.server())
4832            .await
4833    }
4834
4835    #[async_test]
4836    async fn test_discover_rtc_transports_prefers_the_endpoint() {
4837        let server = MatrixMockServer::new().await;
4838        let transports = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4839
4840        let _transports_mock = mock_rtc_transports_endpoint(&server, true).await;
4841
4842        // The homeserver implements the discovery endpoint, so the well-known must not
4843        // be queried at all.
4844        let _well_known_mock = server
4845            .mock_well_known()
4846            .ok()
4847            .named("well-known mock")
4848            .expect(0)
4849            .mount_as_scoped()
4850            .await;
4851
4852        let client = server.client_builder().build().await;
4853
4854        assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(transports));
4855    }
4856
4857    #[async_test]
4858    async fn test_discover_rtc_transports_falls_back_to_well_known() {
4859        let server = MatrixMockServer::new().await;
4860        // The `m.rtc_foci` advertised by `WellKnownEndpoint::ok`.
4861        let rtc_foci = vec![RtcTransport::livekit("https://livekit.example.com".to_owned())];
4862
4863        let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4864
4865        let _well_known_mock = server
4866            .mock_well_known()
4867            .ok()
4868            .named("well-known mock")
4869            .expect(1)
4870            .mount_as_scoped()
4871            .await;
4872
4873        let client = server.client_builder().build().await;
4874
4875        // The homeserver doesn't implement the discovery endpoint, so the well-known
4876        // foci are used instead.
4877        assert_eq!(client.discover_rtc_transports().await.unwrap(), Some(rtc_foci));
4878    }
4879
4880    /// Mounts a well-known mock that must never be hit.
4881    async fn mock_well_known_never_called(server: &MatrixMockServer) -> wiremock::MockGuard {
4882        server.mock_well_known().ok().named("well-known mock").expect(0).mount_as_scoped().await
4883    }
4884
4885    #[async_test]
4886    async fn test_well_known_lookup_disabled() {
4887        let server = MatrixMockServer::new().await;
4888
4889        let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4890
4891        // There should be no requests to fetch the well-known.
4892        let _well_known_mock = mock_well_known_never_called(&server).await;
4893
4894        // Disable well-known lookups at client build time.
4895        let client = server
4896            .client_builder()
4897            .on_builder(|builder| builder.disable_well_known_lookup(true))
4898            .build()
4899            .await;
4900
4901        // The homeserver doesn't implement the discovery endpoint, and falling back to
4902        // the well-known isn't allowed, so nothing could be discovered.
4903        assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
4904        // The other well-known consumers are disabled too.
4905        assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
4906        assert!(client.tile_server().await.is_none());
4907        assert!(client.fetch_client_well_known().await.is_none());
4908    }
4909
4910    #[async_test]
4911    async fn test_well_known_lookup_disabled_after_build() {
4912        let server = MatrixMockServer::new().await;
4913
4914        let _transports_mock = mock_rtc_transports_endpoint(&server, false).await;
4915
4916        // There should be no requests to fetch the well-known.
4917        let _well_known_mock = mock_well_known_never_called(&server).await;
4918
4919        // Disable well-known lookups after building the client.
4920        let client = server.client_builder().build().await;
4921        client.disable_well_known_lookup(true);
4922
4923        // The homeserver doesn't implement the discovery endpoint, and falling back to
4924        // the well-known isn't allowed, so nothing could be discovered.
4925        assert_eq!(client.discover_rtc_transports().await.unwrap(), None);
4926        // The other well-known consumers are disabled too.
4927        assert!(client.well_known_rtc_transports().await.unwrap().is_empty());
4928        assert!(client.tile_server().await.is_none());
4929        assert!(client.fetch_client_well_known().await.is_none());
4930    }
4931
4932    #[async_test]
4933    async fn test_missing_well_known_caching() {
4934        let server = MatrixMockServer::new().await;
4935        let rtc_foci: Vec<RtcTransport> = vec![];
4936
4937        let well_known_mock = server
4938            .mock_well_known()
4939            .error_unrecognized()
4940            .named("first well-known mock")
4941            .expect(1)
4942            .mount_as_scoped()
4943            .await;
4944
4945        let memory_store = Arc::new(MemoryStore::new());
4946        let client = server
4947            .client_builder()
4948            .on_builder(|builder| {
4949                builder.store_config(
4950                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4951                        .state_store(memory_store.clone()),
4952                )
4953            })
4954            .build()
4955            .await;
4956
4957        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4958
4959        // This subsequent call hits the in-memory cache.
4960        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4961
4962        drop(client);
4963
4964        let client = server
4965            .client_builder()
4966            .on_builder(|builder| {
4967                builder.store_config(
4968                    StoreConfig::new(CrossProcessLockConfig::SingleProcess)
4969                        .state_store(memory_store.clone()),
4970                )
4971            })
4972            .build()
4973            .await;
4974
4975        // This call to the new client hits the on-disk cache.
4976        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4977
4978        // Then this call hits the in-memory cache.
4979        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4980
4981        drop(well_known_mock);
4982
4983        // Now, reset the cache, and observe the endpoints being called again once.
4984        client.reset_well_known().await.unwrap();
4985
4986        server
4987            .mock_well_known()
4988            .error_unrecognized()
4989            .expect(1)
4990            .named("second well-known mock")
4991            .mount()
4992            .await;
4993
4994        // Hits network again.
4995        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4996        // Hits in-memory cache again.
4997        assert_eq!(client.well_known_rtc_transports().await.unwrap(), rtc_foci);
4998    }
4999
5000    #[async_test]
5001    async fn test_no_network_doesnt_cause_infinite_retries() {
5002        // We want infinite retries for transient errors.
5003        let client = MockClientBuilder::new(None)
5004            .on_builder(|builder| builder.request_config(RequestConfig::new()))
5005            .build()
5006            .await;
5007
5008        // We don't define a mock server on purpose here, so that the error is really a
5009        // network error.
5010        client.whoami().await.unwrap_err();
5011    }
5012
5013    #[async_test]
5014    async fn test_await_room_remote_echo_returns_the_room_if_it_was_already_synced() {
5015        let server = MatrixMockServer::new().await;
5016        let client = server.client_builder().build().await;
5017
5018        let room_id = room_id!("!room:example.org");
5019
5020        server
5021            .mock_sync()
5022            .ok_and_run(&client, |builder| {
5023                builder.add_joined_room(JoinedRoomBuilder::new(room_id));
5024            })
5025            .await;
5026
5027        let room = client.await_room_remote_echo(room_id).now_or_never().unwrap();
5028        assert_eq!(room.room_id(), room_id);
5029    }
5030
5031    #[async_test]
5032    async fn test_await_room_remote_echo_returns_the_room_when_it_is_ready() {
5033        let server = MatrixMockServer::new().await;
5034        let client = server.client_builder().build().await;
5035
5036        let room_id = room_id!("!room:example.org");
5037
5038        let client = Arc::new(client);
5039
5040        // Perform the /sync request with a delay so it starts after the
5041        // `await_room_remote_echo` call has happened
5042        spawn({
5043            let client = client.clone();
5044            async move {
5045                sleep(Duration::from_millis(100)).await;
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        });
5055
5056        let room =
5057            timeout(Duration::from_secs(10), client.await_room_remote_echo(room_id)).await.unwrap();
5058        assert_eq!(room.room_id(), room_id);
5059    }
5060
5061    #[async_test]
5062    async fn test_await_room_remote_echo_will_timeout_if_no_room_is_found() {
5063        let client = MockClientBuilder::new(None).build().await;
5064
5065        let room_id = room_id!("!room:example.org");
5066        // Room is not present so the client won't be able to find it. The call will
5067        // timeout.
5068        timeout(Duration::from_secs(1), client.await_room_remote_echo(room_id)).await.unwrap_err();
5069    }
5070
5071    #[async_test]
5072    async fn test_await_room_remote_echo_will_timeout_if_room_is_found_but_not_synced() {
5073        let server = MatrixMockServer::new().await;
5074        let client = server.client_builder().build().await;
5075
5076        server.mock_create_room().ok().mount().await;
5077
5078        // Create a room in the internal store
5079        let room = client
5080            .create_room(assign!(CreateRoomRequest::new(), {
5081                invite: vec![],
5082                is_direct: false,
5083            }))
5084            .await
5085            .unwrap();
5086
5087        // Room is locally present, but not synced, the call will timeout
5088        timeout(Duration::from_secs(1), client.await_room_remote_echo(room.room_id()))
5089            .await
5090            .unwrap_err();
5091    }
5092
5093    #[async_test]
5094    async fn test_is_room_alias_available_if_alias_is_not_resolved() {
5095        let server = MatrixMockServer::new().await;
5096        let client = server.client_builder().build().await;
5097
5098        server.mock_room_directory_resolve_alias().not_found().expect(1).mount().await;
5099
5100        let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5101        assert_matches!(ret, Ok(true));
5102    }
5103
5104    #[async_test]
5105    async fn test_is_room_alias_available_if_alias_is_resolved() {
5106        let server = MatrixMockServer::new().await;
5107        let client = server.client_builder().build().await;
5108
5109        server
5110            .mock_room_directory_resolve_alias()
5111            .ok("!some_room_id:matrix.org", Vec::new())
5112            .expect(1)
5113            .mount()
5114            .await;
5115
5116        let ret = client.is_room_alias_available(room_alias_id!("#some_alias:matrix.org")).await;
5117        assert_matches!(ret, Ok(false));
5118    }
5119
5120    #[async_test]
5121    async fn test_is_room_alias_available_if_error_found() {
5122        let server = MatrixMockServer::new().await;
5123        let client = server.client_builder().build().await;
5124
5125        server.mock_room_directory_resolve_alias().error500().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, Err(_));
5129    }
5130
5131    #[async_test]
5132    async fn test_create_room_alias() {
5133        let server = MatrixMockServer::new().await;
5134        let client = server.client_builder().build().await;
5135
5136        server.mock_room_directory_create_room_alias().ok().expect(1).mount().await;
5137
5138        let ret = client
5139            .create_room_alias(
5140                room_alias_id!("#some_alias:matrix.org"),
5141                room_id!("!some_room:matrix.org"),
5142            )
5143            .await;
5144        assert_matches!(ret, Ok(()));
5145    }
5146
5147    #[async_test]
5148    async fn test_join_room_by_id_or_alias() {
5149        use wiremock::{
5150            Mock, ResponseTemplate,
5151            matchers::{method, path_regex},
5152        };
5153        let server = MatrixMockServer::new().await;
5154        let client = server.client_builder().build().await;
5155
5156        let target_room_id = room_id!("!some_id:matrix.org");
5157        let target_alias = room_alias_id!("#some_alias:matrix.org");
5158
5159        Mock::given(method("POST"))
5160            .and(path_regex("^/_matrix/client/v3/join/.*$"))
5161            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
5162                "room_id": target_room_id
5163            })))
5164            .mount(server.server())
5165            .await;
5166
5167        server
5168            .mock_room_directory_resolve_alias()
5169            .ok(target_room_id.as_str(), Vec::new())
5170            .mount()
5171            .await;
5172
5173        server.mock_room_join(target_room_id).ok().mount().await;
5174
5175        let ret = client.join_room_by_id_or_alias(target_alias.into(), &[]).await;
5176        assert!(ret.is_ok());
5177
5178        let ret = client.join_room_by_id_or_alias(target_room_id.into(), &[]).await;
5179        assert!(ret.is_ok());
5180    }
5181
5182    #[async_test]
5183    async fn test_room_preview_for_invited_room_hits_summary_endpoint() {
5184        let server = MatrixMockServer::new().await;
5185        let client = server.client_builder().build().await;
5186
5187        let room_id = room_id!("!a-room:matrix.org");
5188
5189        // Make sure the summary endpoint is called once
5190        server.mock_room_summary().ok(room_id).mock_once().mount().await;
5191
5192        // We create a locally cached invited room
5193        let invited_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Invited);
5194
5195        // And we get a preview, the server endpoint was reached
5196        let preview = client
5197            .get_room_preview(room_id.into(), Vec::new())
5198            .await
5199            .expect("Room preview should be retrieved");
5200
5201        assert_eq!(invited_room.room_id(), preview.room_id);
5202    }
5203
5204    #[async_test]
5205    async fn test_room_preview_for_left_room_hits_summary_endpoint() {
5206        let server = MatrixMockServer::new().await;
5207        let client = server.client_builder().build().await;
5208
5209        let room_id = room_id!("!a-room:matrix.org");
5210
5211        // Make sure the summary endpoint is called once
5212        server.mock_room_summary().ok(room_id).mock_once().mount().await;
5213
5214        // We create a locally cached left room
5215        let left_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Left);
5216
5217        // And we get a preview, the server endpoint was reached
5218        let preview = client
5219            .get_room_preview(room_id.into(), Vec::new())
5220            .await
5221            .expect("Room preview should be retrieved");
5222
5223        assert_eq!(left_room.room_id(), preview.room_id);
5224    }
5225
5226    #[async_test]
5227    async fn test_room_preview_for_knocked_room_hits_summary_endpoint() {
5228        let server = MatrixMockServer::new().await;
5229        let client = server.client_builder().build().await;
5230
5231        let room_id = room_id!("!a-room:matrix.org");
5232
5233        // Make sure the summary endpoint is called once
5234        server.mock_room_summary().ok(room_id).mock_once().mount().await;
5235
5236        // We create a locally cached knocked room
5237        let knocked_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Knocked);
5238
5239        // And we get a preview, the server endpoint was reached
5240        let preview = client
5241            .get_room_preview(room_id.into(), Vec::new())
5242            .await
5243            .expect("Room preview should be retrieved");
5244
5245        assert_eq!(knocked_room.room_id(), preview.room_id);
5246    }
5247
5248    #[async_test]
5249    async fn test_room_preview_for_joined_room_retrieves_local_room_info() {
5250        let server = MatrixMockServer::new().await;
5251        let client = server.client_builder().build().await;
5252
5253        let room_id = room_id!("!a-room:matrix.org");
5254
5255        // Make sure the summary endpoint is not called
5256        server.mock_room_summary().ok(room_id).never().mount().await;
5257
5258        // We create a locally cached joined room
5259        let joined_room = client.inner.base_client.get_or_create_room(room_id, RoomState::Joined);
5260
5261        // And we get a preview, no server endpoint was reached
5262        let preview = client
5263            .get_room_preview(room_id.into(), Vec::new())
5264            .await
5265            .expect("Room preview should be retrieved");
5266
5267        assert_eq!(joined_room.room_id(), preview.room_id);
5268    }
5269
5270    #[async_test]
5271    async fn test_media_preview_config() {
5272        let server = MatrixMockServer::new().await;
5273        let client = server.client_builder().build().await;
5274
5275        server
5276            .mock_sync()
5277            .ok_and_run(&client, |builder| {
5278                builder.add_custom_global_account_data(json!({
5279                    "content": {
5280                        "media_previews": "private",
5281                        "invite_avatars": "off"
5282                    },
5283                    "type": "m.media_preview_config"
5284                }));
5285            })
5286            .await;
5287
5288        let (initial_value, stream) =
5289            client.account().observe_media_preview_config().await.unwrap();
5290
5291        let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5292        assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5293        assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5294        pin_mut!(stream);
5295        assert_pending!(stream);
5296
5297        server
5298            .mock_sync()
5299            .ok_and_run(&client, |builder| {
5300                builder.add_custom_global_account_data(json!({
5301                    "content": {
5302                        "media_previews": "off",
5303                        "invite_avatars": "on"
5304                    },
5305                    "type": "m.media_preview_config"
5306                }));
5307            })
5308            .await;
5309
5310        assert_next_matches!(
5311            stream,
5312            MediaPreviewConfigEventContent {
5313                media_previews: Some(MediaPreviews::Off),
5314                invite_avatars: Some(InviteAvatars::On),
5315                ..
5316            }
5317        );
5318        assert_pending!(stream);
5319    }
5320
5321    #[async_test]
5322    async fn test_unstable_media_preview_config() {
5323        let server = MatrixMockServer::new().await;
5324        let client = server.client_builder().build().await;
5325
5326        server
5327            .mock_sync()
5328            .ok_and_run(&client, |builder| {
5329                builder.add_custom_global_account_data(json!({
5330                    "content": {
5331                        "media_previews": "private",
5332                        "invite_avatars": "off"
5333                    },
5334                    "type": "io.element.msc4278.media_preview_config"
5335                }));
5336            })
5337            .await;
5338
5339        let (initial_value, stream) =
5340            client.account().observe_media_preview_config().await.unwrap();
5341
5342        let initial_value: MediaPreviewConfigEventContent = initial_value.unwrap();
5343        assert_eq!(initial_value.invite_avatars, Some(InviteAvatars::Off));
5344        assert_eq!(initial_value.media_previews, Some(MediaPreviews::Private));
5345        pin_mut!(stream);
5346        assert_pending!(stream);
5347
5348        server
5349            .mock_sync()
5350            .ok_and_run(&client, |builder| {
5351                builder.add_custom_global_account_data(json!({
5352                    "content": {
5353                        "media_previews": "off",
5354                        "invite_avatars": "on"
5355                    },
5356                    "type": "io.element.msc4278.media_preview_config"
5357                }));
5358            })
5359            .await;
5360
5361        assert_next_matches!(
5362            stream,
5363            MediaPreviewConfigEventContent {
5364                media_previews: Some(MediaPreviews::Off),
5365                invite_avatars: Some(InviteAvatars::On),
5366                ..
5367            }
5368        );
5369        assert_pending!(stream);
5370    }
5371
5372    #[async_test]
5373    async fn test_media_preview_config_not_found() {
5374        let server = MatrixMockServer::new().await;
5375        let client = server.client_builder().build().await;
5376
5377        let (initial_value, _) = client.account().observe_media_preview_config().await.unwrap();
5378
5379        assert!(initial_value.is_none());
5380    }
5381
5382    #[async_test]
5383    async fn test_load_or_fetch_max_upload_size_with_auth_matrix_version() {
5384        // The default Matrix version we use is 1.11 or higher, so authenticated media
5385        // is supported.
5386        let server = MatrixMockServer::new().await;
5387        let client = server.client_builder().build().await;
5388
5389        assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5390
5391        server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5392        client.load_or_fetch_max_upload_size().await.unwrap();
5393
5394        assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5395    }
5396
5397    #[async_test]
5398    async fn test_load_or_fetch_max_upload_size_with_auth_stable_feature() {
5399        // The server must advertise support for the stable feature for authenticated
5400        // media support, so we mock the `GET /versions` response.
5401        let server = MatrixMockServer::new().await;
5402        let client = server.client_builder().no_server_versions().build().await;
5403
5404        server
5405            .mock_versions()
5406            .with_versions(vec!["v1.7", "v1.8", "v1.9", "v1.10"])
5407            .with_feature("org.matrix.msc3916.stable", true)
5408            .ok()
5409            .named("versions")
5410            .expect(1)
5411            .mount()
5412            .await;
5413
5414        assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5415
5416        server.mock_authenticated_media_config().ok(uint!(2)).mock_once().mount().await;
5417        client.load_or_fetch_max_upload_size().await.unwrap();
5418
5419        assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5420    }
5421
5422    #[async_test]
5423    async fn test_load_or_fetch_max_upload_size_no_auth() {
5424        // The server must not support Matrix 1.11 or higher for unauthenticated
5425        // media requests, so we mock the `GET /versions` response.
5426        let server = MatrixMockServer::new().await;
5427        let client = server.client_builder().no_server_versions().build().await;
5428
5429        server
5430            .mock_versions()
5431            .with_versions(vec!["v1.1"])
5432            .ok()
5433            .named("versions")
5434            .expect(1)
5435            .mount()
5436            .await;
5437
5438        assert!(!client.inner.server_max_upload_size.lock().await.initialized());
5439
5440        server.mock_media_config().ok(uint!(2)).mock_once().mount().await;
5441        client.load_or_fetch_max_upload_size().await.unwrap();
5442
5443        assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(2));
5444    }
5445
5446    #[async_test]
5447    async fn test_uploading_a_too_large_media_file() {
5448        let server = MatrixMockServer::new().await;
5449        let client = server.client_builder().build().await;
5450
5451        server.mock_authenticated_media_config().ok(uint!(1)).mock_once().mount().await;
5452        client.load_or_fetch_max_upload_size().await.unwrap();
5453        assert_eq!(*client.inner.server_max_upload_size.lock().await.get().unwrap(), uint!(1));
5454
5455        let data = vec![1, 2];
5456        let upload_request =
5457            ruma::api::client::media::create_content::v3::Request::new(data.clone());
5458        let request = SendRequest {
5459            client: client.clone(),
5460            request: upload_request,
5461            config: None,
5462            send_progress: SharedObservable::new(TransmissionProgress::default()),
5463        };
5464        let media_request = SendMediaUploadRequest::new(request);
5465
5466        let error = media_request.await.err();
5467        assert_let!(Some(Error::Media(MediaError::MediaTooLargeToUpload { max, current })) = error);
5468        assert_eq!(max, uint!(1));
5469        assert_eq!(current, UInt::new_wrapping(data.len() as u64));
5470    }
5471
5472    #[async_test]
5473    async fn test_dont_ignore_timeout_on_first_sync() {
5474        let server = MatrixMockServer::new().await;
5475        let client = server.client_builder().build().await;
5476
5477        server
5478            .mock_sync()
5479            .timeout(Some(Duration::from_secs(30)))
5480            .ok(|_| {})
5481            .mock_once()
5482            .named("sync_with_timeout")
5483            .mount()
5484            .await;
5485
5486        // Call the endpoint once to check the timeout.
5487        let mut stream = Box::pin(client.sync_stream(SyncSettings::new()).await);
5488
5489        timeout(Duration::from_secs(1), async {
5490            stream.next().await.unwrap().unwrap();
5491        })
5492        .await
5493        .unwrap();
5494    }
5495
5496    #[async_test]
5497    async fn test_ignore_timeout_on_first_sync() {
5498        let server = MatrixMockServer::new().await;
5499        let client = server.client_builder().build().await;
5500
5501        server
5502            .mock_sync()
5503            .timeout(None)
5504            .ok(|_| {})
5505            .mock_once()
5506            .named("sync_no_timeout")
5507            .mount()
5508            .await;
5509        server
5510            .mock_sync()
5511            .timeout(Some(Duration::from_secs(30)))
5512            .ok(|_| {})
5513            .mock_once()
5514            .named("sync_with_timeout")
5515            .mount()
5516            .await;
5517
5518        // Call each version of the endpoint once to check the timeouts.
5519        let mut stream = Box::pin(
5520            client.sync_stream(SyncSettings::new().ignore_timeout_on_first_sync(true)).await,
5521        );
5522
5523        timeout(Duration::from_secs(1), async {
5524            stream.next().await.unwrap().unwrap();
5525            stream.next().await.unwrap().unwrap();
5526        })
5527        .await
5528        .unwrap();
5529    }
5530
5531    #[async_test]
5532    async fn test_get_dm_room_returns_the_room_we_have_with_this_user() {
5533        let server = MatrixMockServer::new().await;
5534        let client = server.client_builder().build().await;
5535        // This is the user ID that is inside MemberAdditional.
5536        // Note the confusing username, so we can share
5537        // GlobalAccountDataTestEvent::Direct with the invited test.
5538        let user_id = user_id!("@invited:localhost");
5539
5540        // When we receive a sync response saying "invited" is invited to a DM
5541        let f = EventFactory::new().sender(user_id!("@example:localhost"));
5542        let response = SyncResponseBuilder::default()
5543            .add_joined_room(JoinedRoomBuilder::default().add_state_event(f.member(user_id)))
5544            .add_global_account_data(
5545                f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5546            )
5547            .build_sync_response();
5548        client.base_client().receive_sync_response(response).await.unwrap();
5549
5550        // Then get_dm_room finds this room
5551        let found_room = client.get_dm_room(user_id).expect("DM not found!");
5552        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5553    }
5554
5555    #[async_test]
5556    async fn test_get_dm_room_still_finds_room_where_participant_is_only_invited() {
5557        let server = MatrixMockServer::new().await;
5558        let client = server.client_builder().build().await;
5559        // This is the user ID that is inside MemberInvite
5560        let user_id = user_id!("@invited:localhost");
5561
5562        // When we receive a sync response saying "invited" is invited to a DM
5563        let f = EventFactory::new().sender(user_id!("@example:localhost"));
5564        let response = SyncResponseBuilder::default()
5565            .add_joined_room(
5566                JoinedRoomBuilder::default()
5567                    .add_state_event(f.member(user_id).invited(user_id).display_name("example")),
5568            )
5569            .add_global_account_data(
5570                f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5571            )
5572            .build_sync_response();
5573        client.base_client().receive_sync_response(response).await.unwrap();
5574
5575        // Then get_dm_room finds this room
5576        let found_room = client.get_dm_room(user_id).expect("DM not found!");
5577        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5578    }
5579
5580    #[async_test]
5581    async fn test_get_dm_room_still_finds_left_room() {
5582        // See the discussion in https://github.com/matrix-org/matrix-rust-sdk/issues/2017
5583        // and the high-level issue at https://github.com/vector-im/element-x-ios/issues/1077
5584
5585        let server = MatrixMockServer::new().await;
5586        let client = server.client_builder().build().await;
5587        // This is the user ID that is inside MemberAdditional.
5588        // Note the confusing username, so we can share
5589        // GlobalAccountDataTestEvent::Direct with the invited test.
5590        let user_id = user_id!("@invited:localhost");
5591
5592        // When we receive a sync response saying "invited" has left a DM
5593        let f = EventFactory::new().sender(user_id);
5594        let response = SyncResponseBuilder::default()
5595            .add_joined_room(
5596                JoinedRoomBuilder::default().add_state_event(f.member(user_id).leave()),
5597            )
5598            .add_global_account_data(
5599                f.direct().add_user(user_id.to_owned().into(), *DEFAULT_TEST_ROOM_ID),
5600            )
5601            .build_sync_response();
5602        client.base_client().receive_sync_response(response).await.unwrap();
5603
5604        // Then get_dm_room finds this room
5605        let found_room = client.get_dm_room(user_id).expect("DM not found!");
5606        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
5607    }
5608
5609    #[async_test]
5610    async fn test_device_exists() {
5611        let server = MatrixMockServer::new().await;
5612        let client = server.client_builder().build().await;
5613
5614        server.mock_get_device().ok().expect(1).mount().await;
5615
5616        assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(true));
5617    }
5618
5619    #[async_test]
5620    async fn test_device_exists_404() {
5621        let server = MatrixMockServer::new().await;
5622        let client = server.client_builder().build().await;
5623
5624        assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Ok(false));
5625    }
5626
5627    #[async_test]
5628    async fn test_device_exists_500() {
5629        let server = MatrixMockServer::new().await;
5630        let client = server.client_builder().build().await;
5631
5632        server.mock_get_device().error500().expect(1).mount().await;
5633
5634        assert_matches!(client.device_exists(owned_device_id!("ABCDEF")).await, Err(_));
5635    }
5636
5637    #[async_test]
5638    async fn test_fetching_well_known_with_homeserver_url() {
5639        let server = MatrixMockServer::new().await;
5640        let client = server.client_builder().build().await;
5641        server.mock_well_known().ok().mount().await;
5642
5643        assert_matches!(client.fetch_client_well_known().await, Some(_));
5644    }
5645
5646    #[async_test]
5647    async fn test_fetching_well_known_with_server_name() {
5648        let server = MatrixMockServer::new().await;
5649        let server_name = ServerName::parse(server.server().address().to_string()).unwrap();
5650
5651        server.mock_well_known().ok().mount().await;
5652
5653        let client = MockClientBuilder::new(None)
5654            .on_builder(|builder| builder.insecure_server_name_no_tls(&server_name))
5655            .build()
5656            .await;
5657
5658        assert_matches!(client.fetch_client_well_known().await, Some(_));
5659    }
5660
5661    #[async_test]
5662    async fn test_fetching_well_known_with_domain_part_of_user_id() {
5663        let server = MatrixMockServer::new().await;
5664        server.mock_well_known().ok().mount().await;
5665
5666        let user_id =
5667            UserId::parse(format!("@user:{}", server.server().address())).expect("Invalid user id");
5668        let client = MockClientBuilder::new(None)
5669            .logged_in_with_token("A_TOKEN".to_owned(), user_id, owned_device_id!("ABCDEF"))
5670            .build()
5671            .await;
5672
5673        assert_matches!(client.fetch_client_well_known().await, Some(_));
5674    }
5675
5676    #[cfg(feature = "e2e-encryption")]
5677    #[async_test]
5678    async fn test_syncing_one_time_key_counts_updates() -> Result<()> {
5679        use wiremock::ResponseTemplate;
5680
5681        macro_rules! assert_key_count {
5682            ($client: ident, $count:literal) => {{
5683                let machine = $client.olm_machine().await;
5684                let uploaded_key_counts =
5685                    machine.as_ref().unwrap().uploaded_key_count().await.unwrap();
5686                assert_eq!(uploaded_key_counts, $count)
5687            }};
5688        }
5689
5690        macro_rules! sync_with_key_count {
5691            ($client: ident, $server:ident, $count:literal) => {
5692                let count = Some($count);
5693                sync_with_key_count!($client, $server, count);
5694            };
5695            ($client: ident, $server:ident, $count:ident) => {{
5696                use rand::RngExt as _;
5697
5698                let next_batch: String = rand::rng()
5699                    .sample_iter(&rand::distr::Alphanumeric)
5700                    .take(16)
5701                    .map(char::from)
5702                    .collect();
5703
5704                let count: Option<u32> = $count;
5705
5706                let template = if let Some(count) = count {
5707                    ResponseTemplate::new(200).set_body_json(json!({
5708                        "next_batch": next_batch,
5709                        "rooms": {"leave": {}, "join": {}, "invite": {}},
5710                        "device_lists": {
5711                          "changed": [],
5712                          "left": [],
5713                        },
5714                        "device_one_time_keys_count": {
5715                          "signed_curve25519": count
5716                        },
5717                    }))
5718                } else {
5719                    ResponseTemplate::new(200).set_body_json(json!({
5720                        "next_batch": next_batch,
5721                        "rooms": {"leave": {}, "join": {}, "invite": {}},
5722                        "device_lists": {
5723                          "changed": [],
5724                          "left": [],
5725                        },
5726                        "device_one_time_keys_count": {},
5727                    }))
5728                };
5729
5730                let _sync_mock_guard = $server.mock_sync().respond_with(template).mount_as_scoped().await;
5731                $client.sync_once(Default::default()).await?;
5732            }}
5733        }
5734
5735        let server = MatrixMockServer::new().await;
5736        let client = server.client_builder().build().await;
5737
5738        server.mock_upload_keys().ok_with_signed_curve_key_count(50).mock_once().mount().await;
5739
5740        // In the beginning there were no uploaded keys.
5741        assert_key_count!(client, 0);
5742
5743        // The first sync will upload 50 one-time keys.
5744        sync_with_key_count!(client, server, 50);
5745        assert_key_count!(client, 50);
5746
5747        // Syncing with a key count, will update the key count.
5748        sync_with_key_count!(client, server, 10);
5749        assert_key_count!(client, 10);
5750
5751        // Syncing with no key count will set the key count to zero.
5752        sync_with_key_count!(client, server, None);
5753        assert_key_count!(client, 0);
5754
5755        Ok(())
5756    }
5757
5758    #[async_test]
5759    async fn test_get_retention_configuration() {
5760        use wiremock::{
5761            Mock, ResponseTemplate,
5762            matchers::{method, path},
5763        };
5764
5765        let server = MatrixMockServer::new().await;
5766        let client = server.client_builder().build().await;
5767
5768        Mock::given(method("GET"))
5769            .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5770            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
5771                "policies": {},
5772                "limits": {},
5773            })))
5774            .expect(1)
5775            .mount(server.server())
5776            .await;
5777
5778        let response = client.get_retention_configuration().await;
5779        assert!(response.is_ok());
5780        let response = response.unwrap();
5781        assert!(response.policies.is_empty());
5782        assert!(response.limits.max_lifetime.is_none());
5783        assert!(response.limits.min_lifetime.is_none());
5784    }
5785}