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