Skip to main content

matrix_sdk/client/
mod.rs

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