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