Skip to main content

matrix_sdk/client/
mod.rs

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