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