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