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