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